Compare commits
11 Commits
23e24401f5
...
feat/adgua
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e713a2669 | |||
| 94eb132962 | |||
| 8e01e1621b | |||
| b4e74fc5b2 | |||
| bbac95cac0 | |||
| e5bacc53be | |||
| 5f52a78efa | |||
| dc210d4985 | |||
| 2237c3eea3 | |||
| 771a0df831 | |||
| cfa01fb550 |
49
configs/nixos/guests/adguard.nix
Normal file
49
configs/nixos/guests/adguard.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
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 = "Internal DNS";
|
||||
icon = "adguard-home.svg";
|
||||
href = "http://${cfg.address}";
|
||||
};
|
||||
|
||||
guests.adguard = {
|
||||
interior = {
|
||||
modules.services.adguardhome = {
|
||||
enable = true;
|
||||
inherit (cfg) adminPasswordHash;
|
||||
};
|
||||
|
||||
services.resolved.settings.Resolve.DNSStubListener = false;
|
||||
};
|
||||
|
||||
limits = {
|
||||
memory = lib.mkDefault "256M";
|
||||
cpu = lib.mkDefault "100%";
|
||||
tasksMax = lib.mkDefault 128;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -94,6 +94,14 @@ in
|
||||
}
|
||||
];
|
||||
|
||||
registry.homepage.${config.networking.hostName}.romm = {
|
||||
tab = "Apps";
|
||||
section = "Media";
|
||||
name = "RomM";
|
||||
description = "Games";
|
||||
icon = "romm.png";
|
||||
};
|
||||
|
||||
guests.romm = {
|
||||
interior = {
|
||||
modules.services.romm.enable = true;
|
||||
|
||||
@@ -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;
|
||||
@@ -104,6 +112,7 @@
|
||||
domain = "games.alexion.dev";
|
||||
protocol = "https";
|
||||
port = 80;
|
||||
clientMaxBodySize = "0";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
modules.desktop.enable = true;
|
||||
modules.desktop.apps.obsidian.enable = true;
|
||||
modules.desktop.apps.r2modman.enable = true;
|
||||
modules.desktop.emulation.enable = true;
|
||||
modules.desktop.apps.signal.enable = true;
|
||||
modules.desktop.apps.steam.enable = true;
|
||||
modules.desktop.apps.vencord.enable = true;
|
||||
|
||||
36
configs/nixos/modules/desktop/emulation.nix
Normal file
36
configs/nixos/modules/desktop/emulation.nix
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
# Shared directories for emulators.
|
||||
let
|
||||
user = config.user.name;
|
||||
directory = "/home/${user}/emulation";
|
||||
in
|
||||
{
|
||||
options.modules.desktop.emulation.enable = lib.mkEnableOption "shared emulation directories";
|
||||
|
||||
config = lib.mkIf config.modules.desktop.emulation.enable {
|
||||
modules.desktop.emulation.retroarch = {
|
||||
enable = lib.mkDefault true;
|
||||
browserDirectory = "${directory}/roms";
|
||||
};
|
||||
|
||||
home-manager.users.${user} = hm: {
|
||||
home.file = {
|
||||
".config/retroarch/saves".source = hm.config.lib.file.mkOutOfStoreSymlink "${directory}/saves";
|
||||
".config/retroarch/states".source = hm.config.lib.file.mkOutOfStoreSymlink "${directory}/states";
|
||||
".config/retroarch/system".source = hm.config.lib.file.mkOutOfStoreSymlink "${directory}/bios";
|
||||
};
|
||||
|
||||
systemd.user.tmpfiles.rules = map (path: "d ${path} 0700 - - -") [
|
||||
directory
|
||||
"${directory}/bios"
|
||||
"${directory}/roms"
|
||||
"${directory}/saves"
|
||||
"${directory}/states"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
62
configs/nixos/modules/desktop/emulation/retroarch.nix
Normal file
62
configs/nixos/modules/desktop/emulation/retroarch.nix
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# RetroArch with the selected PlayStation and Sega Saturn cores.
|
||||
let
|
||||
cfg = config.modules.desktop.emulation.retroarch;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.emulation.retroarch = {
|
||||
enable = lib.mkEnableOption "RetroArch with PlayStation and Sega Saturn cores";
|
||||
|
||||
browserDirectory = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "Directory where RetroArch starts its content browser.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
home.packages = [
|
||||
(pkgs.wrapRetroArch {
|
||||
cores = with pkgs.libretro; [
|
||||
beetle-psx-hw
|
||||
beetle-saturn
|
||||
];
|
||||
settings = {
|
||||
savefiles_in_content_dir = "false";
|
||||
savestates_in_content_dir = "false";
|
||||
system_directory = "/home/${user}/.config/retroarch/system";
|
||||
video_fullscreen = "true";
|
||||
sort_savefiles_enable = "true";
|
||||
sort_savestates_enable = "true";
|
||||
cloud_sync_enable = "true";
|
||||
cloud_sync_driver = "webdav";
|
||||
webdav_url = "https://games.alexion.dev/api/sync/retroarch/";
|
||||
cloud_sync_sync_saves = "true";
|
||||
cloud_sync_sync_configs = "true";
|
||||
cloud_sync_sync_thumbs = "false";
|
||||
cloud_sync_sync_system = "false";
|
||||
cloud_sync_sync_mode = "0";
|
||||
cloud_sync_destructive = "false";
|
||||
}
|
||||
// lib.optionalAttrs (cfg.browserDirectory != "") {
|
||||
rgui_browser_directory = cfg.browserDirectory;
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
registry.window-icon.${config.networking.hostName}.retroarch = {
|
||||
class = "com.libretro.RetroArch";
|
||||
icon = util.glyph "f11b";
|
||||
};
|
||||
};
|
||||
}
|
||||
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 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -71,7 +71,10 @@ let
|
||||
extraConfig = lib.optionalString (route.clientMaxBodySize != null) ''
|
||||
client_max_body_size ${route.clientMaxBodySize};
|
||||
'';
|
||||
locations."/".proxyPass = mkBackend address port;
|
||||
locations."/" = {
|
||||
proxyPass = mkBackend address port;
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -31,13 +32,33 @@ in
|
||||
|
||||
services.romm = {
|
||||
enable = true;
|
||||
package = pkgs.alexion.romm;
|
||||
group = "storage";
|
||||
database.createLocally = true;
|
||||
redis.createLocally = true;
|
||||
watcher.enable = true;
|
||||
extraEnvironment = {
|
||||
HASHEOUS_API_ENABLED = "true";
|
||||
PLAYMATCH_API_ENABLED = "true";
|
||||
ROM_CONVERTO_ENABLED = "false";
|
||||
};
|
||||
inherit (cfg) environmentFile;
|
||||
};
|
||||
|
||||
systemd.services.romm-worker.serviceConfig.ExecStart =
|
||||
lib.mkForce "${lib.getExe' config.services.romm.package "romm-worker"} scans";
|
||||
|
||||
systemd.tmpfiles.settings."10-romm"."${config.services.romm.dataDir}/config/config.yml"."L+".argument =
|
||||
toString (
|
||||
(pkgs.formats.yaml { }).generate "romm-config.yml" {
|
||||
emulatorjs.auto_save_sync = false;
|
||||
filesystem.structure = {
|
||||
default = "roms/{platform}/{game}";
|
||||
firmware = "bios/{platform}";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
users.users = {
|
||||
romm.uid = config.registry."stable-uid".${config.networking.hostName}.romm;
|
||||
redis-romm.uid = config.registry."stable-uid".${config.networking.hostName}."redis-romm";
|
||||
|
||||
@@ -47,6 +47,8 @@ or begin
|
||||
return $status
|
||||
end
|
||||
|
||||
set remote_nar
|
||||
|
||||
if test (count $missing_paths) -gt 0
|
||||
set nar_file (mktemp --suffix=.nar)
|
||||
or return $status
|
||||
@@ -70,26 +72,23 @@ if test (count $missing_paths) -gt 0
|
||||
return $scp_status
|
||||
end
|
||||
|
||||
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
|
||||
echo "deploy-to: importing the closure, recording $system_path, and activating it on $host" >&2
|
||||
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
|
||||
|
||||
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: system profile update failed for $host" >&2
|
||||
return $status
|
||||
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'
|
||||
)
|
||||
|
||||
echo "deploy-to: activating $system_path on $host" >&2
|
||||
ssh -t $host sudo "$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)
|
||||
or begin
|
||||
echo "deploy-to: activation failed for $host" >&2
|
||||
echo "deploy-to: deployment 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.
|
||||
@@ -3,8 +3,8 @@ aliases:
|
||||
- "0015"
|
||||
- RomM 5.4 Package
|
||||
date-created: 2026-10-19 19:40
|
||||
date-modified: 2026-10-19 19:40
|
||||
status: open
|
||||
date-modified: 2026-10-21 00:01
|
||||
status: closed
|
||||
area: "[[homelab]]"
|
||||
blocked-by:
|
||||
- "[[0014-romm-guest]]"
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
inherit root;
|
||||
};
|
||||
|
||||
romm = pkgs.callPackage ./romm.nix { };
|
||||
|
||||
waybar = pkgs.callPackage ./waybar.nix { };
|
||||
}
|
||||
|
||||
124
src/flake/packages/romm.nix
Normal file
124
src/flake/packages/romm.nix
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
buildNpmPackage,
|
||||
fetchFromGitHub,
|
||||
lib,
|
||||
python3,
|
||||
romm,
|
||||
}:
|
||||
romm.overrideAttrs (
|
||||
finalAttrs: previousAttrs:
|
||||
let
|
||||
frontend = buildNpmPackage {
|
||||
pname = "romm-frontend";
|
||||
inherit (finalAttrs) version;
|
||||
src = "${finalAttrs.src}/frontend";
|
||||
|
||||
npmDepsHash = "sha256-f5IcpOVv7JYFzUPkZIq5nbT3dS1f/KCSllh8Vkgl1P0=";
|
||||
npmFlags = [ "--ignore-scripts" ];
|
||||
makeCacheWritable = true;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
cp -r dist $out
|
||||
chmod u+w $out/assets
|
||||
cp -r assets/. $out/assets/
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
|
||||
# RomM 5.4 requires packages absent from the Nixpkgs 5.2 environment.
|
||||
pythonEnv = python3.withPackages (
|
||||
ps: with ps; [
|
||||
aiohttp
|
||||
alembic
|
||||
anyio
|
||||
apprise
|
||||
asyncssh
|
||||
authlib
|
||||
bcrypt
|
||||
colorama
|
||||
croniter
|
||||
cryptography
|
||||
defusedxml
|
||||
email-validator
|
||||
fastapi
|
||||
gunicorn
|
||||
httpx
|
||||
httpx2
|
||||
itsdangerous
|
||||
jinja2
|
||||
joserfc
|
||||
mariadb
|
||||
mutagen
|
||||
mysql-connector-python
|
||||
opentelemetry-distro
|
||||
opentelemetry-exporter-otlp
|
||||
opentelemetry-instrumentation-aiohttp-client
|
||||
opentelemetry-instrumentation-fastapi
|
||||
opentelemetry-instrumentation-httpx
|
||||
opentelemetry-instrumentation-redis
|
||||
opentelemetry-instrumentation-sqlalchemy
|
||||
passlib
|
||||
pillow
|
||||
psycopg
|
||||
pydantic
|
||||
pydash
|
||||
python-dotenv
|
||||
python-magic
|
||||
python-multipart
|
||||
python-socketio
|
||||
pyyaml
|
||||
redis
|
||||
rq
|
||||
rq-scheduler
|
||||
sentry-sdk
|
||||
sqlalchemy
|
||||
starlette
|
||||
streaming-form-data
|
||||
strsimpy
|
||||
ua-parser
|
||||
unidecode
|
||||
uvicorn
|
||||
uvicorn-worker
|
||||
watchfiles
|
||||
yarl
|
||||
zipfile-inflate64
|
||||
zstandard
|
||||
]
|
||||
);
|
||||
in
|
||||
{
|
||||
version = "5.4.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "rommapp";
|
||||
repo = "romm";
|
||||
tag = finalAttrs.version;
|
||||
hash = "sha256-oIYEdIAJgXkcqjZtWlWKD/NFiQ3gbo7gxwLhqZOCWpM=";
|
||||
};
|
||||
|
||||
postPatch = previousAttrs.postPatch + ''
|
||||
# Redis 8.0.1 removed this argument.
|
||||
# RomM disables the related maintenance notifications.
|
||||
substituteInPlace backend/handler/redis_handler.py \
|
||||
--replace-fail 'from redis.maint_notifications import MaintNotificationsConfig' "" \
|
||||
--replace-fail ' "maint_notifications_config": MaintNotificationsConfig(enabled=False),' ""
|
||||
'';
|
||||
|
||||
installPhase =
|
||||
lib.replaceStrings
|
||||
[
|
||||
"${previousAttrs.passthru.frontend}"
|
||||
"${previousAttrs.passthru.pythonEnv}"
|
||||
]
|
||||
[
|
||||
"${frontend}"
|
||||
"${pythonEnv}"
|
||||
]
|
||||
previousAttrs.installPhase;
|
||||
|
||||
passthru = previousAttrs.passthru // {
|
||||
inherit frontend pythonEnv;
|
||||
};
|
||||
}
|
||||
)
|
||||
@@ -3,16 +3,20 @@ name: grill
|
||||
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
||||
---
|
||||
|
||||
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
||||
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: each decision branches into the decisions it unblocks.
|
||||
|
||||
Maintain the design tree in a new Markdown file. Use the file as the source of truth.
|
||||
|
||||
Work the tree one question at a time. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask now without guessing at answers you have not heard yet. Record the next frontier question in the file, then ask it and wait for the user's answer.
|
||||
Build the complete **frontier** before the first question.
|
||||
After each answer, recompute and display every frontier decision.
|
||||
|
||||
Ask one frontier decision per turn.
|
||||
A clarification leaves that decision open.
|
||||
|
||||
Format a question like so:
|
||||
|
||||
```
|
||||
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
||||
❓ **D1 — <decision title>**: <question body, might be multiple paragraphs, including multiple choices>
|
||||
|
||||
**Options**
|
||||
|
||||
@@ -22,7 +26,7 @@ Format a question like so:
|
||||
|
||||
---
|
||||
|
||||
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
||||
❓ **D2 — <decision title>**: <question body, might be multiple paragraphs, including multiple choices>
|
||||
|
||||
**Options**
|
||||
|
||||
@@ -34,7 +38,10 @@ Format a question like so:
|
||||
List every viable answer in an ordered list and mark your recommended answer.
|
||||
Word each question so "yes" accepts your recommended answer.
|
||||
|
||||
Each user answer reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier in the file and ask the next question. A question whose answer depends on another open question belongs later, not next.
|
||||
Before asking the next question, account for every decision that the response settles.
|
||||
Record preferences as constraints.
|
||||
Settle an implementation decision only after facts establish that it is viable.
|
||||
A decision with unresolved prerequisites belongs outside the frontier.
|
||||
|
||||
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), find it with available tools; don't ask the user for anything you could look up yourself. The _decisions_ are the user's: put each to them and wait.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user