diff --git a/nix/atproto-likes.nix b/nix/atproto-likes.nix new file mode 100644 index 0000000..17398cf --- /dev/null +++ b/nix/atproto-likes.nix @@ -0,0 +1,89 @@ +# atproto-likes — the "most-liked accounts" web UI, run as a docker-compose stack +# behind the host's nginx. +# +# ⚠️ COPY. Canonical source: +# /srv/project-orchestrator/projects/notplants-atproto/nix/atproto-likes.nix +# A flake cannot import an absolute path outside its own tree under pure +# evaluation, so the module is copied here. Re-copy after editing the original. +# +# Import this from the host configuration: +# imports = [ /srv/project-orchestrator/projects/notplants-atproto/nix/atproto-likes.nix ]; +# +# What it sets up: +# * the Docker daemon (this host had none before) +# * a systemd unit that builds and runs docker-compose.yml from the project checkout +# * an nginx vhost for atproto.commoninternet.net with a Let's Encrypt cert, +# proxying to the container on 127.0.0.1:8731 +# * ports 80/443 open (they were closed — only 22 was public) +# +# DNS: *.commoninternet.net is a wildcard pointing at 143.244.213.108, which is NOT +# this host. Until an explicit A record +# atproto.commoninternet.net -> 168.119.126.100 +# overrides that wildcard, ACME's HTTP-01 challenge cannot succeed and the domain +# keeps resolving elsewhere. nginx still starts in the meantime — NixOS installs a +# self-signed placeholder cert — so nothing else on the host is affected. +{ config, pkgs, lib, ... }: + +let + projectDir = "/srv/project-orchestrator/projects/notplants-atproto"; + domain = "atproto.commoninternet.net"; + hostPort = 8731; # must match the ports: mapping in docker-compose.yml + compose = "${pkgs.docker-compose}/bin/docker-compose"; +in +{ + virtualisation.docker = { + enable = true; + # Reclaim dangling images from repeated `--build` runs. + autoPrune = { enable = true; dates = "weekly"; }; + }; + + # Build + run the compose stack. Type=oneshot with RemainAfterExit: compose + # detaches, and the containers' own restart policy keeps them alive. + systemd.services.atproto-likes = { + description = "atproto-likes — most-liked-accounts web UI (docker compose)"; + wantedBy = [ "multi-user.target" ]; + after = [ "docker.service" "docker.socket" "network-online.target" ]; + requires = [ "docker.service" ]; + wants = [ "network-online.target" ]; + path = [ pkgs.docker pkgs.docker-compose ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + WorkingDirectory = projectDir; + # A cold `--build` pulls python:3.12-slim and installs pip deps. + TimeoutStartSec = "1800"; + ExecStart = "${compose} up -d --build --remove-orphans"; + ExecStop = "${compose} down"; + Restart = "on-failure"; + RestartSec = "30s"; + }; + }; + + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts.${domain} = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString hostPort}"; + # The loading page streams scan progress over a websocket. + proxyWebsockets = true; + # A cold scan can run for minutes with the socket open; the default 60s + # proxy read timeout would cut the loading page off mid-flower. + extraConfig = '' + proxy_read_timeout 1800s; + proxy_send_timeout 1800s; + ''; + }; + }; + }; + + security.acme = { + acceptTerms = true; + defaults.email = "mfowler.email@protonmail.com"; + }; + + # Public HTTP/HTTPS. Before this, only 22 was open on the public interface. + networking.firewall.allowedTCPPorts = [ 80 443 ]; +} diff --git a/nix/hosts/cc-ci-orchestrator-hetzner/configuration.nix b/nix/hosts/cc-ci-orchestrator-hetzner/configuration.nix index a39a24e..3aa5b06 100644 --- a/nix/hosts/cc-ci-orchestrator-hetzner/configuration.nix +++ b/nix/hosts/cc-ci-orchestrator-hetzner/configuration.nix @@ -9,6 +9,13 @@ { # hardware.nix is the nixos-infect generated hardware-configuration.nix (see README Stage 2a). + # atproto-likes — the notplants-atproto "most-liked accounts" web UI, served at + # atproto.commoninternet.net. Brings in Docker + a compose stack + an nginx vhost + # with ACME, and opens 80/443 (previously only 22 was public). Canonical source of + # the module is the project repo; ../atproto-likes.nix is a copy kept in this tree + # because pure evaluation cannot import a path outside the flake. + imports = [ ../../atproto-likes.nix ]; + services.openssh = { enable = true; settings.PermitRootLogin = "yes";