blob: 37c076d6a37e0c9226fe016a49cc1b0e86a07103 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
{
pkgs,
lib,
config,
...
}: let
# The domain to serve. Also kinda embedded in the name of the module??
domain = "linus.onl";
# Enable HTTPS stuff.
useACME = true;
# When run, this command causes a rebuild of the website source. See the service defintion for how the site is rebuilt.
startServiceCommand = "/run/current-system/sw/bin/systemctl start ${domain}-source.service";
# Name of the "production" branch where the live content goes.
mainBranch = "main";
in {
config = {
# Create a user to run the build script under.
users.users."${domain}-builder" = {
description = "builds ${domain}";
group = "${domain}-builder";
isSystemUser = true;
};
users.groups."${domain}-builder" = {};
# Create the output directory.
system.activationScripts."${domain}-create-www" = lib.stringAfter ["var"] ''
mkdir -p /var/www/${domain}
chown ${domain}-builder /var/www/${domain}
chgrp ${domain}-builder /var/www/${domain}
chmod 0755 /var/www/${domain}
'';
# Create a systemd service which rebuild the site.
#
# This can't be done using Nix because the site relies on the git build and
# there are some inherent difficulties with including .git/ in the
# inputSource for derivations.
#
# See: https://github.com/NixOS/nix/issues/6900
# See: https://github.com/NixOS/nixpkgs/issues/8567
systemd.services."${domain}-source" = {
description = "generate https://${domain} source";
serviceConfig = {
Type = "oneshot";
User = "${domain}-builder";
Group = "${domain}-builder";
};
path = with pkgs; [
git
rsync
coreutils-full
tcl-8_5
gnumake
];
environment.TCLLIBPATH = "$TCLLIBPATH ${pkgs.tcl-cmark}/lib/tclcmark1.0";
script = ''
# Wait for network to become available first. One would certainly think
# that setting `After=network-online.target` was enough but empirically
# that isn't the case. So here. Have this instead. Fucking shit shit.
max_tries=20
for ((i=0; i<max_tries; i++)); do
if host github.com >/dev/null 2>&1; then
break
else
sleep 2
fi
done
if [ $i -eq $max_tries ]; then
echo >&2 "WARNING: Can't connect to github.com! Trying anyways..."
fi
# Create a temporary directory to work in.
tmpdir="$(mktemp -d -t linus.onl-source.XXXXXXXXXXXX)"
cd "$tmpdir"
trap 'rm -rf $tmpdir' EXIT
# Build the site
git clone --branch=${mainBranch} --filter=blob:none https://github.com/linnnus/${domain} .
make _build
# Copy to destination. Most will likely be unchanged.
rsync --archive --delete _build/ /var/www/${domain}
'';
# TODO: Harden service
# Network must be online for us to check.
# FIXME: This configuration still attempts to run without network/DNS/something, which fails and breaks automatic NixOS updrades.
# https://wiki.archlinux.org/title/Systemd#Running_services_after_the_network_is_up
# https://systemd.io/NETWORK_ONLINE/#discussion
after = ["network-online.target" "nss-lookup.target"];
wants = ["network-online.target" "nss-lookup.target"];
};
# This service will listen for webhook events from GitHub's API. Whenever
# it receives a "push" event, it will start the rebuild service.
services.webhook-listener = {
enable = true;
commands = [
{
event = "push";
command = toString (pkgs.writeShellScript "handle-push-event.sh" ''
${pkgs.jq}/bin/jq --exit-status '.ref == "refs/heads/${mainBranch}"' >/dev/null
case $? in
0)
${config.security.wrapperDir}/sudo ${startServiceCommand}
;;
1)
echo "Not the target ref. Exciting"
exit 0
;;
*)
echo "Got jq error. Exiting." >&2
exit 1
;;
esac
'');
}
];
max-idle-time = "10min";
secret-path = config.age.secrets."linus.onl-github-secret".path;
};
# We have shared a secret with GitHub, which we use to verify requests. Here we decrypt that secret.
age.secrets."linus.onl-github-secret" = {
file = ../../../secrets/linus.onl-github-secret.txt.age;
owner = config.services.webhook-listener.user;
group = config.services.webhook-listener.group;
};
# Commands run by `webhook-listener` are run as an inpriviledged user for
# security reasons. We have to specifically give that user permission to start this one service.
security.sudo.extraRules = [
{
users = [config.services.webhook-listener.user];
commands = [
{
command = startServiceCommand;
options = ["NOPASSWD"];
}
];
}
];
# Register domain name with ddns.
services.cloudflare-dyndns.domains = [domain];
# Register virtual host.
services.nginx = {
virtualHosts."${domain}" = {
# NOTE: 'forceSSL' will cause an infite loop, if the cloudflare proxy does NOT connect over HTTPS.
enableACME = useACME;
forceSSL = useACME;
root = "/var/www/${domain}";
# I have pointed the GitHub webhook requests at <https://linus.onl/webhook>.
# These should be forwarded to `/` on the listening socket server.
locations."= /webhook" = {
recommendedProxySettings = true;
proxyPass = "http://unix:${config.services.webhook-listener.socket-path}:/";
};
};
};
};
}
|