diff --git a/hosts/global/core/default.nix b/hosts/global/core/default.nix index cd86c93..ca0c5eb 100644 --- a/hosts/global/core/default.nix +++ b/hosts/global/core/default.nix @@ -17,6 +17,7 @@ in (map lib.custom.relativeToRoot [ "modules/global" + "modules/nixos" ]) ]; diff --git a/hosts/x86/proxy/config/adguard.nix b/hosts/x86/proxy/config/adguard.nix index 6a67d17..6868b7c 100644 --- a/hosts/x86/proxy/config/adguard.nix +++ b/hosts/x86/proxy/config/adguard.nix @@ -5,7 +5,7 @@ */ { # Containers - virtualisation.oci-containers.containers."adguard-adguard" = { + virtualisation.oci-containers.containers."adguard" = { image = "adguard/adguardhome:latest"; volumes = [ "/etc/adguard/confdir:/opt/adguardhome/conf:rw" @@ -24,7 +24,8 @@ "--network=adguard_default" ]; }; - systemd.services."docker-adguard-adguard" = { + + systemd.services."docker-adguard" = { serviceConfig = { Restart = lib.mkOverride 90 "always"; RestartMaxDelaySec = lib.mkOverride 90 "1m"; diff --git a/hosts/x86/proxy/config/caddy.nix b/hosts/x86/proxy/config/caddy.nix deleted file mode 100644 index 87d47ea..0000000 --- a/hosts/x86/proxy/config/caddy.nix +++ /dev/null @@ -1,72 +0,0 @@ -{ - services.caddy = { - enable = true; - virtualHosts = { - "adguard.ryot.foo" = { - useACMEHost = "ryot.foo"; - extraConfig = '' - reverse_proxy localhost:3000 - ''; - }; - - "cloudflared.ryot.foo" = { - useACMEHost = "ryot.foo"; - extraConfig = '' - reverse_proxy localhost:14333 - ''; - }; - - ## openWRT ## - - "wrt.ryot.foo" = { - useACMEHost = "ryot.foo"; - extraConfig = '' - reverse_proxy http://104.40.3.1 { - header_up Host {host} - header_up X-Real-IP {remote} - header_up X-Forwarded-For {remote} - header_up X-Forwarded-Proto {scheme} - header_up X-Forwarded-Port {server_port} - } - ''; - }; - - ## PROXMOX NODES ## - - "ochre.ryot.foo" = { - useACMEHost = "ryot.foo"; - extraConfig = '' - reverse_proxy https://104.40.3.2:8006 { - transport http { - tls_insecure_skip_verify - # optional: tls_server_name 104.40.3.2 - } - # ensure Proxmox sees the right Host - header_up Host {host} - header_up X-Real-IP {remote} - header_up X-Forwarded-For {remote} - header_up X-Forwarded-Proto {scheme} - header_up X-Forwarded-Port {server_port} - } - ''; - }; - - "pve.ryot.foo" = { - useACMEHost = "ryot.foo"; - extraConfig = '' - reverse_proxy https://104.40.3.3:8006 { - transport http { - tls_insecure_skip_verify - # optional: tls_server_name 104.40.3.3 - } - header_up Host {host} - header_up X-Real-IP {remote} - header_up X-Forwarded-For {remote} - header_up X-Forwarded-Proto {scheme} - header_up X-Forwarded-Port {server_port} - } - ''; - }; - }; - }; -} diff --git a/hosts/x86/proxy/config/newt.nix b/hosts/x86/proxy/config/newt.nix new file mode 100644 index 0000000..9d12bb0 --- /dev/null +++ b/hosts/x86/proxy/config/newt.nix @@ -0,0 +1,8 @@ +{ + services.newt = { + enable = true; + id = "1jh3j2bhucdiq09"; + secret = "f51n59bjwbg9c1wuyjg62my634mqqs1y199cwd1bxq3bes6p"; + useHostNetwork = true; + }; +} diff --git a/hosts/x86/proxy/config/pangolin/compose.nix b/hosts/x86/proxy/config/pangolin/compose.nix new file mode 100644 index 0000000..4e3da1d --- /dev/null +++ b/hosts/x86/proxy/config/pangolin/compose.nix @@ -0,0 +1,156 @@ +# Auto-generated using compose2nix v0.3.1. +{ + pkgs, + lib, + config, + ... +}: + +{ + # Containers + virtualisation.oci-containers.containers."gerbil" = { + image = "fosrl/gerbil:1.0.0"; + volumes = [ + "/etc/pangolin/config:/var/config:rw" + ]; + ports = [ + "51820:51820/udp" + "443:443/tcp" + "222:222/tcp" + "80:80/tcp" + ]; + cmd = [ + "--reachableAt=http://gerbil:3003" + "--generateAndSaveKeyTo=/var/config/key" + "--remoteConfig=http://pangolin:3001/api/v1/gerbil/get-config" + "--reportBandwidthTo=http://pangolin:3001/api/v1/gerbil/receive-bandwidth" + ]; + dependsOn = [ + "pangolin" + ]; + log-driver = "journald"; + extraOptions = [ + "--cap-add=NET_ADMIN" + # "--cap-add=SYS_MODULE" + "--network-alias=gerbil" + "--network=pangolin" + ]; + }; + systemd.services."docker-gerbil" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + after = [ + "docker-network-pangolin.service" + ]; + requires = [ + "docker-network-pangolin.service" + ]; + partOf = [ + "docker-compose-pangolin-root.target" + ]; + wantedBy = [ + "docker-compose-pangolin-root.target" + ]; + }; + virtualisation.oci-containers.containers."pangolin" = { + image = "fosrl/pangolin:1.5.1"; + volumes = [ + "/etc/pangolin/config:/app/config:rw" + ]; + log-driver = "journald"; + extraOptions = [ + "--health-cmd=[\"curl\", \"-f\", \"http://localhost:3001/api/v1/\"]" + "--health-interval=3s" + "--health-retries=15" + "--health-timeout=3s" + "--network-alias=pangolin" + "--network=pangolin" + ]; + }; + + systemd.services."docker-pangolin" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + after = [ + "docker-network-pangolin.service" + "pangolin-config-sync.service" + ]; + requires = [ + "docker-network-pangolin.service" + "pangolin-config-sync.service" + ]; + partOf = [ + "docker-compose-pangolin-root.target" + ]; + wantedBy = [ + "docker-compose-pangolin-root.target" + ]; + }; + + virtualisation.oci-containers.containers."traefik" = { + image = "traefik:v3.4.0"; + environment = { + "CLOUDFLARE_DNS_API_TOKEN" = config.secretsSpec.api.cloudflare; + }; + volumes = [ + "/etc/pangolin/config/letsencrypt:/letsencrypt:rw" + "/etc/pangolin/config/traefik:/etc/traefik:ro" + ]; + cmd = [ "--configFile=/etc/traefik/traefik_config.yml" ]; + dependsOn = [ + "gerbil" + "pangolin" + ]; + log-driver = "journald"; + extraOptions = [ + "--network=container:gerbil" + ]; + }; + systemd.services."docker-traefik" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + partOf = [ + "docker-compose-pangolin-root.target" + ]; + wantedBy = [ + "docker-compose-pangolin-root.target" + ]; + }; + + # Networks + systemd.services."docker-network-pangolin" = { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "docker network rm -f pangolin"; + }; + script = '' + docker network inspect pangolin || docker network create pangolin --driver=bridge + ''; + partOf = [ "docker-compose-pangolin-root.target" ]; + wantedBy = [ "docker-compose-pangolin-root.target" ]; + }; + + # Root service + # When started, this will automatically create all resources and start + # the containers. When stopped, this will teardown all resources. + systemd.targets."docker-compose-pangolin-root" = { + unitConfig = { + Description = "Root target generated by compose2nix."; + }; + wantedBy = [ "multi-user.target" ]; + }; +} diff --git a/hosts/x86/proxy/config/pangolin/default.nix b/hosts/x86/proxy/config/pangolin/default.nix new file mode 100644 index 0000000..f41fa69 --- /dev/null +++ b/hosts/x86/proxy/config/pangolin/default.nix @@ -0,0 +1,230 @@ +{ + lib, + config, + pkgs, + ... +}: +let + smtp = config.secretsSpec.users.admin.smtp; + pangolin = config.secretsSpec.docker.pangolin; + + # Create the configuration files as derivations + pangolinConfigFile = pkgs.writeText "pangolin-config.yml" '' + app: + dashboard_url: "https://pangolin.ryot.foo" + log_level: "debug" + save_logs: true + + domains: + domain1: + base_domain: "ryot.foo" + cert_resolver: "letsencrypt" + prefer_wildcard_cert: true + domain2: + base_domain: "toph.cc" + cert_resolver: "letsencrypt" + prefer_wildcard_cert: true + domain3: + base_domain: "goldenlemon.cc" + cert_resolver: "letsencrypt" + prefer_wildcard_cert: true + domain4: + base_domain: "kwahson.xyz" + cert_resolver: "letsencrypt" + prefer_wildcard_cert: true + + server: + external_port: 3000 + internal_port: 3001 + next_port: 3002 + internal_hostname: "pangolin" + session_cookie_name: "p_session_token" + resource_access_token_param: "p_token" + resource_access_token_headers: + id: "P-Access-Token-Id" + token: "P-Access-Token" + resource_session_request_param: "p_session_request" + secret: "${pangolin.SECRET}" + + traefik: + cert_resolver: "letsencrypt" + http_entrypoint: "web" + https_entrypoint: "websecure" + + gerbil: + start_port: 51820 + base_endpoint: "pangolin.ryot.foo" + use_subdomain: false + block_size: 24 + site_block_size: 30 + subnet_group: 104.40.3.1/24 + + rate_limits: + global: + window_minutes: 1 + max_requests: 100 + + email: + smtp_host: "${smtp.host}" + smtp_port: ${toString smtp.port} + smtp_user: "${smtp.user}" + smtp_pass: "${smtp.password}" + no_reply: "no-reply@ryot.foo" + + users: + server_admin: + email: "${pangolin.USER}" + password: "${pangolin.PASSWORD}" + + flags: + require_email_verification: true + disable_signup_without_invite: true + disable_user_create_org: true + allow_raw_resources: true + allow_base_domain_resources: true + ''; + + traefikConfigFile = pkgs.writeText "traefik-config.yml" '' + api: + insecure: true + dashboard: true + + providers: + http: + endpoint: "http://pangolin:3001/api/v1/traefik-config" + pollInterval: "5s" + file: + filename: "/etc/traefik/dynamic_config.yml" + + experimental: + plugins: + badger: + moduleName: "github.com/fosrl/badger" + version: "v1.2.0" + + log: + level: "DEBUG" + format: "common" + + certificatesResolvers: + letsencrypt: + acme: + dnsChallenge: + provider: cloudflare + delayBeforeCheck: 60 + resolvers: + - "1.1.1.1:53" + - "8.8.8.8:53" + email: chris@toph.cc + storage: "/letsencrypt/acme.json" + caServer: "https://acme-v02.api.letsencrypt.org/directory" + + entryPoints: + web: + address: ":80" + websecure: + address: ":443" + transport: + respondingTimeouts: + readTimeout: "30m" + http: + tls: + certResolver: "letsencrypt" + tcp-222: + address: ":222/tcp" + + serversTransport: + insecureSkipVerify: true + ''; + + dynamicConfigFile = pkgs.writeText "dynamic-config.yml" '' + http: + middlewares: + redirect-to-https: + redirectScheme: + scheme: https + + routers: + # HTTP to HTTPS redirect router + main-app-router-redirect: + rule: "Host(`pangolin.ryot.foo`)" + service: next-service + entryPoints: + - web + middlewares: + - redirect-to-https + + # Next.js router + next-router: + rule: "Host(`pangolin.ryot.foo`) && !PathPrefix(`/api/v1`)" + service: next-service + entryPoints: + - websecure + tls: + certResolver: letsencrypt + domains: + - main: "ryot.foo" + sans: + - "*.ryot.foo" + + # API router + api-router: + rule: "Host(`pangolin.ryot.foo`) && PathPrefix(`/api/v1`)" + service: api-service + entryPoints: + - websecure + tls: + certResolver: letsencrypt + + # WebSocket router + ws-router: + rule: "Host(`pangolin.ryot.foo`)" + service: api-service + entryPoints: + - websecure + tls: + certResolver: letsencrypt + + services: + next-service: + loadBalancer: + servers: + - url: "http://pangolin:3002" + + api-service: + loadBalancer: + servers: + - url: "http://pangolin:3000" + ''; + + keyFile = pkgs.writeText "pangolin-key" pangolin.KEY; +in +{ + imports = lib.custom.scanPaths ./.; + + boot.kernelModules = [ "wireguard" ]; + + ## Tmp files and Service to Avoid symlinks + systemd.tmpfiles.rules = [ + "d /etc/pangolin/config 0755 root root -" + "d /etc/pangolin/config/traefik 0755 root root -" + "d /etc/pangolin/config/letsencrypt 0755 root root -" + ]; + + systemd.services.pangolin-config-sync = { + description = "Sync Pangolin configuration files"; + wantedBy = [ "multi-user.target" ]; + before = [ "docker-compose-pangolin-root.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + cp ${keyFile} /etc/pangolin/config/key + chmod 0600 /etc/pangolin/config/key + cp ${pangolinConfigFile} /etc/pangolin/config/config.yml + cp ${traefikConfigFile} /etc/pangolin/config/traefik/traefik_config.yml + cp ${dynamicConfigFile} /etc/pangolin/config/traefik/dynamic_config.yml + ''; + }; +} diff --git a/modules/nixos/newt.nix b/modules/nixos/newt.nix new file mode 100644 index 0000000..e1d97ab --- /dev/null +++ b/modules/nixos/newt.nix @@ -0,0 +1,129 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.newt; +in +{ + options.services.newt = { + enable = mkEnableOption "Newt container service"; + + id = mkOption { + type = types.str; + description = "Newt ID for authentication"; + }; + + image = mkOption { + type = types.str; + default = "fosrl/newt"; + description = "Docker image to use for Newt"; + }; + + networkName = mkOption { + type = types.str; + default = "newt"; + description = "Docker network name to use"; + }; + + networkAlias = mkOption { + type = types.str; + default = "newt"; + description = "Network alias for the container"; + }; + + pangolinEndpoint = mkOption { + type = types.str; + default = "https://pangolin.ryot.foo"; + description = "Pangolin endpoint URL"; + }; + + secret = mkOption { + type = types.str; + description = "Newt secret for authentication"; + }; + + useHostNetwork = mkOption { + type = types.bool; + default = false; + description = "Whether to use host networking instead of Docker networks"; + }; + }; + + config = mkIf cfg.enable { + # Container + virtualisation.oci-containers.containers."newt" = { + image = cfg.image; + environment = { + "DOCKER_SOCKET" = "/var/run/docker.sock"; + "NEWT_ID" = cfg.id; + "NEWT_SECRET" = cfg.secret; + "PANGOLIN_ENDPOINT" = cfg.pangolinEndpoint; + }; + volumes = [ + "/var/run/docker.sock:/var/run/docker.sock:rw" + ]; + log-driver = "journald"; + extraOptions = + if cfg.useHostNetwork then + [ + "--network=host" + ] + else + [ + "--network-alias=${cfg.networkAlias}" + "--network=${cfg.networkName}" + ]; + }; + + # Container service with proper dependencies + systemd.services."docker-newt" = { + serviceConfig = { + Restart = lib.mkOverride 90 "always"; + RestartMaxDelaySec = lib.mkOverride 90 "1m"; + RestartSec = lib.mkOverride 90 "100ms"; + RestartSteps = lib.mkOverride 90 9; + }; + after = mkIf (!cfg.useHostNetwork) [ + "docker-network-${cfg.networkName}.service" + ]; + requires = mkIf (!cfg.useHostNetwork) [ + "docker-network-${cfg.networkName}.service" + ]; + partOf = [ + "docker-compose-newt-root.target" + ]; + wantedBy = [ + "docker-compose-newt-root.target" + ]; + }; + + # Docker network service (only when not using host network) + systemd.services."docker-network-${cfg.networkName}" = mkIf (!cfg.useHostNetwork) { + path = [ pkgs.docker ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "docker network rm -f ${cfg.networkName}"; + }; + script = '' + docker network inspect ${cfg.networkName} || docker network create ${cfg.networkName} + ''; + partOf = [ "docker-compose-newt-root.target" ]; + wantedBy = [ "docker-compose-newt-root.target" ]; + }; + + # Root target + systemd.targets."docker-compose-newt-root" = { + unitConfig = { + Description = "Root target generated by compose2nix."; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; +} diff --git a/secrets.nix b/secrets.nix index 5ff1f9c..b2cb729 100644 Binary files a/secrets.nix and b/secrets.nix differ