# AdGuard migration to Pikachu: design tree ## Research facts - The pinned Nixpkgs revision provides `adguardhome` version `0.107.79`. - NixOS provides the `services.adguardhome` module. - The module runs AdGuard Home as a hardened dynamic systemd user. - It stores state in `/var/lib/AdGuardHome`. - It grants `CAP_NET_BIND_SERVICE` for DNS port 53. - It grants `CAP_NET_RAW` when AdGuard Home DHCP is enabled. - The module supports an arbitrary YAML configuration through `settings`. - The module supports fully declarative operation with `mutableSettings = false`. - In fully declarative operation, the module replaces `/var/lib/AdGuardHome/AdGuardHome.yaml` on each service start. - Fully declarative operation requires `dns.bootstrap_dns` as a list. - Declarative settings pass an AdGuard configuration check during the build. - `mutableSettings = true` merges declared settings into UI changes at service start. - `mutableSettings = false` replaces the state configuration on every service start. - `openFirewall` opens only the web interface port. - The migration must explicitly open DNS ports 53/TCP and 53/UDP when clients need them. - Pikachu has address `10.23.10.102` on its untagged network. - Pikachu carries VLAN 20 and creates bridge `br-vlan20`. - The guest framework runs each guest in its own systemd-nspawn container. - Each guest attaches to a host VLAN bridge and receives an address through DHCP. - Pikachu currently configures only VLAN 20 for guests. - The repository has no AdGuard configuration, Proxmox inventory, or DHCP-provider configuration. - The current DHCP provider remains unestablished. - The current Proxmox AdGuard container uses MAC `bc:24:11:91:60:bf`. - The current Proxmox AdGuard container uses address `10.23.20.33` on VLAN 20. - This environment has no Proxmox SSH target or local Proxmox control utility. - The current AdGuard configuration listens on all guest addresses at DNS port 53. - The current AdGuard configuration has no allowed-client or disallowed-client restrictions. - The current AdGuard configuration uses Cloudflare, Google, and Quad9 encrypted upstreams in parallel. - The current AdGuard configuration disables DNSSEC. - The current AdGuard configuration defines three local rewrites. - The repository configures no host backup system or monitoring system. - Hosts update through explicit `nixos-rebuild switch --flake` commands. - The working tree has unrelated operator changes. ## Service tradeoffs | Service | NixOS support | Domain filtering | Administration UI | DHCP | Encrypted DNS | Main tradeoff | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | AdGuard Home | `services.adguardhome`; checked YAML and mutable or immutable state | Native block lists, rules, and per-client policy | Native | Native | Upstream and client-facing encrypted DNS | Direct migration with the closest state and feature match. UI state conflicts with fully declarative operation unless configuration is immutable. | | Blocky | `services.blocky`; checked declarative YAML | Native lists, allow lists, rewrites, and per-client groups | No UI; REST API and metrics only | No | Encrypted upstreams and client-facing DoH, DoT, and DoQ | Strong Git-managed configuration and low operational surface. It lacks an interactive administration interface and DHCP. | | Pi-hole | `services.pihole-ftl` plus `services.pihole-web`; declarative TOML, lists, and dashboard | Native gravity lists, allow lists, regex rules, and groups | Native | Native | Use a companion for encrypted upstream DNS | Familiar filtering product with a dashboard. The migration changes product data and uses more coupled services. | | Technitium DNS Server | `services.technitium-dns-server`; service and firewall options only | Native block lists and DNS applications | Native | Native | Upstream and client-facing encrypted DNS | Broadest DNS feature set, including authoritative DNS. NixOS does not expose its runtime configuration declaratively. | | Unbound | `services.unbound`; structured server and free-form settings | Local zones or response-policy zones require custom policy management | No | No | DNS-over-TLS support | Mature validating recursive resolver. Filtering and user operations require separate design. | | dnscrypt-proxy | `services.dnscrypt-proxy`; declarative TOML | Local block and allow rules, without a list-management UI | No | No | Encrypted upstream DNS, including DNSCrypt and DoH | Strong upstream privacy and resolver selection. It is not a full network filtering appliance. | | Knot Resolver | `services.kresd`; structured listener settings and extra Lua | Custom policy modules | No | No | Client-facing DoH and TLS listeners | Flexible high-performance recursive resolver. Filtering requires resolver expertise and custom policy. | | SmartDNS | `services.smartdns`; free-form settings | Domain and address rules | No NixOS-integrated UI | No | Encrypted upstream and client-facing DNS | Optimizes upstream selection and latency. It offers less complete filtering management than dedicated blockers. | `Native` means that the product provides the feature without a companion service. ## Decision tree - **D1 — DNS service product** [settled] - Retain AdGuard Home on Pikachu. - Constraint: Preserve AdGuard Home as the DNS service product. - **D2A — AdGuard Home role** [settled] - Provides DNS filtering and resolution only. - Constraint: The existing DHCP provider continues to assign addresses and DHCP options. - **D3A — Execution placement** [settled] - Runs in a dedicated guest on Pikachu's existing VLAN 20. - Constraint: Isolate AdGuard Home from the Pikachu host. - **D4A — Guest network identity** [settled] - Reuses MAC `bc:24:11:91:60:bf` and address `10.23.20.33` on VLAN 20. - Constraint: Stop the Proxmox container before the guest starts. - **D5A — Network exposure** [settled] - Listens on all guest addresses at port 53. - Accepts requests from every reachable internal device. - **D6A — Resolver policy** [settled] - Preserves the existing upstream resolvers and parallel resolver mode. - Preserves disabled DNSSEC and the three local rewrites. - **D7A — Configuration authority** [settled] - Uses fully declarative Nix configuration. - Constraint: Set `services.adguardhome.mutableSettings = false`. - Constraint: Declare `dns.bootstrap_dns` as a list. - Constraint: Web UI changes do not persist. - Constraint: Match existing service behavior except for the administrator password. - **D8A — Migration data** [settled] - Migrate functional DNS configuration only. - Constraint: Do not migrate query history or statistics. - **D9A — Operations** [settled] - Declares a newly generated bcrypt verifier in Nix. - Constraint: Keep the plaintext password outside the repository and Nix store. - Constraint: The verifier is readable from the Nix store. - **D10A — Cutover and rollback** [settled] - Uses a planned atomic cutover with rollback. - Stop the Proxmox container before starting the guest. - Validate the guest before restoring the Proxmox container during rollback. - **D11A — Runtime-state backup** [settled] - Do not back up rebuildable AdGuard Home runtime state. - **D12A — Monitoring** [settled] - Do not add DNS availability monitoring. - **D13A — Upgrade ownership** [settled] - The normal flake update and explicit NixOS rebuild workflow owns upgrades. - Replace AdGuard Home. [not selected] - **D2B — Replacement product** [not applicable] - Choose Blocky, Pi-hole, Technitium, or a composed resolver. ## Frontier None. ## Sources - Pinned Nixpkgs module: `nixos/modules/services/networking/adguardhome.nix`. - Pinned Nixpkgs modules: `blocky.nix`, `pihole-ftl.nix`, `pihole-web.nix`, `technitium-dns-server.nix`, `unbound.nix`, `dnscrypt-proxy.nix`, `knot-resolver.nix`, and `smartdns.nix`. - AdGuard Home README, release `v0.107.79`. - Technitium DNS Server README, current upstream.