Compare commits
2 Commits
main
...
feat/adgua
| Author | SHA1 | Date | |
|---|---|---|---|
| a1b7897efd | |||
| bf99d30934 |
45
configs/nixos/guests/adguard.nix
Normal file
45
configs/nixos/guests/adguard.nix
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.guests.adguard;
|
||||
in
|
||||
{
|
||||
options.guests.adguard.adminPasswordHash = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Bcrypt verifier for the AdGuard Home administrator password.";
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.address != null;
|
||||
message = "guests.adguard.address must identify the AdGuard Home guest.";
|
||||
}
|
||||
];
|
||||
|
||||
registry.homepage.${config.networking.hostName}.adguard = {
|
||||
tab = "System";
|
||||
section = "Network";
|
||||
name = "AdGuard Home";
|
||||
description = "Blocks advertisements and trackers.";
|
||||
icon = "adguard-home.svg";
|
||||
href = "http://${cfg.address}";
|
||||
};
|
||||
|
||||
guests.adguard = {
|
||||
interior.modules.services.adguardhome = {
|
||||
enable = true;
|
||||
inherit (cfg) adminPasswordHash;
|
||||
};
|
||||
|
||||
limits = {
|
||||
memory = lib.mkDefault "256M";
|
||||
cpu = lib.mkDefault "100%";
|
||||
tasksMax = lib.mkDefault 128;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -29,6 +29,14 @@
|
||||
|
||||
registry.address.pikachu.self = "10.23.10.102";
|
||||
|
||||
guests.adguard = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
mac = "bc:24:11:91:60:bf";
|
||||
address = "10.23.20.33";
|
||||
adminPasswordHash = "$2b$12$wariglnrCvJOnh4b8KnB9eDX4RdQRaaTZtyXMUOilaYSn7xTR0g1e";
|
||||
};
|
||||
|
||||
guests.valheim = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
|
||||
272
configs/nixos/modules/services/adguardhome.nix
Normal file
272
configs/nixos/modules/services/adguardhome.nix
Normal file
@@ -0,0 +1,272 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.modules.services.adguardhome;
|
||||
in
|
||||
{
|
||||
options.modules.services.adguardhome = {
|
||||
enable = lib.mkEnableOption "AdGuard Home";
|
||||
|
||||
adminPasswordHash = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Bcrypt verifier for the AdGuard Home administrator password.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.adguardhome = {
|
||||
enable = true;
|
||||
mutableSettings = false;
|
||||
openFirewall = true;
|
||||
settings = {
|
||||
http = {
|
||||
pprof = {
|
||||
port = 6060;
|
||||
enabled = false;
|
||||
};
|
||||
address = "0.0.0.0:80";
|
||||
session_ttl = "720h";
|
||||
};
|
||||
users = [
|
||||
{
|
||||
name = config.user.name;
|
||||
password = cfg.adminPasswordHash;
|
||||
}
|
||||
];
|
||||
auth_attempts = 5;
|
||||
block_auth_min = 15;
|
||||
http_proxy = "";
|
||||
language = "";
|
||||
theme = "auto";
|
||||
|
||||
dns = {
|
||||
bind_hosts = [ "0.0.0.0" ];
|
||||
port = 53;
|
||||
anonymize_client_ip = false;
|
||||
ratelimit = 20;
|
||||
ratelimit_subnet_len_ipv4 = 24;
|
||||
ratelimit_subnet_len_ipv6 = 56;
|
||||
ratelimit_whitelist = [ ];
|
||||
refuse_any = true;
|
||||
upstream_dns = [
|
||||
"https://dns.cloudflare.com/dns-query"
|
||||
"tls://1.1.1.1"
|
||||
"https://dns.google/dns-query"
|
||||
"tls://8.8.8.8"
|
||||
"https://dns10.quad9.net/dns-query"
|
||||
"tls://9.9.9.9"
|
||||
];
|
||||
upstream_dns_file = "";
|
||||
bootstrap_dns = [
|
||||
"9.9.9.10"
|
||||
"149.112.112.10"
|
||||
"2620:fe::10"
|
||||
"2620:fe::fe:10"
|
||||
];
|
||||
fallback_dns = [ ];
|
||||
upstream_mode = "parallel";
|
||||
fastest_timeout = "1s";
|
||||
allowed_clients = [ ];
|
||||
disallowed_clients = [ ];
|
||||
blocked_hosts = [
|
||||
"version.bind"
|
||||
"id.server"
|
||||
"hostname.bind"
|
||||
];
|
||||
trusted_proxies = [
|
||||
"127.0.0.0/8"
|
||||
"::1/128"
|
||||
];
|
||||
cache_enabled = true;
|
||||
cache_size = 4194304;
|
||||
cache_ttl_min = 0;
|
||||
cache_ttl_max = 0;
|
||||
cache_optimistic = false;
|
||||
cache_optimistic_answer_ttl = "30s";
|
||||
cache_optimistic_max_age = "12h";
|
||||
bogus_nxdomain = [ ];
|
||||
aaaa_disabled = false;
|
||||
enable_dnssec = false;
|
||||
edns_client_subnet = {
|
||||
custom_ip = "";
|
||||
enabled = false;
|
||||
use_custom = false;
|
||||
};
|
||||
max_goroutines = 300;
|
||||
handle_ddr = true;
|
||||
ipset = [ ];
|
||||
ipset_file = "";
|
||||
bootstrap_prefer_ipv6 = false;
|
||||
upstream_timeout = "10s";
|
||||
private_networks = [ ];
|
||||
use_private_ptr_resolvers = true;
|
||||
local_ptr_upstreams = [ ];
|
||||
use_dns64 = false;
|
||||
dns64_prefixes = [ ];
|
||||
serve_http3 = false;
|
||||
use_http3_upstreams = false;
|
||||
serve_plain_dns = true;
|
||||
hostsfile_enabled = true;
|
||||
pending_requests.enabled = true;
|
||||
};
|
||||
|
||||
tls = {
|
||||
enabled = false;
|
||||
server_name = "";
|
||||
force_https = false;
|
||||
port_https = 443;
|
||||
port_dns_over_tls = 853;
|
||||
port_dns_over_quic = 853;
|
||||
port_dnscrypt = 0;
|
||||
dnscrypt_config_file = "";
|
||||
allow_unencrypted_doh = false;
|
||||
certificate_chain = "";
|
||||
private_key = "";
|
||||
certificate_path = "";
|
||||
private_key_path = "";
|
||||
strict_sni_check = false;
|
||||
};
|
||||
|
||||
querylog = {
|
||||
dir_path = "";
|
||||
ignored = [ ];
|
||||
interval = "2160h";
|
||||
size_memory = 1000;
|
||||
enabled = true;
|
||||
ignored_enabled = false;
|
||||
file_enabled = true;
|
||||
};
|
||||
|
||||
statistics = {
|
||||
dir_path = "";
|
||||
ignored = [ ];
|
||||
interval = "24h";
|
||||
enabled = true;
|
||||
ignored_enabled = false;
|
||||
};
|
||||
|
||||
filters = [
|
||||
{
|
||||
enabled = true;
|
||||
url = "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt";
|
||||
name = "AdGuard DNS filter";
|
||||
id = 1;
|
||||
}
|
||||
{
|
||||
enabled = false;
|
||||
url = "https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt";
|
||||
name = "AdAway Default Blocklist";
|
||||
id = 2;
|
||||
}
|
||||
];
|
||||
whitelist_filters = [ ];
|
||||
user_rules = [ ];
|
||||
|
||||
dhcp = {
|
||||
enabled = false;
|
||||
interface_name = "";
|
||||
local_domain_name = "lan";
|
||||
dhcpv4 = {
|
||||
gateway_ip = "";
|
||||
subnet_mask = "";
|
||||
range_start = "";
|
||||
range_end = "";
|
||||
lease_duration = 86400;
|
||||
icmp_timeout_msec = 1000;
|
||||
options = [ ];
|
||||
};
|
||||
dhcpv6 = {
|
||||
range_start = "";
|
||||
lease_duration = 86400;
|
||||
ra_slaac_only = false;
|
||||
ra_allow_slaac = false;
|
||||
};
|
||||
};
|
||||
|
||||
filtering = {
|
||||
blocking_ipv4 = "";
|
||||
blocking_ipv6 = "";
|
||||
blocked_services = {
|
||||
schedule.time_zone = "Local";
|
||||
ids = [ ];
|
||||
};
|
||||
protection_disabled_until = null;
|
||||
safe_search = {
|
||||
enabled = false;
|
||||
bing = true;
|
||||
duckduckgo = true;
|
||||
ecosia = true;
|
||||
google = true;
|
||||
pixabay = true;
|
||||
yandex = true;
|
||||
youtube = true;
|
||||
};
|
||||
blocking_mode = "default";
|
||||
parental_block_host = "family-block.dns.adguard.com";
|
||||
safebrowsing_block_host = "standard-block.dns.adguard.com";
|
||||
rewrites = [
|
||||
{
|
||||
domain = "*.home";
|
||||
answer = "10.23.20.32";
|
||||
enabled = true;
|
||||
}
|
||||
{
|
||||
domain = "*.alexion.dev";
|
||||
answer = "10.23.20.109";
|
||||
enabled = true;
|
||||
}
|
||||
{
|
||||
domain = "home";
|
||||
answer = "10.23.20.109";
|
||||
enabled = true;
|
||||
}
|
||||
];
|
||||
safe_fs_patterns = [ "/opt/AdGuardHome/userfilters/*" ];
|
||||
safebrowsing_cache_size = 1048576;
|
||||
safesearch_cache_size = 1048576;
|
||||
parental_cache_size = 1048576;
|
||||
cache_time = 30;
|
||||
filters_update_interval = 24;
|
||||
blocked_response_ttl = 10;
|
||||
filtering_enabled = true;
|
||||
rewrites_enabled = true;
|
||||
parental_enabled = false;
|
||||
safebrowsing_enabled = false;
|
||||
protection_enabled = true;
|
||||
};
|
||||
|
||||
clients = {
|
||||
runtime_sources = {
|
||||
whois = true;
|
||||
arp = true;
|
||||
rdns = true;
|
||||
dhcp = true;
|
||||
hosts = true;
|
||||
};
|
||||
persistent = [ ];
|
||||
};
|
||||
|
||||
log = {
|
||||
enabled = true;
|
||||
file = "";
|
||||
max_backups = 0;
|
||||
max_size = 100;
|
||||
max_age = 3;
|
||||
compress = false;
|
||||
local_time = false;
|
||||
verbose = false;
|
||||
};
|
||||
|
||||
os = {
|
||||
group = "";
|
||||
user = "";
|
||||
rlimit_nofile = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 53 ];
|
||||
allowedUDPPorts = [ 53 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -47,8 +47,6 @@ or begin
|
||||
return $status
|
||||
end
|
||||
|
||||
set remote_nar
|
||||
|
||||
if test (count $missing_paths) -gt 0
|
||||
set nar_file (mktemp --suffix=.nar)
|
||||
or return $status
|
||||
@@ -72,23 +70,26 @@ if test (count $missing_paths) -gt 0
|
||||
return $scp_status
|
||||
end
|
||||
|
||||
echo "deploy-to: importing the closure, recording $system_path, and activating it on $host" >&2
|
||||
echo "deploy-to: importing closure on $host" >&2
|
||||
ssh -t $host "sudo sh -c 'nix-store --import < $remote_nar' && rm -f $remote_nar"
|
||||
or begin
|
||||
echo "deploy-to: closure import failed for $host" >&2
|
||||
return $status
|
||||
end
|
||||
else
|
||||
echo "deploy-to: $host already has the full closure for $system_path" >&2
|
||||
echo "deploy-to: recording $system_path and activating it on $host" >&2
|
||||
end
|
||||
|
||||
set remote_script (
|
||||
string join '; ' \
|
||||
'system_path=$1' \
|
||||
'remote_nar=$2' \
|
||||
'if test -n "$remote_nar"; then nix-store --import < "$remote_nar"; rm -f "$remote_nar"; fi' \
|
||||
'nix-env --profile /nix/var/nix/profiles/system --set "$system_path"' \
|
||||
'"$system_path/bin/switch-to-configuration" switch'
|
||||
)
|
||||
|
||||
ssh -t $host sudo sh -e -c (string escape -- $remote_script) sh (string escape -- $system_path $remote_nar)
|
||||
echo "deploy-to: recording $system_path as the system profile on $host" >&2
|
||||
ssh -t $host sudo nix-env --profile /nix/var/nix/profiles/system --set $system_path
|
||||
or begin
|
||||
echo "deploy-to: deployment failed for $host" >&2
|
||||
echo "deploy-to: system profile update failed for $host" >&2
|
||||
return $status
|
||||
end
|
||||
|
||||
echo "deploy-to: activating $system_path on $host" >&2
|
||||
ssh -t $host sudo "$system_path/bin/switch-to-configuration" switch
|
||||
or begin
|
||||
echo "deploy-to: activation failed for $host" >&2
|
||||
return $status
|
||||
end
|
||||
|
||||
110
docs/00 INBOX/ai/adguard-pikachu-migration-design-tree.md
Normal file
110
docs/00 INBOX/ai/adguard-pikachu-migration-design-tree.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user