···11# Restray
2233-Restray is a simple, handy GUI/CLI/daemon to manage and schedule restic backups with ease. Supports multiple profiles and quite a bit of configuration, along with other features (see feature list below). Runs on MacOS, Windows, and Linux.
33+Restray is a powerful but friendly backup program, powered by [restic](https://restic.net), that runs in your tray. Supports multiple profiles and quite a bit of configuration, along with other features (see feature list below). Runs on MacOS, Windows, and Linux.
44+55+You can also configure Restray to use [rustic](https://rustic.cli.rs) instead, but please keep in mind that rustic support is currently experimental.
4657I needed a replacement for Time Machine, since it's painfully slow, only exclusive to Macs, and only supports SMB now. I quite like it's simplicity and ease-of-use though. Looking for other solutions, restic appeared to be a great fit, and I already use it for my server. However, I didn't find any option for scheduling restic backups that I actually liked. I did see [resticprofile](https://github.com/creativeprojects/resticprofile) which looks cool, and may be a better solution for some, please do check it out! But I'd still prefer a simple GUI personally. So... here's Restray which should hopefully solve that :)
68···34363537To install, grab a build artifact for your OS and architecture from the [repository's tags](https://tangled.org/devins.page/restray/tags). On MacOS you'll need to [remove quarantine](https://disable-gatekeeper.github.io#disabling-gatekeeper-for-one-application-only) from the `.app`, since I can't pay Apple $100/year to sign it. Sorry.
36383737-On MacOS, a restic binary is downloaded and bundled to the `.app` at build time, though I'd recommend installing restic to your PATH so you have the latest version. On Windows and Linux, Restray can download and update it's own restic binary itself if it isn't already in PATH.
3939+On macOS, latest restic/rustic backends are bundled into the `.app` at build time. On Windows and Linux, Restray can download and update the backend. If you install the backend to your path, e.g. with your package manager, it is preferred over the self-managed binary.
38403941_If you're a Nix/NixOS/Nix-darwin user, I also provide package/module in the project's [flake](https://tangled.org/devins.page/restray) for Linux and MacOS._
4042···6163| Key | Type | Default | Description |
6264| ------------------ | ------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
6365| `check_updates` | bool | `true` | **MacOS/Windows only.** Check for new Restray versions on startup and periodically. When an update is found, the version item in the menu changes to an update button |
6464-| `manage_restic` | bool | `false` | **Windows/Linux only.** If restic is not found in PATH, automatically downloads and updates a self-managed restic binary daily from GitHub. |
6666+| `manage_backend` | bool | `false` | **Windows/Linux only.** If the selected backend is not found in PATH, automatically self-manages it's binary from GitHub. |
6567| `notifications` | string | `"none"` | `"all"` (success + errors), `"errors"` (errors only), or `"none"` (silent) |
6668| `icon` | string | platform | `"color"`, `"mono"`, `"white"`, or `"black"`. Default is `"mono"` on MacOS/Windows and `"color"` elsewhere. `"mono"` follows OS light/dark mode. |
6769| `schedule_display` | string | `"description"` | `"description"` (human-readable, e.g. "every 6 hours"), `"cron"` (raw expression, e.g. "0 \* \* \* \*"), `"last"` (time since last backup, e.g. "2 hours ago"), or `"none"` |
···71737274You can create multiple of these `[[profiles]]` sections for separate backup targets. Each profile has its own schedule, repository, environment, etc.
73757474-| Key | Type | Default | Description |
7575-| -------------------- | ------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7676-| `name` | string | `"default"` | Name for the profile |
7777-| `env_file` | string | `<config_dir>/<name>.env` | Path to the environment file with `RESTIC_REPOSITORY`, `RESTIC_PASSWORD`, etc. |
7878-| `rclone_config_file` | string | `""` | Path to an rclone config file, set as `RCLONE_CONFIG` for this profile. When using an [`rclone:`](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#other-services-via-rclone) repository |
7979-| `retry_lock` | string | `"2m"` | How long to retry acquiring a repository lock (e.g. `"5m"`, `"30s"`). Will automatically attempt to clear stale locks after this timeout. Set to `"0"` to disable |
8080-| `pre_hook` | string | `""` | Shell command to run before a scheduled run. Inherits the profile's environment and restic binary. The schedule is aborted if the hook exits non-zero. Can also be run manually from the Operations menu |
8181-| `post_hook` | string | `""` | Shell command to run after a scheduled run. Inherits the profile's environment and restic binary. Can also be run manually from the Operations menu |
7676+| Key | Type | Default | Description |
7777+| -------------------- | ------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7878+| `name` | string | `"default"` | Name for the profile |
7979+| `backend` | string | `"restic"` | `"restic"` or `"rustic"`. Do not change unless you know what this does. |
8080+| `env_file` | string | `<config_dir>/<name>.env` | Path to the environment file with RESTIC__, RUSTIC__, etc |
8181+| `rclone_config_file` | string | `""` | Path to an rclone config file, set as `RCLONE_CONFIG`. When using an [`rclone:`](https://restic.readthedocs.io/en/stable/030_preparing_a_new_repo.html#other-services-via-rclone) repository |
8282+| `retry_lock` | string | `"2m"` | How long to retry acquiring a repository lock (e.g. `"5m"`, `"30s"`). Will automatically attempt to clear stale locks after this timeout. Set to `"0"` to disable |
8383+| `pre_hook` | string | `""` | Shell command to run before a scheduled run. Inherits the profile's environment and backend binary. |
8484+| `post_hook` | string | `""` | Shell command to run after a scheduled run. Inherits the profile's environment and backend binary. |
82858386Hooks receive the following environment variables when run as part of a schedule:
8487···101104102105#### `[profiles.backup]`
103106104104-| Key | Type | Default | Description |
105105-| ---------------- | -------- | -------------------------------------------- | ------------------------------------------------------------------------- |
106106-| `paths` | string[] | `[]` | Paths to back up, e.g. `["/Users/user/Documents", "/home/user/Projects"]` |
107107-| `args` | string[] | `["--exclude-caches", "--exclude", "*.tmp"]` | Extra arguments passed to `restic backup` |
108108-| `args_scheduled` | string[] | `["--tag", "scheduled"]` | Extra arguments appended (not replaced) when running from schedule |
107107+| Key | Type | Default | Description |
108108+| ---------------- | -------- | -------------------------------------------- | ------------------------------------------------------------------------------ |
109109+| `paths` | string[] | `[]` | Paths to back up, e.g. `["/Users/user/Documents", "/home/user/Projects"]` |
110110+| `args` | string[] | `["--exclude-caches", "--exclude", "*.tmp"]` | Extra arguments passed to `backup` |
111111+| `args_scheduled` | string[] | `["--tag", "scheduled"]` | Extra arguments appended (not replaced) to `backup` when running from schedule |
109112110113#### `[profiles.prune]`
111114112112-| Key | Type | Default | Description |
113113-| ------ | -------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
114114-| `args` | string[] | `["--keep-last", "4", "--keep-hourly", "24", "--keep-daily", "7", "--keep-weekly", "4", "--keep-monthly", "12"]` | Extra arguments passed to `restic forget --prune` |
115115+| Key | Type | Default | Description |
116116+| ------ | -------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
117117+| `args` | string[] | `["--keep-last", "4", "--keep-hourly", "24", "--keep-daily", "7", "--keep-weekly", "4", "--keep-monthly", "12"]` | Extra arguments passed to `forget --prune` |
115118116119#### `[profiles.check]`
117120118118-| Key | Type | Default | Description |
119119-| ------ | -------- | ------- | ----------------------------------------------------------------- |
120120-| `args` | string[] | `[]` | Extra arguments passed to `restic check` (e.g. `["--read-data"]`) |
121121+| Key | Type | Default | Description |
122122+| ------ | -------- | ------- | --------------------------------- |
123123+| `args` | string[] | `[]` | Extra arguments passed to `check` |
121124122125#### `[profiles.mount]`
123126124124-| Key | Type | Default | Description |
125125-| ------ | -------- | ------- | ------------------------------------------------------------------- |
126126-| `args` | string[] | `[]` | Extra arguments passed to `restic mount` (e.g. `["--allow-other"]`) |
127127+| Key | Type | Default | Description |
128128+| ------ | -------- | ------- | --------------------------------- |
129129+| `args` | string[] | `[]` | Extra arguments passed to `mount` |
127130128131## Credits
132132+133133+Wouldn't have been possible without:
134134+135135+- [github.com/restic/restic](https://github.com/restic/restic)
136136+- [github.com/rustic-rs/rustic](https://github.com/rustic-rs/rustic)
129137130138Inspirations and cool things:
131139
···99 default = true;
1010 description = "MacOS/Windows only. Check for new Restray versions on startup and periodically. When an update is found, the version item in the menu changes to an update button";
1111 };
1212- manageRestic = lib.mkOption {
1212+ manageBackend = lib.mkOption {
1313 type = lib.types.bool;
1414 default = false;
1515- description = "Windows/Linux only. If restic is not found in PATH, automatically downloads and updates a self-managed restic binary daily from GitHub.";
1515+ description = "Windows/Linux only. If the selected backend is not found in PATH, automatically self-manages it's binary from GitHub.";
1616 };
1717 notifications = lib.mkOption {
1818 type = lib.types.str;
···4444 default = name;
4545 description = "Name for the profile.";
4646 };
4747+ backend = lib.mkOption {
4848+ type = lib.types.enum ["restic" "rustic"];
4949+ default = "restic";
5050+ description = ''"restic" or "rustic". Do not change unless you know what this does.'';
5151+ };
4752 envFile = lib.mkOption {
4853 type = lib.types.nullOr lib.types.str;
4954 default = null;
5050- description = "Path to the environment file with RESTIC_REPOSITORY, RESTIC_PASSWORD, etc.";
5555+ description = "Path to the environment file with RESTIC_*, RUSTIC_*, etc.";
5156 example = "/run/secrets/restray-home-env";
5257 };
5358 rcloneConfigFile = lib.mkOption {
5459 type = lib.types.nullOr lib.types.str;
5560 default = null;
5656- description = "Path to an rclone config file, set as RCLONE_CONFIG for this profile. When using an rclone: repository.";
6161+ description = "Path to an rclone config file, set as RCLONE_CONFIG. When using an rclone: repository.";
5762 example = "/run/agenix/rclone-onedrive";
5863 };
5964 retryLock = lib.mkOption {
6065 type = lib.types.str;
6166 default = "2m";
6262- description = "How long to retry acquiring a repository lock (e.g. \"5m\", \"30s\"). Will automatically attempt to clear stale locks after this timeout. Set to \"0\" to disable.";
6767+ description = "How long to retry acquiring a repository lock (e.g. \"5m\", \"30s\"). Will automatically attempt to clear stale locks after this timeout. Set to \"0\" to disable";
6368 };
6469 preHook = lib.mkOption {
6570 type = lib.types.str;
6671 default = "";
6767- description = "Shell command to run before a scheduled run. Inherits the profile's environment and restic binary. The schedule is aborted if the hook exits non-zero. Can also be run manually from the Operations menu.";
7272+ description = "Shell command to run before a scheduled run. Inherits the profile's environment and backend binary.";
6873 };
6974 postHook = lib.mkOption {
7075 type = lib.types.str;
7176 default = "";
7272- description = "Shell command to run after a scheduled run. Inherits the profile's environment and restic binary. Can also be run manually from the Operations menu.";
7777+ description = "Shell command to run after a scheduled run. Inherits the profile's environment and backend binary.";
7378 };
7479 schedule = {
7580 cron = lib.mkOption {
···105110 description = "Paths to back up, e.g. [\"/Users/user/Documents\" \"/home/user/Projects\"].";
106111 };
107112 args = lib.mkOption {
108108- type = lib.types.listOf lib.types.str;
109109- default = ["--exclude-caches" "--exclude" "*.tmp"];
110110- description = "Extra arguments passed to restic backup.";
113113+ type = lib.types.nullOr (lib.types.listOf lib.types.str);
114114+ default = null;
115115+ description = "Extra arguments passed to `backup`.";
111116 };
112117 argsScheduled = lib.mkOption {
113118 type = lib.types.listOf lib.types.str;
114119 default = ["--tag" "scheduled"];
115115- description = "Extra arguments appended (not replaced) when running from schedule.";
120120+ description = "Extra arguments appended (not replaced) to `backup` when running from schedule.";
116121 };
117122 };
118123 prune = {
···130135 "--keep-monthly"
131136 "12"
132137 ];
133133- description = "Extra arguments passed to restic forget --prune.";
138138+ description = "Extra arguments passed to `forget --prune`.";
134139 };
135140 };
136141 check = {
137142 args = lib.mkOption {
138143 type = lib.types.listOf lib.types.str;
139144 default = [];
140140- description = "Extra arguments passed to restic check (e.g. [\"--read-data\"]).";
145145+ description = "Extra arguments passed to `check`.";
141146 };
142147 };
143148 mount = {
144149 args = lib.mkOption {
145150 type = lib.types.listOf lib.types.str;
146151 default = [];
147147- description = "Extra arguments passed to restic mount (e.g. [\"--allow-other\"]).";
152152+ description = "Extra arguments passed to `mount`.";
148153 };
149154 };
150155 };
···152157153158 profileToToml = name: prof:
154159 lib.filterAttrsRecursive (_: v: v != null && v != [] && v != "") {
155155- inherit (prof) name;
160160+ inherit (prof) name backend;
156161 env_file = prof.envFile;
157162 rclone_config_file = prof.rcloneConfigFile;
158163 retry_lock = prof.retryLock;
···178183 guiToToml = gui:
179184 lib.filterAttrsRecursive (_: v: v != null && v != [] && v != "") {
180185 check_updates = gui.checkUpdates;
181181- manage_restic = gui.manageRestic;
186186+ manage_backend = gui.manageBackend;
182187 notifications = gui.notifications;
183188 icon = gui.icon;
184189 schedule_display = gui.scheduleDisplay;