feat(gateway): hivectl gateway user management + fix htpasswdFile assertion

Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.

Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.

Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
This commit is contained in:
atlas 2026-06-01 23:00:38 +02:00
commit 4bff450343
61 changed files with 1084 additions and 547 deletions

View file

@ -147,7 +147,9 @@ fn migrate_harness_files(name: &str) {
}
match std::fs::rename(&src, &dst) {
Ok(()) => tracing::info!(%name, %file, "migration: moved to harness dir"),
Err(e) => tracing::warn!(%name, %file, error = ?e, "migration: move to harness dir failed"),
Err(e) => {
tracing::warn!(%name, %file, error = ?e, "migration: move to harness dir failed")
}
}
}
}
@ -190,7 +192,11 @@ async fn rename_manager_container(coord: &Arc<Coordinator>) {
// Stop the old container. Abort if stop fails — continuing with a
// running `root` and then starting `h-root` risks two manager
// instances racing for the same broker / state files.
match Command::new("nixos-container").args(["stop", "root"]).status().await {
match Command::new("nixos-container")
.args(["stop", "root"])
.status()
.await
{
Ok(s) if s.success() => {}
Ok(s) => {
tracing::warn!(status = %s, "migration phase 5: nixos-container stop root failed — aborting");
@ -218,12 +224,20 @@ async fn rename_manager_container(coord: &Arc<Coordinator>) {
}
// Daemon reload so systemd picks up the new container@h-root unit.
if let Err(e) = Command::new("systemctl").args(["daemon-reload"]).status().await {
if let Err(e) = Command::new("systemctl")
.args(["daemon-reload"])
.status()
.await
{
tracing::warn!(error = ?e, "migration phase 5: systemctl daemon-reload failed");
}
// Start the renamed container.
if let Err(e) = Command::new("nixos-container").args(["start", "h-root"]).status().await {
if let Err(e) = Command::new("nixos-container")
.args(["start", "h-root"])
.status()
.await
{
tracing::warn!(error = ?e, "migration phase 5: nixos-container start h-root failed");
return;
}