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/repokeyandkeys/repokey.2) as byte-identical copies, so a corrupted copy can be distinguished from a wrong passphrase.initwrites both, opening a legacy repository backfills the missing copy best-effort, andcheck/check --repaircompare 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-repohint. Only without a pin is repository-authored ciphertext consulted. Unambiguous blob damage, including Argon2 parameters violating them >= 8pfloor, 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 importmove 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 onRepository::opensucceeding and writes nothing. Import reconciles copies skip → create → replace, so the common recovery case works against append-only servers;--trust-repore-pins after a successful write. Both commands require-Rwhen several repositories are configured.
Cancellation
-
Ctrl-C now cancels
restoreandcheck. 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 partialOk: 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 incheckandrestorewould each have mistranslated a cancel, two into a fabricated corruption report thatcheck --repairwould 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-servershuts down gracefully on SIGINT/SIGTERM.axum::serveran 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:0shows the assigned port.
Bug fixes
-
check.full_everyno longer defaults to 60d and silently overridescheck.max_percent. The default wasSome("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 withoutHOME/XDG_CACHE_HOMErecorded nothing and escalated every cycle to a full 100% check, ignoring a configuredmax_percent. There was also no way to switch it off: omitting the field gave 60d, and the documentedfull_every: nullis rejected by the parser. Periodic full checks are now opt-in,vykar configgains a commentedcheck:block, and the docs drop the wrong default. -
Repository writes no longer fail on SMB, NFS, exFAT and FUSE mounts on macOS.
File::sync_allissuesfcntl(F_FULLFSYNC)on Apple targets, which smbfs rejects withENOTSUP—initdied on its first put with a bare "os error 45". Every repository sync site now routes through helpers that retry with plainfsync(2)when, and only when, the primary attempt reportsENOTSUP/EOPNOTSUPP; any other errno, and any failure of the fallback itself, propagates. Non-Apple targets are unchanged. -
niceis now applied one-way. Unprivileged processes may raise their nice value but not lower it, so the restore-on-drop failed withEACCESonce 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 undernice -nno longer fails either. Non-Unix returnsOkinstead of warning on every run. (#187) -
Nix builds work again. Moving
versioninto[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.mdclaimed 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
HOMEand the XDG variables, which Windows ignores, so the CLI under test read and wrote the runner's real profile.USERPROFILE,LOCALAPPDATAandAPPDATAare 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 | shSee 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.