+1
-170
flake.nix
+1
-170
flake.nix
···
200
200
};
201
201
});
202
202
203
-
nixosModules.knot = {
204
-
config,
205
-
pkgs,
206
-
lib,
207
-
...
208
-
}: let
209
-
cfg = config.services.tangled-knot;
210
-
in
211
-
with lib; {
212
-
options = {
213
-
services.tangled-knot = {
214
-
enable = mkOption {
215
-
type = types.bool;
216
-
default = false;
217
-
description = "Enable a tangled knot";
218
-
};
219
-
220
-
appviewEndpoint = mkOption {
221
-
type = types.str;
222
-
default = "https://tangled.sh";
223
-
description = "Appview endpoint";
224
-
};
225
-
226
-
gitUser = mkOption {
227
-
type = types.str;
228
-
default = "git";
229
-
description = "User that hosts git repos and performs git operations";
230
-
};
231
-
232
-
openFirewall = mkOption {
233
-
type = types.bool;
234
-
default = true;
235
-
description = "Open port 22 in the firewall for ssh";
236
-
};
237
-
238
-
stateDir = mkOption {
239
-
type = types.path;
240
-
default = "/home/${cfg.gitUser}";
241
-
description = "Tangled knot data directory";
242
-
};
243
-
244
-
repo = {
245
-
scanPath = mkOption {
246
-
type = types.path;
247
-
default = cfg.stateDir;
248
-
description = "Path where repositories are scanned from";
249
-
};
250
-
251
-
mainBranch = mkOption {
252
-
type = types.str;
253
-
default = "main";
254
-
description = "Default branch name for repositories";
255
-
};
256
-
};
257
-
258
-
server = {
259
-
listenAddr = mkOption {
260
-
type = types.str;
261
-
default = "0.0.0.0:5555";
262
-
description = "Address to listen on";
263
-
};
264
-
265
-
internalListenAddr = mkOption {
266
-
type = types.str;
267
-
default = "127.0.0.1:5444";
268
-
description = "Internal address for inter-service communication";
269
-
};
270
-
271
-
secretFile = mkOption {
272
-
type = lib.types.path;
273
-
example = "KNOT_SERVER_SECRET=<hash>";
274
-
description = "File containing secret key provided by appview (required)";
275
-
};
276
-
277
-
dbPath = mkOption {
278
-
type = types.path;
279
-
default = "${cfg.stateDir}/knotserver.db";
280
-
description = "Path to the database file";
281
-
};
282
-
283
-
hostname = mkOption {
284
-
type = types.str;
285
-
example = "knot.tangled.sh";
286
-
description = "Hostname for the server (required)";
287
-
};
288
-
289
-
dev = mkOption {
290
-
type = types.bool;
291
-
default = false;
292
-
description = "Enable development mode (disables signature verification)";
293
-
};
294
-
};
295
-
};
296
-
};
297
-
298
-
config = mkIf cfg.enable {
299
-
environment.systemPackages = with pkgs; [git];
300
-
301
-
system.activationScripts.gitConfig = ''
302
-
mkdir -p "${cfg.repo.scanPath}"
303
-
chown -R ${cfg.gitUser}:${cfg.gitUser} \
304
-
"${cfg.repo.scanPath}"
305
-
306
-
mkdir -p "${cfg.stateDir}/.config/git"
307
-
cat > "${cfg.stateDir}/.config/git/config" << EOF
308
-
[user]
309
-
name = Git User
310
-
email = git@example.com
311
-
EOF
312
-
chown -R ${cfg.gitUser}:${cfg.gitUser} \
313
-
"${cfg.stateDir}"
314
-
'';
315
-
316
-
users.users.${cfg.gitUser} = {
317
-
isSystemUser = true;
318
-
useDefaultShell = true;
319
-
home = cfg.stateDir;
320
-
createHome = true;
321
-
group = cfg.gitUser;
322
-
};
323
-
324
-
users.groups.${cfg.gitUser} = {};
325
-
326
-
services.openssh = {
327
-
enable = true;
328
-
extraConfig = ''
329
-
Match User ${cfg.gitUser}
330
-
AuthorizedKeysCommand /etc/ssh/keyfetch_wrapper
331
-
AuthorizedKeysCommandUser nobody
332
-
'';
333
-
};
334
-
335
-
environment.etc."ssh/keyfetch_wrapper" = {
336
-
mode = "0555";
337
-
text = ''
338
-
#!${pkgs.stdenv.shell}
339
-
${self.packages.${pkgs.system}.knot}/bin/knot keys \
340
-
-output authorized-keys \
341
-
-internal-api "http://${cfg.server.internalListenAddr}" \
342
-
-git-dir "${cfg.repo.scanPath}" \
343
-
-log-path /tmp/knotguard.log
344
-
'';
345
-
};
346
-
347
-
systemd.services.knot = {
348
-
description = "knot service";
349
-
after = ["network.target" "sshd.service"];
350
-
wantedBy = ["multi-user.target"];
351
-
serviceConfig = {
352
-
User = cfg.gitUser;
353
-
WorkingDirectory = cfg.stateDir;
354
-
Environment = [
355
-
"KNOT_REPO_SCAN_PATH=${cfg.repo.scanPath}"
356
-
"KNOT_REPO_MAIN_BRANCH=${cfg.repo.mainBranch}"
357
-
"APPVIEW_ENDPOINT=${cfg.appviewEndpoint}"
358
-
"KNOT_SERVER_INTERNAL_LISTEN_ADDR=${cfg.server.internalListenAddr}"
359
-
"KNOT_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}"
360
-
"KNOT_SERVER_DB_PATH=${cfg.server.dbPath}"
361
-
"KNOT_SERVER_HOSTNAME=${cfg.server.hostname}"
362
-
];
363
-
EnvironmentFile = cfg.server.secretFile;
364
-
ExecStart = "${self.packages.${pkgs.system}.knot}/bin/knot server";
365
-
Restart = "always";
366
-
};
367
-
};
368
-
369
-
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [22];
370
-
};
371
-
};
372
-
373
203
nixosConfigurations.knotVM = nixpkgs.lib.nixosSystem {
374
204
system = "x86_64-linux";
375
205
modules = [
···
403
233
];
404
234
};
405
235
nixosModules.appview = import ./nix/modules/appview.nix {inherit self;};
236
+
nixosModules.knot = import ./nix/modules/knot.nix {inherit self;};
406
237
};
407
238
}
+172
nix/modules/knot.nix
+172
nix/modules/knot.nix
···
1
+
{self}: {
2
+
config,
3
+
pkgs,
4
+
lib,
5
+
...
6
+
}: let
7
+
cfg = config.services.tangled-knot;
8
+
in
9
+
with lib; {
10
+
options = {
11
+
services.tangled-knot = {
12
+
enable = mkOption {
13
+
type = types.bool;
14
+
default = false;
15
+
description = "Enable a tangled knot";
16
+
};
17
+
18
+
appviewEndpoint = mkOption {
19
+
type = types.str;
20
+
default = "https://tangled.sh";
21
+
description = "Appview endpoint";
22
+
};
23
+
24
+
gitUser = mkOption {
25
+
type = types.str;
26
+
default = "git";
27
+
description = "User that hosts git repos and performs git operations";
28
+
};
29
+
30
+
openFirewall = mkOption {
31
+
type = types.bool;
32
+
default = true;
33
+
description = "Open port 22 in the firewall for ssh";
34
+
};
35
+
36
+
stateDir = mkOption {
37
+
type = types.path;
38
+
default = "/home/${cfg.gitUser}";
39
+
description = "Tangled knot data directory";
40
+
};
41
+
42
+
repo = {
43
+
scanPath = mkOption {
44
+
type = types.path;
45
+
default = cfg.stateDir;
46
+
description = "Path where repositories are scanned from";
47
+
};
48
+
49
+
mainBranch = mkOption {
50
+
type = types.str;
51
+
default = "main";
52
+
description = "Default branch name for repositories";
53
+
};
54
+
};
55
+
56
+
server = {
57
+
listenAddr = mkOption {
58
+
type = types.str;
59
+
default = "0.0.0.0:5555";
60
+
description = "Address to listen on";
61
+
};
62
+
63
+
internalListenAddr = mkOption {
64
+
type = types.str;
65
+
default = "127.0.0.1:5444";
66
+
description = "Internal address for inter-service communication";
67
+
};
68
+
69
+
secretFile = mkOption {
70
+
type = lib.types.path;
71
+
example = "KNOT_SERVER_SECRET=<hash>";
72
+
description = "File containing secret key provided by appview (required)";
73
+
};
74
+
75
+
dbPath = mkOption {
76
+
type = types.path;
77
+
default = "${cfg.stateDir}/knotserver.db";
78
+
description = "Path to the database file";
79
+
};
80
+
81
+
hostname = mkOption {
82
+
type = types.str;
83
+
example = "knot.tangled.sh";
84
+
description = "Hostname for the server (required)";
85
+
};
86
+
87
+
dev = mkOption {
88
+
type = types.bool;
89
+
default = false;
90
+
description = "Enable development mode (disables signature verification)";
91
+
};
92
+
};
93
+
};
94
+
};
95
+
96
+
config = mkIf cfg.enable {
97
+
environment.systemPackages = with pkgs; [
98
+
git
99
+
self.packages."${pkgs.system}".knot
100
+
];
101
+
102
+
system.activationScripts.gitConfig = ''
103
+
mkdir -p "${cfg.repo.scanPath}"
104
+
chown -R ${cfg.gitUser}:${cfg.gitUser} \
105
+
"${cfg.repo.scanPath}"
106
+
107
+
mkdir -p "${cfg.stateDir}/.config/git"
108
+
cat > "${cfg.stateDir}/.config/git/config" << EOF
109
+
[user]
110
+
name = Git User
111
+
email = git@example.com
112
+
EOF
113
+
chown -R ${cfg.gitUser}:${cfg.gitUser} \
114
+
"${cfg.stateDir}"
115
+
'';
116
+
117
+
users.users.${cfg.gitUser} = {
118
+
isSystemUser = true;
119
+
useDefaultShell = true;
120
+
home = cfg.stateDir;
121
+
createHome = true;
122
+
group = cfg.gitUser;
123
+
};
124
+
125
+
users.groups.${cfg.gitUser} = {};
126
+
127
+
services.openssh = {
128
+
enable = true;
129
+
extraConfig = ''
130
+
Match User ${cfg.gitUser}
131
+
AuthorizedKeysCommand /etc/ssh/keyfetch_wrapper
132
+
AuthorizedKeysCommandUser nobody
133
+
'';
134
+
};
135
+
136
+
environment.etc."ssh/keyfetch_wrapper" = {
137
+
mode = "0555";
138
+
text = ''
139
+
#!${pkgs.stdenv.shell}
140
+
${self.packages.${pkgs.system}.knot}/bin/knot keys \
141
+
-output authorized-keys \
142
+
-internal-api "http://${cfg.server.internalListenAddr}" \
143
+
-git-dir "${cfg.repo.scanPath}" \
144
+
-log-path /tmp/knotguard.log
145
+
'';
146
+
};
147
+
148
+
systemd.services.knot = {
149
+
description = "knot service";
150
+
after = ["network.target" "sshd.service"];
151
+
wantedBy = ["multi-user.target"];
152
+
serviceConfig = {
153
+
User = cfg.gitUser;
154
+
WorkingDirectory = cfg.stateDir;
155
+
Environment = [
156
+
"KNOT_REPO_SCAN_PATH=${cfg.repo.scanPath}"
157
+
"KNOT_REPO_MAIN_BRANCH=${cfg.repo.mainBranch}"
158
+
"APPVIEW_ENDPOINT=${cfg.appviewEndpoint}"
159
+
"KNOT_SERVER_INTERNAL_LISTEN_ADDR=${cfg.server.internalListenAddr}"
160
+
"KNOT_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}"
161
+
"KNOT_SERVER_DB_PATH=${cfg.server.dbPath}"
162
+
"KNOT_SERVER_HOSTNAME=${cfg.server.hostname}"
163
+
];
164
+
EnvironmentFile = cfg.server.secretFile;
165
+
ExecStart = "${self.packages.${pkgs.system}.knot}/bin/knot server";
166
+
Restart = "always";
167
+
};
168
+
};
169
+
170
+
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [22];
171
+
};
172
+
}