Skip to content

v1.6.0 — privileged restore to original locations, and honest verify state

Choose a tag to compare

@amplogik amplogik released this 26 Jul 15:25
· 2 commits to main since this release

Restore to original locations now escalates instead of silently failing

Restoring a system-class plan's files back to where they came from needs root, but that path had no pkexec support at all. The GUI's Original location… button pointed an unprivileged extract at /, so anything outside your own home failed with permission errors partway through — leaving a half-finished restore on disk. The workaround was to extract to $HOME and copy into place with sudo: fine for a sysadmin, not discoverable for the audience the GUI exists for.

  • New privileged helper /usr/libexec/timetraveller-restore-system-files with a matching com.timetraveller.restore-system-files polkit action, same allow_active=auth_admin_keep posture as the other four.
  • The helper's destination is hardcoded to / and is not a parameter. That is load-bearing: accepting a caller-supplied --into would turn it into a write-anywhere-as-root primitive, and any other destination is somewhere you could have written to yourself. It takes no archive path either, deriving the archive directory from the root-owned /etc/timetraveller/<plan>.yaml. Every member must be ./-rooted (which doubles as argv-injection defence), with no .. component, no NUL, no control characters, and capped count and length.
  • Privileges are pre-flighted before any bytes are written, in both the GUI and the CLI. Routing is decided by the destination, not the plan class, so extracting a system-plan member into ~/Restored stays unprivileged and prompt-free.
  • A root-owned destination that is not / is refused rather than escalated — since the helper forces /, routing e.g. --into /opt/foo through it would have scattered files to their original locations instead of the directory you asked for.
  • Source mode (Restore from location…) gets no automatic escalation, because the helper takes its archive directory from the root-owned plan config and never from a caller-supplied path. Documented rather than silently absent.

Two ways the tool could report success it had not earned

The naive (no-sidecar) extract path. It discarded tar's stderr, ignored its exit status entirely (the check was literally pass), then tallied results by walking the destination directory — so a wholly failed extract into a non-empty directory returned a large bytes_written and read as a clean restore. It now honours tar's exit status, distinguishes a genuinely benign "Not found in archive" from a real failure, and counts only the members tar reports having written.

⚠️ Behaviour change: extract.extract_files can now raise extract.ExtractError where it previously returned stats describing a failure as a success.

verify-after-write. It returned a bare [] for four different outcomes — verified-clean, the check threw, there was no checksum-bearing sidecar to compare against, and verification was switched off — so corrupt_frames=0 was unfalsifiable and a backup nobody verified rendered identically to a verified one. The only distinguishing signal was a stderr warning that never reached the log file, and cron entries don't redirect stdout either, so on a scheduled run it left no trace anywhere.

  • New ArchiveEntry.verify_state: verified / unverified / error / disabled. Additive and optional, so old manifests load unchanged.
  • The Archives list shows "ok (unverified)" in muted grey with a tooltip explaining which case applied. Archives written before v1.6.0 carry no verify state and are left unmarked — greying out all history would be noise, and the distinction self-heals from the next backup onward.
  • It deliberately does not feed status or is_complete. An unverified shard is a gap in knowledge, not damage; treating it as corruption would mark healthy cycles incomplete and change what retention prunes.

Logs stop disappearing

/var/log/timetraveller is now owned by the package, so dpkg creates it on install and recreates it on upgrade. It previously existed only because the worker mkdir'd it at runtime, which meant an OS reinstall took the backup logs with it. Verify and corruption diagnostics are now written to the shard's log file rather than only to streams nothing captures.

367 tests pass (42 new).