SKILL.md

  1---
  2name: backing-up-with-keld
  3description: Writes and manages keld configuration for restic backups. Use when the user mentions keld, backup presets, restic config, or needs help writing TOML config for backups.
  4license: GPL-3.0-or-later
  5metadata:
  6  author: Amolith <amolith@secluded.site>
  7---
  8
  9Keld is a TOML-configured wrapper around [restic](https://restic.net/). It resolves layered config presets and exec's restic with the merged result.
 10
 11## Quick reference
 12
 13```bash
 14# Run with a preset
 15keld --preset home@nas backup
 16
 17# Dry-run: show what restic command would execute
 18keld --show-command --preset home@nas backup
 19
 20# Interactive mode (TUI menu)
 21keld
 22
 23# Override restic flags after the command
 24keld --preset home backup --tag daily --exclude '*.tmp'
 25
 26# Override configured backup paths
 27keld --preset home backup /other/path
 28```
 29
 30## Config file discovery
 31
 32Files are loaded in ascending priority order (later wins):
 33
 341. `/usr/share/keld/config.toml`, then sorted `/usr/share/keld/conf.d/*.toml`
 352. `/etc/keld/config.toml`, then sorted `/etc/keld/conf.d/*.toml`
 363. `~/.config/keld/config.toml`, then sorted `~/.config/keld/conf.d/*.toml`
 374. Paths/globs from `KELD_CONFIG_PATHS` (colon-separated)
 385. `KELD_CONFIG_FILE` replaces **all** of the above if set
 39
 40## Config structure
 41
 42### Section merge order
 43
 44For `keld --preset home@cloud backup`, sections merge in this order:
 45
 46```
 47[global] → [global.backup] → [@cloud] → [@cloud.backup] →
 48[home@] → [home@.backup] → [home@cloud] → [home@cloud.backup] →
 49CLI overrides (replace, not append)
 50```
 51
 52Plain presets (no `@`) are simpler: `[global] → [global.backup] → [mypreset] → [mypreset.backup]`.
 53
 54CLI flags and positional args **replace** their config counterparts — they do not merge with arrays or `_arguments`.
 55
 56### Split presets
 57
 58The `@` in a preset name composes two independent halves:
 59
 60- **Prefix** (`home@`): the _what_ — defines sources, excludes, tags
 61- **Suffix** (`@cloud`): the _where_ — defines repository, credentials
 62
 63This lets you mix and match: `home@cloud`, `home@nas`, `media@cloud` all reuse their respective halves.
 64
 65### Special keys
 66
 67| Key          | Purpose                                                            |
 68| ------------ | ------------------------------------------------------------------ |
 69| `_arguments` | Positional args for restic (prefer arrays; string form is whitespace-split) |
 70| `_workdir`   | Directory to chdir before exec                                     |
 71| `_command`   | Restic subcommand (allows aliasing)                                |
 72| `*.environ`  | Section suffix for environment variables                           |
 73
 74### Interpolation
 75
 76Regular config values (not `.environ`) can reference other sections with `${section.key}`:
 77
 78```toml
 79[vars]
 80cache-root = "~/.cache/keld"
 81
 82[global]
 83cache-dir = "${vars.cache-root}/restic"
 84```
 85
 86## Writing config
 87
 88### Minimal example
 89
 90```toml
 91["@nas"]
 92repository = "sftp:nas:/backups/restic"
 93
 94["home@".environ]
 95RESTIC_PASSWORD_COMMAND = "op read 'op://Vault/Backup/password'"
 96
 97["home@".backup]
 98_arguments = ["/home/user/Documents", "/home/user/Projects"]
 99exclude-if-present = ".nobackup"
100```
101
102Invoke with: `keld --preset home@nas backup`
103
104### Repository paths within a backend
105
106Restic supports paths inside rclone remotes and S3 buckets, so one
107remote or bucket can hold multiple independent repositories. Use the
108full preset section to set the path:
109
110```toml
111# Shared S3/B2 credentials — one suffix for the whole account
112["@b2".environ]
113AWS_ACCESS_KEY_ID = "your-key-id"
114AWS_SECRET_ACCESS_KEY = "your-secret"
115
116# Each dataset gets its own path within the bucket
117["media@b2"]
118repository = "s3:https://s3.example.com/my-bucket/media"
119
120["docs@b2"]
121repository = "s3:https://s3.example.com/my-bucket/docs"
122```
123
124The same works for rclone — one remote, different paths:
125
126```toml
127["media@hetzner"]
128repository = "rclone:hetzner_restic:/media"
129
130["docs@hetzner"]
131repository = "rclone:hetzner_restic:/docs"
132```
133
134Services like BorgBase provide per-repository URLs with embedded
135credentials, so each repo is specific to one dataset. Use a split preset
136where the suffix is unique to that dataset (there's no shared suffix to
137reuse):
138
139```toml
140["media@borgbase_media"]
141repository = "rest:https://user:pass@user.repo.borgbase.com"
142```
143
144### Multi-value flags
145
146Use TOML arrays for flags that accept multiple values:
147
148```toml
149["home@".backup]
150_arguments = ["/home/user/Documents", "/home/user/Projects"]
151exclude = ["*.tmp", "node_modules", ".git"]
152tag = ["daily", "home"]
153```
154
155Multi-line strings also work — each line becomes a separate flag value:
156
157```toml
158exclude = """
159*.tmp
160node_modules
161.git"""
162```
163
164### Environment variables
165
166Use the `.environ` suffix on any section to set env vars for restic:
167
168```toml
169["media@".environ]
170RESTIC_PASSWORD_COMMAND = "op read 'op://Vault/Media Backup/password'"
171
172["@b2".environ]
173AWS_ACCESS_KEY_ID = "your-key-id"
174AWS_SECRET_ACCESS_KEY = "your-secret"
175```
176
177#### Fetching secrets with `_COMMAND`
178
179For env vars that restic doesn't natively support fetching via command
180(anything other than `RESTIC_PASSWORD_COMMAND`), keld recognises a
181`_COMMAND` suffix. The value is executed as a shell command, stdout is
182captured (trailing newlines stripped), and the result is set as the env
183var with the suffix removed:
184
185```toml
186["@b2".environ]
187AWS_ACCESS_KEY_ID_COMMAND = "op read 'op://Vault/B2/access-key-id'"
188AWS_SECRET_ACCESS_KEY_COMMAND = "op read 'op://Vault/B2/secret-access-key'"
189```
190
191This keeps secrets out of config files entirely. Any command that prints
192the secret to stdout works (1Password CLI, `pass`, `vault`, etc.).
193
194`RESTIC_PASSWORD_COMMAND` and `RESTIC_FROM_PASSWORD_COMMAND` are passed
195through to restic as-is — restic handles those natively.
196
197#### rclone credentials via env vars
198
199rclone config values can be supplied as env vars named
200`RCLONE_CONFIG_<REMOTE>_<KEY>`. Combined with `_COMMAND`, this lets
201keld fetch rclone credentials from a secret manager too. Remote names
202**must use underscores** (not hyphens) for this to work. Passwords must
203be piped through `rclone obscure -`:
204
205```toml
206["@hetzner".environ]
207RCLONE_CONFIG_HETZNER_RESTIC_USER_COMMAND = "op read 'op://Vault/Hetzner/username'"
208RCLONE_CONFIG_HETZNER_RESTIC_PASS_COMMAND = "op read 'op://Vault/Hetzner/password' | rclone obscure -"
209```
210
211### Key alias
212
213`repository` in config maps to restic's `--repo` flag automatically.
214
215### Wrapped commands
216
217Keld's interactive menu exposes: `backup`, `restore`, `snapshots`, `forget`, `check`, `init`. All other restic commands pass through as subcommands.
218
219## Environment variables
220
221| Variable            | Purpose                                 |
222| ------------------- | --------------------------------------- |
223| `KELD_CONFIG_FILE`  | Single config file (replaces discovery) |
224| `KELD_CONFIG_PATHS` | Colon-separated additional config paths |
225| `KELD_DRYRUN`       | Enable dry-run mode                     |
226| `KELD_EXECUTABLE`   | Override restic binary path             |
227
228## Gotchas
229
230- Across multiple config files, later files win at the top-level table layer
231- Nested tables like `[preset.backup]` and `[*.environ]` are **replaced** wholesale, not deep-merged across files
232- `restic.Run()` uses `syscall.Exec` — it replaces the process. No Go code runs after, no defers execute
233- String-form `_arguments` splits on whitespace — paths with spaces require array form
234- Always verify new config before running live:
235  ```bash
236  keld --show-command --preset <preset> <command>
237  # Or test a specific file before placing it in a discovery path:
238  keld --config ./keld.toml --show-command --preset <preset> <command>
239  ```
240
241## Automation with systemd
242
243For scheduled backups (with daily structural checks) and monthly full integrity verification with systemd user timers, see [config-examples.md](references/config-examples.md).