12 files changed,
+562,
-8
+1,
-0
1@@ -0,0 +1 @@
2+local.yaml
+40,
-0
1@@ -0,0 +1,40 @@
2+#!/usr/bin/env bash
3+set -eou pipefail
4+
5+function job_backend() {
6+ selfci job start "build::client"
7+ selfci job wait "build::client"
8+ selfci step start "backend test"
9+ cd server && GOWORK=off make test
10+}
11+
12+function job_client() {
13+ selfci step start "client test"
14+ cd client && make test
15+}
16+
17+function job_build_client() {
18+ cd client && pnpm run build
19+}
20+
21+case "$SELFCI_JOB_NAME" in
22+ main)
23+ selfci job start "test::client"
24+ selfci job start "test::server"
25+ ;;
26+
27+ test::server)
28+ job_backend
29+ ;;
30+
31+ test::client)
32+ job_client
33+ ;;
34+
35+ build::client)
36+ job_build_client
37+ ;;
38+ *)
39+ echo "Unknown job: $SELFCI_JOB_NAME"
40+ exit 1
41+esac
+56,
-0
1@@ -0,0 +1,56 @@
2+# SelfCI Configuration
3+
4+job:
5+ # This command will be executed for all jobs
6+ # Use $SELFCI_JOB_NAME env var to determine which job is running
7+ command: ./.config/selfci/ci.sh
8+
9+ # Command prefix for executing the command
10+ # Can be used to change the shell, or disable shell interpretation
11+ # of `command` altogether.
12+ # command-prefix: ["bash", "-c"]
13+
14+ # Clone mode: "partial", "shallow", or "full" (default: partial)
15+ # - partial: Fast, uses partial clone (--filter=blob:none) - all commits, blobs on-demand
16+ # - shallow: Most compact, uses shallow clone (--depth=1) - single commit only
17+ # - full: Complete clone with all history and objects
18+ clone-mode: full
19+
20+# Merge queue configuration (optional)
21+mq:
22+ # # Default base branch for merge queue
23+ base-branch: main
24+ #
25+ # # Merge mode: "rebase" or "merge" (default: rebase)
26+ # # - rebase: Rebase candidate commits on top of base branch (no merge commit)
27+ # # - merge: Create a merge commit
28+ merge-mode: merge
29+#
30+# # MQ hooks - run at different stages of the merge queue process
31+# # Each hook has: command (required), command-prefix (optional, default: ["bash", "-c"])
32+# # Hooks can also be defined in local.yaml (not checked into source control)
33+# # Local hooks override hooks defined here.
34+#
35+# # Runs before daemonization with inherited stdio (allows interactive input)
36+# # pre-start:
37+# # command: 'echo "Pre-start (interactive)"'
38+#
39+# # Runs after daemonization with captured output (non-interactive)
40+# # post-start:
41+# # command: 'echo "Post-start (captured)"'
42+#
43+# # Runs before worktrees are created for each candidate
44+# # pre-clone:
45+# # command: 'echo "Running pre-clone"'
46+#
47+# # Runs after worktrees are created, before the job command
48+# # post-clone:
49+# # command: 'echo "Running post-clone"'
50+#
51+# # Runs after check passes, before merging
52+# # pre-merge:
53+# # command: 'echo "Running pre-merge"'
54+#
55+# # Runs after a successful merge
56+# # post-merge:
57+# # command: 'echo "Merge completed"'
+1,
-0
1@@ -1,4 +1,5 @@
2 result
3+dev/
4 server/server
5 go.work
6 data/
+456,
-3
1@@ -1,5 +1,30 @@
2 {
3 "nodes": {
4+ "android-nixpkgs": {
5+ "inputs": {
6+ "devshell": "devshell",
7+ "flake-utils": "flake-utils_5",
8+ "nixpkgs": [
9+ "selfci",
10+ "flakebox",
11+ "nixpkgs"
12+ ]
13+ },
14+ "locked": {
15+ "lastModified": 1762287806,
16+ "narHash": "sha256-tmF7Fm2VNukLV32w60QiFLDJB4cO26lhKdDLR8k1BX0=",
17+ "owner": "tadfisher",
18+ "repo": "android-nixpkgs",
19+ "rev": "d3cbdd2a82b3d054b4e283d28ba27548186a88e7",
20+ "type": "github"
21+ },
22+ "original": {
23+ "owner": "tadfisher",
24+ "repo": "android-nixpkgs",
25+ "rev": "d3cbdd2a82b3d054b4e283d28ba27548186a88e7",
26+ "type": "github"
27+ }
28+ },
29 "bug": {
30 "inputs": {
31 "flake-utils": "flake-utils",
32@@ -19,6 +44,29 @@
33 "url": "https://git.kilimanjaro.io/bug"
34 }
35 },
36+ "bundlers": {
37+ "inputs": {
38+ "nix-bundle": "nix-bundle",
39+ "nix-utils": "nix-utils",
40+ "nixpkgs": [
41+ "selfci",
42+ "nixpkgs"
43+ ]
44+ },
45+ "locked": {
46+ "lastModified": 1756736145,
47+ "narHash": "sha256-gKlLhzQ6PUTeC0Mi7aaX0tZ0wl6QB/slHLlk7jy0IN4=",
48+ "owner": "NixOS",
49+ "repo": "bundlers",
50+ "rev": "dce9249b74500ef75110153e6da455ffec2532ba",
51+ "type": "github"
52+ },
53+ "original": {
54+ "owner": "NixOS",
55+ "repo": "bundlers",
56+ "type": "github"
57+ }
58+ },
59 "concurrently": {
60 "inputs": {
61 "flake-utils": "flake-utils_2",
62@@ -38,6 +86,37 @@
63 "url": "https://git.kilimanjaro.io/concurrently"
64 }
65 },
66+ "crane": {
67+ "locked": {
68+ "lastModified": 1758758545,
69+ "narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=",
70+ "owner": "ipetkov",
71+ "repo": "crane",
72+ "rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364",
73+ "type": "github"
74+ },
75+ "original": {
76+ "owner": "ipetkov",
77+ "ref": "v0.21.1",
78+ "repo": "crane",
79+ "type": "github"
80+ }
81+ },
82+ "crane_2": {
83+ "locked": {
84+ "lastModified": 1755993354,
85+ "narHash": "sha256-FCRRAzSaL/+umLIm3RU3O/+fJ2ssaPHseI2SSFL8yZU=",
86+ "owner": "ipetkov",
87+ "repo": "crane",
88+ "rev": "25bd41b24426c7734278c2ff02e53258851db914",
89+ "type": "github"
90+ },
91+ "original": {
92+ "owner": "ipetkov",
93+ "repo": "crane",
94+ "type": "github"
95+ }
96+ },
97 "devenv": {
98 "locked": {
99 "dir": "src/modules",
100@@ -55,6 +134,52 @@
101 "type": "github"
102 }
103 },
104+ "devshell": {
105+ "inputs": {
106+ "nixpkgs": [
107+ "selfci",
108+ "flakebox",
109+ "android-nixpkgs",
110+ "nixpkgs"
111+ ]
112+ },
113+ "locked": {
114+ "lastModified": 1741473158,
115+ "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
116+ "owner": "numtide",
117+ "repo": "devshell",
118+ "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
119+ "type": "github"
120+ },
121+ "original": {
122+ "owner": "numtide",
123+ "repo": "devshell",
124+ "type": "github"
125+ }
126+ },
127+ "fenix": {
128+ "inputs": {
129+ "nixpkgs": [
130+ "selfci",
131+ "flakebox",
132+ "nixpkgs"
133+ ],
134+ "rust-analyzer-src": "rust-analyzer-src"
135+ },
136+ "locked": {
137+ "lastModified": 1767941162,
138+ "narHash": "sha256-7qJDycrXto4xrQWHbj5BkrRWt/hcfZtjlCstEJTyfJ8=",
139+ "owner": "nix-community",
140+ "repo": "fenix",
141+ "rev": "80b1a19a713e2558c411f3259fecb1edd4b5b327",
142+ "type": "github"
143+ },
144+ "original": {
145+ "owner": "nix-community",
146+ "repo": "fenix",
147+ "type": "github"
148+ }
149+ },
150 "flake-utils": {
151 "inputs": {
152 "systems": "systems"
153@@ -92,8 +217,23 @@
154 }
155 },
156 "flake-utils_3": {
157+ "locked": {
158+ "lastModified": 1623875721,
159+ "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=",
160+ "owner": "numtide",
161+ "repo": "flake-utils",
162+ "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772",
163+ "type": "github"
164+ },
165+ "original": {
166+ "owner": "numtide",
167+ "repo": "flake-utils",
168+ "type": "github"
169+ }
170+ },
171+ "flake-utils_4": {
172 "inputs": {
173- "systems": "systems_3"
174+ "systems": "systems_4"
175 },
176 "locked": {
177 "lastModified": 1731533236,
178@@ -109,6 +249,131 @@
179 "type": "github"
180 }
181 },
182+ "flake-utils_5": {
183+ "inputs": {
184+ "systems": "systems_5"
185+ },
186+ "locked": {
187+ "lastModified": 1731533236,
188+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
189+ "owner": "numtide",
190+ "repo": "flake-utils",
191+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
192+ "type": "github"
193+ },
194+ "original": {
195+ "owner": "numtide",
196+ "repo": "flake-utils",
197+ "type": "github"
198+ }
199+ },
200+ "flake-utils_6": {
201+ "inputs": {
202+ "systems": [
203+ "selfci",
204+ "flakebox",
205+ "systems"
206+ ]
207+ },
208+ "locked": {
209+ "lastModified": 1731533236,
210+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
211+ "owner": "numtide",
212+ "repo": "flake-utils",
213+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
214+ "type": "github"
215+ },
216+ "original": {
217+ "owner": "numtide",
218+ "repo": "flake-utils",
219+ "type": "github"
220+ }
221+ },
222+ "flake-utils_7": {
223+ "inputs": {
224+ "systems": "systems_7"
225+ },
226+ "locked": {
227+ "lastModified": 1731533236,
228+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
229+ "owner": "numtide",
230+ "repo": "flake-utils",
231+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
232+ "type": "github"
233+ },
234+ "original": {
235+ "owner": "numtide",
236+ "repo": "flake-utils",
237+ "type": "github"
238+ }
239+ },
240+ "flakebox": {
241+ "inputs": {
242+ "android-nixpkgs": "android-nixpkgs",
243+ "crane": "crane",
244+ "fenix": "fenix",
245+ "flake-utils": "flake-utils_6",
246+ "nixpkgs": "nixpkgs_5",
247+ "systems": "systems_6",
248+ "wild": "wild"
249+ },
250+ "locked": {
251+ "lastModified": 1768170748,
252+ "narHash": "sha256-RZ1PQEVhNtlqBYCvAA8eRSm4zmtpxZEFa88stifg25o=",
253+ "owner": "rustshop",
254+ "repo": "flakebox",
255+ "rev": "62af969ab344229d2a0d585a482293b3f186b221",
256+ "type": "github"
257+ },
258+ "original": {
259+ "owner": "rustshop",
260+ "repo": "flakebox",
261+ "rev": "62af969ab344229d2a0d585a482293b3f186b221",
262+ "type": "github"
263+ }
264+ },
265+ "nix-bundle": {
266+ "inputs": {
267+ "nixpkgs": [
268+ "selfci",
269+ "bundlers",
270+ "nixpkgs"
271+ ],
272+ "utils": "utils"
273+ },
274+ "locked": {
275+ "lastModified": 1756736056,
276+ "narHash": "sha256-8YFhvulVX3iS4TYnKisA9zSImJeFN21G75HOUUFjzuE=",
277+ "owner": "nix-community",
278+ "repo": "nix-bundle",
279+ "rev": "eff01593f62794d458ec714090091419194ab64d",
280+ "type": "github"
281+ },
282+ "original": {
283+ "owner": "nix-community",
284+ "repo": "nix-bundle",
285+ "type": "github"
286+ }
287+ },
288+ "nix-utils": {
289+ "inputs": {
290+ "flake-utils": "flake-utils_3",
291+ "nixpkgs": "nixpkgs_4"
292+ },
293+ "locked": {
294+ "lastModified": 1744222205,
295+ "narHash": "sha256-di1eNHQdpvvyXv6i7Z+S79KF7cQyhTs7AdFHp7q1e3Q=",
296+ "owner": "juliosueiras-nix",
297+ "repo": "nix-utils",
298+ "rev": "53282197ad090c8cf47c96e99bf6c6c3b2cdc7c0",
299+ "type": "github"
300+ },
301+ "original": {
302+ "owner": "juliosueiras-nix",
303+ "repo": "nix-utils",
304+ "type": "github"
305+ }
306+ },
307 "nixpkgs": {
308 "locked": {
309 "lastModified": 1775710090,
310@@ -178,6 +443,53 @@
311 }
312 },
313 "nixpkgs_4": {
314+ "locked": {
315+ "lastModified": 1629252929,
316+ "narHash": "sha256-Aj20gmGBs8TG7pyaQqgbsqAQ6cB+TVuL18Pk3DPBxcQ=",
317+ "owner": "nixos",
318+ "repo": "nixpkgs",
319+ "rev": "3788c68def67ca7949e0864c27638d484389363d",
320+ "type": "github"
321+ },
322+ "original": {
323+ "owner": "nixos",
324+ "repo": "nixpkgs",
325+ "type": "github"
326+ }
327+ },
328+ "nixpkgs_5": {
329+ "locked": {
330+ "lastModified": 1767799921,
331+ "narHash": "sha256-r4GVX+FToWVE2My8VVZH4V0pTIpnu2ZE8/Z4uxGEMBE=",
332+ "owner": "nixos",
333+ "repo": "nixpkgs",
334+ "rev": "d351d0653aeb7877273920cd3e823994e7579b0b",
335+ "type": "github"
336+ },
337+ "original": {
338+ "owner": "nixos",
339+ "ref": "nixos-25.11",
340+ "repo": "nixpkgs",
341+ "type": "github"
342+ }
343+ },
344+ "nixpkgs_6": {
345+ "locked": {
346+ "lastModified": 1766201043,
347+ "narHash": "sha256-eplAP+rorKKd0gNjV3rA6+0WMzb1X1i16F5m5pASnjA=",
348+ "owner": "NixOS",
349+ "repo": "nixpkgs",
350+ "rev": "b3aad468604d3e488d627c0b43984eb60e75e782",
351+ "type": "github"
352+ },
353+ "original": {
354+ "owner": "NixOS",
355+ "ref": "nixos-25.11",
356+ "repo": "nixpkgs",
357+ "type": "github"
358+ }
359+ },
360+ "nixpkgs_7": {
361 "locked": {
362 "lastModified": 1777268161,
363 "narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
364@@ -199,9 +511,48 @@
365 "concurrently": "concurrently",
366 "devenv": "devenv",
367 "nixpkgs": "nixpkgs_3",
368+ "selfci": "selfci",
369 "vibe": "vibe"
370 }
371 },
372+ "rust-analyzer-src": {
373+ "flake": false,
374+ "locked": {
375+ "lastModified": 1767905519,
376+ "narHash": "sha256-mRU9VEhGQE9dnOU3pu1Rx3dZO4NpZO+cnC0rPMFcCqE=",
377+ "owner": "rust-lang",
378+ "repo": "rust-analyzer",
379+ "rev": "ff9a2e88b14907562294838f83963e5966f717de",
380+ "type": "github"
381+ },
382+ "original": {
383+ "owner": "rust-lang",
384+ "ref": "nightly",
385+ "repo": "rust-analyzer",
386+ "type": "github"
387+ }
388+ },
389+ "selfci": {
390+ "inputs": {
391+ "bundlers": "bundlers",
392+ "flake-utils": "flake-utils_4",
393+ "flakebox": "flakebox",
394+ "nixpkgs": "nixpkgs_6"
395+ },
396+ "locked": {
397+ "lastModified": 1781636864,
398+ "narHash": "sha256-ENHVYKVqsoVwadFWwuuiPFqI1jebSUdmTkCvnb6/K3s=",
399+ "ref": "refs/heads/master",
400+ "rev": "030c74a59d2325a669926d1ac4d7438e9621d30f",
401+ "revCount": 129,
402+ "type": "git",
403+ "url": "https://radicle.dpc.pw/z2tDzYbAXxTQEKTGFVwiJPajkbeDU.git"
404+ },
405+ "original": {
406+ "type": "git",
407+ "url": "https://radicle.dpc.pw/z2tDzYbAXxTQEKTGFVwiJPajkbeDU.git"
408+ }
409+ },
410 "systems": {
411 "locked": {
412 "lastModified": 1681028828,
413@@ -247,10 +598,88 @@
414 "type": "github"
415 }
416 },
417+ "systems_4": {
418+ "locked": {
419+ "lastModified": 1681028828,
420+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
421+ "owner": "nix-systems",
422+ "repo": "default",
423+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
424+ "type": "github"
425+ },
426+ "original": {
427+ "owner": "nix-systems",
428+ "repo": "default",
429+ "type": "github"
430+ }
431+ },
432+ "systems_5": {
433+ "locked": {
434+ "lastModified": 1681028828,
435+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
436+ "owner": "nix-systems",
437+ "repo": "default",
438+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
439+ "type": "github"
440+ },
441+ "original": {
442+ "owner": "nix-systems",
443+ "repo": "default",
444+ "type": "github"
445+ }
446+ },
447+ "systems_6": {
448+ "locked": {
449+ "lastModified": 1681028828,
450+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
451+ "owner": "nix-systems",
452+ "repo": "default",
453+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
454+ "type": "github"
455+ },
456+ "original": {
457+ "owner": "nix-systems",
458+ "repo": "default",
459+ "type": "github"
460+ }
461+ },
462+ "systems_7": {
463+ "locked": {
464+ "lastModified": 1681028828,
465+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
466+ "owner": "nix-systems",
467+ "repo": "default",
468+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
469+ "type": "github"
470+ },
471+ "original": {
472+ "owner": "nix-systems",
473+ "repo": "default",
474+ "type": "github"
475+ }
476+ },
477+ "utils": {
478+ "inputs": {
479+ "systems": "systems_3"
480+ },
481+ "locked": {
482+ "lastModified": 1731533236,
483+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
484+ "owner": "numtide",
485+ "repo": "flake-utils",
486+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
487+ "type": "github"
488+ },
489+ "original": {
490+ "owner": "numtide",
491+ "repo": "flake-utils",
492+ "type": "github"
493+ }
494+ },
495 "vibe": {
496 "inputs": {
497- "flake-utils": "flake-utils_3",
498- "nixpkgs": "nixpkgs_4"
499+ "flake-utils": "flake-utils_7",
500+ "nixpkgs": "nixpkgs_7"
501 },
502 "locked": {
503 "lastModified": 1777803757,
504@@ -265,6 +694,30 @@
505 "type": "git",
506 "url": "https://git.kilimanjaro.io/vibe"
507 }
508+ },
509+ "wild": {
510+ "inputs": {
511+ "crane": "crane_2",
512+ "nixpkgs": [
513+ "selfci",
514+ "flakebox",
515+ "nixpkgs"
516+ ]
517+ },
518+ "locked": {
519+ "lastModified": 1762215977,
520+ "narHash": "sha256-x0IZuWjj0LRMj4pu2FVaD8SENm/UVtE1e4rl0EOZZZM=",
521+ "owner": "davidlattimore",
522+ "repo": "wild",
523+ "rev": "7daaf0c89f7b4b9c9ded36e7b3c72aa6512537a1",
524+ "type": "github"
525+ },
526+ "original": {
527+ "owner": "davidlattimore",
528+ "ref": "0.7.0",
529+ "repo": "wild",
530+ "type": "github"
531+ }
532 }
533 },
534 "root": "root",
+4,
-0
1@@ -35,6 +35,10 @@
2 inputs.bug.packages.${pkgs.system}.bug
3 inputs.vibe.packages.${pkgs.system}.jail
4 inputs.vibe.packages.${pkgs.system}.vibe
5+ inputs.selfci.packages.${pkgs.system}.selfci
6+
7+ #atproto
8+ pkgs.atproto-goat
9 ];
10
11 # https://devenv.sh/languages/
+2,
-0
1@@ -8,6 +8,8 @@ inputs:
2 url: git+https://git.kilimanjaro.io/bug
3 vibe:
4 url: git+https://git.kilimanjaro.io/vibe
5+ selfci:
6+ url: git+https://radicle.dpc.pw/z2tDzYbAXxTQEKTGFVwiJPajkbeDU.git
7
8 # If you're using non-OSS software, you can set allowUnfree to true.
9 # allowUnfree: true
+1,
-1
1@@ -37,7 +37,7 @@ func TestStaticAssets(t *testing.T) {
2 t.Fatalf("NewApp failed: %v", err)
3 }
4
5- for _, path := range []string{"/static/favicon.svg", "/static/css/style.css"} {
6+ for _, path := range []string{"/static/favicon.svg", "/static/404.jpg"} {
7 req := httptest.NewRequest("GET", path, nil)
8 rr := httptest.NewRecorder()
9 app.Router.ServeHTTP(rr, req)
+1,
-1
1@@ -18,7 +18,7 @@ func init() {
2 fromHashCache = make(map[string]string)
3 }
4
5-//go:embed static/*
6+//go:embed static/* static/**/*
7 var static embed.FS
8
9 type fsys struct {
+0,
-0
+0,
-0
+0,
-3
1@@ -13,7 +13,6 @@ func TestStaticAssetsHashed(t *testing.T) {
2
3 for _, path := range []string{
4 "/static/favicon.svg",
5- "/static/css/style.css",
6 "/static/404.jpg",
7 } {
8 hashed := hashStatic(path)
9@@ -40,7 +39,6 @@ func TestStaticAssetsHashed(t *testing.T) {
10 func TestHashStaticRoundTrip(t *testing.T) {
11 for _, path := range []string{
12 "/static/favicon.svg",
13- "/static/css/style.css",
14 "/static/404.jpg",
15 "/static/401.jpg",
16 "/static/500.jpg",
17@@ -56,7 +54,6 @@ func TestHashStaticRoundTrip(t *testing.T) {
18 func TestStaticFSUnhashing(t *testing.T) {
19 for _, path := range []string{
20 "/static/favicon.svg",
21- "/static/css/style.css",
22 "/static/404.jpg",
23 } {
24 hashed := hashStatic(path)