docs: add AdGuard Home implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4eeeef121e
commit
30d5ce8134
1 changed files with 436 additions and 0 deletions
436
docs/superpowers/plans/2026-03-18-adguard-home.md
Normal file
436
docs/superpowers/plans/2026-03-18-adguard-home.md
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
# AdGuard Home Module Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Add a NixOS module for AdGuard Home providing private DoH with ClientID-based access control.
|
||||||
|
|
||||||
|
**Architecture:** AdGuard Home runs in a Podman container with a generated config from SOPS secrets. Nginx proxies DoH requests at `dns.ashisgreat.xyz`. ClientIDs act as passwords - only recognized IDs get DNS resolution.
|
||||||
|
|
||||||
|
**Tech Stack:** NixOS, Podman, AdGuard Home, nginx, SOPS-nix
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| File | Action | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `modules/adguard.nix` | Create | Main module with options, container, nginx, SOPS config |
|
||||||
|
| `modules/default.nix` | Modify | Add import for adguard.nix |
|
||||||
|
| `configuration.nix` | Modify | Enable module with domain and clients |
|
||||||
|
| `secrets/secrets.yaml` | Modify | Add ClientID secrets (encrypted) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Create the AdGuard Module File
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `modules/adguard.nix`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create module skeleton with options**
|
||||||
|
|
||||||
|
Create `modules/adguard.nix` with the module header, options definition, and empty config block:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# AdGuard Home Module
|
||||||
|
# Provides: Private DNS-over-HTTPS with ClientID-based access control
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# myModules.adguard = {
|
||||||
|
# enable = true;
|
||||||
|
# domain = "dns.example.com";
|
||||||
|
# clients = [
|
||||||
|
# { name = "phone"; idSecret = "adguard_client_phone"; }
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.myModules.adguard;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.myModules.adguard = {
|
||||||
|
enable = lib.mkEnableOption "AdGuard Home DNS server";
|
||||||
|
|
||||||
|
domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "dns.example.com";
|
||||||
|
description = "Public domain name for DoH endpoint";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 3000;
|
||||||
|
description = "Internal port for AdGuard HTTP/DoH listener";
|
||||||
|
};
|
||||||
|
|
||||||
|
upstreamDoh = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "https://dns.mullvad.net/dns-query";
|
||||||
|
description = "Upstream DoH server URL";
|
||||||
|
};
|
||||||
|
|
||||||
|
bootstrapDns = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.str);
|
||||||
|
default = [ "194.242.2.2" "2a07:e340::2" ];
|
||||||
|
description = "Bootstrap DNS servers for resolving DoH upstream";
|
||||||
|
};
|
||||||
|
|
||||||
|
clients = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Friendly name for the client device";
|
||||||
|
};
|
||||||
|
idSecret = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "SOPS secret name containing the ClientID";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = [ ];
|
||||||
|
description = "List of clients with their ClientID secrets";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# Implementation in next step
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add podman dependency and container definition**
|
||||||
|
|
||||||
|
Add inside the `config = lib.mkIf cfg.enable { }` block:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# Ensure Podman is enabled
|
||||||
|
myModules.podman.enable = true;
|
||||||
|
|
||||||
|
# AdGuard Home Container
|
||||||
|
virtualisation.oci-containers.containers."adguard" = {
|
||||||
|
image = "docker.io/adguard/adguardhome:latest";
|
||||||
|
ports = [ "127.0.0.1:${toString cfg.port}:3000/tcp" ];
|
||||||
|
extraOptions = [
|
||||||
|
"--cap-drop=ALL"
|
||||||
|
"--read-only"
|
||||||
|
"--tmpfs=/tmp"
|
||||||
|
];
|
||||||
|
volumes = [
|
||||||
|
"${config.sops.templates."adguardhome.yaml".path}:/opt/adguardhome/conf/AdGuardHome.yaml:ro"
|
||||||
|
"adguard-data:/opt/adguardhome/work"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add SOPS template for AdGuardHome.yaml**
|
||||||
|
|
||||||
|
Add after the container definition (still inside config block):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# SOPS template for AdGuard configuration
|
||||||
|
sops.templates."adguardhome.yaml" = {
|
||||||
|
content = ''
|
||||||
|
http:
|
||||||
|
address: 0.0.0.0:3000
|
||||||
|
|
||||||
|
dns:
|
||||||
|
upstream_dns:
|
||||||
|
- ${cfg.upstreamDoh}
|
||||||
|
bootstrap_dns:
|
||||||
|
${lib.concatStringsSep "\n " (map (d: "- ${d}") cfg.bootstrapDns)}
|
||||||
|
querylog_enabled: true
|
||||||
|
querylog_file_enabled: true
|
||||||
|
statistics_enabled: true
|
||||||
|
|
||||||
|
clients:
|
||||||
|
persistent:
|
||||||
|
${lib.concatStringsSep "\n " (
|
||||||
|
map (client: ''
|
||||||
|
- name: ${client.name}
|
||||||
|
ids:
|
||||||
|
- ${config.sops.placeholder.${client.idSecret}}
|
||||||
|
'') cfg.clients
|
||||||
|
)}
|
||||||
|
|
||||||
|
filtering:
|
||||||
|
protection_enabled: true
|
||||||
|
filtering_enabled: true
|
||||||
|
|
||||||
|
safebrowsing:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
parental:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
safesearch:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
log:
|
||||||
|
file: ""
|
||||||
|
max_backups: 0
|
||||||
|
max_size: 100
|
||||||
|
compress: false
|
||||||
|
local_time: false
|
||||||
|
verbose: false
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Add SOPS secret declarations for clients**
|
||||||
|
|
||||||
|
Add after the SOPS template:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# Auto-declare SOPS secrets for each client
|
||||||
|
sops.secrets = lib.mkMerge (
|
||||||
|
map (client: {
|
||||||
|
${client.idSecret} = { };
|
||||||
|
}) cfg.clients
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Add nginx virtual host configuration**
|
||||||
|
|
||||||
|
Add after SOPS secrets:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# Nginx configuration for DoH endpoint
|
||||||
|
services.nginx.virtualHosts."${cfg.domain}" = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
|
||||||
|
# Regex location to match /dns-query and /dns-query/{clientId}
|
||||||
|
locations."~ ^/dns-query" = {
|
||||||
|
proxyPass = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# DoH uses POST with application/dns-message
|
||||||
|
proxy_pass_request_body on;
|
||||||
|
proxy_set_header Content-Type "application/dns-message";
|
||||||
|
|
||||||
|
# Buffer settings for DNS queries
|
||||||
|
proxy_buffers 8 16k;
|
||||||
|
proxy_buffer_size 32k;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Block all other paths including admin UI
|
||||||
|
locations."/" = {
|
||||||
|
return = "404";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ensure nginx user can access ACME certs
|
||||||
|
users.users.nginx.extraGroups = [ "acme" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Verify complete module file**
|
||||||
|
|
||||||
|
The complete `modules/adguard.nix` should have:
|
||||||
|
- Module header comment with usage example
|
||||||
|
- All 6 options (enable, domain, port, upstreamDoh, bootstrapDns, clients)
|
||||||
|
- Container definition with security options
|
||||||
|
- SOPS template generating AdGuardHome.yaml
|
||||||
|
- SOPS secret declarations for clients
|
||||||
|
- Nginx virtual host with DoH proxy config
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit the module**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add modules/adguard.nix
|
||||||
|
git commit -m "feat(modules): add AdGuard Home module with DoH and ClientID support
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Register Module in default.nix
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `modules/default.nix`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add adguard.nix import**
|
||||||
|
|
||||||
|
Add `./adguard.nix` to the imports list in `modules/default.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./system.nix
|
||||||
|
./podman.nix
|
||||||
|
./nginx.nix
|
||||||
|
./searxng.nix
|
||||||
|
./openclaw-podman.nix
|
||||||
|
./vaultwarden.nix
|
||||||
|
./crowdsec.nix
|
||||||
|
./backup.nix
|
||||||
|
./adguard.nix # Add this line
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit the change**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add modules/default.nix
|
||||||
|
git commit -m "feat(modules): register adguard module in default.nix
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Enable Module in configuration.nix
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `configuration.nix`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add AdGuard module configuration**
|
||||||
|
|
||||||
|
Add after the existing module configurations (around line 88, after nginx config):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# === AdGuard Home (DoH) ===
|
||||||
|
myModules.adguard = {
|
||||||
|
enable = true;
|
||||||
|
domain = "dns.ashisgreat.xyz";
|
||||||
|
clients = [
|
||||||
|
{ name = "phone"; idSecret = "adguard_client_phone"; }
|
||||||
|
{ name = "laptop"; idSecret = "adguard_client_laptop"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit the change**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add configuration.nix
|
||||||
|
git commit -m "feat(config): enable AdGuard Home module with two clients
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Add ClientID Secrets
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `secrets/secrets.yaml`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add ClientID secrets to SOPS**
|
||||||
|
|
||||||
|
Edit `secrets/secrets.yaml` using SOPS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix-shell -p sops --run "sops secrets/secrets.yaml"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add these entries (replace with your actual secret ClientIDs):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
adguard_client_phone: "your-secret-phone-client-id"
|
||||||
|
adguard_client_laptop: "your-secret-laptop-client-id"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Choose random strings for ClientIDs. They act as passwords. Example format: `xK9mP2vL7nQ4`
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify secrets are encrypted**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Should show encrypted content, not plaintext
|
||||||
|
head -5 secrets/secrets.yaml | grep -c ENC
|
||||||
|
# Expected: output > 0 (shows encrypted entries exist)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit the encrypted secrets**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add secrets/secrets.yaml
|
||||||
|
git commit -m "chore(secrets): add AdGuard ClientID secrets
|
||||||
|
|
||||||
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Build and Deploy
|
||||||
|
|
||||||
|
- [ ] **Step 1: Dry-run to verify configuration**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nixos-rebuild build --flake .#nixos
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: Build succeeds without errors
|
||||||
|
|
||||||
|
- [ ] **Step 2: Deploy to system**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nixos-rebuild switch --flake .#nixos
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: System switches to new configuration, AdGuard container starts
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify container is running**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo podman ps | grep adguard
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: Shows adguard container running, port 127.0.0.1:3000->3000/tcp
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify nginx configuration**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nginx -t
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `syntax is ok` and `test is successful`
|
||||||
|
|
||||||
|
- [ ] **Step 5: Test DoH endpoint (manual)**
|
||||||
|
|
||||||
|
From a client device configured with the DoH URL:
|
||||||
|
```
|
||||||
|
https://dns.ashisgreat.xyz/dns-query/your-secret-phone-client-id
|
||||||
|
```
|
||||||
|
|
||||||
|
Or test with curl:
|
||||||
|
```bash
|
||||||
|
# Create a simple DNS query for google.com (A record)
|
||||||
|
# This is a base64url-encoded DNS query
|
||||||
|
curl -H "content-type: application/dns-message" \
|
||||||
|
--data-binary @- \
|
||||||
|
"https://dns.ashisgreat.xyz/dns-query/YOUR_CLIENT_ID" \
|
||||||
|
< /dev/null
|
||||||
|
# Note: Real DNS query needs proper wire format, this just tests connectivity
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Task | Description | Commits |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| 1 | Create adguard.nix module | 1 |
|
||||||
|
| 2 | Register in default.nix | 1 |
|
||||||
|
| 3 | Enable in configuration.nix | 1 |
|
||||||
|
| 4 | Add secrets to SOPS | 1 |
|
||||||
|
| 5 | Build, deploy, verify | 0 (deployment) |
|
||||||
|
|
||||||
|
**Total: 4 commits + deployment**
|
||||||
Loading…
Add table
Add a link
Reference in a new issue