diff --git a/CLAUDE.md b/CLAUDE.md index 08d6d65f..ecc18dcc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,7 +155,10 @@ hand-maintained per-file tree drifts out of sync with the code. rejected. Does not link `swarm-controller`, mirroring `hivectl` ÷ `hive-c0re`. ⚠️ Its user store is **two files, one authoritative**: `users.json` is canonical, authelia's `users.yml` is a *rendered - artifact* that is written and never read back. + artifact* that is written and never read back. Full, always-current + verb reference (CI-enforced against the clap tree, same pattern as + `hivectl`'s — see `docs/conventions.md`): + [`docs/tools/swarmctl-cli.md`](docs/tools/swarmctl-cli.md). ### External dependencies with no directory here diff --git a/Cargo.lock b/Cargo.lock index d93491cd..aa54e4dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4438,6 +4438,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "clap-markdown", "serde", "serde_json", ] diff --git a/docs/conventions.md b/docs/conventions.md index c260d7a6..e98b6ab6 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -506,6 +506,11 @@ check derivations they don't: flake check catches the drift, and `ci-log` often can't show you why (it 500s on a fast failure), so you're left guessing "builder flake" when it's a stale doc. +- **`swarmctl-docs`** is the same check for `swarmctl` / + `docs/tools/swarmctl-cli.md`: + ```sh + nix develop -c cargo run --bin swarmctl -- markdown-docs > docs/tools/swarmctl-cli.md + ``` - there's also a flake `cargo-test` check and NixOS module evaluation in the set. diff --git a/docs/tools/README.md b/docs/tools/README.md index 67d988eb..8b032235 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -15,6 +15,15 @@ debug agent behavior. - **[hivectl-cli](hivectl-cli.md)** — the exhaustive, auto-generated flag-by-flag reference, kept in lockstep with the binary by CI. +## For the swarm operator + +- **[swarmctl-cli](swarmctl-cli.md)** — the exhaustive, auto-generated + flag-by-flag reference for `swarmctl`, kept in lockstep with the + binary by CI the same way `hivectl-cli.md` is. `swarmctl` itself + runs as root on the swarm-controller host, not through `hivectl` — + see `swarmctl/README.md` for why. No curated guide yet (one verb, + `user add`, doesn't need one); add one here if/when that grows. + ## What your agents can do - **[bash](bash.md)** — background shell execution (`mcp__bash__*`), diff --git a/docs/tools/swarmctl-cli.md b/docs/tools/swarmctl-cli.md new file mode 100644 index 00000000..fab4c561 --- /dev/null +++ b/docs/tools/swarmctl-cli.md @@ -0,0 +1,66 @@ +# Command-Line Help for `swarmctl` + +This document contains the help content for the `swarmctl` command-line program. + +**Command Overview:** + +* [`swarmctl`↴](#swarmctl) +* [`swarmctl user`↴](#swarmctl-user) +* [`swarmctl user add`↴](#swarmctl-user-add) + +## `swarmctl` + +swarm-level operator CLI + +**Usage:** `swarmctl [OPTIONS] ` + +###### **Subcommands:** + +* `user` — Manage subjects in the swarm's SSO provider + +###### **Options:** + +* `--authelia-bin ` — authelia binary used to hash passwords. The argon2 parameters must match the verifier's, so this has to be the *configured* package rather than whatever is on `PATH` +* `--users-file ` — Host-side path of authelia's users database — i.e. the path inside the container, prefixed with the container's root +* `--machine ` — Machine name of the authelia container, for `systemctl -M` +* `--unit ` — authelia's systemd unit inside that container +* `--store ` — Canonical user store + + + +## `swarmctl user` + +Manage subjects in the swarm's SSO provider + +**Usage:** `swarmctl user ` + +###### **Subcommands:** + +* `add` — Add a user, generating a password for them + + + +## `swarmctl user add` + +Add a user, generating a password for them + +**Usage:** `swarmctl user add [OPTIONS] ` + +###### **Arguments:** + +* `` — Login name. Conservative ASCII only — it is a YAML map key and reaches access-control rules and logs + +###### **Options:** + +* `--display-name ` — Name shown in the SSO UI. Defaults to the username +* `--email
` +* `--group ` — Repeatable + + + +
+ + + This document was generated automatically by + clap-markdown. + diff --git a/nix/checks.nix b/nix/checks.nix index 45f500a5..d73b205c 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -138,4 +138,20 @@ in fi touch "$out" ''; + + # `swarmctl` CLI reference freshness check — same shape as + # `hivectl-docs` above, `swarmctl markdown-docs` (clap-markdown over + # its own command tree) instead. Reuses `packages..swarmctl` + # (already built as its own package, out of `daemonBins` — see + # nix/packages/default.nix's comment on it). + swarmctl-docs = pkgs.runCommand "swarmctl-docs-fresh" { nativeBuildInputs = [ pkgs.diffutils ]; } '' + ${self.packages.${system}.swarmctl}/bin/swarmctl markdown-docs > generated.md + if ! diff -u ${../docs/tools/swarmctl-cli.md} generated.md; then + echo "" >&2 + echo "ERROR: docs/tools/swarmctl-cli.md is out of date — regenerate it:" >&2 + echo " nix build .#swarmctl && ./result/bin/swarmctl markdown-docs > docs/tools/swarmctl-cli.md" >&2 + exit 1 + fi + touch "$out" + ''; } diff --git a/swarmctl/Cargo.toml b/swarmctl/Cargo.toml index adacba4e..ed572751 100644 --- a/swarmctl/Cargo.toml +++ b/swarmctl/Cargo.toml @@ -11,6 +11,7 @@ path = "src/main.rs" [dependencies] anyhow.workspace = true clap.workspace = true +clap-markdown = "0.1" serde.workspace = true serde_json.workspace = true diff --git a/swarmctl/src/main.rs b/swarmctl/src/main.rs index 0c767e2d..d8c78c25 100644 --- a/swarmctl/src/main.rs +++ b/swarmctl/src/main.rs @@ -132,6 +132,17 @@ enum Verb { #[command(subcommand)] command: UserVerb, }, + /// Emit the full CLI reference as `CommonMark` to stdout. + /// + /// Hidden tooling command used by the docs build to keep the published + /// `swarmctl` reference in lockstep with the code — same pattern as + /// `hivectl markdown-docs` (`hivectl/src/main.rs`). Deliberately + /// dispatched *before* `PathArgs::resolve()` in `main` below: this + /// verb needs none of the `SWARMCTL_AUTHELIA_*` deployment env vars, + /// and requiring them here would make `swarmctl markdown-docs` fail + /// outside a real deployment — exactly where the docs build runs it. + #[command(hide = true)] + MarkdownDocs, } #[derive(Subcommand)] @@ -157,11 +168,17 @@ struct AddArgs { fn main() -> Result<()> { let Cli { paths, command } = Cli::parse(); - let paths = paths.resolve()?; match command { + // Resolved lazily, inside the one arm that actually touches the + // deployment env vars — see the `MarkdownDocs` doc comment above + // for why an unconditional resolve up front would be wrong. Verb::User { command: UserVerb::Add(args), - } => user_add(&paths, args), + } => user_add(&paths.resolve()?, args), + Verb::MarkdownDocs => { + print!("{}", clap_markdown::help_markdown::()); + Ok(()) + } } }