aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/adguard.nix58
-rw-r--r--modules/sops.nix1
-rw-r--r--secrets/secrets.yaml6
3 files changed, 45 insertions, 20 deletions
diff --git a/modules/adguard.nix b/modules/adguard.nix
index b3974d5..b3a7ee2 100644
--- a/modules/adguard.nix
+++ b/modules/adguard.nix
@@ -34,14 +34,15 @@
enable_dnssec = true;
};
- # Users block: username "admin", password from sops secret at activation.
- # The activation script below writes the hash into the config before
- # AdGuard starts, because mutableSettings=false uses a static config file
- # but the password hash must be injected at runtime (it contains a secret).
+ # Users block: username "admin", password from sops secret.
+ # The ExecStartPre below writes the hash into the config on every
+ # service start, because mutableSettings=false uses a static config
+ # file but the password hash must be injected at runtime (it contains
+ # a secret).
users = [
{
name = "admin";
- # Placeholder - replaced at activation time by the script below
+ # Placeholder - replaced at service start by the ExecStartPre below
password = "REPLACED_AT_ACTIVATION";
}
];
@@ -70,17 +71,40 @@
};
};
- # At activation: inject the bcrypt password hash from the sops secret into
- # the AdGuard config so the declarative config has the real hash.
- system.activationScripts.adguard-password = {
- deps = [ "setupSecrets" ];
- text = ''
- HASH_FILE="${config.sops.secrets."adguard/password_hash".path}"
- CFG="/var/lib/AdGuardHome/AdGuardHome.yaml"
- if [ -f "$HASH_FILE" ] && [ -f "$CFG" ]; then
- HASH=$(cat "$HASH_FILE")
- ${pkgs.gnused}/bin/sed -i "s|REPLACED_AT_ACTIVATION|$HASH|g" "$CFG"
- fi
- '';
+ # AdGuardHome needs a static system user rather than the module's default
+ # DynamicUser=true: sops-nix chowns the "adguard/password_hash" secret to
+ # this user *during activation*, which happens while the service is
+ # stopped (for a restart) - a DynamicUser only exists while its service is
+ # actually running, so that chown would fail to resolve the user otherwise.
+ users.users.adguardhome = {
+ isSystemUser = true;
+ group = "adguardhome";
+ };
+ users.groups.adguardhome = { };
+
+ # mutableSettings = false makes the module's own ExecStartPre unconditionally
+ # `cp --force` the store-generated config (with the literal placeholder)
+ # over $STATE_DIRECTORY/AdGuardHome.yaml on every service start. An
+ # activation script can't win that race, so inject the real hash as a
+ # second ExecStartPre, ordered after the module's via mkAfter, so it always
+ # runs right before ExecStart.
+ #
+ # Plain bash substring substitution + `>` redirect is used instead of
+ # `sed -i`: sed -i creates a temp file and chowns/renames it in place, and
+ # this unit's hardened SystemCallFilter (~@privileged) blocks the chown
+ # syscall, killing sed with SIGSYS ("Bad system call").
+ systemd.services.adguardhome.serviceConfig = {
+ DynamicUser = lib.mkForce false;
+ User = "adguardhome";
+ Group = "adguardhome";
+ ExecStartPre = lib.mkAfter [
+ "${pkgs.writeShellScript "adguard-inject-password" ''
+ set -eu
+ CFG="$STATE_DIRECTORY/AdGuardHome.yaml"
+ HASH=$(cat "${config.sops.secrets."adguard/password_hash".path}")
+ CONTENT=$(cat "$CFG")
+ printf '%s\n' "''${CONTENT//REPLACED_AT_ACTIVATION/$HASH}" > "$CFG"
+ ''}"
+ ];
};
}
diff --git a/modules/sops.nix b/modules/sops.nix
index 5e0fbf6..a71b370 100644
--- a/modules/sops.nix
+++ b/modules/sops.nix
@@ -30,6 +30,7 @@
owner = "adguardhome";
group = "adguardhome";
mode = "0400";
+ restartUnits = [ "adguardhome.service" ];
};
};
};
diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml
index cceb667..9743031 100644
--- a/secrets/secrets.yaml
+++ b/secrets/secrets.yaml
@@ -15,7 +15,7 @@ adguard:
#ENC[AES256_GCM,data:1ocY/CjzEgCHElmYvQ2tOjYWNvzcZoJ2+OkVCpfJacFFiTj2CdQMcll73A==,iv:DARKyf3VVaak5OYpm2S+PZA2N/IPPtagsmyZ2eITvoY=,tag:4ZqRiLndJq+mJ81bC4ziEw==,type:comment]
#ENC[AES256_GCM,data:cC440EbfvOXi6wDg5n156S2erjdQdPPovQcKwJnzC5lDlg==,iv:M/oFb/VOdJ0KUITHIM637PCPrqwYIWZcRT+7IPRFzS0=,tag:X97wxUPbR17VonGEgi1VKQ==,type:comment]
#ENC[AES256_GCM,data:i2oBLbaTXungNYXNrOvAzWllxgqFqbvV63HaJwtmPTlnRQfcecLRJqvQQ8JqiZuvpodbOo4=,iv:N5jzPJndgMAYOoKd4ilxesHcn8f9CWlLnMXfkt43umI=,tag:F/KgIP0bYLS77iT2YAME6Q==,type:comment]
- password_hash: ENC[AES256_GCM,data:vXg5gCnoKKEr9SiIzrKTvQpmZ+VdEsPgSS6VkngfMfDNjZi8HajdyTr/yEf3o+4aCUpspavK3mUo2OrT,iv:DwEo/n8Mj1tuaVeMjAut2RnPrS7Zb4QVQgZAnmi8c7c=,tag:enfnWA9xgkKz6gfC2iSttw==,type:str]
+ password_hash: ENC[AES256_GCM,data:0P/TJIh1GMr8qMkKcXeY8r8W1kCgxtL281RXOsnuOltuE0NUo3TX4MR3xtte+50HtSR+X+JaGjwieA2E,iv:6g1oGO9qy69HoTjzWamw/1WXJB7WUr7/9a7Ellxord4=,tag:7V48yLNEmQ8wedsGEi31qg==,type:str]
sops:
age:
- enc: |
@@ -45,7 +45,7 @@ sops:
UlIAI7hmcHeknssKI8/yiR6KqLU0PSqQQaFBZWELeUeVcR7l6NiRWA==
-----END AGE ENCRYPTED FILE-----
recipient: age1rudk653pqzcly6dvemsc5wvq005cfjg4esgrk46zm963tlhs6cjqcrad7t
- lastmodified: "2026-07-18T16:14:49Z"
- mac: ENC[AES256_GCM,data:tNI1cA3J/J3KjINpY0jl79z53tRkCOVqT+ZfnJNNanuVYNcVUaPU44CRXk6LbPtWREo8bgRdk8peVUxo/C8UJCsooh6XdrjHWfLpwTyy2B0hs9i3pkL4Fqr9DxLWu7KqrZLaCEqMzydS4PkvVsjzZ3Lq55sDG9a4S3BU9cl7Dhw=,iv:SP2CUu1jUO4/082Pmt8D8JuuqaZW7wNEJSSBzZPeOro=,tag:sMg7/fsJBDDoWkAIJsk28g==,type:str]
+ lastmodified: "2026-07-18T20:03:40Z"
+ mac: ENC[AES256_GCM,data:iY7U9K8UqsPpvxSLsYrT2pyOsveSgQYQHybVDWVNSo+R/XbQqE6XvbOINZ5C/BQfMOJJdOVDm9atadQckrZNZNuCTd8dYyt8f/cwxL/RnzY/kqGbyTz+6/1LolP5M+r+Lz4z4XFhD1wQZutrlU5Vk9dTT6yZiJaB4pu+qL1NZek=,iv:0WFvNQeo3CuqI38wDM1DCULDY8ho22MnLudUZMUPN2k=,tag:9LgUCN4l5Dk5+mF0NwcHvA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.2