nix-config/hosts/c3d2-web/default.nix

294 lines
8.4 KiB
Nix

{ config, pkgs, ... }:
let
webroot = "/var/www";
geminiRoot = "/var/gemini";
deployCommand = "${pkgs.systemd}/bin/systemctl start deploy-c3d2-web.service";
in
{
microvm.vcpu = 8;
microvm.mem = 1024;
c3d2.deployment = {
server = "server10";
mounts = [ "etc" "home" "var"];
};
boot.tmpOnTmpfs = true;
system.stateVersion = "22.05";
# Network setup
networking.hostName = "c3d2-web";
networking.firewall.allowedTCPPorts = [
# telme10
23
# 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" "openpgpkey.c3d2.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 = {
# Mastodon
"/.well-known/webfinger".return = "301 https://c3d2.social$request_uri";
# 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";
# WKD: Web Key Directory for PGP Keys
"/openpgp" = {
extraConfig = ''
autoindex off;
default_type "application/octet-stream";
add_header Access-Control-Allow-Origin "* always";
'';
};
};
};
# 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 ^/$ /2022/ redirect;
'';
};
# 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";
};
systemd.packages = with pkgs; [ telme10 ];
systemd.services.telme10 = {
serviceConfig = {
AmbientCapabilities="CAP_NET_BIND_SERVICE";
};
};
# 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";
};
users.groups.telme10 = {};
users.users.telme10 = {
isSystemUser = true;
group = "telme10";
};
systemd.tmpfiles.rules = with config.users.users.c3d2-web; [
"d ${webroot}/c3d2 0755 c3d2-web ${group} -"
"d ${webroot}/log 0755 c3d2-web ${group} -"
"d ${geminiRoot} 0755 c3d2-web ${group} -"
"d ${home} 0700 c3d2-web ${group} -"
];
# Build script
systemd.services.deploy-c3d2-web = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
path = with pkgs; [
git nix curl
(libxslt.override {
cryptoSupport = true;
}) libxml2 wget rsync gnumake bash
];
script = ''
# Build at least once
touch ${config.users.users.c3d2-web.home}/deploy-pending
status() {
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 "$1"
}
if [ ! -d c3d2-web ]; then
git clone --depth=1 https://gitea.c3d2.de/c3d2/c3d2-web.git
cd c3d2-web
else
cd c3d2-web
git fetch origin
git reset --hard origin/master
# `make export` may have created read-only files,
# fix that before cleaning up
chmod -R u+w .
git clean -d -f -x
fi
# 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
status "{ \"context\": \"c3d2-web\", \"description\": \"building...\", \"state\": \"pending\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-$REV.txt\"}"
make -j$(nproc) export DESTDIR=${webroot}/c3d2 \
&> ${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
git clean -fx
# gemini
status "{ \"context\": \"c3d2-gemini\", \"description\": \"building...\", \"state\": \"pending\", \"target_url\": \"https://c3d2-web.serv.zentralwerk.org/log/build-gemini-$REV.txt\"}"
make -f Makefile.gemini -j$(nproc) export DESTDIR=${geminiRoot} \
&> ${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
set -e
done
'';
serviceConfig = {
User = "c3d2-web";
Group = config.users.users.c3d2-web.group;
PrivateTmp = true;
ProtectSystem = "full";
WorkingDirectory = config.users.users.c3d2-web.home;
ReadWritePaths = [ webroot config.users.users.c3d2-web.home ];
};
};
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";
};
};
}