From ff054e790c87e198c7c7ea6546d263caaa8bda6d Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Sat, 1 Aug 2026 18:35:41 +0000 Subject: [PATCH] feat(host): atproto-likes web UI at atproto.commoninternet.net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Imports the notplants-atproto module: Docker daemon (this host had none), a systemd unit running the project's docker-compose stack, and an nginx vhost proxying to it on 127.0.0.1:8731 with ACME and websocket support. Opens 80/443 publicly. Until now only 22 was open and everything else was tailnet-only, so this is a real change in exposure — nginx is now reachable from the internet. The existing oc.commoninternet.net vhost is untouched and stays bound to the tailscale IP. nix/atproto-likes.nix is a COPY; the canonical file lives in the project repo at /srv/project-orchestrator/projects/notplants-atproto/nix/. Pure evaluation forbids importing an absolute path outside the flake tree, so it has to be duplicated here — and a new file must be git-added or nix silently ignores it. ACME currently FAILS: *.commoninternet.net is a wildcard pointing at 143.244.213.108, so the HTTP-01 challenge is answered by that host (500). nginx serves a self-signed placeholder and starts fine. Fix is an explicit A record atproto.commoninternet.net -> 168.119.126.100, then `systemctl start acme-atproto.commoninternet.net.service`. Applied with nixos-rebuild switch; container healthy, TLS proxy and wss verified end to end. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SmEK2voMnBa23495aLk1Ce --- nix/atproto-likes.nix | 89 +++++++++++++++++++ .../configuration.nix | 7 ++ 2 files changed, 96 insertions(+) create mode 100644 nix/atproto-likes.nix 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";