# 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 ]; }