diff options
| author | Karan Jayachandra <mail@karanjayachandra.com> | 2026-07-18 22:09:05 +0200 |
|---|---|---|
| committer | Karan Jayachandra <mail@karanjayachandra.com> | 2026-07-18 22:09:05 +0200 |
| commit | 3fdf4a3e0dbd55b2b1dce12ca0396dc018c4e0e3 (patch) | |
| tree | b5f207fd899673597fe2cc8e3825866eb904296f | |
| parent | e0a0b397fd015b6ec724fa20fa1ede5b8c8c3ddf (diff) | |
Added the changes for the Adguard fix
| -rw-r--r-- | modules/adguard.nix | 58 | ||||
| -rw-r--r-- | modules/sops.nix | 1 | ||||
| -rw-r--r-- | secrets/secrets.yaml | 6 |
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 |
