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

@ -213,7 +213,8 @@ in
'';
htpasswdFile = lib.mkOption {
type = lib.types.path;
type = lib.types.nullOr lib.types.path;
default = null;
example = "/etc/hyperhive/gateway.htpasswd";
description = ''
Path on the **host** to an htpasswd-format file whose
@ -254,16 +255,15 @@ in
'';
}
{
assertion = !cfg.auth.enable || cfg.auth ? htpasswdFile;
assertion = !cfg.auth.enable || cfg.auth.htpasswdFile != null;
message = ''
services.hyperhive.gateway.auth.enable = true requires
services.hyperhive.gateway.auth.htpasswdFile to be set.
Create an htpasswd file with: htpasswd -Bc /path/to/file <username>
Create an htpasswd file with: hivectl gateway create-user --file /path/to/file <username>
'';
}
];
# Ensure bind-mount sources exist at host boot before the gateway
# container's first start. nspawn would auto-create missing dirs
# tmpfiles rules make the intent explicit
@ -316,7 +316,7 @@ in
# Using the parent directory (not the file itself) because nspawn
# bind-mounts need a pre-existing destination — binding a directory
# is always safe; nginx picks the file up by name inside.
bindMounts."/run/gateway-auth" = lib.mkIf cfg.auth.enable {
bindMounts."/run/gateway-auth" = lib.mkIf (cfg.auth.enable && cfg.auth.htpasswdFile != null) {
hostPath = builtins.dirOf cfg.auth.htpasswdFile;
isReadOnly = true;
};
@ -583,7 +583,7 @@ in
extraConfig = ''
proxy_buffering off;
proxy_read_timeout 1d;
${lib.optionalString cfg.auth.enable ''
${lib.optionalString (cfg.auth.enable && cfg.auth.htpasswdFile != null) ''
auth_basic "${cfg.auth.realm}";
auth_basic_user_file /run/gateway-auth/${builtins.baseNameOf cfg.auth.htpasswdFile};
''}