diff --git a/flake.lock b/flake.lock index 9c8b824..998f4e7 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1787536184, - "narHash": "sha256-BElMdZnbe7Trt7Rs0u5utMbLm8Fydq6g0XhRHQCixAU=", + "lastModified": 1790589489, + "narHash": "sha256-Sn9W64W4KOga829nzathqymFvtwZsb/klyQFS/3Nq1A=", "owner": "ogulcancelik", "repo": "herdr", - "rev": "7d56b4c51b046a5d477439bcea4512339f8841aa", + "rev": "7f89b11a11160874ba89f3be7c64b29960bd2df3", "type": "github" }, "original": { @@ -26,11 +26,11 @@ ] }, "locked": { - "lastModified": 1787377438, - "narHash": "sha256-Sxu1NLTD/Ern6hFGLlZmtKCSct3YQXZI/lls8RE1XeM=", + "lastModified": 1790215976, + "narHash": "sha256-VkR1rMGyjU9jtw8ISFx5uqR9e+LEaOlhHwrseFkiIVk=", "owner": "nix-community", "repo": "home-manager", - "rev": "65258d5c65a250189fde2e35f490d15e064c4c62", + "rev": "a6631107a83ceab5872f298a2ea710859c80c4cb", "type": "github" }, "original": { @@ -42,11 +42,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1779357205, - "narHash": "sha256-cCO8aTqss5x9Ky8GWkpY0Hy5fyTZEbtifSUV8QjSzic=", + "lastModified": 1779911860, + "narHash": "sha256-ubcLAcKBDCZjhxw/kH5DL5GdBZQdzLqpkTe7HonvJVs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f83fc3c307e74bc5fd5adb7eb6b8b13ffd2a36e1", + "rev": "c0a89c379b4ac67c7b13b051ddbd4e0dbc9b0eaf", "type": "github" }, "original": { @@ -58,11 +58,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1787414105, - "narHash": "sha256-8FnH9CMQCQ9i7d7xrXK30SBjgODxlkltCa0thsRgrxg=", - "rev": "a9e6d84f9c2f9012f5fe7d964a7851352300e61a", + "lastModified": 1790533781, + "narHash": "sha256-hzJtu90JIQXnmgxKeP23CAx86G4L+dcQ+GHB1JK04qA=", + "rev": "cf5e76507c6e23b59f7e0ffcc7baa2a39ddd8442", "type": "tarball", - "url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.8255.a9e6d84f9c2f/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.10769.cf5e76507c6e/nixexprs.tar.zst" }, "original": { "id": "nixpkgs", diff --git a/hosts/nixserve/default.nix b/hosts/nixserve/default.nix index 41524b4..eec65e7 100644 --- a/hosts/nixserve/default.nix +++ b/hosts/nixserve/default.nix @@ -47,7 +47,7 @@ users.groups.media = {}; users.users.jellyfin.extraGroups = [ "media" ]; - networking.firewall.allowedTCPPorts = [ 80 443 /* HTTP */ 111 2049 4000 4001 4002 20048 /* NFS */ 3923 /* copyparty */ ]; + networking.firewall.allowedTCPPorts = [ 80 443 /* HTTP */ 111 2049 4000 4001 4002 20048 /* NFS */ 3923 /* copyparty */ 8123 /* Home Assistant (LAN devices, e.g. Cast TTS fetch) */ ]; networking.firewall.allowedUDPPorts = [ 111 2049 4000 4001 4002 20048 /* NFS */ ]; networking.firewall.trustedInterfaces = [ "tailscale0" ]; diff --git a/hosts/nixserve/services.nix b/hosts/nixserve/services.nix index 5043b5f..d876a31 100644 --- a/hosts/nixserve/services.nix +++ b/hosts/nixserve/services.nix @@ -3,7 +3,7 @@ let rssdownloader = pkgs.callPackage ./rssdownloader.nix { src = rssdownloader-src; }; - proxyVhost = { upstream, websockets ? true, maxBodySize ? "1g" }: { + proxyVhost = { upstream, websockets ? true, maxBodySize ? "1g", proxySsl ? false }: { forceSSL = true; enableACME = true; locations."/" = { @@ -18,7 +18,9 @@ let proxy_send_timeout 3600; proxy_buffering off; client_max_body_size ${maxBodySize}; - ''; + '' + (if proxySsl then '' + proxy_ssl_verify off; + '' else ""); }; }; @@ -82,10 +84,10 @@ in }; }; - services.immich = { - enable = true; - mediaLocation = "/media/immich"; - }; + #services.immich = { + # enable = true; + # mediaLocation = "/media/immich"; + #}; users.users.git = { isSystemUser = true; @@ -127,6 +129,82 @@ in pools = [ "media" ]; }; + # DNS ad-blocking for other LAN/Tailscale clients only. + # nixserve's own DNS resolution is untouched (Tailscale MagicDNS via resolv.conf). + services.pihole-ftl = { + enable = true; + openFirewallDNS = true; + openFirewallWebserver = true; + + lists = [ + { url = "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"; description = "StevenBlack unified hosts"; } + ]; + + settings = { + dns.upstreams = [ "1.1.1.1" "1.0.0.1" ]; + webserver.api.cli_pw = true; + }; + }; + + services.pihole-web = { + enable = true; + ports = [ "8081o" ]; + }; + + # Lock the smart TV down to *.maybec.at only: everything else fails to resolve. + # This seeds Pi-hole's gravity.db (groups/clients/rules) declaratively on every + # boot/rebuild -- that data lives outside pihole.toml so it isn't covered by + # services.pihole-ftl.settings. + systemd.services.pihole-tv-lockdown = { + description = "Seed Pi-hole gravity.db with smart TV DNS lockdown rules"; + # gravity.db's group/client/rule tables are queried live per DNS lookup + # (not cached at FTL startup), so this only needs to run after FTL has + # created the database -- no need to race it to run "before" FTL starts. + after = [ "pihole-ftl.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = config.services.pihole-ftl.user; + Group = config.services.pihole-ftl.group; + }; + script = '' + set -euo pipefail + ${pkgs.sqlite}/bin/sqlite3 "${config.services.pihole-ftl.stateDirectory}/gravity.db" <<'SQL' + INSERT OR IGNORE INTO "group" (name, enabled, description) + VALUES ('tv-only', 1, 'Smart TV: only *.maybec.at may resolve'); + + INSERT OR IGNORE INTO domainlist (type, domain, enabled, comment) + VALUES (2, '.*', 1, 'tv-only: deny everything'); + INSERT OR IGNORE INTO domainlist (type, domain, enabled, comment) + VALUES (3, '(^|\.)maybec\.at$', 1, 'tv-only: allow maybec.at and subdomains'); + + INSERT OR IGNORE INTO domainlist_by_group (domainlist_id, group_id) + SELECT d.id, g.id FROM domainlist d, "group" g + WHERE d.domain IN ('.*', '(^|\.)maybec\.at$') AND g.name = 'tv-only'; + + INSERT OR IGNORE INTO client (ip, comment) + VALUES ('20:28:bc:15:1d:c0', 'Smart TV (locked to maybec.at only)'); + + INSERT OR IGNORE INTO client_by_group (client_id, group_id) + SELECT c.id, g.id FROM client c, "group" g + WHERE c.ip = '20:28:bc:15:1d:c0' AND g.name = 'tv-only'; + + -- Pi-hole auto-links every new domainlist/client row into the Default + -- group (tr_domainlist_add / tr_client_add triggers). Strip that back + -- out, otherwise the deny-all regex applies to every client on the + -- network instead of just the TV. + DELETE FROM domainlist_by_group + WHERE group_id = 0 + AND domainlist_id IN (SELECT id FROM domainlist WHERE domain IN ('.*', '(^|\.)maybec\.at$')); + + DELETE FROM client_by_group + WHERE group_id = 0 + AND client_id IN (SELECT id FROM client WHERE ip = '20:28:bc:15:1d:c0'); +SQL + ''; + }; + virtualisation.docker = { enable = true; enableOnBoot = true; @@ -180,6 +258,30 @@ in dir = "/home/ulve/code/colony-archive"; }; + systemd.services.pingvin-share = dockerService { + name = "pingvin-share"; + description = "Pingvin Share file sharing service"; + dir = "/home/ulve/code/pingvin-share"; + }; + + systemd.services.frigate = dockerService { + name = "frigate"; + description = "Frigate NVR"; + dir = "/home/ulve/code/frigate"; + }; + + systemd.services.mosquitto = dockerService { + name = "mosquitto"; + description = "Mosquitto MQTT broker"; + dir = "/home/ulve/code/mosquitto"; + }; + + systemd.services.homeassistant = dockerService { + name = "homeassistant"; + description = "Home Assistant"; + dir = "/home/ulve/code/homeassistant"; + }; + systemd.services.dozzle = { description = "Dozzle Docker log viewer"; after = [ "docker.service" "network.target" ]; @@ -208,5 +310,8 @@ virtualHosts."aftersummer.maybec.at" = proxyVhost { upstream = "http://127.0.0. virtualHosts."photos.maybec.at" = proxyVhost { upstream = "http://[::1]:2283"; maxBodySize = "50g"; }; virtualHosts."colony.maybec.at" = proxyVhost { upstream = "http://127.0.0.1:3333"; }; virtualHosts."git.maybec.at" = proxyVhost { upstream = "http://127.0.0.1:3001"; maxBodySize = "10g"; }; + virtualHosts."share.maybec.at" = proxyVhost { upstream = "http://127.0.0.1:8090"; maxBodySize = "10g"; }; + virtualHosts."cam.maybec.at" = proxyVhost { upstream = "https://127.0.0.1:8971"; proxySsl = true; }; + virtualHosts."home.maybec.at" = proxyVhost { upstream = "http://127.0.0.1:8123"; }; }; }