{ zentralwerk, nixpkgs, config, lib, pkgs, ... }: let webroot = "/var/www"; geminiRoot = "/var/gemini"; deployCommand = "${pkgs.systemd}/bin/systemctl start deploy-c3d2-web.service"; in { boot.tmpOnTmpfs = true; # Network setup networking.hostName = "c3d2-web"; networking.useNetworkd = true; networking.interfaces.eth0.ipv4.addresses = [{ address = config.c3d2.hosts."${config.networking.hostName}".ip4; prefixLength = zentralwerk.lib.config.site.net.serv.subnet4Len; }]; networking.defaultGateway = "172.20.73.1"; networking.firewall.allowedTCPPorts = [ # http/https 80 443 # gemini 1965 ]; security.acme.certs = { # agate cannot load "ec256" keys "www.c3d2.de".keyType = "rsa4096"; }; # Web server services.nginx = { enable = true; virtualHosts = { # c3d2 "www.c3d2.de" = { default = true; serverAliases = [ "c3d2.de" "c3dd.de" "www.c3dd.de" "cccdd.de" "www.cccdd.de" "dresden.ccc.de" "www.dresden.ccc.de" "netzbiotop.org" "www.netzbiotop.org" ]; enableACME = true; forceSSL = true; root = "${webroot}/c3d2"; extraConfig = '' index portal.html index.html; ''; locations = { # SpaceAPI "/status.png".proxyPass = "http://[${config.c3d2.hosts.spaceapi.ip6}]:3000/status.png"; "/spaceapi.json".proxyPass = "http://[${config.c3d2.hosts.spaceapi.ip6}]:3000/spaceapi.json"; }; }; # datenspuren "datenspuren.de" = { serverAliases = [ "www.datenspuren.de" "ds.c3d2.de" "datenspuren.c3d2.de" ]; enableACME = true; forceSSL = true; root = "${webroot}/c3d2/datenspuren"; extraConfig = '' index index.html; rewrite ^/$ /2021/ redirect; # redirect für Jahre ohne eigene Website (gedacht für kommende DS) try_files ^/\d{4}/.*$ /future.html =404; ''; }; # autotopia "autotopia.c3d2.de" = { enableACME = true; forceSSL = true; root = "${webroot}/c3d2/autotopia"; extraConfig = '' index index.html; rewrite ^/$ /2020/ redirect; ''; }; # hooks, logs "c3d2-web.serv.zentralwerk.org" = { enableACME = true; forceSSL = true; root = webroot; locations."/hooks/".proxyPass = "http://localhost:9000/hooks/"; }; }; }; # Gemini server services.agate = { enable = true; addresses = [ # sysctl net.ipv6.bindv6only = 0 "[::]:1965" ]; certificatesDir = "/var/lib/agate/certificates"; contentDir = geminiRoot; language = "de"; }; # let agate access the tls certs systemd.services.agate = { requires = [ "agate-keys.service" ]; after = [ "agate-keys.service" ]; serviceConfig = { Group = "keys"; }; }; systemd.services.agate-keys = { path = with pkgs; [ openssl ]; script = let stateDir = "/var/lib/agate/certificates"; in '' mkdir -p ${stateDir} openssl x509 \ -in /var/lib/acme/www.c3d2.de/cert.pem \ -out ${stateDir}/cert.der \ -outform DER openssl rsa \ -in /var/lib/acme/www.c3d2.de/key.pem \ -out ${stateDir}/key.der \ -outform DER chown root:keys ${stateDir}/* chmod 0640 ${stateDir}/* ''; serviceConfig = { Type = "oneshot"; }; }; # Build user users.groups.c3d2-web = {}; users.users.c3d2-web = { isSystemUser = true; group = "c3d2-web"; home = "/var/lib/c3d2-web"; }; systemd.tmpfiles.rules = [ "d ${webroot}/c3d2 0755 c3d2-web ${config.users.users.c3d2-web.group} -" "d ${webroot}/log 0755 c3d2-web ${config.users.users.c3d2-web.group} -" "d ${geminiRoot} 0755 c3d2-web ${config.users.users.c3d2-web.group} -" "d ${config.users.users.c3d2-web.home} 0700 c3d2-web ${config.users.users.c3d2-web.group} -" ]; # Build script systemd.services.deploy-c3d2-web = { wantedBy = [ "multi-user.target" ]; path = with pkgs; [ git nix curl ]; script = '' # Build at least once touch ${config.users.users.c3d2-web.home}/deploy-pending TEMP=$(mktemp -d) cd $TEMP git clone --depth=1 https://gitea.c3d2.de/c3d2/c3d2-web.git cd c3d2-web # Loop in case the webhook was called while we were building while [ -e ${config.users.users.c3d2-web.home}/deploy-pending ]; do rm ${config.users.users.c3d2-web.home}/deploy-pending git pull REV=$(git rev-parse HEAD) # web set +e curl -X POST \ "https://gitea.c3d2.de/api/v1/repos/c3d2/c3d2-web/statuses/$REV?token=${pkgs.c3d2-web.giteaToken}" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "{ \"context\": \"c3d2-web\", \"description\": \"building...\", \"state\": \"pending\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-$REV.txt\"}" nix-shell shell.nix \ -I nixpkgs=${nixpkgs} \ --run "make -j$(nproc) export DESTDIR=${webroot}/c3d2" \ 2>&1 \ >${webroot}/log/build-$REV.txt if [ $? = 0 ]; then STATUS="{ \"context\": \"c3d2-web\", \"description\": \"deployed\", \"state\": \"success\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-$REV.txt\"}" else STATUS="{ \"context\": \"c3d2-web\", \"description\": \"build failure\", \"state\": \"failure\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-$REV.txt\"}" fi curl -X POST \ "https://gitea.c3d2.de/api/v1/repos/c3d2/c3d2-web/statuses/$REV?token=${pkgs.c3d2-web.giteaToken}" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "$STATUS" git clean -fx # gemini curl -X POST \ "https://gitea.c3d2.de/api/v1/repos/c3d2/c3d2-web/statuses/$REV?token=${pkgs.c3d2-web.giteaToken}" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "{ \"context\": \"c3d2-gemini\", \"description\": \"building...\", \"state\": \"pending\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-gemini-$REV.txt\"}" nix-shell shell.nix \ -I nixpkgs=${nixpkgs} \ --run "make -f Makefile.gemini -j$(nproc) export DESTDIR=${geminiRoot}" \ 2>&1 \ >${webroot}/log/build-gemini-$REV.txt if [ $? = 0 ]; then STATUS="{ \"context\": \"c3d2-gemini\", \"description\": \"deployed\", \"state\": \"success\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-gemini-$REV.txt\"}" else STATUS="{ \"context\": \"c3d2-gemini\", \"description\": \"build failure\", \"state\": \"failure\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-gemini-$REV.txt\"}" fi curl -X POST \ "https://gitea.c3d2.de/api/v1/repos/c3d2/c3d2-web/statuses/$REV?token=${pkgs.c3d2-web.giteaToken}" \ -H "accept: application/json" \ -H "Content-Type: application/json" \ -d "$STATUS" set -e done ''; serviceConfig = { User = "c3d2-web"; Group = config.users.users.c3d2-web.group; PrivateTmp = true; ProtectSystem = "full"; ReadWritePaths = webroot; }; }; systemd.timers.deploy-c3d2-web = { partOf = [ "deploy-c3d2-web.service" ]; wantedBy = [ "timers.target" ]; timerConfig.OnCalendar = "hourly"; }; security.sudo.extraRules = [ { users = [ "c3d2-web" ]; commands = [ { command = deployCommand; options = [ "NOPASSWD" ]; } ]; } ]; systemd.services.webhook = let hooksJson = pkgs.writeText "hooks.json" (builtins.toJSON [ { id = "deploy-c3d2-web"; execute-command = pkgs.writeShellScript "deploy-c3d2-web" '' # Request (re-)deployment touch ${config.users.users.c3d2-web.home}/deploy-pending # Start deploy-c3d2-web.service if not already running exec /run/wrappers/bin/sudo ${deployCommand} ''; } ]); in { wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${pkgs.webhook}/bin/webhook -hooks ${hooksJson} -verbose -ip 127.0.0.1"; User = "c3d2-web"; Group = config.users.users.c3d2-web.group; PrivateTmp = true; ProtectSystem = "full"; }; }; }