From 0138b0e0ac655af18f577d30a4a1afad53f3a3a8 Mon Sep 17 00:00:00 2001 From: damocles Date: Mon, 17 Aug 2026 18:50:27 +0200 Subject: [PATCH] swarmctl: add `user list` --- docs/tools/swarmctl-cli.md | 12 ++++++++++++ swarmctl/src/main.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/docs/tools/swarmctl-cli.md b/docs/tools/swarmctl-cli.md index 1b654fd0..d38368fc 100644 --- a/docs/tools/swarmctl-cli.md +++ b/docs/tools/swarmctl-cli.md @@ -8,6 +8,7 @@ This document contains the help content for the `swarmctl` command-line program. * [`swarmctl user`↴](#swarmctl-user) * [`swarmctl user add`↴](#swarmctl-user-add) * [`swarmctl user update`↴](#swarmctl-user-update) +* [`swarmctl user list`↴](#swarmctl-user-list) * [`swarmctl completions`↴](#swarmctl-completions) ## `swarmctl` @@ -41,6 +42,7 @@ Manage subjects in the swarm's SSO provider * `add` — Add a user, generating a password for them * `update` — Change an existing user's attributes +* `list` — List every user in the canonical store @@ -83,6 +85,16 @@ Every flag is optional and they compose, so one call can set several things at o +## `swarmctl user list` + +List every user in the canonical store. + +Reads `store` only — never authelia's rendered `users.yml`, which is a derived artifact this crate writes and never reads back (see the crate doc comment). One line per user: username, display name, email (if set), groups (if any). + +**Usage:** `swarmctl user list` + + + ## `swarmctl completions` Generate a shell completion script for `swarmctl` and print it to stdout. diff --git a/swarmctl/src/main.rs b/swarmctl/src/main.rs index 3eee3917..b3ff4dc2 100644 --- a/swarmctl/src/main.rs +++ b/swarmctl/src/main.rs @@ -172,6 +172,13 @@ enum UserVerb { /// editing an attribute, and folded together an attribute edit can /// invalidate a login by accident. Update(UpdateArgs), + /// List every user in the canonical store. + /// + /// Reads `store` only — never authelia's rendered `users.yml`, which + /// is a derived artifact this crate writes and never reads back (see + /// the crate doc comment). One line per user: username, display name, + /// email (if set), groups (if any). + List, } #[derive(Args)] @@ -220,6 +227,9 @@ fn main() -> Result<()> { Verb::User { command: UserVerb::Update(args), } => user_update(&paths.resolve()?, args), + Verb::User { + command: UserVerb::List, + } => user_list(&paths.resolve()?), Verb::MarkdownDocs => { print!("{}", clap_markdown::help_markdown::()); Ok(()) @@ -306,6 +316,29 @@ fn user_update(paths: &Paths, args: UpdateArgs) -> Result<()> { Ok(()) } +/// `swarmctl user list` — read-only, never touches authelia's users file +/// or restarts it. Prints one line per user from the canonical store. +fn user_list(paths: &Paths) -> Result<()> { + use std::fmt::Write as _; + + let store = load_store(&paths.store, &paths.users_file)?; + if store.users.is_empty() { + println!("no users in {}", paths.store.display()); + return Ok(()); + } + for (username, user) in &store.users { + let mut line = format!("{username}\t{}", user.displayname); + if let Some(email) = &user.email { + let _ = write!(line, "\t{email}"); + } + if !user.groups.is_empty() { + let _ = write!(line, "\tgroups: {}", users::fmt_groups(&user.groups)); + } + println!("{line}"); + } + Ok(()) +} + /// Write the store + the rendered users file, then restart authelia. /// /// Shared by every verb that mutates the store, so the ordering rules