github borgbase/vykar v0.20.1

4 hours ago

A maintenance release centred on recoverability and interruptibility: the repository key is now stored twice and can be exported to a password manager, Ctrl-C actually cancels restore and check, and vykar-server drains in-flight uploads before exiting.

Key redundancy and portability

  • The wrapped master key is now stored twice (keys/repokey and keys/repokey.2) as byte-identical copies, so a corrupted copy can be distinguished from a wrong passphrase. init writes both, opening a legacy repository backfills the missing copy best-effort, and check / check --repair compare and heal them without the data-loss prompt.

  • Divergent copies are resolved only on positive proof. The local identity pin is decisive in both directions — a matching pin short-circuits before any storage read, and a stale pin that contradicts both candidates is reported as an identity mismatch with the --trust-repo hint. Only without a pin is repository-authored ciphertext consulted. Unambiguous blob damage, including Argon2 parameters violating the m >= 8p floor, is reported as corruption rather than a passphrase failure, so the GUI does not re-prompt for it. An unreadable copy is never confused with an absent one, and is never overwritten.

  • vykar key export / vykar key import move the passphrase-protected blob in and out of a password manager as a PEM-style armor block. Export works on the raw-storage loader, so it does not depend on Repository::open succeeding and writes nothing. Import reconciles copies skip → create → replace, so the common recovery case works against append-only servers; --trust-repo re-pins after a successful write. Both commands require -R when several repositories are configured.

Cancellation

  • Ctrl-C now cancels restore and check. The CLI's SIGINT/SIGTERM handlers and the GUI's Cancel button were never forwarded to either command, so an interrupt did nothing until a second signal hard-killed the process, stranding a .vykar-restore-* staging directory.

    Cancellation surfaces as Interrupted, never as a short partial Ok: every worker pool and early loop exit is followed by a guard on the orchestrating thread before its results are used, so a cancelled restore never publishes a partial tree and a cancelled check is never recorded as a completed step. Interrupted is likewise never reclassified as corruption — five handlers in check and restore would each have mistranslated a cancel, two into a fabricated corruption report that check --repair would then have planned repairs against.

    Restore polls per item, per file, and per storage request; check polls per snapshot, item, pack and chunk. Repair is cancellable up to and including planning; from the first mutation onward it deliberately is not.

  • vykar-server shuts down gracefully on SIGINT/SIGTERM. axum::serve ran with no shutdown hook, so a stop signal killed the process mid-request and dropped in-flight pack uploads. The first signal stops accepting, drains in-flight requests and exits 0; a second exits immediately with 130 (SIGINT) or 143 (SIGTERM), matching the CLI convention. The readiness log now prints after bind, so --listen 127.0.0.1:0 shows the assigned port.

Bug fixes

  • check.full_every no longer defaults to 60d and silently overrides check.max_percent. The default was Some("60d"), and the due-check is fail-open — a missing or unreadable state file returns true. Since the timestamp is only written when a 100% check finishes clean and the cache directory is writable, running under systemd or cron without HOME/XDG_CACHE_HOME recorded nothing and escalated every cycle to a full 100% check, ignoring a configured max_percent. There was also no way to switch it off: omitting the field gave 60d, and the documented full_every: null is rejected by the parser. Periodic full checks are now opt-in, vykar config gains a commented check: block, and the docs drop the wrong default.

  • Repository writes no longer fail on SMB, NFS, exFAT and FUSE mounts on macOS. File::sync_all issues fcntl(F_FULLFSYNC) on Apple targets, which smbfs rejects with ENOTSUPinit died on its first put with a bare "os error 45". Every repository sync site now routes through helpers that retry with plain fsync(2) when, and only when, the primary attempt reports ENOTSUP/EOPNOTSUPP; any other errno, and any failure of the fallback itself, propagates. Non-Apple targets are unchanged.

  • nice is now applied one-way. Unprivileged processes may raise their nice value but not lower it, so the restore-on-drop failed with EACCES once per thread at the end of every backup — and was useless where it did work, since the CLI exits immediately after. Threads already at or above the target are skipped, so a per-repo override or a process started under nice -n no longer fails either. Non-Unix returns Ok instead of warning on every run. (#187)

  • Nix builds work again. Moving version into [workspace.package] meant the per-crate packaging read the attrset { workspace = true; } and flake evaluation failed with "cannot coerce a set to a string" before any build started. The version is now read once from the root manifest.

Performance

  • Streamed chunks are borrowed rather than copied. Chunk slices are lent from a reader-owned buffer sized like bounded StreamCDC, released at the end of each file or segment, with owned bytes retained only where deferred processing or dedup fallback needs them. FastCDC cut points, bounded reads and drift handling are preserved.

Infrastructure

  • MSRV is now declared: Rust 1.98, via [workspace.package] inheritance. Nothing in the repository constrained the Rust version before; docs/src/install.md claimed 1.89 and was the only (unverified) version claim. This also collapses the release version bump to a single field. It is a lower bound only — the toolchain is still unpinned.

  • Test isolation on Windows: the fixtures set HOME and the XDG variables, which Windows ignores, so the CLI under test read and wrote the runner's real profile. USERPROFILE, LOCALAPPDATA and APPDATA are now set everywhere the fixtures spawn the binary, and the in-process helpers get the same treatment.

  • Two Windows CI flakes fixed in the REST and server mocks: closing a socket with an unread request body sends RST, and Windows then discards the not-yet-read response. The mocks now drain request bodies and send Connection: close.

  • Benchmarks updated.

Updating

To update to the latest version, run:

curl -fsSL https://vykar.borgbase.com/install.sh | sh

See the quickstart guide for other install options.

Downloads

Platform Artifact
Linux x86_64 (gnu) vykar-v0.20.1-x86_64-unknown-linux-gnu.tar.gz
Linux x86_64 (musl) vykar-v0.20.1-x86_64-unknown-linux-musl.tar.gz
Linux aarch64 (gnu) vykar-v0.20.1-aarch64-unknown-linux-gnu.tar.gz
Linux aarch64 (musl) vykar-v0.20.1-aarch64-unknown-linux-musl.tar.gz
macOS aarch64 vykar-v0.20.1-aarch64-apple-darwin.tar.gz
Windows x86_64 vykar-v0.20.1-x86_64-pc-windows-msvc.zip
GUI (AppImage) vykar-gui-v0.20.1-x86_64.AppImage

SHA256 checksums are attached to this release.

Don't miss a new vykar release

NewReleases is sending notifications on new releases.