Skip to content

v0.1.0 enhancements: local-testing setup, release/packaging pipeline, install docs, login agent, and test coverage#7

Open
CraZySacX wants to merge 8 commits into
masterfrom
6-v010-enhancements
Open

v0.1.0 enhancements: local-testing setup, release/packaging pipeline, install docs, login agent, and test coverage#7
CraZySacX wants to merge 8 commits into
masterfrom
6-v010-enhancements

Conversation

@CraZySacX

@CraZySacX CraZySacX commented Jun 15, 2026

Copy link
Copy Markdown
Member

This branch carries the v0.1.0 enhancements: an isolated local-testing setup for debug builds, a full tag-driven release/distribution pipeline, per-platform package install documentation with the service-lifecycle fixes it surfaced, an ssh-agent-style login agent with share enrollment, and a CI/packaging fix to drop the system libdbus build dependency.

1. Release pipeline (AUR, DEB/RPM, Homebrew, crates.io, systemd)

A tag-driven release pipeline modeled on the sister projects — moshpit (AUR/dist/packaging), cargo-matrix (crates.io publish), barto (Homebrew tap) — scaled to salus's single-package layout (salusd daemon + salusc client).

  • xtask cratecargo xtask dist salusd|salusc generates bash/zsh/fish completions, man pages, licenses, and (for salusd) the systemd unit and example config. publish = false + cargo-matrix skip-package.
  • packaging/ — a source salus PKGBUILD and a prebuilt static-musl salus-bin PKGBUILD, nfpm DEB/RPM configs (x86_64 + aarch64), a salusd systemd user service, a Homebrew formula template, and a .SRCINFO helper.
  • docker/ + Cross.toml — cross-rs musl images carrying cmake/clang for aws-lc-sys.
  • .github/workflows/release.yml — jobs: build (musl) + build-macos, GitHub release, crates.io publish (libsalussalusdsalusc), PKGBUILD-update PR, AUR publish, signed apt/rpm repo, Homebrew tap update. Publishing jobs are gated off -rc tags so an vX.Y.Z-rc* tag is build-only. Plus test-aur-publish.yml for manual AUR connectivity checks.
  • crates.io prep — pin the libsalus path dep to a version in salusd/salusc so the crates are publishable.
  • scriptsrun_install.fish (cargo install to ~/.cargo/bin) and run_musl.fish (blackdex/rust-musl build), wired into run_all.fish with --no-install / --no-musl / --unstable; the fuzz crate gains a forwarding unstable feature.
  • docs — README "Installation"/"Releasing" sections; CLAUDE.md command list and final-verification note.

Required repository secrets (before the first tag)

CRATES_IO_TOKEN (crates.io), AUR_SSH_PRIVATE_KEY (AUR), HOMEBREW_TAP_TOKEN (Homebrew tap), and PACKAGES_REPO_TOKEN + PACKAGES_GPG_PRIVATE_KEY + PACKAGES_GPG_KEY_ID (signed apt/rpm repo). Companion repos: rustyhorde/homebrew-salus and rustyhorde/salus-packages.

2. Isolated local-testing setup for debug builds

Adds a tracked dev/ directory and a fish helper so debug builds of salusd/salusc can be exercised from the project tree without colliding with a production install on the same machine.

  • Socket. On Linux the default IPC socket is an abstract-namespace name (salus.sock) shared by every install, so a debug daemon would bind the same name as a running production daemon. Pointing the socket at a file path under dev/ switches socket_target to its File branch, isolating the dev pair.
  • Database/config/log. These paths are CLI-only (-d/-c/-t), not read from env or the TOML file, so without -d a debug daemon reads/writes the production DB. The helper always redirects them into dev/.

Changes: dev/salusd.toml, dev/salusc.toml (relaxed key_timeout = 300), dev/.gitignore (tracks only configs), scripts/dev_env.fish (salusd-dev/salusc-dev wrappers), and a README "Local testing (debug builds)" section. Minor maintenance: bump bon 3.9.2 → 3.9.3 and reformat the salusd tracing-subscriber feature list.

3. Package install docs + service-lifecycle fixes

Expands the README "Installation" section into per-platform guides modeled on the moshpit README — Arch/AUR (salus source + salus-bin prebuilt), Debian/Ubuntu (signed apt repo, direct .deb, dpkg, upgrade/removal), Fedora/RHEL (signed dnf repo, direct .rpm), cargo, and Homebrew — and fixes two packaging defects found while writing them.

  • systemd unit path (bug). Packages install the binary at /usr/bin/salusd, but salusd.service pointed ExecStart at %h/.cargo/bin/salusd, so a package-installed systemctl --user enable --now salusd referenced a binary that isn't there. ExecStart now targets /usr/bin/salusd, with a comment for cargo install users (binary at ~/.cargo/bin/salusd).
  • Service lifecycle on install/upgrade. salusd is a per-user service by design (per-user store, config, and socket), so root-run package scripts cannot stop or restart it the way a system service could — and an auto-restart would clear the in-memory key and lock the store. Added DEB/RPM postinstall scriptlets (nfpm overrides) and AUR salusd.install hooks that print the manual systemctl --user daemon-reload && restart + re-unlock guidance instead. The release workflow carries the new .install sidecars automatically (update-pkgbuilds uploads all of packaging/arch/; aur-publish copies every sidecar).
  • README "Running salusd as a systemd user service" gains an Upgrades note and a cargo install path caveat.

4. salus-agent login agent, share enrollment, unlock duration, and lock

An ssh-agent-style login agent and an enrollment workflow that make unlocking convenient without weakening the Shamir guarantee: of the threshold shares needed to unlock, threshold - 1 are stored in the OS keyring (auto-unlocked at login) and the final share is sealed behind a passphrase, so the auto-accessible material alone stays below threshold. Default behavior is unchanged — without enrollment, salusc unlock still prompts for every share.

  • salus-agent crate (4th workspace member, lib + bin) — loads enrolled share sets from the OS keyring at login and serves them to the client over its own IPC socket, with a per-set, generation-guarded passphrase cache (default 3600s TTL; 0 disables). A systemd user unit launches it.
  • keystore (in salus-agent, reused by salusc) — keyring layout (sets registry, shared auto-share-N, per-set <set>/final-blob) and argon2id + AES-256-GCM seal/unseal of the single passphrase share. Multiple named sets are supported (enroll --name), each adding a distinct passphrase share over the shared auto shares so the keyring never holds ≥ threshold.
  • libsalusAgentAction/AgentResponse protocol, agent_socket_name (SALUS_AGENT_SOCKET), UnlockTimeout, and Action::Lock.
  • salusdunlock honors a hold duration (default / N seconds clamped to 24h / forever); new Action::Lock clears the key and bumps the unlock generation so a pending auto-clear timer becomes a no-op.
  • saluscenroll/forget/enroll-status; unlock pulls the auto shares from the agent and prompts only for the passphrase, falling back to manual share entry when the agent is absent; adds unlock --set/--for, lock, and --agent-socket-path.
  • packaging/scripts — xtask dist salus-agent, systemd unit, example config, nfpm DEB/RPM (x86_64 + aarch64), AUR + Homebrew, release publish order libsalus → salus-agent → salusd → salusc, and a salus-agent-dev wrapper / third-binary builds in the helper scripts.

5. Drop the system libdbus build dependency (CI/packaging fix)

The login agent pulled in keyring with the sync-secret-service feature, which links the system libdbus (dbus-secret-service → dbus → libdbus-sys, resolved via pkg-config). The Linux CI runners lack libdbus-1-dev, so the build script panicked (Package dbus-1 was not found); the static-musl release Dockerfiles install pkg-config but not libdbus-1-dev, so the release build would have failed the same way.

Switched keyring to its pure-Rust zbus backend (async-secret-service + async-io + crypto-rust), which speaks the D-Bus wire protocol with no system or C dependency — the right fit for a tool shipping fully-static MUSL binaries and per-distro packages, rather than patching one CI job with an apt install. The async-io runtime (not tokio) is used so keyring's internal block_on does not panic with a nested-runtime error when its synchronous Entry API is called from within the tokio runtime. The public keyring API is unchanged, so no salus-agent code changes were needed.

6. Test coverage for the new agent crate and handlers

Codecov flagged this PR at 24.8% patch / 35.87% project coverage (50% gate), almost entirely from the new salus-agent crate and the daemon/client plumbing landing untested. Added unit tests that match the repo's inline #[cfg(test)] conventions and lift local line coverage from ~36% to ~60%.

  • Handlers made testable — relaxed the sender bound from SendHalf + Unpin to tokio::io::AsyncWrite + Unpin in both the agent and daemon handlers. interprocess's SendHalf already implements tokio AsyncWrite, so production callers are unaffected, while tests drive a handler with a Vec<u8> sender and decode the response bytes.
  • In-memory keyring backend (salus-agent/src/test_keyring.rs) — the stock keyring::mock builds a fresh, non-persistent credential per Entry::new, but keystore opens a new Entry per operation, so writes never round-trip. A custom CredentialBuilder over a process-shared map (the keyring::credential traits are public) fixes this; a guard() helper installs it once, clears it, and serializes keyring tests.
  • Pure helper extraction — pulled the selection parse out of the stdin-bound choose_set into parse_set_choice so it can be unit-tested.
  • New tests cover the keystore enroll/forget/load lifecycle, AgentState cache/generation/lock logic, agent + daemon handler dispatch, logging directives, config loading/defaults, CLI flags, and libsalus socket resolution. Highlights: keystore 26%→97%, agent store 0%→96%, both handlers 0%→~66%, libsalus/lib.rs 44%→91%. The remaining gap is salusc/src/inter/mod.rs (socket/stdin UI), left for an integration pass.

Verification

  • cargo xtask dist salusd|salusc|salus-agent produces the expected completions/man pages/licenses/systemd units; libsalus packages cleanly; both release workflows parse; gen_srcinfo.py output is clean.
  • scripts/run_all.fish --no-fuzz --no-musl --no-install — all tests pass (97 across the workspace), all steps OK (including nightly clippy with -Dwarnings); cargo llvm-cov reports ~60% line coverage, up from ~36%.
  • cargo tree -i libdbus-sys is empty — the C dbus stack is out of the build graph (the lockfile still lists dbus-secret-service only because Cargo.lock records the union of a crate's optional deps).
  • Debug daemon bound dev/salus.sock and created dev/salusd.redb + dev/salusd.log; production paths under ~/.config/salusd/ and ~/.local/share/salusd/ were never touched.
  • New scriptlets validate with sh -n; nfpm YAMLs parse. (nfpm isn't installed locally, so a built .deb/.rpm was not inspected — worth doing before the first tag along with the static-musl aws-lc-rs build via scripts/run_musl.fish.)

🤖 Generated with Claude Code

Introduce a tracked dev/ directory and a fish helper so debug builds of
salusd/salusc can be exercised from the project tree without colliding with
a production install on the same machine.

- dev/salusd.toml, dev/salusc.toml: base config (relaxed key_timeout=300 so
  the in-memory key does not auto-clear during manual testing)
- dev/.gitignore: track only the configs; ignore the runtime db, log, socket
- scripts/dev_env.fish: salusd-dev / salusc-dev wrappers that pass the
  config/db/log/socket paths as explicit CLI flags, pointing everything at dev/
- README: new "Local testing (debug builds)" section explaining socket
  isolation (the default Linux abstract-namespace socket is shared by every
  install, so a file socket under dev/ avoids binding the same name) and that
  the db/config/log paths are CLI-only and must be redirected with -d/-c/-t

Also bump bon 3.9.2 -> 3.9.3 and reformat the salusd tracing-subscriber
feature list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.89504% with 452 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.79%. Comparing base (1f75f57) to head (440f32b).

Files with missing lines Patch % Lines
salusc/src/inter/mod.rs 20.22% 209 Missing ⚠️
salus-agent/src/logging/mod.rs 42.10% 55 Missing ⚠️
salus-agent/src/runtime/mod.rs 0.00% 49 Missing ⚠️
salus-agent/src/config/mod.rs 76.15% 36 Missing ⚠️
salusd/src/handler/mod.rs 73.07% 21 Missing ⚠️
salus-agent/src/handler/mod.rs 90.00% 15 Missing ⚠️
salus-agent/src/test_keyring.rs 81.13% 10 Missing ⚠️
salus-agent/src/runtime/cli.rs 89.02% 9 Missing ⚠️
salusc/src/runtime/mod.rs 0.00% 9 Missing ⚠️
salus-agent/src/store/mod.rs 95.89% 8 Missing ⚠️
... and 9 more
Additional details and impacted files
@@             Coverage Diff             @@
##           master       #7       +/-   ##
===========================================
+ Coverage   44.09%   63.79%   +19.69%     
===========================================
  Files          22       35       +13     
  Lines        1313     2831     +1518     
===========================================
+ Hits          579     1806     +1227     
- Misses        734     1025      +291     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@CraZySacX CraZySacX self-assigned this Jun 15, 2026
@CraZySacX CraZySacX added the enhancement New feature or request label Jun 15, 2026
Introduce a tag-driven release/distribution pipeline modeled on the
sister projects (moshpit for AUR/dist/packaging, cargo-matrix for the
crates.io publish, barto for the Homebrew tap), scaled to salus's
single-package layout (salusd daemon + salusc client).

- xtask crate: `cargo xtask dist salusd|salusc` generates shell
  completions, man pages, licenses, and (for salusd) the systemd unit
  and example config. publish = false + cargo-matrix skip-package.
- packaging/: source `salus` and prebuilt static-musl `salus-bin`
  PKGBUILDs, nfpm DEB/RPM configs (x86_64 + aarch64), a systemd user
  service for salusd, a Homebrew formula template, and a .SRCINFO helper.
- docker/ + Cross.toml: cross-rs musl images carrying cmake/clang for
  aws-lc-sys.
- .github/workflows/release.yml: build (musl) + build-macos, GitHub
  release, crates.io publish (libsalus -> salusd -> salusc), PKGBUILD
  update PR, AUR publish, signed apt/rpm repo, Homebrew tap update;
  publishing gated off `-rc` tags. Plus test-aur-publish.yml for manual
  AUR connectivity checks.
- crates.io prep: pin the libsalus path dep to a version in salusd/salusc
  so the crates are publishable.
- scripts: run_install.fish (cargo install to ~/.cargo/bin) and
  run_musl.fish (blackdex/rust-musl build), wired into run_all.fish with
  --no-install/--no-musl/--unstable; fuzz crate gains a forwarding
  `unstable` feature.
- docs: README "Installation"/"Releasing" sections; CLAUDE.md commands
  and final-verification note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CraZySacX CraZySacX changed the title Add isolated local-testing setup for debug builds v0.1.0 enhancements: local-testing setup and release/packaging pipeline Jun 15, 2026
CraZySacX and others added 3 commits June 15, 2026 17:01
The release workflow and the source PKGBUILD invoke `cargo xtask dist`,
but salus had no `[alias] xtask` (unlike moshpit), so the step failed
with "no such subcommand: xtask". Add .cargo/config.toml defining the
alias so `cargo xtask` resolves on CI and during makepkg builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The release workflow and Arch PKGBUILD called `cargo xtask dist`, which
only resolves via the `[alias]` in .cargo/config.toml. That dependency on
cargo config discovery is fragile in CI (the macOS build job failed with
"no such command: xtask"). Call the workspace binary directly with
`cargo run --locked -p xtask -- dist` so the step never depends on alias
resolution. The .cargo alias stays for local dev convenience.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expand the README with per-platform install/run instructions (AUR,
Debian/Ubuntu apt, Fedora/RHEL dnf, cargo, Homebrew), modeled on the
moshpit README, and fix two packaging defects found while writing them.

- systemd unit path: packages install the binary at /usr/bin/salusd,
  but salusd.service pointed ExecStart at %h/.cargo/bin/salusd, so a
  package-installed `systemctl --user enable --now salusd` started a
  unit referencing a binary that isn't there. Point ExecStart at
  /usr/bin/salusd and document the cargo-install path adjustment.

- service lifecycle on install/upgrade: add post-install guidance
  scriptlets for DEB/RPM (nfpm postinstall) and AUR (.install hooks).
  salusd is a per-user service, so root-run package scripts cannot
  stop or restart it; the scriptlets print the manual `systemctl --user
  daemon-reload && restart` + re-unlock steps instead of faking a
  root-side restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CraZySacX CraZySacX changed the title v0.1.0 enhancements: local-testing setup and release/packaging pipeline v0.1.0 enhancements: local-testing setup, release/packaging pipeline, and install docs Jun 15, 2026
CraZySacX and others added 2 commits June 15, 2026 22:48
Introduce an ssh-agent-style login agent and an enrollment workflow that
make unlocking convenient without weakening the Shamir guarantee: of the
`threshold` shares needed to unlock, `threshold - 1` are stored in the OS
keyring (auto-unlocked at login) and the final share is sealed behind a
passphrase, so the auto-accessible material alone stays below threshold.
Default behavior is unchanged — without enrollment, `salusc unlock` still
prompts for every share.

- salus-agent crate (4th workspace member, lib + bin): loads enrolled
  share sets from the OS keyring at login and serves them to the client
  over its own IPC socket, with a per-set, generation-guarded passphrase
  cache (default 3600s TTL; 0 disables). A systemd user unit launches it.
- keystore (in salus-agent, reused by salusc): keyring layout (sets
  registry, shared `auto-share-N`, per-set `<set>/final-blob`) and
  argon2id + AES-256-GCM seal/unseal of the single passphrase share.
  Multiple named sets are supported (`enroll --name`), each adding a
  distinct passphrase share over the shared auto shares so the keyring
  never holds >= threshold.
- libsalus: AgentAction/AgentResponse protocol, `agent_socket_name`
  (SALUS_AGENT_SOCKET), `UnlockTimeout`, and `Action::Lock`.
- salusd: `unlock` honors a hold duration (default / N seconds clamped to
  24h / forever); new `Action::Lock` clears the key and bumps the unlock
  generation so a pending auto-clear timer becomes a no-op.
- salusc: `enroll`/`forget`/`enroll-status`; `unlock` pulls the auto
  shares from the agent and prompts only for the passphrase, falling back
  to manual share entry when the agent is absent; adds `unlock --set/--for`,
  `lock`, and `--agent-socket-path`.
- packaging: xtask `dist salus-agent`, systemd unit, example config, nfpm
  DEB/RPM (x86_64 + aarch64), AUR `salus` + `salus-bin`, Homebrew, and the
  release workflow (build/dist/checksums + publish order
  libsalus -> salus-agent -> salusd -> salusc).
- scripts: salus-agent-dev wrapper in dev_env.fish; run_install/run_musl
  build the third binary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The keyring `sync-secret-service` feature pulls in
dbus-secret-service -> dbus -> libdbus-sys, which links the system
libdbus via pkg-config. The Linux CI runners lack libdbus-1-dev, so the
build script panicked ("Package dbus-1 was not found"). The static MUSL
release Dockerfiles install pkg-config but not libdbus-1-dev, so the
release build would have failed the same way on its next run.

Switch to keyring's pure-Rust Secret Service backend
(async-secret-service + async-io + crypto-rust), which speaks the D-Bus
wire protocol via zbus and needs no system or C dependency. This fits a
tool that ships fully-static MUSL binaries and per-distro packages,
rather than patching a single CI job with an apt install.

The async-io runtime (not tokio) is used so keyring's internal block_on
does not panic with a nested-runtime error when its synchronous Entry
API is called from within the tokio runtime (AgentState::load at startup
and the spawned handlers). The public keyring API is unchanged, so no
salus-agent code changes are needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CraZySacX CraZySacX changed the title v0.1.0 enhancements: local-testing setup, release/packaging pipeline, and install docs v0.1.0 enhancements: local-testing setup, release/packaging pipeline, install docs, and login agent Jun 16, 2026
Codecov flagged PR #7 at 24.8% patch / 35.87% project coverage, below the
50% gate, mostly from the new salus-agent crate and daemon/client plumbing
that shipped with little or no test coverage.

Make handlers unit-testable by relaxing the sender bound from
`SendHalf + Unpin` to `tokio::io::AsyncWrite + Unpin` in both the agent and
daemon handlers; interprocess's `SendHalf` already implements tokio
`AsyncWrite`, so production callers are unaffected while tests can drive a
handler with a `Vec<u8>` and decode the response.

Add a persistent in-memory keyring backend (test_keyring.rs) since the stock
`keyring::mock` does not survive across the fresh `Entry::new` calls keystore
makes per operation. Extract the pure `parse_set_choice` helper out of the
stdin-bound `choose_set` so the selection logic can be tested.

New tests cover the keystore enroll/forget/load lifecycle, AgentState
cache/generation/lock logic, agent and daemon handler dispatch, logging
directives, config loading/defaults, CLI flags, and libsalus socket
resolution. Local line coverage rises from ~36% to ~60%, clearing the gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CraZySacX CraZySacX changed the title v0.1.0 enhancements: local-testing setup, release/packaging pipeline, install docs, and login agent v0.1.0 enhancements: local-testing setup, release/packaging pipeline, install docs, login agent, and test coverage Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants