feat: arcticwolfctl admin CLI — exports, audit, metrics, graceful shutdown (closes #25)#29
Merged
Merged
Conversation
|
Important Installation incomplete: to start using Gemini Code Assist, please ask the organization owner(s) to visit the Gemini Code Assist Admin Console and sign the Terms of Services. |
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
May 19, 2026 12:40
6af4dff to
bdebec8
Compare
Phase 1 of polrstorage#25. Adds the admin server skeleton that the rest of the admin CLI work hangs off: - New src/admin/ module with protocol.rs (length-prefixed JSON codec), request/response enums, server.rs accepting Unix socket connections, and an AdminContext placeholder. - New [admin] config section with enabled=false default so the scaffolding is inert unless explicitly turned on. - main.rs binds the admin Unix socket when [admin] enabled=true and spawns the listener alongside the portmap/mount/nfs RPC servers; missing socket parent directory or insufficient write permission causes fail-fast startup error. - Adds tokio-util (codec) and nix (will be used for SO_PEERCRED in the audit phase) to Cargo.toml. - Dispatcher currently returns "Command not implemented in phase 1" for every request; phase 2 wires up Status. Tests cover the codec round-trip and the unknown-command error shape; existing multi-export tests are untouched. Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
4 times, most recently
from
May 21, 2026 05:22
687b42e to
e577017
Compare
Phase 2 of polrstorage#25. The dispatcher now handles two read-only commands end-to-end: - status — daemon uptime, actual listening ports (after bind so port=0 dynamic allocation is resolved), bind address, current log level, and the number of configured exports. - version — daemon CARGO_PKG_VERSION plus vergen-derived build_commit, rustc_version, and build_profile. AdminContext gains the fields these commands read: start_time, ServerMetadata (actual ports), startup_log_level (Phase 3 will replace this with a reload handle), filesystem (for export_count), and config (for bind_address). The new `arcticwolfctl` binary is the operator-facing client. It speaks the Phase 1 wire protocol and supports `status`, `version`, and a global `--socket` flag. Tests cover the in-process client/daemon round trip for both commands and assert the response JSON shape. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> Signed-off-by: amarok-bot <[email protected]>
…age#25) Phase 2.a of polrstorage#25 — a cross-cutting cleanup required before Phase 5 needs admin export mutations to be observable by the NFS path. Until now the daemon binary re-declared every module (mod config; mod fsal; ...) instead of importing them from the library crate, causing every type to be compiled twice and creating two distinct Type identities the borrow checker couldn't unify. Phase 2 of polrstorage#25 worked around this by building a second MultiExportFilesystem instance just so the AdminContext (which lives in the lib crate) could hold an Arc<dyn NfsBackend> of the right type. main.rs now imports modules from the library (use arcticwolf::{config, fsal, ...}) and the daemon owns a single MultiExportFilesystem instance that is shared between the three RPC servers and the admin server. Phase 5 will need this single source of truth so exports add/remove/update is observable by both the NFS path and the admin path. No NFS or admin behavior change; pure structural cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
May 21, 2026 10:17
e577017 to
2afb449
Compare
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
May 31, 2026 14:51
0bc0713 to
75fe6e2
Compare
Phase 3 of polrstorage#25. Migrates the daemon's tracing setup from `tracing_subscriber::fmt().init()` to `Registry` + `EnvFilter` + `reload::Layer`. The reload handle is threaded through `AdminContext`, replacing the static `startup_log_level: String` field added in Phase 2 — `status` now reports the live filter directive, and the new commands let operators tune verbosity at runtime without a daemon restart. AdminRequest gains LogLevelGet and LogLevelSet { level } variants; the dispatcher routes them to handlers in src/admin/commands/log_level.rs. Set rejects unparseable filter strings (e.g. `tomato`) with an error and leaves the previous filter intact, matching the AC. arcticwolfctl learns nested `log-level get` and `log-level set <level>` subcommands; both honour `--socket` and `--json`. Tests cover the set→get round-trip, the rejected-input path where filter must not change, and the arcticwolfctl exit-code contract for both success and failure. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
May 31, 2026 16:44
75fe6e2 to
e66d44c
Compare
Phase 4 of polrstorage#25. Wraps the export map in arc_swap::ArcSwap so admin commands (Phase 5) can mutate it concurrently with live NFS traffic, without holding a guard across any .await. The snapshot type is ExportsSnapshot { by_uid, by_name, retired_uids }. retired_uids tracks export uids that were live and then removed during this daemon's lifetime, preventing reuse via add_export until the next restart (where the config file is re-read fresh). All Filesystem and ExportRegistry trait methods load the snapshot, clone the inner Arc<LocalFilesystem> out, drop the snapshot guard, and only then .await. This pattern is the reason the wrapper exists at all - the dual-compilation cleanup in Phase 2.a already ensured the snapshot is shared between the NFS dispatchers and admin commands. Single-handle ops funnel through `inner_for_handle`; the two cross-export-pair validators (rename, link) share `inner_for_same_export_pair` so the "load -> clone Arc -> drop guard" shape exists in exactly two places and rustfmt has no excuse to wrap either one weirdly. New inherent methods on MultiExportFilesystem: - add_export(&ExportConfig) - rebuilds snapshot, swaps atomically. Rejects uid in use, retired uids, name in use, and invalid config (uid 0, name not starting with `/`, non-absolute local path). - remove_export(&ExportSelector) - moves uid to retired_uids. Returns the removed uid. - update_export(&ExportSelector, Option<bool> read_only) - v1 only mutates read_only. Larger mutations (name, uid, path) are deferred per the polrstorage#25 plan. ExportSelector lives in fsal::multi_export so the storage layer owns its own selector type; Phase 5 wires the admin command on top. This phase does not add any admin command yet. NFS client behaviour is unchanged. Tests cover add/remove/update happy paths, all rejection modes (duplicate uid/name, retired uid reuse, no-op update, not-found, invalid config), and a read-only flip observable via is_read_only(handle) round-trip. Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
June 1, 2026 12:58
96ac41c to
b7bfc06
Compare
Implements Phase 5 of polrstorage#25. New AdminRequest variants exports-list, exports-add, exports-remove, exports-update, config-show; handlers delegate to MultiExportFilesystem inherent methods from Phase 4. arcticwolfctl gains the matching subcommand tree. --dry-run on mutating commands validates without taking the write lock. config show returns the startup config (sensitive-field redaction is a follow-up when non-local fsals land). Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
June 2, 2026 01:33
6b63861 to
e238897
Compare
Implements Phase 6 of polrstorage#25. Every admin request emits one JSONL audit event including peer credentials (uid/gid/pid via SO_PEERCRED), the wire command tag, the request payload, the result (ok/err with error message), and duration in ms. Configurable via [audit] enabled/path in the daemon config; disabled by default. NoopAuditWriter is used when disabled. Sensitive-field redaction in the request payload is deferred to the S3 backend follow-up. Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
June 14, 2026 01:33
7a6c3f2 to
0b43f34
Compare
Implements Phase 7 of polrstorage#25. New AdminRequest::Metrics variant returns a JSON snapshot of server-wide, per-NFS-op, per-export, and per-admin- command counters. Counters are AtomicU64-backed, monotonically increasing within a daemon lifetime, lock-free reads, no external metrics crate. Per-export counters live on ExportEntry and survive update_export across snapshot rebuilds; they are dropped on remove_export. No HTTP endpoint (deferred per spec). arcticwolfctl gains a metrics subcommand printing the JSON pretty-formatted. Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
June 14, 2026 05:27
2b92837 to
667ea98
Compare
Vicente-Cheng
self-requested a review
June 14, 2026 07:39
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
June 14, 2026 09:04
694ba2e to
b60b27b
Compare
Implements Phase 8 of polrstorage#25 (final phase). Daemon catches SIGTERM and SIGINT, signals all four server loops (portmap, mount, NFS, admin) via a CancellationToken, drains in-flight requests bounded by [shutdown] drain_timeout_seconds (default 30), flushes the audit writer, and removes the admin socket file before exit. A second signal during drain escalates to immediate process exit. Shutdown completes with exit status 0; drain timeout logs a warn but is not a failure. Signed-off-by: amarok-bot <[email protected]>
amarok-60T
force-pushed
the
feature/admin-cli
branch
from
June 14, 2026 09:06
b60b27b to
3ba743c
Compare
amarok-60T
marked this pull request as ready for review
June 14, 2026 09:31
Collaborator
Author
|
Architecture overview as flow charts, in case it helps a cold reviewer orient. 1. Request flow —
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #25.
Adds a Unix-socket-backed admin server to the arcticwolf NFSv3 daemon, plus an
arcticwolfctlclient binary. End-to-end commands:status,version,log-level get/set,exports list/add/remove/update,config show,metrics. Includes structured audit logging, in-memory operational metrics, and graceful shutdown on SIGTERM/SIGINT.No HTTP endpoint. No external metrics crate. No new runtime dependencies beyond
arc-swap,vergen-gitcl, andtokio_util::sync::CancellationToken(already-includedtokio-util0.7).Stack (9 commits)
admin: introduce Unix socket scaffolding with length-prefixed JSONadmin: implement status and version commandsrefactor: drop dual lib/bin module compilation(cross-cutting)admin: implement log-level get/set with tracing reload handlefsal: refactor MultiExportFilesystem to ArcSwap snapshotadmin: wire exports list/add/remove/update and config showadmin: structured audit log with SO_PEERCRED captureadmin: metrics command with per-export and per-NFS-op countersadmin: graceful shutdown on SIGTERM/SIGINTEach phase was independently reviewed under Opus 4.7 1M context; review findings (SHOULD FIX and NICE TO HAVE) were folded into the same commit via
--fixup+--autosquashbefore progressing to the next phase. CI (Build / Test / Lint / NFS Integration Test) is green on every squashed SHA.Key design decisions
arcticwolfctl metricsand the JSONL audit file. This keeps the attack surface minimal and avoids the dependency overhead of an HTTP framework.exports add/remove/updatemutateMultiExportFilesystemvia anArcSwapsnapshot but do NOT write back toconfig.toml. v1 only allows mutating theread_onlyflag; structural changes (uid, name, backend, path) require config edits + restart.--dry-runon every mutating command. The dry-run path validates without taking the write lock or mutating state.ts,peer(uid/gid/pid viaSO_PEERCRED),commandtag, verbatimrequestpayload,result,error(if any),duration_ms. Sensitive-field redaction is deferred until the S3 backend lands (the local backend has no sensitive config fields).AtomicU64counters withOrdering::Relaxed, exposed as a JSON snapshot viaarcticwolfctl metrics. Coversrpc_requests_total/rpc_decode_errors_total/rpc_errors_total, all 22 NFSv3 procedure counters, per-exportrequests/bytes_read/bytes_written/errors, and per-admin-command counters via a pre-populated allowlist (to prevent client-supplied tags from growing the map without bound).CancellationTokenthat stops the four accept loops (portmap, mount, NFS, admin). In-flight per-connection handlers are tracked inJoinSetand awaited. The whole drain is bounded by[shutdown] drain_timeout_seconds(default 30); on timeout, a warn is emitted and the daemon exits 0. A second signal during drain escalates toprocess::exit(128 + signum). Audit channel is flushed and admin socket file is unlinked before exit.--fixup+git rebase -i --autosquash. The history is reviewable phase-by-phase, not fix-by-fix.Configuration
New sections in
config.toml:Config::validate()rejectsaudit.enabled = truewithout a non-emptypath. Seearcticwolf.example.tomlfor the full annotated example.Test coverage
update_export, reset onremove_export)Mutex<Option<Sender>>shutdown idempotencystatus,version,log-level,exports,config-show,metrics,audit,graceful-shutdown— all driving the real Unix-socket wire format.make nfstest) green on every squashed SHA — verifies multi-export mount, EROFS enforcement, MNT NOENT, NFS POSIX against/data.Known limitations / follow-up
RpcServer::serveandadmin::serve_with_shutdowndo not check theCancellationTokenbetween requests; an idle client (TCP keepalive on the NFS port, or anarcticwolfctlthat connected and never sent anything) keeps its handler task alive until the drain timeout fires. Tracked separately — see follow-up issue.compile_error!insrc/lib.rsis unchanged; this PR is Linux-only in practice (matches existing repo policy).DCO
All commits carry
Signed-off-by:trailers.