forked from
lass.pds.lassul.us/superconfig
This repository has no description
6.5 kB
216 lines
1{
2 self,
3 config,
4 lib,
5 pkgs,
6 ...
7}:
8let
9 domain = "knot.lassul.us";
10 # DID of the owner's account on pds.lassul.us
11 ownerDid = "did:plc:6nk6tk76up7254wawocoekjr"; # lass.pds.lassul.us
12 user = "tangled-knot";
13 stateDir = "/var/lib/tangled-knot";
14 listenAddr = "127.0.0.1:5555";
15 sshPort = 2222;
16 package = self.packages.${pkgs.stdenv.hostPlatform.system}.tangled.knot-rs;
17
18 # `knot-server config-template` lists the keys; upstream's defaults, except
19 # where commented
20 rendered = (pkgs.formats.toml { }).generate "knot.toml" {
21 server = {
22 hostname = domain;
23 admins = [ ownerDid ];
24 appview_endpoint = "https://git.lassul.us";
25 listen_addr = listenAddr;
26 internal_listen_addr = "[::1]:5444";
27 ssh_listen_addr = "[::]:${toString sshPort}";
28 ssh_host_key_file = "${stateDir}/ssh_host_key";
29 };
30 atproto.plc_directory = "https://plc.directory";
31 acl.admission = "closed";
32 repo.scan_path = "${stateDir}/repos";
33 # sha256 repos can't exchange objects with sha1 ones (github, cgit)
34 git.object_format = "sha1";
35 secrets = {
36 sealed_key_file = "${stateDir}/sealed-keys";
37 master_key_env = "KNOT_MASTER_KEY";
38 };
39 tls = {
40 http3 = true;
41 acme_enabled = false;
42 acme_cache_dir = "${stateDir}/acme";
43 acme_staging = false;
44 mtls_enabled = false;
45 };
46 xrpc = {
47 trusted_proxy_header = "x-forwarded-for";
48 trusted_proxies = [
49 "127.0.0.1"
50 "::1"
51 ];
52 };
53 };
54 configFile = pkgs.runCommandLocal "knot-config.toml" { nativeBuildInputs = [ package ]; } ''
55 knot-server validate --config-only ${rendered}
56 ln -s ${rendered} $out
57 '';
58in
59{
60 # Tangled knot 2 (knot-rs): git hosting for git.lassul.us, following
61 # tangled's nix/modules/knot-rs.nix without its flake (the module files are
62 # in a fetched source, importing them would be IFD): plain HTTP behind
63 # nginx, unprivileged ports, keys in stateDir. The Go knot's unit and user
64 # are both named knot, which the DNS server already uses.
65 # Knot 2 serves ssh itself on [::]:2222 (it binds a single address, and
66 # sshd keeps 22). Port 22 on the public addresses is redirected to it, so
67 # git@knot.lassul.us:<owner>/<repo> works as the appview shows it. Admin
68 # ssh stays on 22 via retiolum/wiregrill and on public 45621
69 # (ssh-redirect.nix).
70
71 clan.core.vars.generators.tangled-knot = {
72 files."knot.env" = { };
73 runtimeInputs = [ pkgs.openssl ];
74 script = ''
75 echo "KNOT_MASTER_KEY=$(openssl rand -base64 32)" > "$out/knot.env"
76 '';
77 };
78
79 environment.systemPackages = [ package ];
80 environment.etc."knot/config.toml".source = configFile;
81
82 users.users.${user} = {
83 isSystemUser = true;
84 home = stateDir;
85 group = user;
86 };
87 users.groups.${user} = { };
88
89 systemd.tmpfiles.settings."10-knot-rs" = {
90 ${stateDir}.d = {
91 mode = "0750";
92 inherit user;
93 group = user;
94 };
95 "${stateDir}/repos".d = {
96 mode = "0750";
97 inherit user;
98 group = user;
99 };
100 };
101
102 systemd.services.knot-rs = {
103 description = "knot git server";
104 after = [ "network-online.target" ];
105 wants = [ "network-online.target" ];
106 wantedBy = [ "multi-user.target" ];
107 restartTriggers = [ configFile ];
108 startLimitIntervalSec = 60;
109 startLimitBurst = 5;
110 serviceConfig = {
111 User = user;
112 Group = user;
113 UMask = "0077";
114 WorkingDirectory = stateDir;
115 EnvironmentFile = config.clan.core.vars.generators.tangled-knot.files."knot.env".path;
116 ExecStart = "${lib.getExe package} /etc/knot/config.toml";
117 Restart = "on-failure";
118 RestartSec = 5;
119 TimeoutStopSec = 120;
120 LimitNOFILE = 65536;
121 # without a cgroup limit the knot budgets for MemTotal and refuses
122 # pushes whose base cache (40% of it) exceeds MemAvailable
123 MemoryMax = "4G";
124 CapabilityBoundingSet = [ ];
125 ReadWritePaths = [
126 stateDir
127 "${stateDir}/repos"
128 ];
129 NoNewPrivileges = true;
130 ProtectProc = "invisible";
131 ProtectSystem = "strict";
132 ProtectHome = true;
133 PrivateTmp = true;
134 PrivateDevices = true;
135 PrivateUsers = true;
136 ProtectHostname = true;
137 ProtectClock = true;
138 ProtectKernelTunables = true;
139 ProtectKernelModules = true;
140 ProtectKernelLogs = true;
141 ProtectControlGroups = true;
142 RestrictAddressFamilies = [
143 "AF_INET"
144 "AF_INET6"
145 "AF_NETLINK"
146 "AF_UNIX"
147 ];
148 RestrictNamespaces = true;
149 LockPersonality = true;
150 MemoryDenyWriteExecute = true;
151 RestrictRealtime = true;
152 RestrictSUIDSGID = true;
153 RemoveIPC = true;
154 PrivateMounts = true;
155 SystemCallFilter = [
156 "@system-service"
157 "~@privileged @resources"
158 ];
159 SystemCallArchitectures = "native";
160 };
161 };
162
163 networking.firewall.allowedTCPPorts = [ sshPort ];
164
165 # knot 2 doesn't request crawls (no KNOT_MIRRORS), so subscribe our
166 # mirror (knotmirror.nix) to the knot's event stream whenever either
167 # starts; the mirror marks a knot offline after it was unreachable for a
168 # while
169 systemd.services.knotmirror-request-crawl = {
170 wantedBy = [
171 "knot-rs.service"
172 "knotmirror.service"
173 ];
174 after = [
175 "knot-rs.service"
176 "knotmirror.service"
177 ];
178 serviceConfig.Type = "oneshot";
179 script = ''
180 ${pkgs.curl}/bin/curl -fsS --retry 10 --retry-connrefused \
181 -H 'Content-Type: application/json' -d '{"hostname":"${domain}"}' \
182 http://127.0.0.1:7000/xrpc/sh.tangled.sync.requestCrawl
183 '';
184 };
185
186 networking.nftables.tables.knot-ssh = {
187 family = "inet";
188 content = ''
189 chain prerouting {
190 type nat hook prerouting priority dstnat; policy accept;
191 ip daddr 95.217.192.59 tcp dport 22 redirect to :${toString sshPort}
192 ip6 daddr 2a01:4f9:4a:4f1a::2 tcp dport 22 redirect to :${toString sshPort}
193 }
194 '';
195 };
196
197 # repos (with the ACL data), sealed keys and ssh host key; the master key
198 # is in vars
199 lass.backup.state.tangled-knot.folders = [ stateDir ];
200
201 services.nginx.virtualHosts.${domain} = {
202 enableACME = true;
203 forceSSL = true;
204 locations."/" = {
205 proxyPass = "http://${listenAddr}";
206 recommendedProxySettings = true;
207 # /events
208 proxyWebsockets = true;
209 extraConfig = ''
210 proxy_read_timeout 1d;
211 client_max_body_size 0;
212 proxy_request_buffering off;
213 '';
214 };
215 };
216}