fix(config-backup): retention survives an unreadable or reset settings.json, restore works on mode-less filesystems, compare checks snapshots too

This commit is contained in:
Devvyn committed 2026-10-20 18:03:55 +08:00
1 parent c5f1e99036
commit c982fa1ce5
6 files changed
+215 -22

No files matched your search

+13 -4
View File
@@ -90,7 +90,7 @@ Two limits, applied after every backup:
| **Keep this many backups** (`configBackupKeepCount`) | 2 – 500 | 20 | Keep the newest N; older ones are removed. (1 is not offered: every change would delete the state before it.) |
| **Delete backups older than (days)** (`configBackupKeepDays`) | 0 – 3650 | 30 | Also remove a backup once it has been replaced for longer than this. `0` means no age limit. |
Both apply together. Three guarantees:
Both apply together. Four guarantees:
- **The newest backup is never removed**, whatever the numbers say, so there is always one to
go back to.
@@ -125,7 +125,11 @@ From the list, press **Restore** on a backup and confirm. Codeman then:
1. **Verifies** every file in the backup against the SHA-256 in its manifest. If any file is
missing or does not match, nothing is changed and you get an error. It also refuses a backup
folder or file that is a symlink or that group or others can read or write (Codeman never
makes one like that, and writing it back as `0600` would turn it into trusted config).
makes one like that, and writing it back as `0600` would turn it into trusted config). The
permission test is skipped when the backup folder's filesystem does not keep file modes
(FAT/exFAT drives, SMB shares, WSL's `/mnt/d` without the `metadata` option report the same
mode for every file, so it would refuse every backup there). The symlink test is not. Compare
applies the same checks to the backups it reads.
2. **Takes a `pre-restore` backup** of the files about to be replaced, so a restore can itself
be undone.
3. **Replaces each file atomically** (written beside it, then renamed) with mode `0600`.
@@ -147,7 +151,9 @@ When the backup folder is shared (a synced folder used by two machines, or prod
each snapshot records a random id that the instance made once and keeps in its data directory
(`config-backup-id`; not the hostname, which a Docker container changes on every recreate). Every
instance only snapshots, lists, prunes, compares and restores its own. Deleting that file makes
the instance a new owner, and its earlier snapshots stop being listed (they stay on disk).
the instance a new owner, and its earlier snapshots stop being listed (they stay on disk). The
same happens after a reinstall of the data directory. To see them again, copy the `origin` value
from any of their `manifest.json` files into `<data dir>/config-backup-id`.
You can also restore by hand: the files in a backup folder are the originals, so copying one back
over the file in the data directory is equivalent (stop Codeman first if you are replacing
@@ -178,11 +184,14 @@ query and fragment.
## Security
- Some tracked files carry credentials (`.env`, `users.json`, `custom-model-hosts.json`,
`push-keys.json`). Each backup folder is mode `0700` and its files `0600`, so the default
`push-keys.json`, `webhook.json`). Each backup folder is mode `0700` and its files `0600`, so the default
location exposes nothing the data directory does not already.
- **A custom folder is your decision.** Putting it on a shared drive, a synced folder or a
network share copies those credentials there, with whatever permissions that location has.
Choose a private location, or turn the feature off if you cannot.
- **Retention survives a bad `settings.json`.** If it does not parse, a pass still takes its
snapshot but deletes nothing. If the four backup settings are missing (a reset file), the pass
prunes with the retention the newest backup recorded, not the 20 copies and 30 days defaults.
- **Anyone who can write to a custom backup folder can plant a backup that Restore applies.**
Restore refuses symlinks and files others can write, but a file of your own user's making
passes, so keep the folder private.
+5
View File
@@ -142,6 +142,11 @@ which is the default, you have copies:
2. **Restore** it, reload the page, and restart Codeman if the response's `restartRequired`
list is not empty.
A reset `settings.json` also drops the backup folder setting. If you use a custom folder, enter
it again and save before looking for the list; until then the list shows the default folder.
After a reinstall of the data directory the old backups are unlisted because the instance has a
new id; see [Config Backups](Config-Backups) for how to make them visible again.
Without the UI: the backups are plain folders in `~/.codeman/backups/config/`
(`cfg-YYYYMMDD-HHMMSS`); copy the file you need back over the one in `~/.codeman/`.