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