A vibe coded tangled fork which supports pijul.
1{
2 nixpkgs,
3 system,
4 hostSystem,
5 self,
6}: let
7 envVar = name: let
8 var = builtins.getEnv name;
9 in
10 if var == ""
11 then throw "\$${name} must be defined, see https://docs.tangled.org/hacking-on-tangled.html#hacking-on-tangled for more details"
12 else var;
13 envVarOr = name: default: let
14 var = builtins.getEnv name;
15 in
16 if var != ""
17 then var
18 else default;
19
20 plcUrl = envVarOr "TANGLED_VM_PLC_URL" "https://plc.directory";
21 jetstream = envVarOr "TANGLED_VM_JETSTREAM_ENDPOINT" "wss://jetstream1.us-west.bsky.network/subscribe";
22 relayUrl = envVarOr "TANGLED_VM_RELAY_URL" "https://relay1.us-east.bsky.network";
23in
24 nixpkgs.lib.nixosSystem {
25 inherit system;
26 modules = [
27 self.nixosModules.did-method-plc
28 self.nixosModules.bluesky-jetstream
29 self.nixosModules.bluesky-relay
30 self.nixosModules.knot
31 self.nixosModules.spindle
32 ({
33 lib,
34 config,
35 pkgs,
36 ...
37 }: {
38 virtualisation.vmVariant.virtualisation = {
39 host.pkgs = import nixpkgs {system = hostSystem;};
40
41 graphics = false;
42 memorySize = 2048;
43 diskSize = 10 * 1024;
44 cores = 2;
45 forwardPorts = [
46 # caddy
47 {
48 from = "host";
49 host.port = 80;
50 guest.port = 80;
51 }
52 {
53 from = "host";
54 host.port = 443;
55 guest.port = 443;
56 }
57 {
58 from = "host";
59 proto = "udp";
60 host.port = 443;
61 guest.port = 443;
62 }
63 # ssh
64 {
65 from = "host";
66 host.port = 2222;
67 guest.port = 22;
68 }
69 # knot
70 {
71 from = "host";
72 host.port = 6444;
73 guest.port = 6444;
74 }
75 # spindle
76 {
77 from = "host";
78 host.port = 6555;
79 guest.port = 6555;
80 }
81 {
82 from = "host";
83 host.port = 6556;
84 guest.port = 2480;
85 }
86 ];
87 sharedDirectories = {
88 # We can't use the 9p mounts directly for most of these
89 # as SQLite is incompatible with them. So instead we
90 # mount the shared directories to a different location
91 # and copy the contents around on service start/stop.
92 caddyData = {
93 source = "$TANGLED_VM_DATA_DIR/caddy";
94 target = config.services.caddy.dataDir;
95 };
96 knotData = {
97 source = "$TANGLED_VM_DATA_DIR/knot";
98 target = "/mnt/knot-data";
99 };
100 spindleData = {
101 source = "$TANGLED_VM_DATA_DIR/spindle";
102 target = "/mnt/spindle-data";
103 };
104 spindleLogs = {
105 source = "$TANGLED_VM_DATA_DIR/spindle-logs";
106 target = "/var/log/spindle";
107 };
108 };
109 };
110 # This is fine because any and all ports that are forwarded to host are explicitly marked above, we don't need a separate guest firewall
111 networking.firewall.enable = false;
112 # resolve `*.tngl.boltless.dev` to host
113 services.dnsmasq.enable = true;
114 services.dnsmasq.settings.address = "/tngl.boltless.dev/10.0.2.2";
115 security.pki.certificates = [
116 (builtins.readFile ../contrib/certs/root.crt)
117 ];
118 time.timeZone = "Europe/London";
119 services.timesyncd.enable = lib.mkVMOverride true;
120 services.getty.autologinUser = "root";
121 environment.systemPackages = with pkgs; [curl vim git sqlite litecli];
122 virtualisation.docker.extraOptions = ''
123 --dns 172.17.0.1
124 '';
125 services.tangled.knot = {
126 enable = true;
127 motd = "Welcome to the development knot!\n";
128 server = {
129 owner = envVar "TANGLED_VM_KNOT_OWNER";
130 hostname = envVarOr "TANGLED_VM_KNOT_HOST" "localhost:6444";
131 plcUrl = plcUrl;
132 jetstreamEndpoint = jetstream;
133 listenAddr = "0.0.0.0:6444";
134 dev = true;
135 };
136 };
137 services.tangled.spindle = {
138 enable = true;
139 server = {
140 owner = envVar "TANGLED_VM_SPINDLE_OWNER";
141 hostname = envVarOr "TANGLED_VM_SPINDLE_HOST" "localhost:6555";
142 plcUrl = plcUrl;
143 relayUrl = relayUrl;
144 listenAddr = "0.0.0.0:6555";
145 dev = false;
146 queueSize = 100;
147 maxJobCount = 2;
148 secrets = {
149 provider = "sqlite";
150 };
151 };
152 };
153 services.did-method-plc.enable = true;
154 services.bluesky-pds = {
155 enable = true;
156 # overriding package version to support emails
157 package = pkgs.bluesky-pds.overrideAttrs (old: rec {
158 version = "0.4.188";
159 src = pkgs.fetchFromGitHub {
160 owner = "bluesky-social";
161 repo = "pds";
162 tag = "v${version}";
163 hash = "sha256-t8KdyEygXdbj/5Rhj8W40e1o8mXprELpjsKddHExmo0=";
164 };
165 pnpmDeps = pkgs.fetchPnpmDeps {
166 inherit version src;
167 pname = old.pname;
168 sourceRoot = old.sourceRoot;
169 fetcherVersion = 2;
170 hash = "sha256-lQie7f8JbWKSpoavnMjHegBzH3GB9teXsn+S2SLJHHU=";
171 };
172 });
173 settings = {
174 LOG_ENABLED = "true";
175
176 PDS_JWT_SECRET = "8cae8bffcc73d9932819650791e4e89a";
177 PDS_ADMIN_PASSWORD = "d6a902588cd93bee1af83f924f60cfd3";
178 PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX = "2e92e336a50a618458e1097d94a1db86ec3fd8829d7735020cbae80625c761d7";
179
180 PDS_EMAIL_SMTP_URL = envVarOr "TANGLED_VM_PDS_EMAIL_SMTP_URL" null;
181 PDS_EMAIL_FROM_ADDRESS = envVarOr "TANGLED_VM_PDS_EMAIL_FROM_ADDRESS" null;
182
183 PDS_DID_PLC_URL = "http://localhost:8080";
184 PDS_CRAWLERS = "https://relay.tngl.boltless.dev";
185 PDS_HOSTNAME = "pds.tngl.boltless.dev";
186 PDS_PORT = 3000;
187 };
188 };
189 services.bluesky-relay = {
190 enable = true;
191 };
192 services.bluesky-jetstream = {
193 enable = true;
194 livenessTtl = 300;
195 websocketUrl = "ws://localhost:3000/xrpc/com.atproto.sync.subscribeRepos";
196 };
197 services.caddy = {
198 enable = true;
199 configFile = pkgs.writeText "Caddyfile" ''
200 {
201 debug
202 cert_lifetime 3601d
203 pki {
204 ca local {
205 intermediate_lifetime 3599d
206 }
207 }
208 }
209
210 plc.tngl.boltless.dev {
211 tls internal
212 reverse_proxy http://localhost:8080
213 }
214
215 *.pds.tngl.boltless.dev, pds.tngl.boltless.dev {
216 tls internal
217 reverse_proxy http://localhost:3000
218 }
219
220 jetstream.tngl.boltless.dev {
221 tls internal
222 reverse_proxy http://localhost:6008
223 }
224
225 relay.tngl.boltless.dev {
226 tls internal
227 reverse_proxy http://localhost:2470
228 }
229
230 knot.tngl.boltless.dev {
231 tls internal
232 reverse_proxy http://localhost:6444
233 }
234
235 spindle.tngl.boltless.dev {
236 tls internal
237 reverse_proxy http://localhost:6555
238 }
239 '';
240 };
241 users = {
242 # So we don't have to deal with permission clashing between
243 # blank disk VMs and existing state
244 users.${config.services.tangled.knot.gitUser}.uid = 666;
245 groups.${config.services.tangled.knot.gitUser}.gid = 666;
246
247 # TODO: separate spindle user
248 };
249 systemd.services = let
250 mkDataSyncScripts = source: target: {
251 enableStrictShellChecks = true;
252
253 preStart = lib.mkBefore ''
254 mkdir -p ${target}
255 ${lib.getExe pkgs.rsync} -a ${source}/ ${target}
256 '';
257
258 postStop = lib.mkAfter ''
259 ${lib.getExe pkgs.rsync} -a ${target}/ ${source}
260 '';
261
262 serviceConfig.PermissionsStartOnly = true;
263 };
264 in {
265 knot = mkDataSyncScripts "/mnt/knot-data" config.services.tangled.knot.stateDir;
266 spindle = mkDataSyncScripts "/mnt/spindle-data" config.services.tangled.spindle.server.stateDir;
267 };
268 })
269 ];
270 }