tangled
alpha
login
or
join now
tangled.org
/
infra
Tangled infrastructure definitions in Nix
15
fork
atom
overview
issues
pulls
pipelines
Compare changes
Choose any two refs to compare.
base:
push-voroxwzkvvtt
push-oqkkllmzurup
master
no tags found
compare:
push-voroxwzkvvtt
push-oqkkllmzurup
master
no tags found
go
+976
-80
20 changed files
expand all
collapse all
unified
split
flake.lock
flake.nix
hosts
appview
configuration.nix
disk-config.nix
services
appview.nix
litestream.nix
nginx-alpha.nix
nginx.nix
knot1
configuration.nix
disk-config.nix
services
knot.nix
nginx.nix
nixery
configuration.nix
services
nginx.nix
spindle
configuration.nix
disk-config.nix
services
nginx.nix
openbao
openbao.nix
proxy.nix
spindle.nix
+57
-25
flake.lock
···
1
1
{
2
2
"nodes": {
3
3
+
"actor-typeahead-src": {
4
4
+
"flake": false,
5
5
+
"locked": {
6
6
+
"lastModified": 1762835797,
7
7
+
"narHash": "sha256-heizoWUKDdar6ymfZTnj3ytcEv/L4d4fzSmtr0HlXsQ=",
8
8
+
"ref": "refs/heads/main",
9
9
+
"rev": "677fe7f743050a4e7f09d4a6f87bbf1325a06f6b",
10
10
+
"revCount": 6,
11
11
+
"type": "git",
12
12
+
"url": "https://tangled.org/@jakelazaroff.com/actor-typeahead"
13
13
+
},
14
14
+
"original": {
15
15
+
"type": "git",
16
16
+
"url": "https://tangled.org/@jakelazaroff.com/actor-typeahead"
17
17
+
}
18
18
+
},
3
19
"colmena": {
4
20
"inputs": {
5
21
"flake-compat": "flake-compat",
···
29
45
]
30
46
},
31
47
"locked": {
32
32
-
"lastModified": 1751854533,
33
33
-
"narHash": "sha256-U/OQFplExOR1jazZY4KkaQkJqOl59xlh21HP9mI79Vc=",
48
48
+
"lastModified": 1766150702,
49
49
+
"narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=",
34
50
"owner": "nix-community",
35
51
"repo": "disko",
36
36
-
"rev": "16b74a1e304197248a1bc663280f2548dbfcae3c",
52
52
+
"rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378",
37
53
"type": "github"
38
54
},
39
55
"original": {
···
58
74
"type": "github"
59
75
}
60
76
},
77
77
+
"flake-compat_2": {
78
78
+
"flake": false,
79
79
+
"locked": {
80
80
+
"lastModified": 1751685974,
81
81
+
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
82
82
+
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
83
83
+
"type": "tarball",
84
84
+
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1"
85
85
+
},
86
86
+
"original": {
87
87
+
"type": "tarball",
88
88
+
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
89
89
+
}
90
90
+
},
61
91
"flake-utils": {
62
92
"locked": {
63
93
"lastModified": 1659877975,
···
78
108
"systems": "systems"
79
109
},
80
110
"locked": {
81
81
-
"lastModified": 1694529238,
82
82
-
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
111
111
+
"lastModified": 1731533236,
112
112
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
83
113
"owner": "numtide",
84
114
"repo": "flake-utils",
85
85
-
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
115
115
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
86
116
"type": "github"
87
117
},
88
118
"original": {
···
100
130
]
101
131
},
102
132
"locked": {
103
103
-
"lastModified": 1754078208,
104
104
-
"narHash": "sha256-YVoIFDCDpYuU3riaDEJ3xiGdPOtsx4sR5eTzHTytPV8=",
133
133
+
"lastModified": 1763982521,
134
134
+
"narHash": "sha256-ur4QIAHwgFc0vXiaxn5No/FuZicxBr2p0gmT54xZkUQ=",
105
135
"owner": "nix-community",
106
136
"repo": "gomod2nix",
107
107
-
"rev": "7f963246a71626c7fc70b431a315c4388a0c95cf",
137
137
+
"rev": "02e63a239d6eabd595db56852535992c898eba72",
108
138
"type": "github"
109
139
},
110
140
"original": {
···
195
225
"nixery-flake": {
196
226
"flake": false,
197
227
"locked": {
198
198
-
"lastModified": 1745149613,
199
199
-
"narHash": "sha256-rcSnsnSWA0IUjmbG2iSpvVB0702tcR3zIyU3iFJBo0g=",
228
228
+
"lastModified": 1762501370,
229
229
+
"narHash": "sha256-WO2NvvFB3KkFfChE5F6ghog7mvBAVKpMsQMqwadZT4k=",
200
230
"owner": "tazjin",
201
231
"repo": "nixery",
202
202
-
"rev": "c6d4692b1b6eb105c9abce0411d2ef4b8708a6e1",
232
232
+
"rev": "be8a4005de3f27f95e677e7b61abef387d4a840d",
203
233
"type": "github"
204
234
},
205
235
"original": {
···
226
256
},
227
257
"nixpkgs_2": {
228
258
"locked": {
229
229
-
"lastModified": 1751792365,
230
230
-
"narHash": "sha256-J1kI6oAj25IG4EdVlg2hQz8NZTBNYvIS0l4wpr9KcUo=",
259
259
+
"lastModified": 1767634882,
260
260
+
"narHash": "sha256-2GffSfQxe3sedHzK+sTKlYo/NTIAGzbFCIsNMUPAAnk=",
231
261
"owner": "nixos",
232
262
"repo": "nixpkgs",
233
233
-
"rev": "1fd8bada0b6117e6c7eb54aad5813023eed37ccb",
263
263
+
"rev": "3c9db02515ef1d9b6b709fc60ba9a540957f661c",
234
264
"type": "github"
235
265
},
236
266
"original": {
237
267
"owner": "nixos",
238
238
-
"ref": "nixos-unstable",
268
268
+
"ref": "nixos-25.11",
239
269
"repo": "nixpkgs",
240
270
"type": "github"
241
271
}
242
272
},
243
273
"nixpkgs_3": {
244
274
"locked": {
245
245
-
"lastModified": 1751984180,
246
246
-
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
275
275
+
"lastModified": 1766070988,
276
276
+
"narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=",
247
277
"owner": "nixos",
248
278
"repo": "nixpkgs",
249
249
-
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
279
279
+
"rev": "c6245e83d836d0433170a16eb185cefe0572f8b8",
250
280
"type": "github"
251
281
},
252
282
"original": {
···
311
341
},
312
342
"tangled": {
313
343
"inputs": {
344
344
+
"actor-typeahead-src": "actor-typeahead-src",
345
345
+
"flake-compat": "flake-compat_2",
314
346
"gomod2nix": "gomod2nix",
315
347
"htmx-src": "htmx-src",
316
348
"htmx-ws-src": "htmx-ws-src",
···
322
354
"sqlite-lib-src": "sqlite-lib-src"
323
355
},
324
356
"locked": {
325
325
-
"lastModified": 1756407744,
326
326
-
"narHash": "sha256-7TtLbIjx3nQRC4dpOupwZxkLhf9iAj254p3n61VJkNY=",
357
357
+
"lastModified": 1768543916,
358
358
+
"narHash": "sha256-dI/7LHNnh1PE2o17hahevgC3pLqW4ShuIVYEQ0i4ePs=",
327
359
"ref": "refs/heads/master",
328
328
-
"rev": "fd6502223cfe1493155524818e65b1eaecaef515",
329
329
-
"revCount": 1259,
360
360
+
"rev": "4b495898a89d2ac1ba36488b9998b17c9d15363d",
361
361
+
"revCount": 1818,
330
362
"type": "git",
331
331
-
"url": "https://tangled.sh/@tangled.sh/core"
363
363
+
"url": "https://tangled.org/tangled.org/core"
332
364
},
333
365
"original": {
334
366
"type": "git",
335
335
-
"url": "https://tangled.sh/@tangled.sh/core"
367
367
+
"url": "https://tangled.org/tangled.org/core"
336
368
}
337
369
}
338
370
},
+99
-54
flake.nix
···
1
1
{
2
2
description = "nix infra for tangled";
3
3
+
3
4
inputs = {
4
4
-
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
5
5
-
tangled.url = "git+https://tangled.sh/@tangled.sh/core";
5
5
+
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
6
6
+
tangled.url = "git+https://tangled.org/tangled.org/core";
6
7
colmena.url = "github:zhaofengli/colmena/release-0.4.x";
7
8
disko = {
8
9
url = "github:nix-community/disko";
···
16
17
};
17
18
};
18
19
19
19
-
outputs =
20
20
-
{ nixpkgs, disko, colmena, nixery-flake, tangled, ... }:
21
21
-
{
22
22
-
nixosConfigurations.nixery = nixpkgs.lib.nixosSystem {
23
23
-
system = "x86_64-linux";
24
24
-
modules = [
25
25
-
disko.nixosModules.disko
26
26
-
tangled.nixosModules.spindle
27
27
-
./hosts/nixery/configuration.nix
28
28
-
];
29
29
-
};
30
30
-
nixosConfigurations.pds = nixpkgs.lib.nixosSystem {
31
31
-
system = "x86_64-linux";
32
32
-
modules = [
33
33
-
disko.nixosModules.disko
34
34
-
./hosts/pds/configuration.nix
35
35
-
];
36
36
-
};
20
20
+
outputs = { nixpkgs, disko, colmena, nixery-flake, tangled, ... }:
21
21
+
let
22
22
+
system = "x86_64-linux";
23
23
+
commonArgs = import ./common/ssh.nix;
37
24
38
38
-
colmenaHive = colmena.lib.makeHive {
39
39
-
meta = {
40
40
-
nixpkgs = nixpkgs.legacyPackages.x86_64-linux;
41
41
-
specialArgs = {
42
42
-
nixery-pkgs = import nixery-flake.outPath {
43
43
-
pkgs = import nixpkgs { system = "x86_64-linux"; };
44
44
-
};
45
45
-
commonArgs = import ./common/ssh.nix;
46
46
-
};
25
25
+
# Helper function to create nixosConfiguration
26
26
+
mkHost = hostname: extraModules:
27
27
+
nixpkgs.lib.nixosSystem {
28
28
+
inherit system;
29
29
+
specialArgs = { inherit commonArgs; };
30
30
+
modules = [
31
31
+
disko.nixosModules.disko
32
32
+
./hosts/${hostname}/configuration.nix
33
33
+
] ++ extraModules;
47
34
};
48
35
49
49
-
defaults = { pkgs, ... }: {
50
50
-
environment.systemPackages = [
51
51
-
pkgs.curl
52
52
-
];
53
53
-
};
54
54
-
pds = { pkgs, ... }: {
36
36
+
# Helper function to create colmena host
37
37
+
mkColmenaHost = hostname: targetHost: targetPort: extraModules:
38
38
+
{
55
39
deployment = {
56
56
-
targetHost = "tngl.sh";
57
57
-
targetPort = 22;
40
40
+
inherit targetHost;
41
41
+
inherit targetPort;
58
42
targetUser = "tangler";
59
43
buildOnTarget = true;
60
44
};
61
61
-
nixpkgs.system = "x86_64-linux";
62
62
-
45
45
+
nixpkgs.system = system;
46
46
+
time.timeZone = "Europe/Helsinki";
63
47
imports = [
64
48
disko.nixosModules.disko
65
65
-
./hosts/pds/configuration.nix
49
49
+
./hosts/${hostname}/configuration.nix
50
50
+
] ++ extraModules;
51
51
+
};
52
52
+
53
53
+
# Host configurations
54
54
+
hosts = {
55
55
+
appview = {
56
56
+
modules = [
57
57
+
tangled.nixosModules.appview
58
58
+
./hosts/appview/services/appview.nix
59
59
+
./hosts/appview/services/nginx.nix
60
60
+
./hosts/appview/services/litestream.nix
61
61
+
];
62
62
+
target = "95.111.205.38";
63
63
+
};
64
64
+
65
65
+
pds = {
66
66
+
modules = [
66
67
./hosts/pds/services/nginx.nix
67
68
./hosts/pds/services/pds.nix
68
69
];
69
69
-
time.timeZone = "Europe/Helsinki";
70
70
+
target = "tngl.sh";
70
71
};
71
72
72
72
-
nixery = { pkgs, ... }: {
73
73
-
deployment = {
74
74
-
targetHost = "nixery.tangled.sh";
75
75
-
targetPort = 22;
76
76
-
targetUser = "tangler";
77
77
-
buildOnTarget = true;
78
78
-
};
79
79
-
nixpkgs.system = "x86_64-linux";
80
80
-
81
81
-
imports = [
82
82
-
disko.nixosModules.disko
73
73
+
nixery = {
74
74
+
modules = [
83
75
tangled.nixosModules.spindle
84
84
-
./hosts/nixery/configuration.nix
85
76
./hosts/nixery/services/nginx.nix
86
77
./hosts/nixery/services/openbao/openbao.nix
87
78
./hosts/nixery/services/openbao/proxy.nix
88
79
./hosts/nixery/services/nixery.nix
89
80
];
90
90
-
time.timeZone = "Europe/Helsinki";
81
81
+
target = "nixery.tangled.sh";
82
82
+
};
83
83
+
84
84
+
spindle = {
85
85
+
modules = [
86
86
+
tangled.nixosModules.spindle
87
87
+
./hosts/spindle/services/openbao/openbao.nix
88
88
+
./hosts/spindle/services/openbao/proxy.nix
89
89
+
./hosts/spindle/services/spindle.nix
90
90
+
./hosts/spindle/services/nginx.nix
91
91
+
];
92
92
+
target = "spindle.alpha.tangled.sh";
91
93
};
94
94
+
95
95
+
knot1 = {
96
96
+
modules = [
97
97
+
tangled.nixosModules.knot
98
98
+
./hosts/knot1/services/knot.nix
99
99
+
./hosts/knot1/services/nginx.nix
100
100
+
];
101
101
+
target = "knot1.alpha.tangled.sh";
102
102
+
};
103
103
+
};
104
104
+
in
105
105
+
{
106
106
+
# nixos-anywhere and nixos-rebuild use these
107
107
+
nixosConfigurations = {
108
108
+
appview = mkHost "appview" hosts.appview.modules;
109
109
+
pds = mkHost "pds" hosts.pds.modules;
110
110
+
nixery = mkHost "nixery" hosts.nixery.modules;
111
111
+
spindle = mkHost "spindle" hosts.spindle.modules;
112
112
+
knot1 = mkHost "knot1" hosts.knot1.modules;
113
113
+
};
114
114
+
115
115
+
# colmena uses this
116
116
+
colmenaHive = colmena.lib.makeHive {
117
117
+
meta = {
118
118
+
nixpkgs = nixpkgs.legacyPackages.${system};
119
119
+
specialArgs = {
120
120
+
inherit commonArgs;
121
121
+
nixery-pkgs = import nixery-flake.outPath {
122
122
+
pkgs = import nixpkgs { inherit system; };
123
123
+
};
124
124
+
tangled-pkgs = tangled.packages.x86_64-linux;
125
125
+
};
126
126
+
};
127
127
+
128
128
+
defaults = { pkgs, ... }: {
129
129
+
environment.systemPackages = [ pkgs.curl ];
130
130
+
};
131
131
+
132
132
+
appview = mkColmenaHost "appview" hosts.appview.target 2222 hosts.appview.modules;
133
133
+
pds = mkColmenaHost "pds" hosts.pds.target 22 hosts.pds.modules;
134
134
+
nixery = mkColmenaHost "nixery" hosts.nixery.target 22 hosts.nixery.modules;
135
135
+
spindle = mkColmenaHost "spindle" hosts.spindle.target 22 hosts.spindle.modules;
136
136
+
knot1 = mkColmenaHost "knot1" hosts.knot1.target 22 hosts.knot1.modules;
92
137
};
93
138
};
94
139
}
+62
hosts/appview/configuration.nix
···
1
1
+
{ modulesPath
2
2
+
, lib
3
3
+
, pkgs
4
4
+
, ...
5
5
+
} @ args:
6
6
+
{
7
7
+
imports = [
8
8
+
(modulesPath + "/installer/scan/not-detected.nix")
9
9
+
(modulesPath + "/profiles/qemu-guest.nix")
10
10
+
./disk-config.nix
11
11
+
];
12
12
+
boot.loader.grub = {
13
13
+
# no need to set devices, disko will add all devices that have a EF02 partition to the list already
14
14
+
# devices = [ ];
15
15
+
efiSupport = true;
16
16
+
efiInstallAsRemovable = true;
17
17
+
};
18
18
+
19
19
+
networking.hostName = "appview-arn";
20
20
+
services = {
21
21
+
openssh.enable = true;
22
22
+
openssh.ports = [2222];
23
23
+
};
24
24
+
25
25
+
# networking.extraHosts = ''
26
26
+
# 85.9.211.103 knot1.tangled.sh
27
27
+
# '';
28
28
+
29
29
+
30
30
+
nix = {
31
31
+
extraOptions = ''
32
32
+
experimental-features = nix-command flakes ca-derivations
33
33
+
warn-dirty = false
34
34
+
keep-outputs = false
35
35
+
'';
36
36
+
};
37
37
+
38
38
+
environment.systemPackages = map lib.lowPrio [
39
39
+
pkgs.curl
40
40
+
pkgs.gitMinimal
41
41
+
];
42
42
+
43
43
+
users.users.tangler = {
44
44
+
extraGroups = [ "networkmanager" "wheel" ];
45
45
+
openssh.authorizedKeys.keys = args.commonArgs.sshKeys;
46
46
+
isNormalUser = true;
47
47
+
};
48
48
+
49
49
+
security.sudo.extraRules = [
50
50
+
{
51
51
+
users = [ "tangler" ];
52
52
+
commands = [
53
53
+
{
54
54
+
command = "ALL";
55
55
+
options = [ "NOPASSWD" ];
56
56
+
}
57
57
+
];
58
58
+
}
59
59
+
];
60
60
+
61
61
+
system.stateVersion = "25.05";
62
62
+
}
+56
hosts/appview/disk-config.nix
···
1
1
+
# Example to create a bios compatible gpt partition
2
2
+
{ lib, ... }:
3
3
+
{
4
4
+
disko.devices = {
5
5
+
disk.disk1 = {
6
6
+
device = lib.mkDefault "/dev/vda";
7
7
+
type = "disk";
8
8
+
content = {
9
9
+
type = "gpt";
10
10
+
partitions = {
11
11
+
boot = {
12
12
+
name = "boot";
13
13
+
size = "1M";
14
14
+
type = "EF02";
15
15
+
};
16
16
+
esp = {
17
17
+
name = "ESP";
18
18
+
size = "500M";
19
19
+
type = "EF00";
20
20
+
content = {
21
21
+
type = "filesystem";
22
22
+
format = "vfat";
23
23
+
mountpoint = "/boot";
24
24
+
};
25
25
+
};
26
26
+
root = {
27
27
+
name = "root";
28
28
+
size = "100%";
29
29
+
content = {
30
30
+
type = "lvm_pv";
31
31
+
vg = "pool";
32
32
+
};
33
33
+
};
34
34
+
};
35
35
+
};
36
36
+
};
37
37
+
lvm_vg = {
38
38
+
pool = {
39
39
+
type = "lvm_vg";
40
40
+
lvs = {
41
41
+
root = {
42
42
+
size = "100%FREE";
43
43
+
content = {
44
44
+
type = "filesystem";
45
45
+
format = "ext4";
46
46
+
mountpoint = "/";
47
47
+
mountOptions = [
48
48
+
"defaults"
49
49
+
];
50
50
+
};
51
51
+
};
52
52
+
};
53
53
+
};
54
54
+
};
55
55
+
};
56
56
+
}
+11
hosts/appview/services/appview.nix
···
1
1
+
{ modulesPath
2
2
+
, lib
3
3
+
, pkgs
4
4
+
, ...
5
5
+
} @ args:
6
6
+
{
7
7
+
services.tangled.appview = {
8
8
+
enable = true;
9
9
+
environmentFile = "/etc/secrets/appview.env";
10
10
+
};
11
11
+
}
+36
hosts/appview/services/litestream.nix
···
1
1
+
{
2
2
+
services.litestream = {
3
3
+
enable = true;
4
4
+
environmentFile = "/etc/secrets/litestream.env";
5
5
+
6
6
+
settings = {
7
7
+
dbs = [
8
8
+
{
9
9
+
path = "/var/lib/appview/appview.db";
10
10
+
replicas = [
11
11
+
{
12
12
+
type = "s3";
13
13
+
bucket = "appview-backup";
14
14
+
region = "europe-2";
15
15
+
path = "appview.db";
16
16
+
endpoint = "$S3_ENDPOINT_URL";
17
17
+
snapshot-interval = "1h";
18
18
+
}
19
19
+
];
20
20
+
}
21
21
+
];
22
22
+
};
23
23
+
};
24
24
+
25
25
+
systemd.services.litestream.serviceConfig = {
26
26
+
ReadWritePaths = [ "/var/lib/appview" ];
27
27
+
};
28
28
+
29
29
+
systemd.tmpfiles.rules = [
30
30
+
"d /var/lib/appview 0775 root litestream - -"
31
31
+
"a+ /var/lib/appview - - - - user:litestream:rwx"
32
32
+
"a+ /var/lib/appview/appview.db - - - - user:litestream:rwx"
33
33
+
"a+ /var/lib/appview/appview.db-wal - - - - user:litestream:rwx"
34
34
+
"a+ /var/lib/appview/appview.db-shm - - - - user:litestream:rwx"
35
35
+
];
36
36
+
}
+53
hosts/appview/services/nginx-alpha.nix
···
1
1
+
{ config, pkgs, ... }:
2
2
+
{
3
3
+
services.nginx = {
4
4
+
enable = true;
5
5
+
recommendedTlsSettings = true;
6
6
+
recommendedOptimisation = true;
7
7
+
recommendedGzipSettings = true;
8
8
+
9
9
+
# Fix proxy headers hash warnings
10
10
+
appendHttpConfig = ''
11
11
+
proxy_headers_hash_max_size 1024;
12
12
+
proxy_headers_hash_bucket_size 128;
13
13
+
'';
14
14
+
15
15
+
virtualHosts = {
16
16
+
# AppView service on alpha.tangled.sh
17
17
+
"alpha.tangled.sh" = {
18
18
+
forceSSL = true;
19
19
+
enableACME = true;
20
20
+
21
21
+
locations."/" = {
22
22
+
proxyPass = "http://127.0.0.1:3000";
23
23
+
extraConfig = ''
24
24
+
proxy_http_version 1.1;
25
25
+
proxy_set_header Host $host;
26
26
+
proxy_set_header X-Real-IP $remote_addr;
27
27
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
28
28
+
proxy_set_header X-Forwarded-Proto $scheme;
29
29
+
client_max_body_size 100M;
30
30
+
'';
31
31
+
};
32
32
+
33
33
+
# WebSocket support for /logs endpoint
34
34
+
locations."~ /logs$" = {
35
35
+
proxyPass = "http://127.0.0.1:3000";
36
36
+
proxyWebsockets = true;
37
37
+
extraConfig = ''
38
38
+
proxy_read_timeout 86400;
39
39
+
'';
40
40
+
};
41
41
+
};
42
42
+
};
43
43
+
};
44
44
+
45
45
+
# Open firewall ports
46
46
+
networking.firewall.allowedTCPPorts = [ 80 443 ];
47
47
+
48
48
+
# ACME configuration for Let's Encrypt
49
49
+
security.acme = {
50
50
+
acceptTerms = true;
51
51
+
defaults.email = "team@tangled.org";
52
52
+
};
53
53
+
}
+123
hosts/appview/services/nginx.nix
···
1
1
+
{ config, pkgs, ... }:
2
2
+
{
3
3
+
services.nginx = {
4
4
+
enable = true;
5
5
+
recommendedProxySettings = true;
6
6
+
recommendedTlsSettings = true;
7
7
+
recommendedOptimisation = true;
8
8
+
recommendedGzipSettings = true;
9
9
+
10
10
+
# bot blocking
11
11
+
appendHttpConfig = ''
12
12
+
map $http_user_agent $block_bot {
13
13
+
default 0;
14
14
+
~*PerplexityBot 1;
15
15
+
~*GPTBot 1;
16
16
+
~*ChatGPT-User 1;
17
17
+
~*CCBot 1;
18
18
+
~*anthropic-ai 1;
19
19
+
~*Claude-Web 1;
20
20
+
}
21
21
+
'';
22
22
+
23
23
+
streamConfig = ''
24
24
+
upstream knot-sailor {
25
25
+
server 94.237.110.185:22;
26
26
+
}
27
27
+
28
28
+
server {
29
29
+
listen 22;
30
30
+
listen [::]:22;
31
31
+
proxy_pass knot-sailor;
32
32
+
}
33
33
+
'';
34
34
+
35
35
+
virtualHosts = {
36
36
+
# Redirect tangled.sh โ tangled.org
37
37
+
"tangled.sh" = {
38
38
+
serverAliases = [ "www.tangled.sh" ];
39
39
+
locations."/" = {
40
40
+
return = "301 https://tangled.org$request_uri";
41
41
+
};
42
42
+
forceSSL = true;
43
43
+
enableACME = true;
44
44
+
};
45
45
+
46
46
+
# Redirect strings.tangled.sh โ tangled.org/strings/*
47
47
+
"strings.tangled.sh" = {
48
48
+
locations."/" = {
49
49
+
return = "301 https://tangled.org/strings$request_uri";
50
50
+
};
51
51
+
forceSSL = true;
52
52
+
enableACME = true;
53
53
+
};
54
54
+
55
55
+
# Redirect strings.tangled.org โ tangled.org/strings/*
56
56
+
"strings.tangled.org" = {
57
57
+
locations."/" = {
58
58
+
return = "301 https://tangled.org/strings$request_uri";
59
59
+
};
60
60
+
forceSSL = true;
61
61
+
enableACME = true;
62
62
+
};
63
63
+
64
64
+
# Main app on tangled.org
65
65
+
"tangled.org" = {
66
66
+
serverAliases = [ "www.tangled.org" ];
67
67
+
68
68
+
forceSSL = true;
69
69
+
enableACME = true;
70
70
+
71
71
+
extraConfig = ''
72
72
+
if ($block_bot) {
73
73
+
return 403;
74
74
+
}
75
75
+
76
76
+
# Redirect www โ bare domain
77
77
+
if ($host = www.tangled.org) {
78
78
+
return 301 https://tangled.org$request_uri;
79
79
+
}
80
80
+
81
81
+
client_max_body_size 100M;
82
82
+
'';
83
83
+
84
84
+
locations."~ ^/@tangled\\.sh(/.*)?$" = {
85
85
+
extraConfig = ''
86
86
+
rewrite ^/@tangled\.sh(.*)$ https://tangled.org/@tangled.org$1 permanent;
87
87
+
'';
88
88
+
};
89
89
+
90
90
+
locations."~ ^/tangled\\.sh(/.*)?$" = {
91
91
+
extraConfig = ''
92
92
+
rewrite ^/tangled\.sh(.*)$ https://tangled.org/tangled.org$1 permanent;
93
93
+
'';
94
94
+
};
95
95
+
96
96
+
97
97
+
locations."~ /logs$" = {
98
98
+
proxyPass = "http://127.0.0.1:3000";
99
99
+
proxyWebsockets = true;
100
100
+
extraConfig = ''
101
101
+
proxy_read_timeout 86400;
102
102
+
'';
103
103
+
};
104
104
+
105
105
+
locations."/" = {
106
106
+
proxyPass = "http://127.0.0.1:3000";
107
107
+
extraConfig = ''
108
108
+
client_max_body_size 100M;
109
109
+
'';
110
110
+
};
111
111
+
};
112
112
+
};
113
113
+
};
114
114
+
115
115
+
# Open firewall ports
116
116
+
networking.firewall.allowedTCPPorts = [ 80 443 2222 22 ];
117
117
+
118
118
+
# ACME configuration for Let's Encrypt
119
119
+
security.acme = {
120
120
+
acceptTerms = true;
121
121
+
defaults.email = "team@tangled.org";
122
122
+
};
123
123
+
}
+57
hosts/knot1/configuration.nix
···
1
1
+
{ modulesPath
2
2
+
, lib
3
3
+
, pkgs
4
4
+
, ...
5
5
+
} @ args:
6
6
+
{
7
7
+
imports = [
8
8
+
(modulesPath + "/installer/scan/not-detected.nix")
9
9
+
(modulesPath + "/profiles/qemu-guest.nix")
10
10
+
./disk-config.nix
11
11
+
];
12
12
+
boot.loader.grub = {
13
13
+
# no need to set devices, disko will add all devices that have a EF02 partition to the list already
14
14
+
# devices = [ ];
15
15
+
efiSupport = true;
16
16
+
efiInstallAsRemovable = true;
17
17
+
};
18
18
+
19
19
+
networking.hostName = "knot1-ams";
20
20
+
services = {
21
21
+
openssh.enable = true;
22
22
+
};
23
23
+
24
24
+
25
25
+
nix = {
26
26
+
extraOptions = ''
27
27
+
experimental-features = nix-command flakes ca-derivations
28
28
+
warn-dirty = false
29
29
+
keep-outputs = false
30
30
+
'';
31
31
+
};
32
32
+
33
33
+
environment.systemPackages = map lib.lowPrio [
34
34
+
pkgs.curl
35
35
+
pkgs.gitMinimal
36
36
+
];
37
37
+
38
38
+
users.users.tangler = {
39
39
+
extraGroups = [ "networkmanager" "wheel" "docker" ];
40
40
+
openssh.authorizedKeys.keys = args.commonArgs.sshKeys;
41
41
+
isNormalUser = true;
42
42
+
};
43
43
+
44
44
+
security.sudo.extraRules = [
45
45
+
{
46
46
+
users = [ "tangler" ];
47
47
+
commands = [
48
48
+
{
49
49
+
command = "ALL";
50
50
+
options = [ "NOPASSWD" ];
51
51
+
}
52
52
+
];
53
53
+
}
54
54
+
];
55
55
+
56
56
+
system.stateVersion = "25.05";
57
57
+
}
+56
hosts/knot1/disk-config.nix
···
1
1
+
# Example to create a bios compatible gpt partition
2
2
+
{ lib, ... }:
3
3
+
{
4
4
+
disko.devices = {
5
5
+
disk.disk1 = {
6
6
+
device = lib.mkDefault "/dev/vda";
7
7
+
type = "disk";
8
8
+
content = {
9
9
+
type = "gpt";
10
10
+
partitions = {
11
11
+
boot = {
12
12
+
name = "boot";
13
13
+
size = "1M";
14
14
+
type = "EF02";
15
15
+
};
16
16
+
esp = {
17
17
+
name = "ESP";
18
18
+
size = "500M";
19
19
+
type = "EF00";
20
20
+
content = {
21
21
+
type = "filesystem";
22
22
+
format = "vfat";
23
23
+
mountpoint = "/boot";
24
24
+
};
25
25
+
};
26
26
+
root = {
27
27
+
name = "root";
28
28
+
size = "100%";
29
29
+
content = {
30
30
+
type = "lvm_pv";
31
31
+
vg = "pool";
32
32
+
};
33
33
+
};
34
34
+
};
35
35
+
};
36
36
+
};
37
37
+
lvm_vg = {
38
38
+
pool = {
39
39
+
type = "lvm_vg";
40
40
+
lvs = {
41
41
+
root = {
42
42
+
size = "100%FREE";
43
43
+
content = {
44
44
+
type = "filesystem";
45
45
+
format = "ext4";
46
46
+
mountpoint = "/";
47
47
+
mountOptions = [
48
48
+
"defaults"
49
49
+
];
50
50
+
};
51
51
+
};
52
52
+
};
53
53
+
};
54
54
+
};
55
55
+
};
56
56
+
}
+11
hosts/knot1/services/knot.nix
···
1
1
+
{
2
2
+
services.tangled.knot = {
3
3
+
enable = true;
4
4
+
stateDir = "/home/git";
5
5
+
server = {
6
6
+
listenAddr = "127.0.0.1:5555";
7
7
+
owner = "did:plc:hwevmowznbiukdf6uk5dwrrq";
8
8
+
hostname = "knot1.alpha.tangled.sh";
9
9
+
};
10
10
+
};
11
11
+
}
+35
hosts/knot1/services/nginx.nix
···
1
1
+
{
2
2
+
services.nginx = {
3
3
+
enable = true;
4
4
+
virtualHosts = {
5
5
+
"knot1.alpha.tangled.sh" = {
6
6
+
forceSSL = true;
7
7
+
enableACME = true;
8
8
+
locations."/" = {
9
9
+
proxyPass = "http://127.0.0.1:5555";
10
10
+
11
11
+
extraConfig = ''
12
12
+
proxy_set_header X-Forwarded-For $remote_addr;
13
13
+
proxy_set_header Host $host;
14
14
+
proxy_set_header X-Real-IP $remote_addr;
15
15
+
proxy_set_header X-Forwarded-Proto $scheme;
16
16
+
'';
17
17
+
};
18
18
+
locations."/events" = {
19
19
+
proxyPass = "http://127.0.0.1:5555";
20
20
+
extraConfig = ''
21
21
+
proxy_set_header X-Forwarded-For $remote_addr;
22
22
+
proxy_set_header Host $host;
23
23
+
proxy_set_header Upgrade $http_upgrade;
24
24
+
proxy_set_header Connection "upgrade";
25
25
+
'';
26
26
+
};
27
27
+
};
28
28
+
};
29
29
+
};
30
30
+
security.acme = {
31
31
+
acceptTerms = true;
32
32
+
defaults.email = "team@tangled.org";
33
33
+
};
34
34
+
networking.firewall.allowedTCPPorts = [ 80 443 ];
35
35
+
}
+1
-1
hosts/nixery/configuration.nix
···
19
19
networking.hostName = "nixery";
20
20
services = {
21
21
openssh.enable = true;
22
22
-
tangled-spindle = {
22
22
+
tangled.spindle = {
23
23
enable = true;
24
24
server = {
25
25
owner = "did:plc:wshs7t2adsemcrrd4snkeqli"; # @tangled.sh
+11
hosts/nixery/services/nginx.nix
···
1
1
+
{ tangled-pkgs, pkgs, ... }:
2
2
+
1
3
{
2
4
services.nginx = {
3
5
enable = true;
4
6
virtualHosts = {
7
7
+
"docs.tangled.org" = {
8
8
+
forceSSL = true;
9
9
+
enableACME = true;
10
10
+
root = "${tangled-pkgs.docs}";
11
11
+
locations."/" = {
12
12
+
tryFiles = "$uri $uri/ =404";
13
13
+
index = "index.html";
14
14
+
};
15
15
+
};
5
16
"nixery.tangled.sh" = {
6
17
forceSSL = true;
7
18
enableACME = true;
+57
hosts/spindle/configuration.nix
···
1
1
+
{ modulesPath
2
2
+
, lib
3
3
+
, pkgs
4
4
+
, ...
5
5
+
} @ args:
6
6
+
{
7
7
+
imports = [
8
8
+
(modulesPath + "/installer/scan/not-detected.nix")
9
9
+
(modulesPath + "/profiles/qemu-guest.nix")
10
10
+
./disk-config.nix
11
11
+
];
12
12
+
boot.loader.grub = {
13
13
+
# no need to set devices, disko will add all devices that have a EF02 partition to the list already
14
14
+
# devices = [ ];
15
15
+
efiSupport = true;
16
16
+
efiInstallAsRemovable = true;
17
17
+
};
18
18
+
19
19
+
networking.hostName = "spindle-waw";
20
20
+
services = {
21
21
+
openssh.enable = true;
22
22
+
};
23
23
+
24
24
+
25
25
+
nix = {
26
26
+
extraOptions = ''
27
27
+
experimental-features = nix-command flakes ca-derivations
28
28
+
warn-dirty = false
29
29
+
keep-outputs = false
30
30
+
'';
31
31
+
};
32
32
+
33
33
+
environment.systemPackages = map lib.lowPrio [
34
34
+
pkgs.curl
35
35
+
pkgs.gitMinimal
36
36
+
];
37
37
+
38
38
+
users.users.tangler = {
39
39
+
extraGroups = [ "networkmanager" "wheel" "docker" ];
40
40
+
openssh.authorizedKeys.keys = args.commonArgs.sshKeys;
41
41
+
isNormalUser = true;
42
42
+
};
43
43
+
44
44
+
security.sudo.extraRules = [
45
45
+
{
46
46
+
users = [ "tangler" ];
47
47
+
commands = [
48
48
+
{
49
49
+
command = "ALL";
50
50
+
options = [ "NOPASSWD" ];
51
51
+
}
52
52
+
];
53
53
+
}
54
54
+
];
55
55
+
56
56
+
system.stateVersion = "25.05";
57
57
+
}
+56
hosts/spindle/disk-config.nix
···
1
1
+
# Example to create a bios compatible gpt partition
2
2
+
{ lib, ... }:
3
3
+
{
4
4
+
disko.devices = {
5
5
+
disk.disk1 = {
6
6
+
device = lib.mkDefault "/dev/vda";
7
7
+
type = "disk";
8
8
+
content = {
9
9
+
type = "gpt";
10
10
+
partitions = {
11
11
+
boot = {
12
12
+
name = "boot";
13
13
+
size = "1M";
14
14
+
type = "EF02";
15
15
+
};
16
16
+
esp = {
17
17
+
name = "ESP";
18
18
+
size = "500M";
19
19
+
type = "EF00";
20
20
+
content = {
21
21
+
type = "filesystem";
22
22
+
format = "vfat";
23
23
+
mountpoint = "/boot";
24
24
+
};
25
25
+
};
26
26
+
root = {
27
27
+
name = "root";
28
28
+
size = "100%";
29
29
+
content = {
30
30
+
type = "lvm_pv";
31
31
+
vg = "pool";
32
32
+
};
33
33
+
};
34
34
+
};
35
35
+
};
36
36
+
};
37
37
+
lvm_vg = {
38
38
+
pool = {
39
39
+
type = "lvm_vg";
40
40
+
lvs = {
41
41
+
root = {
42
42
+
size = "100%FREE";
43
43
+
content = {
44
44
+
type = "filesystem";
45
45
+
format = "ext4";
46
46
+
mountpoint = "/";
47
47
+
mountOptions = [
48
48
+
"defaults"
49
49
+
];
50
50
+
};
51
51
+
};
52
52
+
};
53
53
+
};
54
54
+
};
55
55
+
};
56
56
+
}
+37
hosts/spindle/services/nginx.nix
···
1
1
+
{
2
2
+
services.nginx = {
3
3
+
enable = true;
4
4
+
virtualHosts = {
5
5
+
"spindle.alpha.tangled.sh" = {
6
6
+
forceSSL = true;
7
7
+
enableACME = true;
8
8
+
locations."/" = {
9
9
+
proxyPass = "http://127.0.0.1:6555";
10
10
+
};
11
11
+
locations."/events" = {
12
12
+
proxyPass = "http://127.0.0.1:6555";
13
13
+
extraConfig = ''
14
14
+
proxy_set_header X-Forwarded-For $remote_addr;
15
15
+
proxy_set_header Host $host;
16
16
+
proxy_set_header Upgrade $http_upgrade;
17
17
+
proxy_set_header Connection "upgrade";
18
18
+
'';
19
19
+
};
20
20
+
locations."/logs/" = {
21
21
+
proxyPass = "http://127.0.0.1:6555";
22
22
+
extraConfig = ''
23
23
+
proxy_set_header X-Forwarded-For $remote_addr;
24
24
+
proxy_set_header Host $host;
25
25
+
proxy_set_header Upgrade $http_upgrade;
26
26
+
proxy_set_header Connection "upgrade";
27
27
+
'';
28
28
+
};
29
29
+
};
30
30
+
};
31
31
+
};
32
32
+
security.acme = {
33
33
+
acceptTerms = true;
34
34
+
defaults.email = "team@tangled.org";
35
35
+
};
36
36
+
networking.firewall.allowedTCPPorts = [ 80 443 ];
37
37
+
}
+39
hosts/spindle/services/openbao/openbao.nix
···
1
1
+
{ config, pkgs, lib, ... }:
2
2
+
{
3
3
+
# Create openbao user and group
4
4
+
users.groups.openbao = {};
5
5
+
6
6
+
users.users.openbao = {
7
7
+
isSystemUser = true;
8
8
+
group = "openbao";
9
9
+
home = "/var/lib/openbao";
10
10
+
createHome = true;
11
11
+
description = "OpenBao service user";
12
12
+
};
13
13
+
14
14
+
systemd.services.openbao = {
15
15
+
serviceConfig = {
16
16
+
DynamicUser = lib.mkForce false;
17
17
+
User = "openbao";
18
18
+
Group = "openbao";
19
19
+
};
20
20
+
};
21
21
+
22
22
+
services.openbao = {
23
23
+
enable = true;
24
24
+
settings = {
25
25
+
ui = true;
26
26
+
27
27
+
listener.default = {
28
28
+
type = "tcp";
29
29
+
address = "127.0.0.1:8201";
30
30
+
tls_disable = true;
31
31
+
};
32
32
+
33
33
+
cluster_addr = "http://127.0.0.1:8202";
34
34
+
api_addr = "http://127.0.0.1:8201";
35
35
+
36
36
+
storage.raft.path = "/var/lib/openbao";
37
37
+
};
38
38
+
};
39
39
+
}
+100
hosts/spindle/services/openbao/proxy.nix
···
1
1
+
{ pkgs, ... }:
2
2
+
3
3
+
{
4
4
+
systemd.services.openbao-proxy = {
5
5
+
description = "OpenBao Proxy with Auto-Auth";
6
6
+
after = [ "network.target" ];
7
7
+
wantedBy = [ "multi-user.target" ];
8
8
+
serviceConfig = {
9
9
+
User = "root";
10
10
+
ExecStart = "${pkgs.openbao}/bin/bao proxy -config=/etc/openbao/proxy.hcl";
11
11
+
Restart = "always";
12
12
+
RestartSec = "5";
13
13
+
LimitNOFILE = "65536";
14
14
+
};
15
15
+
};
16
16
+
17
17
+
18
18
+
19
19
+
environment.etc."openbao/proxy.hcl".text = ''
20
20
+
vault {
21
21
+
address = "http://localhost:8201"
22
22
+
23
23
+
# Retry configuration
24
24
+
retry {
25
25
+
num_retries = 5
26
26
+
}
27
27
+
}
28
28
+
29
29
+
# Auto-Auth using AppRole
30
30
+
auto_auth {
31
31
+
method "approle" {
32
32
+
mount_path = "auth/approle"
33
33
+
config = {
34
34
+
role_id_file_path = "/etc/openbao/role-id"
35
35
+
secret_id_file_path = "/etc/openbao/secret-id"
36
36
+
remove_secret_id_file_after_reading = false
37
37
+
}
38
38
+
}
39
39
+
40
40
+
# Write authenticated token to file
41
41
+
sink "file" {
42
42
+
config = {
43
43
+
path = "/var/lib/openbao/token"
44
44
+
mode = 0640
45
45
+
}
46
46
+
}
47
47
+
}
48
48
+
49
49
+
# API Proxy listener for Spindle
50
50
+
listener "tcp" {
51
51
+
address = "127.0.0.1:8200"
52
52
+
tls_disable = true
53
53
+
54
54
+
# Security headers
55
55
+
require_request_header = false
56
56
+
57
57
+
# Enable proxy API for management
58
58
+
proxy_api {
59
59
+
enable_quit = true
60
60
+
}
61
61
+
}
62
62
+
63
63
+
# Enable API proxy with auto-auth token
64
64
+
api_proxy {
65
65
+
use_auto_auth_token = true
66
66
+
}
67
67
+
68
68
+
cache {
69
69
+
}
70
70
+
71
71
+
# Logging configuration
72
72
+
log_level = "info"
73
73
+
log_format = "standard"
74
74
+
log_file = "/var/log/openbao/proxy.log"
75
75
+
log_rotate_duration = "24h"
76
76
+
log_rotate_max_files = 30
77
77
+
78
78
+
# Process management
79
79
+
pid_file = "/var/lib/openbao/proxy.pid"
80
80
+
81
81
+
# Disable idle connections for reliability
82
82
+
disable_idle_connections = ["auto-auth", "proxying"]
83
83
+
'';
84
84
+
85
85
+
# Create necessary directories and files
86
86
+
systemd.tmpfiles.rules = [
87
87
+
# Directories
88
88
+
"d /var/lib/openbao 0755 root root -"
89
89
+
"d /var/lib/openbao/cache 0755 root root -"
90
90
+
"d /var/log/openbao 0755 root root -"
91
91
+
"d /etc/openbao 0755 root root -"
92
92
+
93
93
+
# Credential files (content must be populated externally)
94
94
+
"f /etc/openbao/role-id 0600 root root -"
95
95
+
"f /etc/openbao/secret-id 0600 root root -"
96
96
+
97
97
+
# Configuration file
98
98
+
"f /etc/openbao/proxy.hcl 0644 root root -"
99
99
+
];
100
100
+
}
+19
hosts/spindle/services/spindle.nix
···
1
1
+
{ config, pkgs, ... }:
2
2
+
{
3
3
+
services.tangled.spindle = {
4
4
+
enable = true;
5
5
+
server = {
6
6
+
owner = "did:plc:wshs7t2adsemcrrd4snkeqli"; # @tangled.sh
7
7
+
hostname = "spindle.alpha.tangled.sh";
8
8
+
listenAddr = "127.0.0.1:6555";
9
9
+
queueSize = 100;
10
10
+
maxJobCount = 2;
11
11
+
secrets = {
12
12
+
provider = "openbao";
13
13
+
};
14
14
+
};
15
15
+
pipelines = {
16
16
+
workflowTimeout = "15m";
17
17
+
};
18
18
+
};
19
19
+
}