{ config, lib, modulesPath, pkgs, ... }: { imports = [ (modulesPath + "/virtualisation/digital-ocean-config.nix") ./public-gateway.nix ./observability.nix ]; system.stateVersion = "26.05"; boot.kernelParams = [ "net.ifnames=0" "biosdevname=0" ]; networking.hostName = "noisebell-do"; networking.useDHCP = false; networking.usePredictableInterfaceNames = false; networking.nameservers = [ "67.207.67.3" "67.207.67.2" ]; networking.defaultGateway = "143.198.128.1"; networking.interfaces = { eth0.ipv4.addresses = [ { address = "143.198.141.161"; prefixLength = 20; } { address = "10.48.0.5"; prefixLength = 16; } ]; eth1.ipv4.addresses = [ { address = "10.124.0.2"; prefixLength = 20; } ]; }; networking.firewall = { # SSH is intentionally Tailscale-only via the trusted tailscale0 interface. # Keep direct HTTP(S) open until the Cloudflare Tunnel is enabled, then all # public web traffic enters through the tunnel instead of the droplet IP. allowedTCPPorts = lib.optionals (!config.services.noisebell-public-gateway.enable) [ 80 443 ]; allowedUDPPorts = [ config.services.tailscale.port ]; trustedInterfaces = [ "tailscale0" ]; checkReversePath = "loose"; allowPing = true; }; virtualisation.digitalOcean.rebuildFromUserData = false; services.do-agent.enable = false; boot.kernelPackages = pkgs.linuxPackages_6_12; boot.loader.grub = { enable = true; devices = lib.mkForce [ "/dev/vda" ]; }; fileSystems."/" = { device = "/dev/vda1"; fsType = "ext4"; autoResize = true; }; services.openssh = { enable = true; openFirewall = false; settings = { PasswordAuthentication = false; PermitRootLogin = "prohibit-password"; }; hostKeys = [ { path = "/etc/ssh/ssh_host_ed25519_key"; type = "ed25519"; } ]; }; users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu" ]; users.users.jet = { isNormalUser = true; extraGroups = [ "wheel" ]; openssh.authorizedKeys.keys = config.users.users.root.openssh.authorizedKeys.keys; }; security.sudo.wheelNeedsPassword = false; age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; age.secrets.noisebell-cloudflare-api-token = lib.mkIf config.services.noisebell-public-gateway.enable { file = ../../secrets/cloudflare-api-token.age; mode = "0400"; }; age.secrets.noisebell-cloudflare-tunnel-secret = lib.mkIf config.services.noisebell-public-gateway.enable { file = ../../secrets/cloudflare-tunnel-secret.age; mode = "0400"; }; services.tailscale.enable = true; services.caddy = { enable = true; openFirewall = false; email = "postmaster@extremist.software"; globalConfig = lib.mkIf config.services.noisebell-public-gateway.enable '' auto_https off ''; }; # Reconciles the Cloudflare Tunnel and public DNS through the Cloudflare API. services.noisebell-public-gateway = { enable = true; accountId = "9f7c0277922ab28c45cb85bf4e7838af"; zoneId = "710e3255f43066c4a6bb4081b05a6c3f"; apiTokenFile = lib.mkIf config.services.noisebell-public-gateway.enable config.age.secrets.noisebell-cloudflare-api-token.path; tunnelSecretFile = lib.mkIf config.services.noisebell-public-gateway.enable config.age.secrets.noisebell-cloudflare-tunnel-secret.path; }; services.noisebell-cache = { enable = true; domain = "noisebell.extremist.software"; httpOnly = config.services.noisebell-public-gateway.enable; piAddress = "http://noisebell-pi"; outboundWebhooks = [ { url = "http://127.0.0.1:${toString config.services.noisebell-discord.port}/webhook"; secretFile = config.age.secrets.noisebell-discord-webhook-secret.path; } { url = "http://noisebell-pi:8090/webhook"; secretFile = config.age.secrets.noisebell-relay-webhook-secret.path; } ]; }; services.noisebell-discord = { enable = true; domain = "discord.noisebell.extremist.software"; channelId = "1034916379486322718"; }; services.noisebell-rss = { enable = true; domain = "rss-noisebell.extremist.software"; httpOnly = config.services.noisebell-public-gateway.enable; }; zramSwap = { enable = true; memoryPercent = 100; }; nix.settings = { experimental-features = [ "nix-command" "flakes" ]; trusted-users = [ "root" "jet" ]; max-jobs = 1; cores = 1; auto-optimise-store = true; }; nix.gc = { automatic = true; dates = "daily"; options = "--delete-older-than 7d"; }; services.journald.extraConfig = '' SystemMaxUse=100M ''; environment.systemPackages = [ pkgs.curl pkgs.jq pkgs.tailscale ]; }