Skip to content

Zfs snapshot support - #28

Merged
galeone merged 51 commits into
mainfrom
zfs-snapshot
Oct 10, 2026
Merged

galeone merged 51 commits into
mainfrom
zfs-snapshot

Conversation

@galeone

@galeone galeone commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Add the support for creating zfs snapshots, send them to file, and treat this as a Service - so we can uplaod them to a Remote.

Comment thread src/services/zfs.rs Outdated
];

info!(
"Executing: {} {:?} > {}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real risk I see in this approach is that if your checkpoint is bigger than the free space, you risk locking up the server.

Comment thread src/remotes/remote.rs Outdated
Comment thread src/services/zfs.rs Outdated
Comment on lines +171 to +173
String::from("send"),
String::from("-R"),
String::from("-v"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that this will almost definitely not work with encrypted datasets - or, rather, the send will work but it will send the stuff unencrypted

- remove empty stray src/main.rs that caused duplicate bin target
- aws.rs: box aws_sdk_s3::Error in AwsError::RemoteError (clippy result_large_err)
- backup.rs: drop needless late init in upload loop (clippy needless_late_init)
- folders.rs: assert !is_empty() instead of len() > 0 (clippy len_zero)
galeone added 20 commits October 4, 2026 10:08
Keep the working-tree design (new full_when config option, new-style
full/inc snapshot naming, chain-aware cleanup) and fix the remaining
integration issues:

- ZfsConfig in src/config.rs gains optional full_when; Zfs parses it as
  a croner cron expression
- restore the public Service/DynClone integration (Zfs made pub,
  new(config, name), Box<dyn Error> trait impls)
- drop thiserror/serde_with/crate::backup imports that did not exist
  as dependencies; hand-rolled Error enum with ZfsError(io) and
  String variants
- fix broken helpers: latest_of_kind Option<SnapshotKind> call,
  DateTime::from_naive_utc_and_offset, current_dir in list_files
- send() writes zfs send's stdout (not stdin) to the dump file, as the
  original code did
- plan_* tests use real cron expressions (croner does not parse the
  human 'monthly 1 01:00' form)

38 tests pass, clippy and rustfmt clean.
…ity; zfs-side compression

- Extract the human when-format parser from backup.rs into the shared
  src/when.rs module; zfs full_when now accepts the same format as the
  when field (e.g. 'monthly 1 01:00') or a raw cron expression.
- Fix the weekly day-of-week off-by-one (chrono Weekday numbering
  starts at Sunday): 'weekly monday' now maps to cron dow 1.
- Startup gate: for every zfs backup with full_when, compute the
  required keep_last (max runs between two fulls + 1) and refuse to
  start if the configured keep_last would prune the full that
  incrementals still need to restore from.
- zfs send now uses -c -L so blocks compressed on disk stay compressed
  in the dump (OpenZFS >= 2.1.1 sender); log a hint at startup when
  the dataset is compressed and the backup has compression enabled.
- Document the ZFS backup/restore/keep_last semantics in the README.
…it worktree/pull/push fixes, second-precision archive names, typos

See BUGS.md for the full audit; items 1.1, 1.5, 1.7, 3.1, 3.2,
3.3, 3.5, 3.9 and 3.10 are fixed, the rest remain open.
Legacy (<base>-<ts>) snapshots are no longer recognized: parse_snapshot
returns None for them, so they are invisible to cleanup and list_files
only matches <base>-full-<ts>/<base>-inc-<ts> dump files. Pre-existing
legacy snapshots on the pool and orphan .gz files on remotes are simply
left in place.

Also fix a rollover race in the localhost upload tests: they computed
the expected archive name from the clock *after* the upload, so a
second boundary crossed during the upload made the assertion flake.
Assert on the single matching archive instead.
bacup is an executable, so the lockfile should be committed (as the
cargo-new template comment itself advises): fresh clones and gtr7-style
checkouts now resolve the exact same dependency set instead of keeping
stale untracked lockfiles around.
…etection,

docker info connectivity, postgres password/quotes/exit status, whole-word
weekday parsing, lossy process output, storage typo

- 3.8: put_object small-file path streams via ByteStream::from_path instead
  of buffering the whole file in a Vec (up to 1 GiB RSS spike)
- 3.4: detect encrypted openssh-key-v1 private keys (ciphername != none)
- 3.6: docker connectivity check uses docker info instead of pulling hello-world
- 3.7: optional PGPASSWORD, SQL-escape db_name, fail on psql exit status,
  pg_dump gets PGPASSWORD
- 3.12: parse_weekly matches weekday words exactly instead of .contains()
- 2.2: ssh probe output via from_utf8_lossy (strict text stays read_to_string)
- 3.10: 'Storagee' -> 'Storage'
- BUGS.md: full-repo bug report with status
…or unwraps, #2.3 aws upload_folder (partial)

- docker.rs/postgresql.rs/ssh.rs: match the Command::status() Result and
  format ExitStatus via Display instead of status.code().unwrap()
- bin/bacup.rs: every remote/service constructor .unwrap() becomes a
  match that logs the error and returns Err(-1); upcoming().next()
  becomes a let-else on empty schedules
- aws.rs upload_folder: min_by().unwrap() -> let-else error,
  strip_prefix().unwrap() -> match error
…sible

A full_when placed under [backup.<name>] instead of [zfs.<name>] was
silently dropped by serde, so every zfs run took a full backup (and the
keep_last restorability check was skipped).

- config: deny_unknown_fields on every config struct, so a misplaced or
  misspelled key fails at startup
- zfs: log full_when (or its absence) when the service is configured
- zfs: log why a run takes a full backup
- zfs: a failing `zfs list` is now an error instead of an empty snapshot
  list, which silently forced a full
- zfs: the incremental->full fallback dump is named -full-, not -inc-
@galeone
galeone merged commit f5ec8a6 into main Oct 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants