174 Commits

Author SHA1 Message Date
bae45363bc release: 0.3.6
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / ShareFS (push) Successful in 38s
Test / Sandbox (race detector) (push) Successful in 46s
Test / Sandbox (push) Successful in 47s
Test / Hakurei (race detector) (push) Successful in 52s
Test / Hakurei (push) Successful in 53s
Release / Create release (push) Successful in 1m5s
Test / Flake checks (push) Successful in 1m31s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 16:32:04 +09:00
2c17d1abe0 cmd/mbf: create report with reasonable perm
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m38s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 4m56s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Flake checks (push) Successful in 1m32s
Making it inaccessible certainly is not reasonable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 16:16:47 +09:00
0aa459d1a9 cmd/mbf: check for updates concurrently
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m43s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 6m0s
Test / Flake checks (push) Successful in 1m36s
Runs much faster this way.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 16:05:16 +09:00
00053e6287 internal/rosa: set User-Agent for Anitya requests
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 6m3s
Test / Flake checks (push) Successful in 1m36s
This is cleaner than using the default string.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 16:03:06 +09:00
3a0c020150 internal/rosa/gnu: coreutils 9.9 to 9.10
All checks were successful
Test / Create distribution (push) Successful in 57s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m37s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 3m50s
Test / Flake checks (push) Successful in 1m51s
This breaks two tests, one of them is fixed and the other disabled. Additionally, two fixed tests are re-enabled.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 14:31:03 +09:00
78655f159e internal/rosa/ncurses: use stable Anitya project
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 5m55s
Test / Flake checks (push) Successful in 1m30s
The alpine mapping points to ncurses~devel for some reason.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 13:43:38 +09:00
30bb52e380 internal/rosa/x: libXau 1.0.7 to 1.0.12
All checks were successful
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 4m14s
Test / Hakurei (push) Successful in 4m23s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m22s
Test / Flake checks (push) Successful in 1m30s
This also switches to individual releases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 13:39:48 +09:00
66197ebdb2 internal/rosa/x: xproto 7.0.23 to 7.0.31
All checks were successful
Test / Sandbox (push) Successful in 1m57s
Test / Hakurei (push) Successful in 4m13s
Test / Create distribution (push) Successful in 53s
Test / Sandbox (race detector) (push) Successful in 2m41s
Test / Hakurei (race detector) (push) Successful in 6m15s
Test / ShareFS (push) Successful in 2m51s
Test / Flake checks (push) Successful in 1m28s
This also switches to individual releases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 13:39:23 +09:00
f7a2744025 internal/rosa/x: util-macros 1.17 to 1.20.2
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 4m0s
Test / ShareFS (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m23s
Test / Flake checks (push) Successful in 1m30s
This also switches to individual releases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 13:38:54 +09:00
f16b7bfaf0 internal/rosa: do not keep underlying file
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 4m55s
Test / Hakurei (race detector) (push) Successful in 5m54s
Test / Flake checks (push) Successful in 1m29s
No operation require further filesystem interaction for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 13:04:06 +09:00
6228cda7ad cmd/mbf: optionally read report in info
All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m55s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m3s
Test / Flake checks (push) Successful in 1m28s
This is a useful frontend for the report files before web server is ready.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 02:26:35 +09:00
86c336de88 cmd/mbf: cure status report command
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 46s
Test / Hakurei (push) Successful in 2m40s
Test / ShareFS (push) Successful in 2m38s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m55s
Test / Flake checks (push) Successful in 1m29s
This emits a report stream for the opened cache into the specified file.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 02:20:40 +09:00
ba5d882ef2 internal/rosa: stream format for cure report
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 4m54s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Flake checks (push) Successful in 1m38s
This is for efficient cure status retrieval by the package website server.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-07 02:18:00 +09:00
1e0d68a29e internal/pkg: move output buffer to reader
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m46s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 5m59s
Test / Flake checks (push) Successful in 2m18s
This side is the read end of a pipe and buffering reads from it ended up performing better than buffering one half of the TeeReader (which already goes through the kernel page cache anyway).

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 23:39:12 +09:00
80f2367c16 cmd/mbf: merge status and info commands
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m32s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 4m6s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m55s
Test / Flake checks (push) Successful in 1m24s
This is cleaner, and offers better integration with the work-in-progress report file.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 23:20:00 +09:00
5ea4dae4b8 cmd/mbf: info accept multiple names
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m33s
Test / Hakurei (push) Successful in 3m38s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 5m54s
Test / Flake checks (push) Successful in 1m26s
This also improves formatting for use with multiple info blocks.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 23:10:43 +09:00
eb1a3918a8 internal/rosa/gnu: texinfo 7.2 to 7.3
All checks were successful
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 4m2s
Test / ShareFS (push) Successful in 4m6s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m14s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 22:09:00 +09:00
349011a5e6 internal/rosa/perl: compile dynamic libperl
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m19s
Test / Flake checks (push) Successful in 1m29s
Required by texinfo 7.3.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 22:08:38 +09:00
861249751a internal/rosa/openssl: 3.5.5 to 3.6.1
All checks were successful
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 3m1s
Test / Hakurei (push) Successful in 4m16s
Test / ShareFS (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 6m31s
Test / Flake checks (push) Successful in 1m33s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 21:39:52 +09:00
e3445c2a7e internal/rosa/libffi: 3.4.5 to 3.5.2
All checks were successful
Test / Create distribution (push) Successful in 54s
Test / Sandbox (push) Successful in 3m4s
Test / Hakurei (push) Successful in 4m10s
Test / ShareFS (push) Successful in 3m12s
Test / Sandbox (race detector) (push) Successful in 5m26s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m24s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 21:39:25 +09:00
7315e64a8a internal/rosa/ssl: nss 3.120 to 3.121
All checks were successful
Test / Create distribution (push) Successful in 54s
Test / Sandbox (push) Successful in 2m45s
Test / ShareFS (push) Successful in 4m21s
Test / Hakurei (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 5m22s
Test / Hakurei (race detector) (push) Successful in 6m19s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 21:38:41 +09:00
7d74454f6d internal/rosa/python: 3.14.2 to 3.14.3
All checks were successful
Test / Create distribution (push) Successful in 56s
Test / Sandbox (push) Successful in 2m54s
Test / Hakurei (push) Successful in 4m15s
Test / ShareFS (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m35s
Test / Hakurei (race detector) (push) Successful in 6m16s
Test / Flake checks (push) Successful in 1m34s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 21:38:17 +09:00
96956c849a internal/rosa/gnu: gawk 5.3.2 to 5.4.0
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m43s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Flake checks (push) Successful in 1m29s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 21:30:37 +09:00
aabdcbba1c internal/rosa/gnu: m4 1.4.20 to 1.4.21
All checks were successful
Test / Create distribution (push) Successful in 1m51s
Test / Sandbox (push) Successful in 3m50s
Test / Hakurei (push) Successful in 4m53s
Test / ShareFS (push) Successful in 5m5s
Test / Sandbox (race detector) (push) Successful in 6m11s
Test / Hakurei (race detector) (push) Successful in 3m25s
Test / Flake checks (push) Successful in 1m30s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 21:22:33 +09:00
38cc4a6429 internal/rosa/openssl: check stable versions
All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m42s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m5s
Test / Flake checks (push) Successful in 1m27s
This has a bunch of strange malformed tags.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 19:22:41 +09:00
27ef7f81fa internal/rosa/perl: check stable versions
All checks were successful
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m50s
Test / Hakurei (push) Successful in 4m3s
Test / ShareFS (push) Successful in 4m12s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 6m18s
Test / Flake checks (push) Successful in 1m40s
This uses odd-even versioning.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 19:16:07 +09:00
f7888074b9 internal/rosa/util-linux: check stable versions
All checks were successful
Test / Create distribution (push) Successful in 54s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 4m8s
Test / ShareFS (push) Successful in 3m45s
Test / Sandbox (race detector) (push) Successful in 5m14s
Test / Hakurei (race detector) (push) Successful in 6m8s
Test / Flake checks (push) Successful in 1m36s
Anitya appears to get confused when seeing release candidates.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 19:15:16 +09:00
95ffe0429c internal/rosa: overridable version check
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 1m45s
Test / Hakurei (push) Successful in 2m52s
Test / ShareFS (push) Successful in 2m50s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 6m13s
Test / Flake checks (push) Successful in 1m35s
For projects with strange versioning practices.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 19:13:55 +09:00
16d0cf04c1 internal/rosa/python: setuptools 80.10.1 to 82.0.0
All checks were successful
Test / Sandbox (push) Successful in 46s
Test / Create distribution (push) Successful in 27s
Test / Hakurei (push) Successful in 2m41s
Test / ShareFS (push) Successful in 2m33s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 6m2s
Test / Flake checks (push) Successful in 1m31s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 18:40:55 +09:00
6a2b32b48c internal/rosa/libxml2: 2.15.1 to 2.15.2
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m43s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m27s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 18:36:06 +09:00
c1472fc54d internal/rosa/wayland: 1.24.0 to 1.24.91
All checks were successful
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m42s
Test / ShareFS (push) Successful in 4m7s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (push) Successful in 5m30s
Test / Hakurei (race detector) (push) Successful in 6m31s
Test / Flake checks (push) Successful in 1m36s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 18:33:26 +09:00
179cf07e48 internal/rosa/git: 2.52.0 to 2.53.0
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 2m36s
Test / ShareFS (push) Successful in 4m30s
Test / Hakurei (push) Successful in 4m42s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 6m25s
Test / Flake checks (push) Successful in 1m37s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 18:32:41 +09:00
c2d2795e2b internal/rosa/libexpat: 2.7.3 to 2.7.4
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m8s
Test / Flake checks (push) Successful in 1m30s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 18:22:39 +09:00
2c1d7edd7a internal/rosa/squashfs: 4.7.4 to 4.7.5
All checks were successful
Test / Create distribution (push) Successful in 1m13s
Test / Sandbox (push) Successful in 4m38s
Test / Hakurei (push) Successful in 6m9s
Test / ShareFS (push) Successful in 6m19s
Test / Sandbox (race detector) (push) Successful in 6m30s
Test / Hakurei (race detector) (push) Successful in 8m21s
Test / Flake checks (push) Successful in 1m57s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 17:47:23 +09:00
1ee8d09223 internal/rosa/pcre2: 10.43 to 10.47
All checks were successful
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 4m28s
Test / Hakurei (push) Successful in 6m8s
Test / ShareFS (push) Successful in 6m10s
Test / Hakurei (race detector) (push) Successful in 9m47s
Test / Sandbox (race detector) (push) Successful in 2m40s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 17:46:59 +09:00
7f01cb3d59 internal/rosa/gtk: glib 2.86.4 to 2.87.3
All checks were successful
Test / Create distribution (push) Successful in 2m14s
Test / Sandbox (push) Successful in 4m35s
Test / Hakurei (push) Successful in 6m11s
Test / ShareFS (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 9m44s
Test / Sandbox (race detector) (push) Successful in 2m37s
Test / Flake checks (push) Successful in 1m33s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 17:46:32 +09:00
65ae4f57c2 internal/rosa/go: 1.26.0 to 1.26.1
All checks were successful
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m57s
Test / Hakurei (push) Successful in 5m6s
Test / ShareFS (push) Successful in 4m11s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 3m16s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 17:46:05 +09:00
77110601cc internal/rosa/gnu: binutils 2.45 to 2.46.0
All checks were successful
Test / Create distribution (push) Successful in 29s
Test / Sandbox (push) Successful in 3m30s
Test / Hakurei (push) Successful in 4m57s
Test / ShareFS (push) Successful in 4m41s
Test / Sandbox (race detector) (push) Successful in 5m19s
Test / Hakurei (race detector) (push) Successful in 6m33s
Test / Flake checks (push) Successful in 1m33s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 17:45:10 +09:00
c5b1949430 internal/rosa/kernel: backport AMD display patches
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 3m36s
Test / ShareFS (push) Successful in 3m45s
Test / Sandbox (race detector) (push) Successful in 4m55s
Test / Hakurei (race detector) (push) Successful in 5m49s
Test / Flake checks (push) Successful in 1m29s
These reduce stack usage in dml30_ModeSupportAndSystemConfigurationFull enough to fix compile on clang 22.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 16:22:20 +09:00
17805cdfa8 internal/rosa/kernel: 6.12.73 to 6.12.76
All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 4m11s
Test / Hakurei (push) Successful in 5m21s
Test / ShareFS (push) Successful in 5m21s
Test / Sandbox (race detector) (push) Successful in 5m49s
Test / Hakurei (race detector) (push) Successful in 5m15s
Test / Flake checks (push) Successful in 1m32s
Toolchain is broken on arm64 at the moment so the configuration is not updated.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-06 15:01:01 +09:00
9c9befb4c9 internal/rosa/llvm: separate major version
All checks were successful
Test / Create distribution (push) Successful in 2m25s
Test / ShareFS (push) Successful in 7m56s
Test / Hakurei (race detector) (push) Successful in 10m6s
Test / Hakurei (push) Successful in 2m36s
Test / Sandbox (push) Successful in 1m30s
Test / Sandbox (race detector) (push) Successful in 2m17s
Test / Flake checks (push) Successful in 1m30s
For pathname formatting at compile time.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 22:59:51 +09:00
fcdf9ecee4 internal/rosa/llvm: 21.1.8 to 22.1.0
All checks were successful
Test / Create distribution (push) Successful in 1m35s
Test / Sandbox (push) Successful in 4m16s
Test / Hakurei (push) Successful in 6m17s
Test / ShareFS (push) Successful in 6m28s
Test / Hakurei (race detector) (push) Successful in 7m52s
Test / Sandbox (race detector) (push) Successful in 2m17s
Test / Flake checks (push) Successful in 1m27s
New patch should not be affected next time.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 22:42:27 +09:00
fbd97b658f cmd/mbf: display metadata
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m37s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 5m50s
Test / Flake checks (push) Successful in 1m30s
For viewing package metadata before the website is ready.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 22:11:26 +09:00
c93725ac58 internal/rosa: prefix python constants
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m53s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 5m56s
Test / Flake checks (push) Successful in 1m30s
These have confusing names.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 21:37:06 +09:00
f14ab80253 internal/rosa: populate Anitya project ids
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m36s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 4m54s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / Flake checks (push) Successful in 1m29s
This enables release monitoring for all applicable projects.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 21:32:15 +09:00
9989881dd9 internal/rosa/llvm: populate metadata
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 4m56s
Test / Hakurei (race detector) (push) Successful in 5m52s
Test / Flake checks (push) Successful in 1m37s
This enables use of release monitoring for LLVM.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 21:27:33 +09:00
a36b3ece16 internal/rosa: release monitoring via Anitya
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m35s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 4m56s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / Flake checks (push) Successful in 1m30s
This is much more sustainable than manual package flagging.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 20:57:05 +09:00
75970a5650 internal/rosa: check name uniqueness
All checks were successful
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m53s
Test / Hakurei (push) Successful in 6m2s
Test / ShareFS (push) Successful in 6m3s
Test / Sandbox (race detector) (push) Successful in 6m28s
Test / Hakurei (race detector) (push) Successful in 7m16s
Test / Flake checks (push) Successful in 1m33s
This should prevent adding packages with nonunique names.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 18:37:55 +09:00
572c99825d Revert "internal/rosa/zlib: 1.3.1 to 1.3.2"
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 5m57s
Test / Flake checks (push) Successful in 1m32s
The bump broke elfutils build.

This reverts commit 0eb2bfa12e.
2026-03-05 17:06:15 +09:00
ebdf9dcecc cmd/mbf: preset status command
All checks were successful
Test / Create distribution (push) Successful in 1m6s
Test / Sandbox (push) Successful in 2m50s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 6m9s
Test / Flake checks (push) Successful in 1m42s
This exposes the new OpenStatus cache method.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 16:59:47 +09:00
8ea2a56d5b internal/pkg: expose status file
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m55s
Test / Flake checks (push) Successful in 1m42s
This is useful for external tooling.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 16:58:52 +09:00
159a45c027 internal/rosa: export preset bounds
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m49s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / Flake checks (push) Successful in 1m27s
These are useful for external tooling.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 16:34:25 +09:00
0eb2bfa12e internal/rosa/zlib: 1.3.1 to 1.3.2
All checks were successful
Test / Create distribution (push) Successful in 57s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m50s
Test / Sandbox (race detector) (push) Successful in 6m19s
Test / Hakurei (race detector) (push) Successful in 3m14s
Test / Flake checks (push) Successful in 1m32s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 15:49:18 +09:00
e19a98244a internal/rosa: zlib use stable archive url
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 3m56s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m49s
Test / Flake checks (push) Successful in 1m39s
These do not get removed on new release.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 15:46:10 +09:00
7e2f13fa1b internal/rosa: cure checks
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 4m52s
Test / Hakurei (race detector) (push) Successful in 5m49s
Test / Flake checks (push) Successful in 1m29s
This cures all presets if a cache directory is supplied and verbose is set.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 00:47:39 +09:00
97448e2104 internal/rosa/squashfs: enter correct directory
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m33s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 4m54s
Test / Hakurei (race detector) (push) Successful in 5m52s
Test / Flake checks (push) Successful in 1m35s
This was missed during the make helper migration.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 00:43:50 +09:00
a87ad28b8b internal/pkg: scrub for dangling status
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m39s
Test / ShareFS (push) Successful in 3m51s
Test / Sandbox (race detector) (push) Successful in 4m56s
Test / Hakurei (race detector) (push) Successful in 5m59s
Test / Flake checks (push) Successful in 1m36s
These cause build to fail to start.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 00:39:15 +09:00
883d4ee4af internal/pkg: return writer after sync
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m32s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m47s
Test / Sandbox (race detector) (push) Successful in 4m52s
Test / Hakurei (race detector) (push) Successful in 3m17s
Test / Flake checks (push) Successful in 1m32s
This fixes a use-after-free.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 00:23:24 +09:00
d2c6d486b0 internal/rosa: provide package metadata
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m36s
Test / Hakurei (push) Successful in 3m42s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 4m55s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / Flake checks (push) Successful in 1m35s
This had to be done out-of-band because there was no way to efficiently represent these within Artifact.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-05 00:20:27 +09:00
6fdd800b2b internal/pkg: check filtered error
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m32s
Test / Hakurei (push) Successful in 3m31s
Test / ShareFS (push) Successful in 3m40s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 5m50s
Test / Flake checks (push) Successful in 1m30s
This avoids filtering some unrelated os.ErrExist.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-04 17:43:17 +09:00
94e3debc63 internal/pkg: write per-artifact logs
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m29s
Test / Hakurei (push) Successful in 3m36s
Test / ShareFS (push) Successful in 3m42s
Test / Sandbox (race detector) (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 5m50s
Test / Flake checks (push) Successful in 1m28s
This is currently only used by execArtifact. A later patch will add additional logging facilities.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-04 17:27:54 +09:00
ea87664a75 internal/pkg: cancel on scanner error
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m35s
Test / ShareFS (push) Successful in 3m43s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Flake checks (push) Successful in 1m29s
This avoids discarding output thus appearing unresponsive.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-03 22:20:43 +09:00
04d9984da0 internal/rosa/meson: migrate to helper interface
All checks were successful
Test / Create distribution (push) Successful in 57s
Test / Sandbox (push) Successful in 2m57s
Test / Hakurei (push) Successful in 4m4s
Test / ShareFS (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m26s
This change also removes some unused options.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 14:07:16 +09:00
145ccd1c92 remove .github
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m32s
Test / Hakurei (push) Successful in 3m36s
Test / ShareFS (push) Successful in 3m44s
Test / Sandbox (race detector) (push) Successful in 4m52s
Test / Hakurei (race detector) (push) Successful in 5m47s
Test / Flake checks (push) Successful in 1m28s
This is no longer needed after discontinuation of the Microsoft GitHub mirror.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:46:24 +09:00
c5089cad78 cmd: remove hpkg
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 3m40s
Test / ShareFS (push) Successful in 3m48s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m54s
Test / Flake checks (push) Successful in 1m28s
This proof-of-concept was abandoned long ago. Its test suite is flaky, heavy on I/O and does not increase test coverage. This change fully removes hpkg and supporting code.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:42:49 +09:00
c83905f311 internal/rosa/cmake: enable check
All checks were successful
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 4m26s
Test / ShareFS (push) Successful in 4m35s
Test / Hpkg (push) Successful in 5m7s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hakurei (race detector) (push) Successful in 3m15s
Test / Flake checks (push) Successful in 1m47s
CMake was packaged very early, before the current infrastructure existed to support this. This change patches out broken tests and enables the test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:12:20 +09:00
b7cc14f296 internal/rosa/cmake: 4.2.1 to 4.2.3
All checks were successful
Test / Create distribution (push) Successful in 3m14s
Test / Hakurei (push) Successful in 8m32s
Test / ShareFS (push) Successful in 8m29s
Test / Sandbox (race detector) (push) Successful in 8m38s
Test / Hpkg (push) Successful in 9m4s
Test / Hakurei (race detector) (push) Successful in 9m43s
Test / Sandbox (push) Successful in 1m38s
Test / Flake checks (push) Successful in 2m5s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:09:21 +09:00
57e1e5141d internal/rosa/ninja: remove cmake dependency
All checks were successful
Test / Create distribution (push) Successful in 2m59s
Test / Sandbox (push) Successful in 6m28s
Test / Hakurei (push) Successful in 8m19s
Test / ShareFS (push) Successful in 8m19s
Test / Sandbox (race detector) (push) Successful in 8m32s
Test / Hakurei (race detector) (push) Successful in 9m30s
Test / Hpkg (push) Successful in 3m13s
Test / Flake checks (push) Successful in 1m45s
This does not actually depend on cmake. This is left over from very early on.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 13:08:18 +09:00
1440195c3f internal/rosa/llvm: pass patches via helper
All checks were successful
Test / Hakurei (push) Successful in 6m12s
Test / ShareFS (push) Successful in 6m15s
Test / Sandbox (race detector) (push) Successful in 6m34s
Test / Hpkg (push) Successful in 6m47s
Test / Hakurei (race detector) (push) Successful in 7m49s
Test / Sandbox (push) Successful in 1m38s
Test / Flake checks (push) Successful in 1m43s
Test / Create distribution (push) Successful in 51s
This was missed while migrating LLVM to the new interface.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 01:30:14 +09:00
cc60e0d15d internal/rosa/make: migrate to helper interface
All checks were successful
Test / Create distribution (push) Successful in 1m8s
Test / Sandbox (push) Successful in 2m55s
Test / Hakurei (push) Successful in 5m3s
Test / ShareFS (push) Successful in 5m13s
Test / Hpkg (push) Successful in 5m56s
Test / Hakurei (race detector) (push) Successful in 6m18s
Test / Sandbox (race detector) (push) Successful in 2m37s
Test / Flake checks (push) Successful in 1m43s
This also updates all affected artifacts to use new behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-02 01:24:50 +09:00
9deaf853f0 internal/rosa/cmake: migrate to helper interface
All checks were successful
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m51s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m36s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 6m0s
Test / Flake checks (push) Successful in 1m46s
This change also removes some unused options.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 23:50:22 +09:00
2baa9df133 internal/rosa: general helper abstraction
All checks were successful
Test / Create distribution (push) Successful in 1m30s
Test / Sandbox (push) Successful in 3m33s
Test / Hakurei (push) Successful in 4m41s
Test / ShareFS (push) Successful in 4m46s
Test / Hpkg (push) Successful in 5m21s
Test / Sandbox (race detector) (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 7m9s
Test / Flake checks (push) Successful in 1m43s
This greatly increases code sharing and makes implementations far simpler.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 23:39:43 +09:00
51d3df2419 internal/rosa/make: split build and check
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m41s
Test / Hakurei (push) Successful in 3m53s
Test / ShareFS (push) Successful in 3m57s
Test / Hpkg (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 6m9s
Test / Flake checks (push) Successful in 2m26s
Doing these together breaks far too many buggy makefiles.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 14:41:34 +09:00
1d0fcf3a75 internal/rosa/perl: migrate to make helper
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m52s
Test / Hpkg (push) Successful in 4m20s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 6m7s
Test / Flake checks (push) Successful in 1m45s
This uses the new configure helper behaviour.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 10:45:25 +09:00
e92971e0c2 internal/rosa/make: alternative configure script
All checks were successful
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m57s
Test / Hpkg (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / Flake checks (push) Successful in 1m39s
This enables using the configure helper with non-autotools configure scripts.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 10:42:20 +09:00
6159c74e96 internal/rosa/toybox: migrate to make helper
All checks were successful
Test / Create distribution (push) Successful in 57s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m50s
Test / Hpkg (push) Successful in 4m16s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 5m56s
Test / Flake checks (push) Successful in 1m46s
A previous change caused world rebuild, so it is a good time to do this.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-03-01 10:02:36 +09:00
2a34a269d0 internal/rosa: stricter cure-script options
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m44s
Test / ShareFS (push) Successful in 3m54s
Test / Hpkg (push) Successful in 4m19s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 3m27s
This change also moves .cure-script out of /system/bin.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:29:22 +09:00
ef130adb27 internal/rosa/kernel: early serial
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 4m48s
Test / Sandbox (race detector) (push) Successful in 5m42s
Test / Hakurei (race detector) (push) Successful in 7m43s
Test / Hpkg (push) Successful in 3m25s
Test / Flake checks (push) Successful in 1m55s
Having serial driver before initramfs is helpful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:22:18 +09:00
5694e528e6 cmd/mbf: use standalone musl in container
All checks were successful
Test / Create distribution (push) Successful in 57s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m52s
Test / Hpkg (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 6m1s
Test / Flake checks (push) Successful in 1m52s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:12:13 +09:00
b4e82e68a7 internal/rosa/images: initramfs via gen_init_cpio
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m33s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m53s
Test / Hpkg (push) Successful in 4m20s
Test / Sandbox (race detector) (push) Successful in 4m54s
Test / Hakurei (race detector) (push) Successful in 6m2s
Test / Flake checks (push) Successful in 1m49s
This is much cleaner than hacking around the cpio tool.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 23:04:58 +09:00
d041fee791 internal/rosa: export musl
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m42s
Test / ShareFS (push) Successful in 3m54s
Test / Hpkg (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m52s
This can be useful externally.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 22:49:54 +09:00
cefd02e960 internal/rosa: gen_init_cpio artifact
All checks were successful
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m29s
Test / Hakurei (push) Successful in 3m41s
Test / ShareFS (push) Successful in 3m48s
Test / Hpkg (push) Successful in 4m17s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m45s
Test / Flake checks (push) Successful in 1m35s
This works much better than hacking around the toybox cpio implementation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 21:19:03 +09:00
ad8f799703 container/std: rename seccomp types
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m49s
Test / Hpkg (push) Successful in 4m23s
Test / Sandbox (race detector) (push) Successful in 4m55s
Test / Hakurei (race detector) (push) Successful in 6m3s
Test / Flake checks (push) Successful in 1m39s
Aliases will be kept until 0.4.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:48:30 +09:00
c74c269b66 container: use /proc/self/exe directly
All checks were successful
Test / Create distribution (push) Successful in 57s
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 3m51s
Test / Hpkg (push) Successful in 4m26s
Test / Sandbox (race detector) (push) Successful in 4m55s
Test / Hakurei (race detector) (push) Successful in 5m47s
Test / Flake checks (push) Successful in 1m49s
This is a more reliable form of pathname to self and also cheaper than os.Executable.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:44:44 +09:00
4b0cce4db5 ldd: treat nil pathname as self
All checks were successful
Test / Create distribution (push) Successful in 56s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m46s
Test / ShareFS (push) Successful in 3m51s
Test / Hpkg (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 4m54s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / Flake checks (push) Successful in 1m46s
This is a helpful shortcut for examining a test program's ldd output.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:37:01 +09:00
cd9b534d6b container: improve documentation
All checks were successful
Test / Create distribution (push) Successful in 1m16s
Test / Sandbox (push) Successful in 3m2s
Test / Hakurei (push) Successful in 4m4s
Test / ShareFS (push) Successful in 4m17s
Test / Hpkg (push) Successful in 4m49s
Test / Sandbox (race detector) (push) Successful in 5m22s
Test / Hakurei (race detector) (push) Successful in 6m30s
Test / Flake checks (push) Successful in 1m48s
This change removes inconsistencies collected over time in this package.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-28 20:18:30 +09:00
84e6922f30 cmd/mbf: optionally set SCHED_IDLE
All checks were successful
Test / Create distribution (push) Successful in 1m54s
Test / Sandbox (push) Successful in 3m46s
Test / Hakurei (push) Successful in 5m50s
Test / ShareFS (push) Successful in 6m15s
Test / Sandbox (race detector) (push) Successful in 6m43s
Test / Hpkg (push) Successful in 6m54s
Test / Hakurei (race detector) (push) Successful in 7m54s
Test / Flake checks (push) Successful in 1m50s
None of the other supported policies are applicable here.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 16:50:35 +09:00
c16725a679 internal/pkg: set container scheduling policy
All checks were successful
Test / Create distribution (push) Successful in 2m10s
Test / ShareFS (push) Successful in 11m34s
Test / Hakurei (race detector) (push) Successful in 15m23s
Test / Sandbox (push) Successful in 2m18s
Test / Sandbox (race detector) (push) Successful in 3m8s
Test / Hakurei (push) Successful in 3m43s
Test / Hpkg (push) Successful in 4m3s
Test / Flake checks (push) Successful in 2m38s
This is not as necessary as it was for nix, since internal/pkg only unblocks exclusive artifacts one at a time. Still, this is useful when running alongside an unprivileged music player which cannot set itself to a higher priority.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 16:35:23 +09:00
a6160cd410 container: set scheduling policy
All checks were successful
Test / Create distribution (push) Successful in 1m59s
Test / Hakurei (push) Successful in 10m58s
Test / ShareFS (push) Successful in 11m11s
Test / Hakurei (race detector) (push) Successful in 15m14s
Test / Sandbox (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 2m49s
Test / Flake checks (push) Successful in 1m50s
This is thread-directed so cannot be done externally. The glibc wrapper exposes this behaviour so most multithreaded programs using this is straight up incorrect.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 16:29:47 +09:00
826347fe1f internal/rosa: expose standalone musl
All checks were successful
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 3m19s
Test / Hakurei (push) Successful in 4m35s
Test / ShareFS (push) Successful in 4m43s
Test / Hpkg (push) Successful in 5m12s
Test / Sandbox (race detector) (push) Successful in 5m24s
Test / Hakurei (race detector) (push) Successful in 6m21s
Test / Flake checks (push) Successful in 1m49s
This is useful in the system image and might also be used elsewhere.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-26 14:21:32 +09:00
085eaed7ba cmd/earlyinit: early /dev/ and io setup
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m1s
Test / Hpkg (push) Successful in 4m27s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 3m12s
Test / Flake checks (push) Successful in 1m48s
This establishes an environment where devtmpfs is mounted, and if the kernel fails to set up console, 1 and 2 is pointed at /dev/kmsg.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 16:37:42 +09:00
37d368a7f9 internal/rosa: initramfs artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 4m0s
Test / ShareFS (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 5m57s
Test / Flake checks (push) Successful in 1m57s
This constructs a single-program initramfs and populates /dev/null so the runtime does not throw if the kernel fails to set up console.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 16:31:52 +09:00
2aeac7f582 internal/rosa: fakeroot artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 4m1s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m45s
XSLT is untamable and extremely unpleasant to work with. This patches out the broken docs for now in the interest of getting some work done.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 01:20:31 +09:00
2b93631f52 cmd/mbf: use stage2 musl when possible
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m44s
This avoids pulling in the stage3 toolchain when it is not requested.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-25 01:05:49 +09:00
b3749aaf0b internal/rosa/kernel: arm64 configuration
All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 4m0s
Test / ShareFS (push) Successful in 4m7s
Test / Hpkg (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m1s
Test / Flake checks (push) Successful in 1m51s
These new dependencies do not apply to amd64, but adding them anyway in case they are needed some day.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 23:57:28 +09:00
c8bb88cced internal/rosa: libxslt artifact
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Hakurei (push) Successful in 51s
Test / Sandbox (push) Successful in 1m50s
Test / Sandbox (race detector) (push) Successful in 2m38s
Test / ShareFS (push) Successful in 3m0s
Test / Hakurei (race detector) (push) Successful in 3m36s
Test / Hpkg (push) Successful in 3m35s
Test / Flake checks (push) Successful in 2m0s
For building documentation that cannot be turned off.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 23:41:54 +09:00
f7f80f95b9 internal/rosa/perl: various perl module artifacts
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m56s
Test / Hpkg (push) Successful in 4m42s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 5m57s
Test / ShareFS (push) Successful in 2m53s
Test / Flake checks (push) Successful in 1m50s
This change includes helpers for both Makefile.PL and Build.PL as well as various modules.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 23:09:55 +09:00
6ea6c794fb internal/rosa/gnu: build single-binary coreutils
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m57s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 6m0s
Test / Flake checks (push) Successful in 2m5s
This enables more fine-grained toybox replacements.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 16:15:15 +09:00
6c2da4c4b2 internal/rosa: libcap artifact
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 4m1s
Test / ShareFS (push) Successful in 4m5s
Test / Hpkg (push) Successful in 4m27s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 6m17s
Test / Flake checks (push) Successful in 1m44s
Required by fakeroot. Quite refreshing to package a non-autotools project.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 16:02:54 +09:00
90f915a708 internal/rosa/kernel: disable DEBUG_STACK_USAGE
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / ShareFS (push) Successful in 8m35s
Test / Hpkg (push) Successful in 9m47s
Test / Sandbox (push) Successful in 1m32s
Test / Hakurei (push) Successful in 2m59s
Test / Hakurei (race detector) (push) Successful in 3m23s
Test / Sandbox (race detector) (push) Successful in 2m26s
Test / Flake checks (push) Successful in 1m59s
This is no longer needed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-24 14:10:37 +09:00
a5fea4686e internal/rosa: make toolchain optional
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m27s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 6m9s
Test / Flake checks (push) Successful in 1m35s
The final Rosa OS image does not need the toolchain.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 18:07:38 +09:00
ae8c365c0f internal/rosa/hakurei: optionally use embedded source
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m45s
This builds hakurei in Rosa OS between releases.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 17:56:16 +09:00
485db515f7 internal/pkg/ir: raise string limit to 16 MiB
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 4m6s
Test / ShareFS (push) Successful in 4m8s
Test / Hpkg (push) Successful in 4m39s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 5m59s
Test / Flake checks (push) Successful in 1m45s
A string holds "current" hakurei source code. For now the compressed tarball is 4.9 MiB long.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 15:12:19 +09:00
ec7ee0789e internal/rosa/fuse: fix init script path
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m54s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 6m0s
Test / Flake checks (push) Successful in 1m42s
The default value is quite misleading.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 14:39:36 +09:00
42c93a57a4 internal/rosa: fix patches
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 3m58s
Test / ShareFS (push) Successful in 4m5s
Test / Hpkg (push) Successful in 4m35s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m59s
Test / Flake checks (push) Successful in 1m53s
Turns out alacritty clobbers output. It turns tabs into spaces and also removes whitespace-only lines for some reason.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-23 02:12:09 +09:00
b1b14810ac internal/rosa/kernel: increase audio powersave timeout
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m38s
This feature is incredibly annoying as some amplifiers take time to wake up, and causes a non-insignificant amount of audio to be dropped.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 21:28:39 +09:00
de117ef365 internal/rosa: ncurses artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m1s
Test / Hpkg (push) Successful in 4m40s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m57s
Test / Flake checks (push) Successful in 1m46s
For running menuconfig.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 18:07:24 +09:00
5e4bf23e0c internal/rosa/musl: migrate to make helper
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m57s
Test / ShareFS (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m40s
This is much cleaner and eliminates the early ugliness.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 15:53:41 +09:00
d4519e2075 internal/rosa/make: expose --host
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 4m26s
Test / ShareFS (push) Successful in 4m43s
Test / Hpkg (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m37s
Test / Sandbox (race detector) (push) Successful in 2m30s
Test / Flake checks (push) Successful in 1m54s
This should be set alongside --build.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-22 15:14:09 +09:00
7f1e4cf43c internal/rosa: kernel artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m58s
Test / ShareFS (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m37s
The configuration still wants some cleanup, but this works fine as a generic kernel for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 23:19:16 +09:00
d021621fba internal/rosa: install kernel headers out-of-tree
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 4m5s
Test / ShareFS (push) Successful in 4m8s
Test / Hpkg (push) Successful in 4m37s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 6m19s
Test / Flake checks (push) Successful in 1m45s
This is somewhat cheaper than the implementation with extra artifact and is more friendly to the make helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 19:16:10 +09:00
56567307ec internal/rosa: gnu tar artifact
All checks were successful
Test / Create distribution (push) Successful in 2m21s
Test / Sandbox (push) Successful in 5m17s
Test / Hakurei (push) Successful in 10m39s
Test / ShareFS (push) Successful in 12m32s
Test / Hakurei (race detector) (push) Successful in 3m28s
Test / Hpkg (push) Successful in 5m42s
Test / Sandbox (race detector) (push) Successful in 2m20s
Test / Flake checks (push) Successful in 1m44s
Initially, libarchive was going to be used, but its test suite simply does not want to work under musl, not even with libiconv. The ticket last discussing this ceased any activity in 2020.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 18:09:51 +09:00
0264a1ef09 internal/rosa: libiconv artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 4m3s
Test / ShareFS (push) Successful in 3m56s
Test / Hpkg (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m18s
Test / Flake checks (push) Successful in 1m45s
For software that assumes glibc.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 14:35:32 +09:00
0123bbee3d internal/rosa: bc artifact
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m32s
Test / Hakurei (push) Successful in 3m44s
Test / ShareFS (push) Successful in 3m57s
Test / Hpkg (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 6m10s
Test / Flake checks (push) Successful in 1m41s
Required by the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 00:30:40 +09:00
771adad603 internal/rosa: texinfo artifact
All checks were successful
Test / Create distribution (push) Successful in 1m3s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 4m5s
Test / ShareFS (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m16s
Test / Flake checks (push) Successful in 1m51s
Yet another wheel reinvented by GNU. Required to shut some GNU programs up.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-21 00:28:21 +09:00
178305cb22 internal/rosa: elfutils artifact
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m56s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m37s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m40s
Required by the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:51:07 +09:00
c2456e252c internal/rosa: musl-obstack artifact
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m45s
Test / Hakurei (push) Successful in 3m57s
Test / ShareFS (push) Successful in 4m5s
Test / Hpkg (push) Successful in 4m42s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 6m2s
Test / Flake checks (push) Successful in 1m38s
Yet another nonstandard glibc extension used by elfutils.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:34:59 +09:00
273068b90c internal/rosa: musl-fts artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 3m56s
Test / Hpkg (push) Successful in 4m24s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 3m24s
Test / Flake checks (push) Successful in 1m54s
Another nonstandard glibc extension used by elfutils.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:29:09 +09:00
16b20e1d34 internal/rosa: argp-standalone artifact
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m56s
Test / ShareFS (push) Successful in 4m3s
Test / Hpkg (push) Successful in 4m48s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 5m57s
Test / Flake checks (push) Successful in 1m43s
Nonstandard glibc extension used by elfutils.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 21:19:34 +09:00
b983917a6e internal/rosa: expose kernel source
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m52s
Test / Hakurei (push) Successful in 4m7s
Test / ShareFS (push) Successful in 4m8s
Test / Hpkg (push) Successful in 4m36s
Test / Sandbox (race detector) (push) Successful in 5m11s
Test / Hakurei (race detector) (push) Successful in 6m44s
Test / Flake checks (push) Successful in 3m12s
This also removes the unused kernel helper.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-20 18:45:42 +09:00
e1b8f40add cmd/mbf: cache dir via environment
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 3m3s
Test / Hakurei (push) Successful in 4m5s
Test / ShareFS (push) Successful in 4m12s
Test / Hpkg (push) Successful in 4m41s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m38s
This is much less cumbersome than dragging the flag around all the time.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 15:32:25 +09:00
6df0d37c5a cmd/mbf: Rosa OS container helper
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 3m51s
Test / ShareFS (push) Successful in 3m58s
Test / Hpkg (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 6m13s
Test / Flake checks (push) Successful in 1m43s
This sets up a Rosa OS container with its shell as the initial process.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 15:12:16 +09:00
1619b06541 internal/pkg: export layer promotion
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m58s
This is a useful helper for external tooling.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 15:06:10 +09:00
e335d99c6b internal/pkg: export seccomp presets
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m48s
Test / ShareFS (push) Successful in 3m58s
Test / Hpkg (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m41s
This is useful for external tooling providing an execArtifact-like environment.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 14:30:31 +09:00
d888d09b6d cmd/mbf: explicit help command
All checks were successful
Test / Create distribution (push) Successful in 1m4s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m57s
Test / ShareFS (push) Successful in 4m3s
Test / Hpkg (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 5m20s
Test / Hakurei (race detector) (push) Successful in 6m1s
Test / Flake checks (push) Successful in 1m41s
Not having this command is counterintuitive.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 13:55:05 +09:00
54176e7315 internal/rosa: use LTS kernel
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m36s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m28s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m7s
Test / Flake checks (push) Successful in 1m34s
For out-of-tree modules.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-19 01:22:08 +09:00
3bfe99d3d8 internal/lockedfile: keep objects alive while stopping cleanups
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m23s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 5m54s
Test / Flake checks (push) Successful in 1m43s
Fixes https://go.dev/issues/74780.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 20:00:32 +09:00
149dfbb6af internal/rosa: tamago toolchain artifact
All checks were successful
Test / Create distribution (push) Successful in 1m20s
Test / Sandbox (push) Successful in 3m12s
Test / Hakurei (push) Successful in 4m27s
Test / ShareFS (push) Successful in 4m36s
Test / Hpkg (push) Successful in 5m7s
Test / Sandbox (race detector) (push) Successful in 5m29s
Test / Hakurei (race detector) (push) Successful in 6m38s
Test / Flake checks (push) Successful in 1m41s
Currently used by the (wip) bootloader, might not make it into the final OS.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 19:26:42 +09:00
58801b44d4 internal/rosa: util-linux artifact
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m51s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m35s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m34s
This stuff will likely be implemented natively in the final system. For now, it is useful for debugging.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 14:21:59 +09:00
e065bbf792 internal/rosa: procps artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m45s
Test / ShareFS (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 1m43s
Generally pretty useful, and required by util-linux test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-18 14:07:36 +09:00
a883e57e7d internal/rosa: qemu artifact
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m58s
Test / ShareFS (push) Successful in 4m1s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / Flake checks (push) Successful in 1m42s
This is still a quite minimal build. More features will be enabled as dependencies become available. The powerpc failure will be investigated if it is ever needed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 17:34:25 +09:00
ef9bd8ecbf internal/rosa/go: 1.25.7 to 1.26.0
All checks were successful
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 4m11s
Test / ShareFS (push) Successful in 4m15s
Test / Hpkg (push) Successful in 4m54s
Test / Sandbox (race detector) (push) Successful in 5m6s
Test / Hakurei (race detector) (push) Successful in 6m5s
Test / Flake checks (push) Successful in 1m41s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 14:40:24 +09:00
a40527dcb2 internal/pkg/ir: document reason for avoiding ident cache
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 1m49s
Test / ShareFS (push) Successful in 2m55s
Test / Hakurei (push) Successful in 3m7s
Test / Hpkg (push) Successful in 3m36s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 6m19s
Test / Flake checks (push) Successful in 1m41s
This got brought up earlier today as a potential optimisation. This change documents why it is not viable, and hopefully clears up some performance implications of using IRDecoder, namely that its decoding costs do not amortise.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 14:38:42 +09:00
88d9a6163e container/initplace: return nil for createTemp error injection
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m58s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m11s
Test / Flake checks (push) Successful in 1m37s
This matches os package behaviour, and avoids adding the cleanup.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 14:16:54 +09:00
47860b0387 internal/rosa/python: enable bzip2 and xz
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 7m7s
Test / Sandbox (race detector) (push) Successful in 8m43s
Test / Hpkg (push) Successful in 8m51s
Test / ShareFS (push) Successful in 8m52s
Test / Hakurei (race detector) (push) Successful in 10m58s
Test / Flake checks (push) Successful in 1m49s
This is required by qemu test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 02:39:11 +09:00
50c9da8b6d internal/rosa/python: enable openssl
All checks were successful
Test / Create distribution (push) Successful in 2m27s
Test / Sandbox (push) Successful in 5m47s
Test / ShareFS (push) Successful in 7m28s
Test / Sandbox (race detector) (push) Successful in 7m46s
Test / Hpkg (push) Successful in 8m1s
Test / Hakurei (race detector) (push) Successful in 8m57s
Test / Hakurei (push) Successful in 2m39s
Test / Flake checks (push) Successful in 1m41s
This is required by qemu test suite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 02:01:57 +09:00
16966043c7 internal/rosa: dtc artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m57s
Test / Hakurei (push) Successful in 4m11s
Test / ShareFS (push) Successful in 4m21s
Test / Hpkg (push) Successful in 4m55s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hakurei (race detector) (push) Successful in 6m15s
Test / Flake checks (push) Successful in 1m36s
Required by qemu.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 00:54:34 +09:00
a3515a6ef5 internal/rosa: bison artifact
All checks were successful
Test / Create distribution (push) Successful in 1m5s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 3m58s
Test / ShareFS (push) Successful in 4m7s
Test / Hpkg (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 6m4s
Test / Flake checks (push) Successful in 1m47s
Required by dtc, which is required by qemu.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-17 00:32:04 +09:00
7f05baab28 internal/rosa: flex artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m43s
Test / Hakurei (push) Successful in 4m2s
Test / ShareFS (push) Successful in 4m3s
Test / Hpkg (push) Successful in 4m32s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 6m13s
Test / Flake checks (push) Successful in 1m58s
Required by dtc, which is required by qemu.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 23:55:36 +09:00
d4d5e631ae internal/rosa: glib artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m46s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m56s
Test / Flake checks (push) Successful in 1m45s
Unfortunately required by many programs, even non-gtk ones.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 19:01:02 +09:00
1df3bcc3b9 nix: mount tmpfs on /tmp
All checks were successful
Test / ShareFS (push) Successful in 36s
Test / Sandbox (race detector) (push) Successful in 46s
Test / Sandbox (push) Successful in 47s
Test / Hpkg (push) Successful in 47s
Test / Create distribution (push) Successful in 1m1s
Test / Hakurei (push) Successful in 3m4s
Test / Hakurei (race detector) (push) Successful in 3m30s
Test / Flake checks (push) Successful in 1m38s
This hopefully eliminates spurious test failures caused by /tmp running out of space.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 18:22:29 +09:00
1809b53e52 internal/rosa/wayland: build-only tests patch
All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m56s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m41s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 3m13s
Test / Flake checks (push) Successful in 1m41s
This patch last had any discussion eight months ago and is still not merged.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 13:55:34 +09:00
67b2914c94 internal/rosa: meson helper
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 4m1s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 2m0s
This is used by quite a few projects.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 13:49:13 +09:00
74dee11822 internal/rosa/cmake: optional variant string
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m35s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m11s
Test / Hpkg (push) Successful in 4m39s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 6m1s
Test / Flake checks (push) Successful in 1m44s
This improves consistency with other helpers and removes the usually unnecessary variant suffix.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 12:44:40 +09:00
a58c9258cc internal/rosa/pcre2: downgrade to 10.43
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m39s
Test / ShareFS (push) Successful in 4m1s
Test / Hpkg (push) Successful in 4m29s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 5m55s
Test / Hakurei (push) Successful in 2m33s
Test / Flake checks (push) Successful in 1m34s
Latest release breaks assumptions made by GLib.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 01:51:38 +09:00
710b164c91 internal/pkg: allow devel syscalls
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m47s
Test / Hakurei (push) Successful in 4m0s
Test / ShareFS (push) Successful in 4m1s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m58s
This is required by the GLib test suite, and possibly others.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 01:46:28 +09:00
93911d6015 internal/rosa: pcre2 artifact
All checks were successful
Test / Create distribution (push) Successful in 1m12s
Test / Sandbox (push) Successful in 3m32s
Test / Hakurei (push) Successful in 5m12s
Test / ShareFS (push) Successful in 5m24s
Test / Sandbox (race detector) (push) Successful in 5m51s
Test / Hpkg (push) Successful in 5m50s
Test / Hakurei (race detector) (push) Successful in 7m6s
Test / Flake checks (push) Successful in 1m47s
Required by GLib.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-16 01:20:52 +09:00
bb097536d4 internal/rosa: remove libcxxabi hack
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m59s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 4m58s
Test / Hakurei (race detector) (push) Successful in 6m12s
Test / Flake checks (push) Successful in 1m39s
This was caused by stack overflow which was resolved many commits ago.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 21:59:09 +09:00
49b6526a38 internal/rosa: remove redundant meson flags
All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 4m1s
Test / ShareFS (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m33s
Test / Sandbox (race detector) (push) Successful in 4m59s
Test / Hakurei (race detector) (push) Successful in 5m57s
Test / Flake checks (push) Successful in 1m42s
These have no effect.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 20:18:34 +09:00
f9c31df94d internal/rosa: fixed-size toolchain enum
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m36s
Test / Hakurei (push) Successful in 3m50s
Test / ShareFS (push) Successful in 3m58s
Test / Hpkg (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m14s
Test / Flake checks (push) Successful in 1m37s
This fits in an inlined uint32 IR value.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 00:58:14 +09:00
4f570cc5c9 internal/pkg: expose extra methods to file
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m56s
Test / ShareFS (push) Successful in 4m3s
Test / Hpkg (push) Successful in 4m33s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 5m56s
Test / Flake checks (push) Successful in 1m45s
This is useful for FileArtifact processing another stream.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 00:45:37 +09:00
5828631e79 internal/pkg: split off context common
All checks were successful
Test / Create distribution (push) Successful in 1m1s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 4m1s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m37s
Test / Sandbox (race detector) (push) Successful in 5m3s
Test / Hakurei (race detector) (push) Successful in 48s
Test / Flake checks (push) Successful in 1m53s
For making these methods available to RContext.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-15 00:39:24 +09:00
4f9f4875d7 internal/rosa/openssl: scale jobs based on cpu count
All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m41s
Test / ShareFS (push) Successful in 3m57s
Test / Hpkg (push) Successful in 4m41s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 5m51s
Test / Hakurei (push) Successful in 2m37s
Test / Flake checks (push) Successful in 1m33s
The hardcoded value of 256 causes test failures due to excessive load on some machines. Twice the cpu count appears to almost saturate all cpus without causing spurious failures.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 03:34:08 +09:00
d49e654482 internal/rosa: kmod artifact
All checks were successful
Test / Create distribution (push) Successful in 1m7s
Test / Sandbox (push) Successful in 2m51s
Test / Hakurei (push) Successful in 4m18s
Test / ShareFS (push) Successful in 4m21s
Test / Hpkg (push) Successful in 4m50s
Test / Sandbox (race detector) (push) Successful in 5m15s
Test / Hakurei (race detector) (push) Successful in 3m8s
Test / Flake checks (push) Successful in 1m34s
Required by the kernel.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 03:15:27 +09:00
b746e352e5 internal/rosa/zstd: fix libdir
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 2m49s
Test / Hakurei (push) Successful in 4m3s
Test / ShareFS (push) Successful in 4m10s
Test / Hpkg (push) Successful in 4m49s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 6m5s
Test / Flake checks (push) Successful in 1m44s
CMake implicitly changes it to lib64 which is not supported.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 03:14:30 +09:00
c620d88dce update README document
All checks were successful
Test / Create distribution (push) Successful in 30s
Test / ShareFS (push) Successful in 37s
Test / Sandbox (push) Successful in 44s
Test / Sandbox (race detector) (push) Successful in 43s
Test / Hpkg (push) Successful in 45s
Test / Hakurei (push) Successful in 50s
Test / Hakurei (race detector) (push) Successful in 3m15s
Test / Flake checks (push) Successful in 1m39s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-13 01:38:50 +09:00
7cd14b8865 internal/rosa: squashfs-tools artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m47s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m26s
Test / Sandbox (race detector) (push) Successful in 5m9s
Test / Hakurei (race detector) (push) Successful in 6m15s
Test / Flake checks (push) Successful in 1m46s
The Makefile is very poorly written, so had to be configured through the environment.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-12 02:33:33 +09:00
3e18a4b397 internal/rosa: zstd artifact
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m55s
Test / ShareFS (push) Successful in 4m3s
Test / Hpkg (push) Successful in 4m34s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m14s
Test / Flake checks (push) Successful in 1m45s
Optional dependency of many programs, and generally useful to have around.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-12 01:48:21 +09:00
1791b604b5 internal/rosa/make: configurable configure and install
Some checks failed
Test / Create distribution (push) Successful in 29s
Test / ShareFS (push) Successful in 36s
Test / Sandbox (race detector) (push) Successful in 43s
Test / Sandbox (push) Successful in 44s
Test / Hakurei (race detector) (push) Successful in 49s
Test / Hpkg (push) Successful in 46s
Test / Hakurei (push) Failing after 2m34s
Test / Flake checks (push) Has been skipped
This makes the helper useful for non-autotools build systems.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-12 01:19:40 +09:00
59ff6db7ec internal/rosa: toolchain type methods
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m39s
Test / Hakurei (push) Successful in 3m49s
Test / ShareFS (push) Successful in 4m4s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 5m56s
Test / Flake checks (push) Successful in 1m44s
This improves readability for toolchain-specific checks.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 18:18:11 +09:00
430e099556 internal/rosa/stage0: add arm64 tarball
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m37s
Test / Hakurei (push) Successful in 3m59s
Test / ShareFS (push) Successful in 4m0s
Test / Hpkg (push) Successful in 4m33s
Test / Sandbox (race detector) (push) Successful in 5m12s
Test / Hakurei (race detector) (push) Successful in 5m56s
Test / Flake checks (push) Successful in 1m38s
This took far longer to complete because the aarch64 development machine is much slower.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 17:57:54 +09:00
17b64bb42c internal/pkg: skip resolved cure errors
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m44s
Test / Hakurei (push) Successful in 4m2s
Test / ShareFS (push) Successful in 4m2s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 6m5s
Test / Flake checks (push) Successful in 1m35s
This significantly improves error resolution performance.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 17:50:18 +09:00
dbb89dfb0f internal/pkg: buffer tar reader
All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 4m0s
Test / ShareFS (push) Successful in 4m10s
Test / Hpkg (push) Successful in 4m44s
Test / Sandbox (race detector) (push) Successful in 5m34s
Test / Hakurei (race detector) (push) Successful in 8m7s
Test / Flake checks (push) Successful in 1m49s
This significantly improves performance and is a good assumption since the primary use case of FileArtifact is over the network.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 02:08:44 +09:00
de06ea2be4 internal/pkg: read buffer free list
All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m53s
Test / Hakurei (push) Successful in 4m30s
Test / ShareFS (push) Successful in 4m28s
Test / Hpkg (push) Successful in 5m6s
Test / Sandbox (race detector) (push) Successful in 5m18s
Test / Hakurei (race detector) (push) Successful in 6m35s
Test / Flake checks (push) Successful in 2m3s
Reader has a non-insignificant buffer that is worth saving as well.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 02:02:46 +09:00
1ef7bedfb5 internal/rosa/toybox: do not assume bash location
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m36s
Test / Hakurei (push) Successful in 3m52s
Test / ShareFS (push) Successful in 3m58s
Test / Hpkg (push) Successful in 4m27s
Test / Sandbox (race detector) (push) Successful in 5m7s
Test / Hakurei (race detector) (push) Successful in 5m54s
Test / Flake checks (push) Successful in 1m43s
For compatibility with Gentoo stage3 as bootstrap seed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 01:36:41 +09:00
05a828c474 internal/pkg: validate tar pathnames
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m35s
Test / ShareFS (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 5m2s
Test / Hakurei (race detector) (push) Successful in 5m57s
Test / Hakurei (push) Successful in 4m24s
Test / Flake checks (push) Successful in 1m50s
TContext no longer validates FileArtifact ahead of time, validation outcome is instead determined after consuming the reader to EOF. All data must therefore be treated as untrusted input until the reader is closed.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-11 00:40:54 +09:00
0061d11f93 internal/rosa: use self-hosted stage0
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m42s
Test / Hakurei (push) Successful in 3m53s
Test / ShareFS (push) Successful in 4m3s
Test / Hpkg (push) Successful in 4m31s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m58s
Test / Flake checks (push) Successful in 3m10s
This removes the bootstrap dependency on Gentoo stage3 tarball.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-10 22:31:08 +09:00
fb101a02f2 internal/rosa: self-host stage0 tarball
All checks were successful
Test / Create distribution (push) Successful in 2m26s
Test / Sandbox (push) Successful in 5m22s
Test / Hakurei (push) Successful in 6m37s
Test / ShareFS (push) Successful in 6m39s
Test / Hpkg (push) Successful in 7m10s
Test / Sandbox (race detector) (push) Successful in 7m15s
Test / Hakurei (race detector) (push) Successful in 8m15s
Test / Flake checks (push) Successful in 1m51s
This replaces gentoo stage3 tarballs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-10 21:22:46 +09:00
3dbd67d113 internal/rosa: consistent stage0 paths
All checks were successful
Test / Create distribution (push) Successful in 2m2s
Test / Hakurei (push) Successful in 6m35s
Test / ShareFS (push) Successful in 6m33s
Test / Sandbox (race detector) (push) Successful in 6m52s
Test / Hpkg (push) Successful in 7m20s
Test / Hakurei (race detector) (push) Successful in 7m46s
Test / Sandbox (push) Successful in 1m37s
Test / Flake checks (push) Successful in 1m50s
This makes using the gentoo stage3 as our stage0 compatible with Rosa OS stage0 tarballs.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-10 19:28:47 +09:00
f511f0a9e9 internal/rosa: bzip2 artifact
All checks were successful
Test / Create distribution (push) Successful in 30s
Test / Sandbox (push) Successful in 45s
Test / ShareFS (push) Successful in 2m44s
Test / Hakurei (push) Successful in 3m1s
Test / Hpkg (push) Successful in 3m31s
Test / Sandbox (race detector) (push) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 3m13s
Test / Flake checks (push) Successful in 1m48s
For creating the stage0 tarball. Might be replaced by a custom artifact at some point.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 02:26:52 +09:00
47995137b3 internal/rosa/perl: skip installing manpages
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m48s
Test / Hakurei (push) Successful in 4m13s
Test / ShareFS (push) Successful in 4m10s
Test / Hpkg (push) Successful in 4m48s
Test / Sandbox (race detector) (push) Successful in 5m5s
Test / Hakurei (race detector) (push) Successful in 7m0s
Test / Flake checks (push) Successful in 1m48s
Perl manpages ignore prefix and gets installed to /. This change does not use the configure script because it is completely broken and specifying either "none" or a single space character (undocumented) has no effect.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 01:50:52 +09:00
e1b8607101 internal/rosa: rename stage0 toolchain
All checks were successful
Test / Create distribution (push) Successful in 1m2s
Test / Sandbox (push) Successful in 1m48s
Test / Sandbox (race detector) (push) Successful in 2m52s
Test / Hakurei (push) Successful in 3m1s
Test / ShareFS (push) Successful in 3m16s
Test / Hakurei (race detector) (push) Successful in 3m58s
Test / Hpkg (push) Successful in 3m57s
Test / Flake checks (push) Successful in 1m48s
This is stage0 relative to Rosa OS, and stage3 relative to the toolchain it is compiled on (Gentoo in this case). Referring to the toolchain itself as stage3 is counterintuitive and misleading.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 00:55:07 +09:00
3d3bd45b95 internal/rosa/hakurei: 0.3.4 to 0.3.5
All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Sandbox (push) Successful in 2m37s
Test / ShareFS (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m33s
Test / Sandbox (race detector) (push) Successful in 5m0s
Test / Hakurei (race detector) (push) Successful in 6m16s
Test / Hakurei (push) Successful in 3m0s
Test / Flake checks (push) Successful in 2m7s
This removes all backport patches.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 00:27:45 +09:00
9fb0b2452e release: 0.3.5
All checks were successful
Release / Create release (push) Successful in 1m1s
Test / Hakurei (push) Successful in 3m47s
Test / Create distribution (push) Successful in 30s
Test / ShareFS (push) Successful in 36s
Test / Sandbox (push) Successful in 44s
Test / Hpkg (push) Successful in 45s
Test / Sandbox (race detector) (push) Successful in 4m39s
Test / Hakurei (race detector) (push) Successful in 1m52s
Test / Flake checks (push) Successful in 1m50s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-02-09 00:11:20 +09:00
138 changed files with 33817 additions and 3461 deletions

View File

@@ -89,23 +89,6 @@ jobs:
path: result/* path: result/*
retention-days: 1 retention-days: 1
hpkg:
name: Hpkg
runs-on: nix
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run NixOS test
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.hpkg
- name: Upload test output
uses: actions/upload-artifact@v3
with:
name: "hpkg-vm-output"
path: result/*
retention-days: 1
check: check:
name: Flake checks name: Flake checks
needs: needs:
@@ -114,7 +97,6 @@ jobs:
- sandbox - sandbox
- sandbox-race - sandbox-race
- sharefs - sharefs
- hpkg
runs-on: nix runs-on: nix
steps: steps:
- name: Checkout - name: Checkout

View File

@@ -1,5 +0,0 @@
DO NOT ADD NEW ACTIONS HERE
This port is solely for releasing to the github mirror and serves no purpose during development.
All development happens at https://git.gensokyo.uk/security/hakurei. If you wish to contribute,
request for an account on git.gensokyo.uk.

View File

@@ -1,46 +0,0 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
name: Create release
runs-on: ubuntu-latest
permissions:
packages: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for release
run: nix build --print-out-paths --print-build-logs .#dist
- name: Release
uses: softprops/action-gh-release@v2
with:
files: |-
result/hakurei-**

View File

@@ -1,48 +0,0 @@
name: Test
on:
- push
jobs:
dist:
name: Create distribution
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v32
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: build-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-${{ runner.os }}-
gc-max-store-size-linux: 1G
purge: true
purge-prefixes: build-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for test
id: build-test
run: >-
export HAKUREI_REV="$(git rev-parse --short HEAD)" &&
sed -i.old 's/version = /version = "0.0.0-'$HAKUREI_REV'"; # version = /' package.nix &&
nix build --print-out-paths --print-build-logs .#dist &&
mv package.nix.old package.nix &&
echo "rev=$HAKUREI_REV" >> $GITHUB_OUTPUT
- name: Upload test build
uses: actions/upload-artifact@v4
with:
name: "hakurei-${{ steps.build-test.outputs.rev }}"
path: result/*
retention-days: 1

1
.gitignore vendored
View File

@@ -28,6 +28,7 @@ go.work.sum
# go generate # go generate
/cmd/hakurei/LICENSE /cmd/hakurei/LICENSE
/internal/pkg/testdata/testtool /internal/pkg/testdata/testtool
/internal/rosa/hakurei_current.tar.gz
# release # release
/dist/hakurei-* /dist/hakurei-*

181
README.md
View File

@@ -15,164 +15,51 @@
<a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a> <a href="https://hakurei.app"><img src="https://img.shields.io/website?url=https%3A%2F%2Fhakurei.app" alt="Website" /></a>
</p> </p>
Hakurei is a tool for running sandboxed graphical applications as dedicated subordinate users on the Linux kernel. Hakurei is a tool for running sandboxed desktop applications as dedicated
It implements the application container of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), subordinate users on the Linux kernel. It implements the application container
a self-contained Android-like package manager with modern security features. of [planterette (WIP)](https://git.gensokyo.uk/security/planterette), a
self-contained Android-like package manager with modern security features.
## NixOS Module usage Interaction with hakurei happens entirely through structures described by
package [hst](https://pkg.go.dev/hakurei.app/hst). No native API is available
due to internal details of uid isolation.
The NixOS module currently requires home-manager to configure subordinate users. Full module documentation can be found [here](options.md). ## Notable Packages
To use the module, import it into your configuration with Package [container](https://pkg.go.dev/hakurei.app/container) is general purpose
container tooling. It is used by the hakurei shim process running as the target
subordinate user to set up the application container. It has a single dependency,
[libseccomp](https://github.com/seccomp/libseccomp), to create BPF programs
for the [system call filter](https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html).
```nix Package [internal/pkg](https://pkg.go.dev/hakurei.app/internal/pkg) provides
{ infrastructure for hermetic builds. This replaces the legacy nix-based testing
inputs = { framework and serves as the build system of Rosa OS, currently developed under
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; package [internal/rosa](https://pkg.go.dev/hakurei.app/internal/rosa).
hakurei = { ## Dependencies
url = "git+https://git.gensokyo.uk/security/hakurei";
# Optional but recommended to limit the size of your system closure. `container` depends on:
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, hakurei, ... }: - [libseccomp](https://github.com/seccomp/libseccomp) to generate BPF programs.
{
nixosConfigurations.hakurei = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
hakurei.nixosModules.hakurei
];
};
};
}
```
This adds the `environment.hakurei` option: `cmd/hakurei` depends on:
```nix - [acl](https://savannah.nongnu.org/projects/acl/) to export sockets to
{ pkgs, ... }: subordinate users.
- [wayland](https://gitlab.freedesktop.org/wayland/wayland) to set up
[security-context-v1](https://wayland.app/protocols/security-context-v1).
- [xcb](https://xcb.freedesktop.org/) to grant and revoke subordinate users
access to the X server.
{ `cmd/sharefs` depends on:
environment.hakurei = {
enable = true;
stateDir = "/var/lib/hakurei";
users = {
alice = 0;
nixos = 10;
};
commonPaths = [ - [fuse](https://github.com/libfuse/libfuse) to implement the filesystem.
{
src = "/sdcard";
write = true;
}
];
extraHomeConfig = { New dependencies will generally not be added. Patches adding new dependencies
home.stateVersion = "23.05"; are very likely to be rejected.
};
apps = { ## NixOS Module (deprecated)
"org.chromium.Chromium" = {
name = "chromium";
identity = 1;
packages = [ pkgs.chromium ];
userns = true;
mapRealUid = true;
dbus = {
system = {
filter = true;
talk = [
"org.bluez"
"org.freedesktop.Avahi"
"org.freedesktop.UPower"
];
};
session =
f:
f {
talk = [
"org.freedesktop.FileManager1"
"org.freedesktop.Notifications"
"org.freedesktop.ScreenSaver"
"org.freedesktop.secrets"
"org.kde.kwalletd5"
"org.kde.kwalletd6"
];
own = [
"org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.chromium.*"
];
call = { };
broadcast = { };
};
};
};
"org.claws_mail.Claws-Mail" = { The NixOS module is in maintenance mode and will be removed once planterette is
name = "claws-mail"; feature-complete. Full module documentation can be found [here](options.md).
identity = 2;
packages = [ pkgs.claws-mail ];
gpu = false;
capability.pulse = false;
};
"org.weechat" = {
name = "weechat";
identity = 3;
shareUid = true;
packages = [ pkgs.weechat ];
capability = {
wayland = false;
x11 = false;
dbus = true;
pulse = false;
};
};
"dev.vencord.Vesktop" = {
name = "discord";
identity = 3;
shareUid = true;
packages = [ pkgs.vesktop ];
share = pkgs.vesktop;
command = "vesktop --ozone-platform-hint=wayland";
userns = true;
mapRealUid = true;
capability.x11 = true;
dbus = {
session =
f:
f {
talk = [ "org.kde.StatusNotifierWatcher" ];
own = [ ];
call = { };
broadcast = { };
};
system.filter = true;
};
};
"io.looking-glass" = {
name = "looking-glass-client";
identity = 4;
useCommonPaths = false;
groups = [ "plugdev" ];
extraPaths = [
{
src = "/dev/shm/looking-glass";
write = true;
}
];
extraConfig = {
programs.looking-glass-client.enable = true;
};
};
};
};
}
```

58
cmd/earlyinit/main.go Normal file
View File

@@ -0,0 +1,58 @@
package main
import (
"log"
"os"
"runtime"
. "syscall"
)
func main() {
runtime.LockOSThread()
log.SetFlags(0)
log.SetPrefix("earlyinit: ")
if err := Mount(
"devtmpfs",
"/dev/",
"devtmpfs",
MS_NOSUID|MS_NOEXEC,
"",
); err != nil {
log.Fatalf("cannot mount devtmpfs: %v", err)
}
// The kernel might be unable to set up the console. When that happens,
// printk is called with "Warning: unable to open an initial console."
// and the init runs with no files. The checkfds runtime function
// populates 0-2 by opening /dev/null for them.
//
// This check replaces 1 and 2 with /dev/kmsg to improve the chance
// of output being visible to the user.
if fi, err := os.Stdout.Stat(); err == nil {
if stat, ok := fi.Sys().(*Stat_t); ok {
if stat.Rdev == 0x103 {
var fd int
if fd, err = Open(
"/dev/kmsg",
O_WRONLY|O_CLOEXEC,
0,
); err != nil {
log.Fatalf("cannot open kmsg: %v", err)
}
if err = Dup3(fd, Stdout, 0); err != nil {
log.Fatalf("cannot open stdout: %v", err)
}
if err = Dup3(fd, Stderr, 0); err != nil {
log.Fatalf("cannot open stderr: %v", err)
}
if err = Close(fd); err != nil {
log.Printf("cannot close kmsg: %v", err)
}
}
}
}
}

View File

@@ -1,7 +0,0 @@
This program is a proof of concept and is now deprecated. It is only kept
around for API demonstration purposes and to make the most out of the test
suite.
This program is replaced by planterette, which can be found at
https://git.gensokyo.uk/security/planterette. Development effort should be
focused there instead.

View File

@@ -1,173 +0,0 @@
package main
import (
"encoding/json"
"log"
"os"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
)
type appInfo struct {
Name string `json:"name"`
Version string `json:"version"`
// passed through to [hst.Config]
ID string `json:"id"`
// passed through to [hst.Config]
Identity int `json:"identity"`
// passed through to [hst.Config]
Groups []string `json:"groups,omitempty"`
// passed through to [hst.Config]
Devel bool `json:"devel,omitempty"`
// passed through to [hst.Config]
Userns bool `json:"userns,omitempty"`
// passed through to [hst.Config]
HostNet bool `json:"net,omitempty"`
// passed through to [hst.Config]
HostAbstract bool `json:"abstract,omitempty"`
// passed through to [hst.Config]
Device bool `json:"dev,omitempty"`
// passed through to [hst.Config]
Tty bool `json:"tty,omitempty"`
// passed through to [hst.Config]
MapRealUID bool `json:"map_real_uid,omitempty"`
// passed through to [hst.Config]
DirectWayland bool `json:"direct_wayland,omitempty"`
// passed through to [hst.Config]
SystemBus *hst.BusConfig `json:"system_bus,omitempty"`
// passed through to [hst.Config]
SessionBus *hst.BusConfig `json:"session_bus,omitempty"`
// passed through to [hst.Config]
Enablements *hst.Enablements `json:"enablements,omitempty"`
// passed through to [hst.Config]
Multiarch bool `json:"multiarch,omitempty"`
// passed through to [hst.Config]
Bluetooth bool `json:"bluetooth,omitempty"`
// allow gpu access within sandbox
GPU bool `json:"gpu"`
// store path to nixGL mesa wrappers
Mesa string `json:"mesa,omitempty"`
// store path to nixGL source
NixGL string `json:"nix_gl,omitempty"`
// store path to activate-and-exec script
Launcher *check.Absolute `json:"launcher"`
// store path to /run/current-system
CurrentSystem *check.Absolute `json:"current_system"`
// store path to home-manager activation package
ActivationPackage string `json:"activation_package"`
}
func (app *appInfo) toHst(pathSet *appPathSet, pathname *check.Absolute, argv []string, flagDropShell bool) *hst.Config {
config := &hst.Config{
ID: app.ID,
Enablements: app.Enablements,
SystemBus: app.SystemBus,
SessionBus: app.SessionBus,
DirectWayland: app.DirectWayland,
Identity: app.Identity,
Groups: app.Groups,
Container: &hst.ContainerConfig{
Hostname: formatHostname(app.Name),
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append("store"), Target: pathNixStore}},
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSBind{Source: pathSet.metaPath, Target: hst.AbsPrivateTmp.Append("app")}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
},
Username: "hakurei",
Shell: pathShell,
Home: pathDataData.Append(app.ID),
Path: pathname,
Args: argv,
},
ExtraPerms: []hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
},
}
if app.Devel {
config.Container.Flags |= hst.FDevel
}
if app.Userns {
config.Container.Flags |= hst.FUserns
}
if app.HostNet {
config.Container.Flags |= hst.FHostNet
}
if app.HostAbstract {
config.Container.Flags |= hst.FHostAbstract
}
if app.Device {
config.Container.Flags |= hst.FDevice
}
if app.Tty || flagDropShell {
config.Container.Flags |= hst.FTty
}
if app.MapRealUID {
config.Container.Flags |= hst.FMapRealUID
}
if app.Multiarch {
config.Container.Flags |= hst.FMultiarch
}
config.Container.Flags |= hst.FShareRuntime | hst.FShareTmpdir
return config
}
func loadAppInfo(name string, beforeFail func()) *appInfo {
bundle := new(appInfo)
if f, err := os.Open(name); err != nil {
beforeFail()
log.Fatalf("cannot open bundle: %v", err)
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
beforeFail()
log.Fatalf("cannot parse bundle metadata: %v", err)
} else if err = f.Close(); err != nil {
log.Printf("cannot close bundle metadata: %v", err)
// not fatal
}
if bundle.ID == "" {
beforeFail()
log.Fatal("application identifier must not be empty")
}
if bundle.Launcher == nil {
beforeFail()
log.Fatal("launcher must not be empty")
}
if bundle.CurrentSystem == nil {
beforeFail()
log.Fatal("current-system must not be empty")
}
return bundle
}
func formatHostname(name string) string {
if h, err := os.Hostname(); err != nil {
log.Printf("cannot get hostname: %v", err)
return "hakurei-" + name
} else {
return h + "-" + name
}
}

View File

@@ -1,256 +0,0 @@
{
nixpkgsFor,
system,
nixpkgs,
home-manager,
}:
{
lib,
stdenv,
closureInfo,
writeScript,
runtimeShell,
writeText,
symlinkJoin,
vmTools,
runCommand,
fetchFromGitHub,
zstd,
nix,
sqlite,
name ? throw "name is required",
version ? throw "version is required",
pname ? "${name}-${version}",
modules ? [ ],
nixosModules ? [ ],
script ? ''
exec "$SHELL" "$@"
'',
id ? name,
identity ? throw "identity is required",
groups ? [ ],
userns ? false,
net ? true,
dev ? false,
no_new_session ? false,
map_real_uid ? false,
direct_wayland ? false,
system_bus ? null,
session_bus ? null,
allow_wayland ? true,
allow_x11 ? false,
allow_dbus ? true,
allow_audio ? true,
gpu ? allow_wayland || allow_x11,
}:
let
inherit (lib) optionals;
homeManagerConfiguration = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgsFor.${system};
modules = modules ++ [
{
home = {
username = "hakurei";
homeDirectory = "/data/data/${id}";
stateVersion = "22.11";
};
}
];
};
launcher = writeScript "hakurei-${pname}" ''
#!${runtimeShell} -el
${script}
'';
extraNixOSConfig =
{ pkgs, ... }:
{
environment = {
etc.nixpkgs.source = nixpkgs.outPath;
systemPackages = [ pkgs.nix ];
};
imports = nixosModules;
};
nixos = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
extraNixOSConfig
{ nix.settings.experimental-features = [ "flakes" ]; }
{ nix.settings.experimental-features = [ "nix-command" ]; }
{ boot.isContainer = true; }
{ system.stateVersion = "22.11"; }
];
};
etc = vmTools.runInLinuxVM (
runCommand "etc" { } ''
mkdir -p /etc
${nixos.config.system.build.etcActivationCommands}
# remove unused files
rm -rf /etc/sudoers
mkdir -p $out
tar -C /etc -cf "$out/etc.tar" .
''
);
extendSessionDefault = id: ext: {
filter = true;
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
own =
(optionals (id != null) [
"${id}.*"
"org.mpris.MediaPlayer2.${id}.*"
])
++ ext.own;
inherit (ext) call broadcast;
};
nixGL = fetchFromGitHub {
owner = "nix-community";
repo = "nixGL";
rev = "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a";
hash = "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=";
};
mesaWrappers =
let
isIntelX86Platform = system == "x86_64-linux";
nixGLPackages = import (nixGL + "/default.nix") {
pkgs = nixpkgs.legacyPackages.${system};
enable32bits = isIntelX86Platform;
enableIntelX86Extensions = isIntelX86Platform;
};
in
symlinkJoin {
name = "nixGL-mesa";
paths = with nixGLPackages; [
nixGLIntel
nixVulkanIntel
];
};
info = builtins.toJSON {
inherit
name
version
id
identity
launcher
groups
userns
net
dev
no_new_session
map_real_uid
direct_wayland
system_bus
gpu
;
session_bus =
if session_bus != null then
(session_bus (extendSessionDefault id))
else
(extendSessionDefault id {
talk = [ ];
own = [ ];
call = { };
broadcast = { };
});
enablements = {
wayland = allow_wayland;
x11 = allow_x11;
dbus = allow_dbus;
pipewire = allow_audio;
};
mesa = if gpu then mesaWrappers else null;
nix_gl = if gpu then nixGL else null;
current_system = nixos.config.system.build.toplevel;
activation_package = homeManagerConfiguration.activationPackage;
};
in
stdenv.mkDerivation {
name = "${pname}.pkg";
inherit version;
__structuredAttrs = true;
nativeBuildInputs = [
zstd
nix
sqlite
];
buildCommand = ''
NIX_ROOT="$(mktemp -d)"
export USER="nobody"
# create bootstrap store
bootstrapClosureInfo="${
closureInfo {
rootPaths = [
nix
nixos.config.system.build.toplevel
];
}
}"
echo "copying bootstrap store paths..."
mkdir -p "$NIX_ROOT/nix/store"
xargs -n 1 -a "$bootstrapClosureInfo/store-paths" cp -at "$NIX_ROOT/nix/store/"
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --load-db < "$bootstrapClosureInfo/registration"
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --optimise
sqlite3 "$NIX_ROOT/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
chmod -R +r "$NIX_ROOT/nix/var"
# create binary cache
closureInfo="${
closureInfo {
rootPaths = [
homeManagerConfiguration.activationPackage
launcher
]
++ optionals gpu [
mesaWrappers
nixGL
];
}
}"
echo "copying application paths..."
TMP_STORE="$(mktemp -d)"
mkdir -p "$TMP_STORE/nix/store"
xargs -n 1 -a "$closureInfo/store-paths" cp -at "$TMP_STORE/nix/store/"
NIX_REMOTE="local?root=$TMP_STORE" nix-store --load-db < "$closureInfo/registration"
sqlite3 "$TMP_STORE/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
NIX_REMOTE="local?root=$TMP_STORE" nix --offline --extra-experimental-features nix-command \
--verbose --log-format raw-with-logs \
copy --all --no-check-sigs --to \
"file://$NIX_ROOT/res?compression=zstd&compression-level=19&parallel-compression=true"
# package /etc
mkdir -p "$NIX_ROOT/etc"
tar -C "$NIX_ROOT/etc" -xf "${etc}/etc.tar"
# write metadata
cp "${writeText "bundle.json" info}" "$NIX_ROOT/bundle.json"
# create an intermediate file to improve zstd performance
INTER="$(mktemp)"
tar -C "$NIX_ROOT" -cf "$INTER" .
zstd -T0 -19 -fo "$out" "$INTER"
'';
}

View File

@@ -1,335 +0,0 @@
package main
import (
"context"
"encoding/json"
"errors"
"log"
"os"
"os/signal"
"path"
"syscall"
"hakurei.app/command"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/message"
)
var (
errSuccess = errors.New("success")
)
func main() {
log.SetPrefix("hpkg: ")
log.SetFlags(0)
msg := message.New(log.Default())
if err := os.Setenv("SHELL", pathShell.String()); err != nil {
log.Fatalf("cannot set $SHELL: %v", err)
}
if os.Geteuid() == 0 {
log.Fatal("this program must not run as root")
}
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer stop() // unreachable
var (
flagVerbose bool
flagDropShell bool
)
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { msg.SwapVerbose(flagVerbose); return nil }).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
{
var (
flagDropShellActivate bool
)
c.NewCommand("install", "Install an application from its package", func(args []string) error {
if len(args) != 1 {
log.Println("invalid argument")
return syscall.EINVAL
}
pkgPath := args[0]
if !path.IsAbs(pkgPath) {
if dir, err := os.Getwd(); err != nil {
log.Printf("cannot get current directory: %v", err)
return err
} else {
pkgPath = path.Join(dir, pkgPath)
}
}
/*
Look up paths to programs started by hpkg.
This is done here to ease error handling as cleanup is not yet required.
*/
var (
_ = lookPath("zstd")
tar = lookPath("tar")
chmod = lookPath("chmod")
rm = lookPath("rm")
)
/*
Extract package and set up for cleanup.
*/
var workDir *check.Absolute
if p, err := os.MkdirTemp("", "hpkg.*"); err != nil {
log.Printf("cannot create temporary directory: %v", err)
return err
} else if workDir, err = check.NewAbs(p); err != nil {
log.Printf("invalid temporary directory: %v", err)
return err
}
cleanup := func() {
// should be faster than a native implementation
mustRun(msg, chmod, "-R", "+w", workDir.String())
mustRun(msg, rm, "-rf", workDir.String())
}
beforeRunFail.Store(&cleanup)
mustRun(msg, tar, "-C", workDir.String(), "-xf", pkgPath)
/*
Parse bundle and app metadata, do pre-install checks.
*/
bundle := loadAppInfo(path.Join(workDir.String(), "bundle.json"), cleanup)
pathSet := pathSetByApp(bundle.ID)
a := bundle
if s, err := os.Stat(pathSet.metaPath.String()); err != nil {
if !os.IsNotExist(err) {
cleanup()
log.Printf("cannot access %q: %v", pathSet.metaPath, err)
return err
}
// did not modify app, clean installation condition met later
} else if s.IsDir() {
cleanup()
log.Printf("metadata path %q is not a file", pathSet.metaPath)
return syscall.EBADMSG
} else {
a = loadAppInfo(pathSet.metaPath.String(), cleanup)
if a.ID != bundle.ID {
cleanup()
log.Printf("app %q claims to have identifier %q",
bundle.ID, a.ID)
return syscall.EBADE
}
// sec: should verify credentials
}
if a != bundle {
// do not try to re-install
if a.NixGL == bundle.NixGL &&
a.CurrentSystem == bundle.CurrentSystem &&
a.Launcher == bundle.Launcher &&
a.ActivationPackage == bundle.ActivationPackage {
cleanup()
log.Printf("package %q is identical to local application %q",
pkgPath, a.ID)
return errSuccess
}
// identity determines uid
if a.Identity != bundle.Identity {
cleanup()
log.Printf("package %q identity %d differs from installed %d",
pkgPath, bundle.Identity, a.Identity)
return syscall.EBADE
}
// sec: should compare version string
msg.Verbosef("installing application %q version %q over local %q",
bundle.ID, bundle.Version, a.Version)
} else {
msg.Verbosef("application %q clean installation", bundle.ID)
// sec: should install credentials
}
/*
Setup steps for files owned by the target user.
*/
withCacheDir(ctx, msg, "install", []string{
// export inner bundle path in the environment
"export BUNDLE=" + hst.PrivateTmp + "/bundle",
// replace inner /etc
"mkdir -p etc",
"chmod -R +w etc",
"rm -rf etc",
"cp -dRf $BUNDLE/etc etc",
// replace inner /nix
"mkdir -p nix",
"chmod -R +w nix",
"rm -rf nix",
"cp -dRf /nix nix",
// copy from binary cache
"nix copy --offline --no-check-sigs --all --from file://$BUNDLE/res --to $PWD",
// deduplicate nix store
"nix store --offline --store $PWD optimise",
// make cache directory world-readable for autoetc
"chmod 0755 .",
}, workDir, bundle, pathSet, flagDropShell, cleanup)
if bundle.GPU {
withCacheDir(ctx, msg, "mesa-wrappers", []string{
// link nixGL mesa wrappers
"mkdir -p nix/.nixGL",
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
"ln -s " + bundle.Mesa + "/bin/nixVulkanIntel nix/.nixGL/nixVulkan",
}, workDir, bundle, pathSet, false, cleanup)
}
/*
Activate home-manager generation.
*/
withNixDaemon(ctx, msg, "activate", []string{
// clean up broken links
"mkdir -p .local/state/{nix,home-manager}",
"chmod -R +w .local/state/{nix,home-manager}",
"rm -rf .local/state/{nix,home-manager}",
// run activation script
bundle.ActivationPackage + "/activate",
}, false, func(config *hst.Config) *hst.Config { return config },
bundle, pathSet, flagDropShellActivate, cleanup)
/*
Installation complete. Write metadata to block re-installs or downgrades.
*/
// serialise metadata to ensure consistency
if f, err := os.OpenFile(pathSet.metaPath.String()+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
cleanup()
log.Printf("cannot create metadata file: %v", err)
return err
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
cleanup()
log.Printf("cannot write metadata: %v", err)
return err
} else if err = f.Close(); err != nil {
log.Printf("cannot close metadata file: %v", err)
// not fatal
}
if err := os.Rename(pathSet.metaPath.String()+"~", pathSet.metaPath.String()); err != nil {
cleanup()
log.Printf("cannot rename metadata file: %v", err)
return err
}
cleanup()
return errSuccess
}).
Flag(&flagDropShellActivate, "s", command.BoolFlag(false), "Drop to a shell on activation")
}
{
var (
flagDropShellNixGL bool
flagAutoDrivers bool
)
c.NewCommand("start", "Start an application", func(args []string) error {
if len(args) < 1 {
log.Println("invalid argument")
return syscall.EINVAL
}
/*
Parse app metadata.
*/
id := args[0]
pathSet := pathSetByApp(id)
a := loadAppInfo(pathSet.metaPath.String(), func() {})
if a.ID != id {
log.Printf("app %q claims to have identifier %q", id, a.ID)
return syscall.EBADE
}
/*
Prepare nixGL.
*/
if a.GPU && flagAutoDrivers {
withNixDaemon(ctx, msg, "nix-gl", []string{
"mkdir -p /nix/.nixGL/auto",
"rm -rf /nix/.nixGL/auto",
"export NIXPKGS_ALLOW_UNFREE=1",
"nix build --impure " +
"--out-link /nix/.nixGL/auto/opengl " +
"--override-input nixpkgs path:/etc/nixpkgs " +
"path:" + a.NixGL,
"nix build --impure " +
"--out-link /nix/.nixGL/auto/vulkan " +
"--override-input nixpkgs path:/etc/nixpkgs " +
"path:" + a.NixGL + "#nixVulkanNvidia",
}, true, func(config *hst.Config) *hst.Config {
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsEtc.Append("resolv.conf"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("block"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("bus"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("class"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("dev"), Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsSys.Append("devices"), Optional: true}},
}...)
appendGPUFilesystem(config)
return config
}, a, pathSet, flagDropShellNixGL, func() {})
}
/*
Create app configuration.
*/
pathname := a.Launcher
argv := make([]string, 1, len(args))
if flagDropShell {
pathname = pathShell
argv[0] = bash
} else {
argv[0] = a.Launcher.String()
}
argv = append(argv, args[1:]...)
config := a.toHst(pathSet, pathname, argv, flagDropShell)
/*
Expose GPU devices.
*/
if a.GPU {
config.Container.Filesystem = append(config.Container.Filesystem,
hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath.Append(".nixGL"), Target: hst.AbsPrivateTmp.Append("nixGL")}})
appendGPUFilesystem(config)
}
/*
Spawn app.
*/
mustRunApp(ctx, msg, config, func() {})
return errSuccess
}).
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
Flag(&flagAutoDrivers, "auto-drivers", command.BoolFlag(false), "Attempt automatic opengl driver detection")
}
c.MustParse(os.Args[1:], func(err error) {
msg.Verbosef("command returned %v", err)
if errors.Is(err, errSuccess) {
msg.BeforeExit()
os.Exit(0)
}
})
log.Fatal("unreachable")
}

View File

@@ -1,117 +0,0 @@
package main
import (
"log"
"os"
"os/exec"
"strconv"
"sync/atomic"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/message"
)
const bash = "bash"
var (
dataHome *check.Absolute
)
func init() {
// dataHome
if a, err := check.NewAbs(os.Getenv("HAKUREI_DATA_HOME")); err == nil {
dataHome = a
} else {
dataHome = fhs.AbsVarLib.Append("hakurei/" + strconv.Itoa(os.Getuid()))
}
}
var (
pathBin = fhs.AbsRoot.Append("bin")
pathNix = check.MustAbs("/nix/")
pathNixStore = pathNix.Append("store/")
pathCurrentSystem = fhs.AbsRun.Append("current-system")
pathSwBin = pathCurrentSystem.Append("sw/bin/")
pathShell = pathSwBin.Append(bash)
pathData = check.MustAbs("/data")
pathDataData = pathData.Append("data")
)
func lookPath(file string) string {
if p, err := exec.LookPath(file); err != nil {
log.Fatalf("%s: command not found", file)
return ""
} else {
return p
}
}
var beforeRunFail = new(atomic.Pointer[func()])
func mustRun(msg message.Msg, name string, arg ...string) {
msg.Verbosef("spawning process: %q %q", name, arg)
cmd := exec.Command(name, arg...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
if f := beforeRunFail.Swap(nil); f != nil {
(*f)()
}
log.Fatalf("%s: %v", name, err)
}
}
type appPathSet struct {
// ${dataHome}/${id}
baseDir *check.Absolute
// ${baseDir}/app
metaPath *check.Absolute
// ${baseDir}/files
homeDir *check.Absolute
// ${baseDir}/cache
cacheDir *check.Absolute
// ${baseDir}/cache/nix
nixPath *check.Absolute
}
func pathSetByApp(id string) *appPathSet {
pathSet := new(appPathSet)
pathSet.baseDir = dataHome.Append(id)
pathSet.metaPath = pathSet.baseDir.Append("app")
pathSet.homeDir = pathSet.baseDir.Append("files")
pathSet.cacheDir = pathSet.baseDir.Append("cache")
pathSet.nixPath = pathSet.cacheDir.Append("nix")
return pathSet
}
func appendGPUFilesystem(config *hst.Config) {
config.Container.Filesystem = append(config.Container.Filesystem, []hst.FilesystemConfigJSON{
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("dri"), Device: true, Optional: true}},
// mali
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("mali0"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("umplock"), Device: true, Optional: true}},
// nvidia
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidiactl"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-modeset"), Device: true, Optional: true}},
// nvidia OpenCL/CUDA
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia-uvm-tools"), Device: true, Optional: true}},
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia0"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia1"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia2"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia3"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia4"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia5"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia6"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia7"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia8"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia9"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia10"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia11"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia12"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia13"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia14"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia15"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia16"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia17"), Device: true, Optional: true}},
{FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia18"), Device: true, Optional: true}}, {FilesystemConfig: &hst.FSBind{Source: fhs.AbsDev.Append("nvidia19"), Device: true, Optional: true}},
}...)
}

View File

@@ -1,61 +0,0 @@
package main
import (
"context"
"encoding/json"
"errors"
"io"
"log"
"os"
"os/exec"
"hakurei.app/hst"
"hakurei.app/internal/info"
"hakurei.app/message"
)
var hakureiPathVal = info.MustHakureiPath().String()
func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
var (
cmd *exec.Cmd
st io.WriteCloser
)
if r, w, err := os.Pipe(); err != nil {
beforeFail()
log.Fatalf("cannot pipe: %v", err)
} else {
if msg.IsVerbose() {
cmd = exec.CommandContext(ctx, hakureiPathVal, "-v", "app", "3")
} else {
cmd = exec.CommandContext(ctx, hakureiPathVal, "app", "3")
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = []*os.File{r}
st = w
}
go func() {
if err := json.NewEncoder(st).Encode(config); err != nil {
beforeFail()
log.Fatalf("cannot send configuration: %v", err)
}
}()
if err := cmd.Start(); err != nil {
beforeFail()
log.Fatalf("cannot start hakurei: %v", err)
}
if err := cmd.Wait(); err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
beforeFail()
msg.BeforeExit()
os.Exit(exitError.ExitCode())
} else {
beforeFail()
log.Fatalf("cannot wait: %v", err)
}
}
}

View File

@@ -1,62 +0,0 @@
{ pkgs, ... }:
{
users.users = {
alice = {
isNormalUser = true;
description = "Alice Foobar";
password = "foobar";
uid = 1000;
};
};
home-manager.users.alice.home.stateVersion = "24.11";
# Automatically login on tty1 as a normal user:
services.getty.autologinUser = "alice";
environment = {
variables = {
SWAYSOCK = "/tmp/sway-ipc.sock";
WLR_RENDERER = "pixman";
};
};
# Automatically configure and start Sway when logging in on tty1:
programs.bash.loginShellInit = ''
if [ "$(tty)" = "/dev/tty1" ]; then
set -e
mkdir -p ~/.config/sway
(sed s/Mod4/Mod1/ /etc/sway/config &&
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' &&
echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config
sway --validate
systemd-cat --identifier=session sway && touch /tmp/sway-exit-ok
fi
'';
programs.sway.enable = true;
virtualisation = {
diskSize = 6 * 1024;
qemu.options = [
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
"-vga none -device virtio-gpu-pci"
# Increase zstd performance:
"-smp 8"
];
};
environment.hakurei = {
enable = true;
stateDir = "/var/lib/hakurei";
users.alice = 0;
extraHomeConfig = {
home.stateVersion = "23.05";
};
};
}

View File

@@ -1,34 +0,0 @@
{
testers,
callPackage,
system,
self,
}:
let
buildPackage = self.buildPackage.${system};
in
testers.nixosTest {
name = "hpkg";
nodes.machine = {
environment.etc = {
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };
};
imports = [
./configuration.nix
self.nixosModules.hakurei
self.inputs.home-manager.nixosModules.home-manager
];
};
# adapted from nixos sway integration tests
# testScriptWithTypes:49: error: Cannot call function of unknown type
# (machine.succeed if succeed else machine.execute)(
# ^
# Found 1 error in 1 file (checked 1 source file)
skipTypeCheck = true;
testScript = builtins.readFile ./test.py;
}

View File

@@ -1,48 +0,0 @@
{
lib,
buildPackage,
foot,
wayland-utils,
inconsolata,
}:
buildPackage {
name = "foot";
inherit (foot) version;
identity = 2;
id = "org.codeberg.dnkl.foot";
modules = [
{
home.packages = [
foot
# For wayland-info:
wayland-utils
];
}
];
nixosModules = [
{
# To help with OCR:
environment.etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
main = {
font = "inconsolata:size=14";
};
colors = rec {
foreground = "000000";
background = "ffffff";
regular2 = foreground;
};
};
fonts.packages = [ inconsolata ];
}
];
script = ''
exec foot "$@"
'';
}

View File

@@ -1,110 +0,0 @@
import json
import shlex
q = shlex.quote
NODE_GROUPS = ["nodes", "floating_nodes"]
def swaymsg(command: str = "", succeed=True, type="command"):
assert command != "" or type != "command", "Must specify command or type"
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
with machine.nested(
f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
):
ret = (machine.succeed if succeed else machine.execute)(
f"su - alice -c {shell}"
)
# execute also returns a status code, but disregard.
if not succeed:
_, ret = ret
if not succeed and not ret:
return None
parsed = json.loads(ret)
return parsed
def walk(tree):
yield tree
for group in NODE_GROUPS:
for node in tree.get(group, []):
yield from walk(node)
def wait_for_window(pattern):
def func(last_chance):
nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
if last_chance:
nodes = list(nodes)
machine.log(f"Last call! Current list of windows: {nodes}")
return any(pattern in name for name in nodes)
retry(func)
def collect_state_ui(name):
swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'")
machine.copy_from_vm(f"/tmp/{name}.ps", "")
swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'")
machine.copy_from_vm(f"/tmp/{name}.json", "")
machine.screenshot(name)
def check_state(name, enablements):
instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei --json ps"))
if len(instances) != 1:
raise Exception(f"unexpected state length {len(instances)}")
instance = instances[0]
if len(instance['container']['args']) != 1 or not (instance['container']['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (instance['container']['args'][0]):
raise Exception(f"unexpected args {instance['container']['args']}")
if instance['enablements'] != enablements:
raise Exception(f"unexpected enablements {instance['enablements']}")
start_all()
machine.wait_for_unit("multi-user.target")
# To check hakurei's version:
print(machine.succeed("sudo -u alice -i hakurei version"))
# Wait for Sway to complete startup:
machine.wait_for_file("/run/user/1000/wayland-1")
machine.wait_for_file("/tmp/sway-ipc.sock")
# Prepare hpkg directory:
machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000")
# Install hpkg app:
swaymsg("exec hpkg -v install /etc/foot.pkg && touch /tmp/hpkg-install-ok")
machine.wait_for_file("/tmp/hpkg-install-ok")
# Start app (foot) with Wayland enablement:
swaymsg("exec hpkg -v start org.codeberg.dnkl.foot")
wait_for_window("hakurei@machine-foot")
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
machine.wait_for_file("/tmp/hakurei.0/tmpdir/2/success-client")
collect_state_ui("app_wayland")
check_state("foot", {"wayland": True, "dbus": True, "pipewire": True})
# Verify acl on XDG_RUNTIME_DIR:
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002"))
machine.send_chars("exit\n")
machine.wait_until_fails("pgrep foot")
# Verify acl cleanup on XDG_RUNTIME_DIR:
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /tmp/hakurei.0/runtime | grep 10002")
# Exit Sway and verify process exit status 0:
swaymsg("exit", succeed=False)
machine.wait_for_file("/tmp/sway-exit-ok")
# Print hakurei share and rundir contents:
print(machine.succeed("find /tmp/hakurei.0 "
+ "-path '/tmp/hakurei.0/runtime/*/*' -prune -o "
+ "-path '/tmp/hakurei.0/tmpdir/*/*' -prune -o "
+ "-print"))
print(machine.fail("ls /run/user/1000/hakurei"))

View File

@@ -1,130 +0,0 @@
package main
import (
"context"
"os"
"strings"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/message"
)
func withNixDaemon(
ctx context.Context,
msg message.Msg,
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) {
flags := hst.FMultiarch | hst.FUserns // nix sandbox requires userns
if net {
flags |= hst.FHostNet
}
if dropShell {
flags |= hst.FTty
}
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
ID: app.ID,
ExtraPerms: []hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
},
Identity: app.Identity,
Container: &hst.ContainerConfig{
Hostname: formatHostname(app.Name) + "-" + action,
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: pathSet.cacheDir.Append("etc"), Special: true}},
{FilesystemConfig: &hst.FSBind{Source: pathSet.nixPath, Target: pathNix, Write: true}},
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}},
},
Username: "hakurei",
Shell: pathShell,
Home: pathDataData.Append(app.ID),
Path: pathShell,
Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
// start nix-daemon
"nix-daemon --store / & " +
// wait for socket to appear
"(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
// create directory so nix stops complaining
"mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
strings.Join(command, " && ") +
// terminate nix-daemon
" && pkill nix-daemon",
},
Flags: flags,
},
}), dropShell, beforeFail)
}
func withCacheDir(
ctx context.Context,
msg message.Msg,
action string, command []string, workDir *check.Absolute,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) {
flags := hst.FMultiarch
if dropShell {
flags |= hst.FTty
}
mustRunAppDropShell(ctx, msg, &hst.Config{
ID: app.ID,
ExtraPerms: []hst.ExtraPermConfig{
{Path: dataHome, Execute: true},
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
{Path: workDir, Execute: true},
},
Identity: app.Identity,
Container: &hst.ContainerConfig{
Hostname: formatHostname(app.Name) + "-" + action,
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSBind{Target: fhs.AbsEtc, Source: workDir.Append(fhs.Etc), Special: true}},
{FilesystemConfig: &hst.FSBind{Source: workDir.Append("nix"), Target: pathNix}},
{FilesystemConfig: &hst.FSLink{Target: pathCurrentSystem, Linkname: app.CurrentSystem.String()}},
{FilesystemConfig: &hst.FSLink{Target: pathBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSLink{Target: fhs.AbsUsrBin, Linkname: pathSwBin.String()}},
{FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsPrivateTmp.Append("bundle")}},
{FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID, "cache"), Source: pathSet.cacheDir, Write: true, Ensure: true}},
},
Username: "nixos",
Shell: pathShell,
Home: pathDataData.Append(app.ID, "cache"),
Path: pathShell,
Args: []string{bash, "-lc", strings.Join(command, " && ")},
Flags: flags,
},
}, dropShell, beforeFail)
}
func mustRunAppDropShell(ctx context.Context, msg message.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
if dropShell {
if config.Container != nil {
config.Container.Args = []string{bash, "-l"}
}
mustRunApp(ctx, msg, config, beforeFail)
beforeFail()
msg.BeforeExit()
os.Exit(0)
}
mustRunApp(ctx, msg, config, beforeFail)
}

View File

@@ -4,17 +4,26 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall" "syscall"
"time"
"unique" "unique"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/seccomp"
"hakurei.app/container/std"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
"hakurei.app/internal/rosa" "hakurei.app/internal/rosa"
"hakurei.app/message" "hakurei.app/message"
@@ -51,10 +60,16 @@ func main() {
flagCures int flagCures int
flagBase string flagBase string
flagTShift int flagTShift int
flagIdle bool
) )
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) { c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
msg.SwapVerbose(!flagQuiet) msg.SwapVerbose(!flagQuiet)
flagBase = os.ExpandEnv(flagBase)
if flagBase == "" {
flagBase = "cache"
}
var base *check.Absolute var base *check.Absolute
if flagBase, err = filepath.Abs(flagBase); err != nil { if flagBase, err = filepath.Abs(flagBase); err != nil {
return return
@@ -70,6 +85,11 @@ func main() {
cache.SetThreshold(1 << flagTShift) cache.SetThreshold(1 << flagTShift)
} }
} }
if flagIdle {
pkg.SchedPolicy = container.SCHED_IDLE
}
return return
}).Flag( }).Flag(
&flagQuiet, &flagQuiet,
@@ -81,12 +101,16 @@ func main() {
"Maximum number of dependencies to cure at any given time", "Maximum number of dependencies to cure at any given time",
).Flag( ).Flag(
&flagBase, &flagBase,
"d", command.StringFlag("cache"), "d", command.StringFlag("$MBF_CACHE_DIR"),
"Directory to store cured artifacts", "Directory to store cured artifacts",
).Flag( ).Flag(
&flagTShift, &flagTShift,
"tshift", command.IntFlag(-1), "tshift", command.IntFlag(-1),
"Dependency graph size exponent, to the power of 2", "Dependency graph size exponent, to the power of 2",
).Flag(
&flagIdle,
"sched-idle", command.BoolFlag(false),
"Set SCHED_IDLE scheduling policy",
) )
{ {
@@ -109,47 +133,294 @@ func main() {
) )
} }
c.NewCommand( {
"stage3", var (
"Check for toolchain 3-stage non-determinism", flagStatus bool
func(args []string) (err error) { flagReport string
_, _, _, stage1 := (rosa.Std - 2).NewLLVM() )
_, _, _, stage2 := (rosa.Std - 1).NewLLVM() c.NewCommand(
_, _, _, stage3 := rosa.Std.NewLLVM() "info",
var ( "Display out-of-band metadata of an artifact",
pathname *check.Absolute func(args []string) (err error) {
checksum [2]unique.Handle[pkg.Checksum] if len(args) == 0 {
) return errors.New("info requires at least 1 argument")
if pathname, _, err = cache.Cure(stage1); err != nil {
return err
}
log.Println("stage1:", pathname)
if pathname, checksum[0], err = cache.Cure(stage2); err != nil {
return err
}
log.Println("stage2:", pathname)
if pathname, checksum[1], err = cache.Cure(stage3); err != nil {
return err
}
log.Println("stage3:", pathname)
if checksum[0] != checksum[1] {
err = &pkg.ChecksumMismatchError{
Got: checksum[0].Value(),
Want: checksum[1].Value(),
} }
} else {
log.Println( var r *rosa.Report
"stage2 is identical to stage3", if flagReport != "" {
"("+pkg.Encode(checksum[0].Value())+")", if r, err = rosa.OpenReport(flagReport); err != nil {
) return err
}
defer func() {
if closeErr := r.Close(); err == nil {
err = closeErr
}
}()
defer r.HandleAccess(&err)()
}
for i, name := range args {
if p, ok := rosa.ResolveName(name); !ok {
return fmt.Errorf("unknown artifact %q", name)
} else {
var suffix string
if version := rosa.Std.Version(p); version != rosa.Unversioned {
suffix += "-" + version
}
fmt.Println("name : " + name + suffix)
meta := rosa.GetMetadata(p)
fmt.Println("description : " + meta.Description)
if meta.Website != "" {
fmt.Println("website : " +
strings.TrimSuffix(meta.Website, "/"))
}
const statusPrefix = "status : "
if flagStatus {
if r == nil {
var f io.ReadSeekCloser
f, err = cache.OpenStatus(rosa.Std.Load(p))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Println(
statusPrefix + "not yet cured",
)
} else {
return
}
} else {
fmt.Print(statusPrefix)
_, err = io.Copy(os.Stdout, f)
if err = errors.Join(err, f.Close()); err != nil {
return
}
}
} else {
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
if status == nil {
fmt.Println(
statusPrefix + "not in report",
)
} else {
fmt.Println("size :", n)
fmt.Print(statusPrefix)
if _, err = os.Stdout.Write(status); err != nil {
return
}
}
}
}
if i != len(args)-1 {
fmt.Println()
}
}
}
return nil
},
).
Flag(
&flagStatus,
"status", command.BoolFlag(false),
"Display cure status if available",
).
Flag(
&flagReport,
"report", command.StringFlag(""),
"Load cure status from this report file instead of cache",
)
}
c.NewCommand(
"report",
"Generate an artifact cure report for the current cache",
func(args []string) (err error) {
var w *os.File
switch len(args) {
case 0:
w = os.Stdout
case 1:
if w, err = os.OpenFile(
args[0],
os.O_CREATE|os.O_EXCL|syscall.O_WRONLY,
0400,
); err != nil {
return
}
defer func() {
closeErr := w.Close()
if err == nil {
err = closeErr
}
}()
default:
return errors.New("report requires 1 argument")
} }
return
if container.Isatty(int(w.Fd())) {
return errors.New("output appears to be a terminal")
}
return rosa.WriteReport(msg, w, cache)
}, },
) )
{
var flagJobs int
c.NewCommand("updates", command.UsageInternal, func([]string) error {
var (
errsMu sync.Mutex
errs []error
n atomic.Uint64
)
w := make(chan rosa.PArtifact)
var wg sync.WaitGroup
for range max(flagJobs, 1) {
wg.Go(func() {
for p := range w {
meta := rosa.GetMetadata(p)
if meta.ID == 0 {
continue
}
v, err := meta.GetVersions(ctx)
if err != nil {
errsMu.Lock()
errs = append(errs, err)
errsMu.Unlock()
continue
}
if current, latest :=
rosa.Std.Version(p),
meta.GetLatest(v); current != latest {
n.Add(1)
log.Printf("%s %s < %s", meta.Name, current, latest)
continue
}
msg.Verbosef("%s is up to date", meta.Name)
}
})
}
done:
for i := range rosa.PresetEnd {
select {
case w <- rosa.PArtifact(i):
break
case <-ctx.Done():
break done
}
}
close(w)
wg.Wait()
if v := n.Load(); v > 0 {
errs = append(errs, errors.New(strconv.Itoa(int(v))+
" package(s) are out of date"))
}
return errors.Join(errs...)
}).
Flag(
&flagJobs,
"j", command.IntFlag(32),
"Maximum number of simultaneous connections",
)
}
{
var (
flagGentoo string
flagChecksum string
flagStage0 bool
)
c.NewCommand(
"stage3",
"Check for toolchain 3-stage non-determinism",
func(args []string) (err error) {
t := rosa.Std
if flagGentoo != "" {
t -= 3 // magic number to discourage misuse
var checksum pkg.Checksum
if len(flagChecksum) != 0 {
if err = pkg.Decode(&checksum, flagChecksum); err != nil {
return
}
}
rosa.SetGentooStage3(flagGentoo, checksum)
}
_, _, _, stage1 := (t - 2).NewLLVM()
_, _, _, stage2 := (t - 1).NewLLVM()
_, _, _, stage3 := t.NewLLVM()
var (
pathname *check.Absolute
checksum [2]unique.Handle[pkg.Checksum]
)
if pathname, _, err = cache.Cure(stage1); err != nil {
return err
}
log.Println("stage1:", pathname)
if pathname, checksum[0], err = cache.Cure(stage2); err != nil {
return err
}
log.Println("stage2:", pathname)
if pathname, checksum[1], err = cache.Cure(stage3); err != nil {
return err
}
log.Println("stage3:", pathname)
if checksum[0] != checksum[1] {
err = &pkg.ChecksumMismatchError{
Got: checksum[0].Value(),
Want: checksum[1].Value(),
}
} else {
log.Println(
"stage2 is identical to stage3",
"("+pkg.Encode(checksum[0].Value())+")",
)
}
if flagStage0 {
if pathname, _, err = cache.Cure(
t.Load(rosa.Stage0),
); err != nil {
return err
}
log.Println(pathname)
}
return
},
).
Flag(
&flagGentoo,
"gentoo", command.StringFlag(""),
"Bootstrap from a Gentoo stage3 tarball",
).
Flag(
&flagChecksum,
"checksum", command.StringFlag(""),
"Checksum of Gentoo stage3 tarball",
).
Flag(
&flagStage0,
"stage0", command.BoolFlag(false),
"Create bootstrap stage0 tarball",
)
}
{ {
var ( var (
flagDump string flagDump string
@@ -162,7 +433,7 @@ func main() {
return errors.New("cure requires 1 argument") return errors.New("cure requires 1 argument")
} }
if p, ok := rosa.ResolveName(args[0]); !ok { if p, ok := rosa.ResolveName(args[0]); !ok {
return fmt.Errorf("unsupported artifact %q", args[0]) return fmt.Errorf("unknown artifact %q", args[0])
} else if flagDump == "" { } else if flagDump == "" {
pathname, _, err := cache.Cure(rosa.Std.Load(p)) pathname, _, err := cache.Cure(rosa.Std.Load(p))
if err == nil { if err == nil {
@@ -195,10 +466,157 @@ func main() {
) )
} }
{
var (
flagNet bool
flagSession bool
flagWithToolchain bool
)
c.NewCommand(
"shell",
"Interactive shell in the specified Rosa OS environment",
func(args []string) error {
root := make([]pkg.Artifact, 0, 6+len(args))
for _, arg := range args {
p, ok := rosa.ResolveName(arg)
if !ok {
return fmt.Errorf("unknown artifact %q", arg)
}
root = append(root, rosa.Std.Load(p))
}
if flagWithToolchain {
musl, compilerRT, runtimes, clang := rosa.Std.NewLLVM()
root = append(root, musl, compilerRT, runtimes, clang)
} else {
root = append(root, rosa.Std.Load(rosa.Musl))
}
root = append(root,
rosa.Std.Load(rosa.Mksh),
rosa.Std.Load(rosa.Toybox),
)
type cureRes struct {
pathname *check.Absolute
checksum unique.Handle[pkg.Checksum]
}
cured := make(map[pkg.Artifact]cureRes)
for _, a := range root {
pathname, checksum, err := cache.Cure(a)
if err != nil {
return err
}
cured[a] = cureRes{pathname, checksum}
}
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
*check.Absolute,
unique.Handle[pkg.Checksum],
) {
res := cured[a]
return res.pathname, res.checksum
}, func(i int, d pkg.Artifact) {
r := pkg.Encode(cache.Ident(d).Value())
if s, ok := d.(fmt.Stringer); ok {
if name := s.String(); name != "" {
r += "-" + name
}
}
msg.Verbosef("promoted layer %d as %s", i, r)
})
z := container.New(ctx, msg)
z.WaitDelay = 3 * time.Second
z.SeccompPresets = pkg.SeccompPresets
z.SeccompFlags |= seccomp.AllowMultiarch
z.ParentPerm = 0700
z.HostNet = flagNet
z.RetainSession = flagSession
z.Hostname = "localhost"
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
var tempdir *check.Absolute
if s, err := filepath.Abs(os.TempDir()); err != nil {
return err
} else if tempdir, err = check.NewAbs(s); err != nil {
return err
}
z.Dir = fhs.AbsRoot
z.Env = []string{
"SHELL=/system/bin/mksh",
"PATH=/system/bin",
"HOME=/",
}
z.Path = rosa.AbsSystem.Append("bin", "mksh")
z.Args = []string{"mksh"}
z.
OverlayEphemeral(fhs.AbsRoot, layers...).
Place(
fhs.AbsEtc.Append("hosts"),
[]byte("127.0.0.1 localhost\n"),
).
Place(
fhs.AbsEtc.Append("passwd"),
[]byte("media_rw:x:1023:1023::/:/system/bin/sh\n"+
"nobody:x:65534:65534::/proc/nonexistent:/system/bin/false\n"),
).
Place(
fhs.AbsEtc.Append("group"),
[]byte("media_rw:x:1023:\nnobody:x:65534:\n"),
).
Bind(tempdir, fhs.AbsTmp, std.BindWritable).
Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
if err := z.Start(); err != nil {
return err
}
if err := z.Serve(); err != nil {
return err
}
return z.Wait()
},
).
Flag(
&flagNet,
"net", command.BoolFlag(false),
"Share host net namespace",
).
Flag(
&flagSession,
"session", command.BoolFlag(false),
"Retain session",
).
Flag(
&flagWithToolchain,
"with-toolchain", command.BoolFlag(false),
"Include the stage3 LLVM toolchain",
)
}
c.Command(
"help",
"Show this help message",
func([]string) error { c.PrintHelp(); return nil },
)
c.MustParse(os.Args[1:], func(err error) { c.MustParse(os.Args[1:], func(err error) {
if cache != nil { if cache != nil {
cache.Close() cache.Close()
} }
log.Fatal(err) if w, ok := err.(interface{ Unwrap() []error }); !ok {
log.Fatal(err)
} else {
errs := w.Unwrap()
for i, e := range errs {
if i == len(errs)-1 {
log.Fatal(e)
}
log.Println(e)
}
}
}) })
} }

View File

@@ -33,6 +33,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/std" "hakurei.app/container/std"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/helper/proc" "hakurei.app/internal/helper/proc"
@@ -441,12 +442,7 @@ func _main(s ...string) (exitCode int) {
// keep fuse_parse_cmdline happy in the container // keep fuse_parse_cmdline happy in the container
z.Tmpfs(check.MustAbs(container.Nonexistent), 1<<10, 0755) z.Tmpfs(check.MustAbs(container.Nonexistent), 1<<10, 0755)
if a, err := check.NewAbs(container.MustExecutable(msg)); err != nil { z.Path = fhs.AbsProcSelfExe
log.Println(err)
return 5
} else {
z.Path = a
}
z.Args = s z.Args = s
z.ForwardCancel = true z.ForwardCancel = true
z.SeccompPresets |= std.PresetStrict z.SeccompPresets |= std.PresetStrict

View File

@@ -10,8 +10,7 @@ import (
func init() { gob.Register(new(AutoEtcOp)) } func init() { gob.Register(new(AutoEtcOp)) }
// Etc appends an [Op] that expands host /etc into a toplevel symlink mirror with /etc semantics. // Etc is a helper for appending [AutoEtcOp] to [Ops].
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops { func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
e := &AutoEtcOp{prefix} e := &AutoEtcOp{prefix}
f.Mkdir(fhs.AbsEtc, 0755) f.Mkdir(fhs.AbsEtc, 0755)
@@ -20,6 +19,9 @@ func (f *Ops) Etc(host *check.Absolute, prefix string) *Ops {
return f return f
} }
// AutoEtcOp expands host /etc into a toplevel symlink mirror with /etc semantics.
//
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
type AutoEtcOp struct{ Prefix string } type AutoEtcOp struct{ Prefix string }
func (e *AutoEtcOp) Valid() bool { return e != nil } func (e *AutoEtcOp) Valid() bool { return e != nil }

View File

@@ -11,13 +11,15 @@ import (
func init() { gob.Register(new(AutoRootOp)) } func init() { gob.Register(new(AutoRootOp)) }
// Root appends an [Op] that expands a directory into a toplevel bind mount mirror on container root. // Root is a helper for appending [AutoRootOp] to [Ops].
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
func (f *Ops) Root(host *check.Absolute, flags int) *Ops { func (f *Ops) Root(host *check.Absolute, flags int) *Ops {
*f = append(*f, &AutoRootOp{host, flags, nil}) *f = append(*f, &AutoRootOp{host, flags, nil})
return f return f
} }
// AutoRootOp expands a directory into a toplevel bind mount mirror on container root.
//
// This is not a generic setup op. It is implemented here to reduce ipc overhead.
type AutoRootOp struct { type AutoRootOp struct {
Host *check.Absolute Host *check.Absolute
// passed through to bindMount // passed through to bindMount

View File

@@ -50,10 +50,16 @@ func capset(hdrp *capHeader, datap *[2]capData) error {
} }
// capBoundingSetDrop drops a capability from the calling thread's capability bounding set. // capBoundingSetDrop drops a capability from the calling thread's capability bounding set.
func capBoundingSetDrop(cap uintptr) error { return Prctl(syscall.PR_CAPBSET_DROP, cap, 0) } func capBoundingSetDrop(cap uintptr) error {
return Prctl(syscall.PR_CAPBSET_DROP, cap, 0)
}
// capAmbientClearAll clears the ambient capability set of the calling thread. // capAmbientClearAll clears the ambient capability set of the calling thread.
func capAmbientClearAll() error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0) } func capAmbientClearAll() error {
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0)
}
// capAmbientRaise adds to the ambient capability set of the calling thread. // capAmbientRaise adds to the ambient capability set of the calling thread.
func capAmbientRaise(cap uintptr) error { return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap) } func capAmbientRaise(cap uintptr) error {
return Prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap)
}

View File

@@ -11,7 +11,8 @@ const (
SpecialOverlayPath = ":" SpecialOverlayPath = ":"
) )
// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call. // EscapeOverlayDataSegment escapes a string for formatting into the data
// argument of an overlay mount system call.
func EscapeOverlayDataSegment(s string) string { func EscapeOverlayDataSegment(s string) string {
if s == "" { if s == "" {
return "" return ""

View File

@@ -1,4 +1,5 @@
// Package container implements unprivileged Linux containers with built-in support for syscall filtering. // Package container implements unprivileged Linux containers with built-in
// support for syscall filtering.
package container package container
import ( import (
@@ -37,24 +38,30 @@ type (
Container struct { Container struct {
// Whether the container init should stay alive after its parent terminates. // Whether the container init should stay alive after its parent terminates.
AllowOrphan bool AllowOrphan bool
// Scheduling policy to set via sched_setscheduler(2). The zero value
// skips this call. Supported policies are [SCHED_BATCH], [SCHED_IDLE].
SchedPolicy int
// Cgroup fd, nil to disable. // Cgroup fd, nil to disable.
Cgroup *int Cgroup *int
// ExtraFiles passed through to initial process in the container, // ExtraFiles passed through to initial process in the container, with
// with behaviour identical to its [exec.Cmd] counterpart. // behaviour identical to its [exec.Cmd] counterpart.
ExtraFiles []*os.File ExtraFiles []*os.File
// param pipe for shim and init // Write end of a pipe connected to the init to deliver [Params].
setup *os.File setup *os.File
// cancels cmd // Cancels the context passed to the underlying cmd.
cancel context.CancelFunc cancel context.CancelFunc
// closed after Wait returns // Closed after Wait returns. Keeps the spawning thread alive.
wait chan struct{} wait chan struct{}
Stdin io.Reader Stdin io.Reader
Stdout io.Writer Stdout io.Writer
Stderr io.Writer Stderr io.Writer
Cancel func(cmd *exec.Cmd) error // Custom cancellation behaviour for the underlying [exec.Cmd]. Must
// deliver [CancelSignal] before returning.
Cancel func(cmd *exec.Cmd) error
// Copied to the underlying [exec.Cmd].
WaitDelay time.Duration WaitDelay time.Duration
cmd *exec.Cmd cmd *exec.Cmd
@@ -283,7 +290,11 @@ func (p *Container) Start() error {
// place setup pipe before user supplied extra files, this is later restored by init // place setup pipe before user supplied extra files, this is later restored by init
if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil { if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil {
return &StartError{true, "set up params stream", err, false, false} return &StartError{
Fatal: true,
Step: "set up params stream",
Err: err,
}
} else { } else {
p.setup = f p.setup = f
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)} p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
@@ -295,10 +306,16 @@ func (p *Container) Start() error {
runtime.LockOSThread() runtime.LockOSThread()
p.wait = make(chan struct{}) p.wait = make(chan struct{})
done <- func() error { // setup depending on per-thread state must happen here // setup depending on per-thread state must happen here
// PR_SET_NO_NEW_PRIVS: depends on per-thread state but acts on all processes created from that thread done <- func() error {
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
// created from the calling thread
if err := SetNoNewPrivs(); err != nil { if err := SetNoNewPrivs(); err != nil {
return &StartError{true, "prctl(PR_SET_NO_NEW_PRIVS)", err, false, false} return &StartError{
Fatal: true,
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
Err: err,
}
} }
// landlock: depends on per-thread state but acts on a process group // landlock: depends on per-thread state but acts on a process group
@@ -310,28 +327,40 @@ func (p *Container) Start() error {
if abi, err := LandlockGetABI(); err != nil { if abi, err := LandlockGetABI(); err != nil {
if p.HostAbstract { if p.HostAbstract {
// landlock can be skipped here as it restricts access to resources // landlock can be skipped here as it restricts access
// already covered by namespaces (pid) // to resources already covered by namespaces (pid)
goto landlockOut goto landlockOut
} }
return &StartError{false, "get landlock ABI", err, false, false} return &StartError{Step: "get landlock ABI", Err: err}
} else if abi < 6 { } else if abi < 6 {
if p.HostAbstract { if p.HostAbstract {
// see above comment // see above comment
goto landlockOut goto landlockOut
} }
return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false} return &StartError{
Step: "kernel too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET",
Err: ENOSYS,
Origin: true,
}
} else { } else {
p.msg.Verbosef("landlock abi version %d", abi) p.msg.Verbosef("landlock abi version %d", abi)
} }
if rulesetFd, err := rulesetAttr.Create(0); err != nil { if rulesetFd, err := rulesetAttr.Create(0); err != nil {
return &StartError{true, "create landlock ruleset", err, false, false} return &StartError{
Fatal: true,
Step: "create landlock ruleset",
Err: err,
}
} else { } else {
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil { if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
_ = Close(rulesetFd) _ = Close(rulesetFd)
return &StartError{true, "enforce landlock ruleset", err, false, false} return &StartError{
Fatal: true,
Step: "enforce landlock ruleset",
Err: err,
}
} }
if err = Close(rulesetFd); err != nil { if err = Close(rulesetFd); err != nil {
p.msg.Verbosef("cannot close landlock ruleset: %v", err) p.msg.Verbosef("cannot close landlock ruleset: %v", err)
@@ -342,9 +371,30 @@ func (p *Container) Start() error {
landlockOut: landlockOut:
} }
// sched_setscheduler: thread-directed but acts on all processes
// created from the calling thread
if p.SchedPolicy > 0 {
p.msg.Verbosef("setting scheduling policy %d", p.SchedPolicy)
if err := schedSetscheduler(
0, // calling thread
p.SchedPolicy,
&schedParam{0},
); err != nil {
return &StartError{
Fatal: true,
Step: "enforce landlock ruleset",
Err: err,
}
}
}
p.msg.Verbose("starting container init") p.msg.Verbose("starting container init")
if err := p.cmd.Start(); err != nil { if err := p.cmd.Start(); err != nil {
return &StartError{false, "start container init", err, false, true} return &StartError{
Step: "start container init",
Err: err,
Passthrough: true,
}
} }
return nil return nil
}() }()
@@ -356,6 +406,7 @@ func (p *Container) Start() error {
} }
// Serve serves [Container.Params] to the container init. // Serve serves [Container.Params] to the container init.
//
// Serve must only be called once. // Serve must only be called once.
func (p *Container) Serve() error { func (p *Container) Serve() error {
if p.setup == nil { if p.setup == nil {
@@ -365,12 +416,21 @@ func (p *Container) Serve() error {
setup := p.setup setup := p.setup
p.setup = nil p.setup = nil
if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil { if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil {
return &StartError{true, "set init pipe deadline", err, false, true} return &StartError{
Fatal: true,
Step: "set init pipe deadline",
Err: err,
Passthrough: true,
}
} }
if p.Path == nil { if p.Path == nil {
p.cancel() p.cancel()
return &StartError{false, "invalid executable pathname", EINVAL, true, false} return &StartError{
Step: "invalid executable pathname",
Err: EINVAL,
Origin: true,
}
} }
// do not transmit nil // do not transmit nil
@@ -395,7 +455,8 @@ func (p *Container) Serve() error {
return err return err
} }
// Wait waits for the container init process to exit and releases any resources associated with the [Container]. // Wait blocks until the container init process to exit and releases any
// resources associated with the [Container].
func (p *Container) Wait() error { func (p *Container) Wait() error {
if p.cmd == nil || p.cmd.Process == nil { if p.cmd == nil || p.cmd.Process == nil {
return EINVAL return EINVAL
@@ -440,11 +501,13 @@ func (p *Container) StderrPipe() (r io.ReadCloser, err error) {
} }
func (p *Container) String() string { func (p *Container) String() string {
return fmt.Sprintf("argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x", return fmt.Sprintf(
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets)) "argv: %q, filter: %v, rules: %d, flags: %#x, presets: %#x",
p.Args, !p.SeccompDisable, len(p.SeccompRules), int(p.SeccompFlags), int(p.SeccompPresets),
)
} }
// ProcessState returns the address to os.ProcessState held by the underlying [exec.Cmd]. // ProcessState returns the address of os.ProcessState held by the underlying [exec.Cmd].
func (p *Container) ProcessState() *os.ProcessState { func (p *Container) ProcessState() *os.ProcessState {
if p.cmd == nil { if p.cmd == nil {
return nil return nil
@@ -452,7 +515,8 @@ func (p *Container) ProcessState() *os.ProcessState {
return p.cmd.ProcessState return p.cmd.ProcessState
} }
// New returns the address to a new instance of [Container] that requires further initialisation before use. // New returns the address to a new instance of [Container]. This value requires
// further initialisation before use.
func New(ctx context.Context, msg message.Msg) *Container { func New(ctx context.Context, msg message.Msg) *Container {
if msg == nil { if msg == nil {
msg = message.New(nil) msg = message.New(nil)
@@ -461,12 +525,18 @@ func New(ctx context.Context, msg message.Msg) *Container {
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}} p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
c, cancel := context.WithCancel(ctx) c, cancel := context.WithCancel(ctx)
p.cancel = cancel p.cancel = cancel
p.cmd = exec.CommandContext(c, MustExecutable(msg)) p.cmd = exec.CommandContext(c, fhs.ProcSelfExe)
return p return p
} }
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields. // NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
func NewCommand(ctx context.Context, msg message.Msg, pathname *check.Absolute, name string, args ...string) *Container { func NewCommand(
ctx context.Context,
msg message.Msg,
pathname *check.Absolute,
name string,
args ...string,
) *Container {
z := New(ctx, msg) z := New(ctx, msg)
z.Path = pathname z.Path = pathname
z.Args = append([]string{name}, args...) z.Args = append([]string{name}, args...)

View File

@@ -773,14 +773,13 @@ func TestMain(m *testing.M) {
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) { func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
msg := message.New(nil) msg := message.New(nil)
msg.SwapVerbose(testing.Verbose()) msg.SwapVerbose(testing.Verbose())
executable := check.MustAbs(container.MustExecutable(msg))
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...) c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
c.Env = append(c.Env, envDoCheck+"=1") c.Env = append(c.Env, envDoCheck+"=1")
c.Bind(executable, absHelperInnerPath, 0) c.Bind(fhs.AbsProcSelfExe, absHelperInnerPath, 0)
// in case test has cgo enabled // in case test has cgo enabled
if entries, err := ldd.Resolve(ctx, msg, executable); err != nil { if entries, err := ldd.Resolve(ctx, msg, nil); err != nil {
log.Fatalf("ldd: %v", err) log.Fatalf("ldd: %v", err)
} else { } else {
*libPaths = ldd.Path(entries) *libPaths = ldd.Path(entries)

View File

@@ -21,7 +21,8 @@ type osFile interface {
fs.File fs.File
} }
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour. // syscallDispatcher provides methods that make state-dependent system calls as
// part of their behaviour.
type syscallDispatcher interface { type syscallDispatcher interface {
// new starts a goroutine with a new instance of syscallDispatcher. // new starts a goroutine with a new instance of syscallDispatcher.
// A syscallDispatcher must never be used in any goroutine other than the one owning it, // A syscallDispatcher must never be used in any goroutine other than the one owning it,

View File

@@ -238,8 +238,11 @@ func sliceAddr[S any](s []S) *[]S { return &s }
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile { func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr} f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
// check happens in Close, and cleanup is not guaranteed to run, so relying on it for sloppy implementations will cause sporadic test results // check happens in Close, and cleanup is not guaranteed to run, so relying
f.cleanup = runtime.AddCleanup(f, func(name string) { f.t.Fatalf("checkedOsFile %s became unreachable without a call to Close", name) }, f.name) // on it for sloppy implementations will cause sporadic test results
f.cleanup = runtime.AddCleanup(f, func(name string) {
panic("checkedOsFile " + name + " became unreachable without a call to Close")
}, name)
return f return f
} }

View File

@@ -43,7 +43,8 @@ func messageFromError(err error) (m string, ok bool) {
} }
// messagePrefix checks and prefixes the error message of a non-pointer error. // messagePrefix checks and prefixes the error message of a non-pointer error.
// While this is usable for pointer errors, such use should be avoided as nil check is omitted. // While this is usable for pointer errors, such use should be avoided as nil
// check is omitted.
func messagePrefix[T error](prefix string, err error) (string, bool) { func messagePrefix[T error](prefix string, err error) (string, bool) {
var targetError T var targetError T
if errors.As(err, &targetError) { if errors.As(err, &targetError) {

View File

@@ -28,6 +28,9 @@ func copyExecutable(msg message.Msg) {
} }
} }
// MustExecutable calls [os.Executable] and terminates the process on error.
//
// Deprecated: This is no longer used and will be removed in 0.4.
func MustExecutable(msg message.Msg) string { func MustExecutable(msg message.Msg) string {
executableOnce.Do(func() { copyExecutable(msg) }) executableOnce.Do(func() { copyExecutable(msg) })
return executable return executable

View File

@@ -42,6 +42,8 @@ var (
AbsDevShm = unsafeAbs(DevShm) AbsDevShm = unsafeAbs(DevShm)
// AbsProc is [Proc] as [check.Absolute]. // AbsProc is [Proc] as [check.Absolute].
AbsProc = unsafeAbs(Proc) AbsProc = unsafeAbs(Proc)
// AbsProcSelfExe is [ProcSelfExe] as [check.Absolute].
AbsProcSelfExe = unsafeAbs(ProcSelfExe)
// AbsSys is [Sys] as [check.Absolute]. // AbsSys is [Sys] as [check.Absolute].
AbsSys = unsafeAbs(Sys) AbsSys = unsafeAbs(Sys)
) )

View File

@@ -9,7 +9,8 @@ const (
// Tmp points to the place for small temporary files. // Tmp points to the place for small temporary files.
Tmp = "/tmp/" Tmp = "/tmp/"
// Run points to a "tmpfs" file system for system packages to place runtime data, socket files, and similar. // Run points to a "tmpfs" file system for system packages to place runtime
// data, socket files, and similar.
Run = "/run/" Run = "/run/"
// RunUser points to a directory containing per-user runtime directories, // RunUser points to a directory containing per-user runtime directories,
// each usually individually mounted "tmpfs" instances. // each usually individually mounted "tmpfs" instances.
@@ -17,10 +18,12 @@ const (
// Usr points to vendor-supplied operating system resources. // Usr points to vendor-supplied operating system resources.
Usr = "/usr/" Usr = "/usr/"
// UsrBin points to binaries and executables for user commands that shall appear in the $PATH search path. // UsrBin points to binaries and executables for user commands that shall
// appear in the $PATH search path.
UsrBin = Usr + "bin/" UsrBin = Usr + "bin/"
// Var points to persistent, variable system data. Writable during normal system operation. // Var points to persistent, variable system data. Writable during normal
// system operation.
Var = "/var/" Var = "/var/"
// VarLib points to persistent system data. // VarLib points to persistent system data.
VarLib = Var + "lib/" VarLib = Var + "lib/"
@@ -29,12 +32,20 @@ const (
// Dev points to the root directory for device nodes. // Dev points to the root directory for device nodes.
Dev = "/dev/" Dev = "/dev/"
// DevShm is the place for POSIX shared memory segments, as created via shm_open(3). // DevShm is the place for POSIX shared memory segments, as created via
// shm_open(3).
DevShm = "/dev/shm/" DevShm = "/dev/shm/"
// Proc points to a virtual kernel file system exposing the process list and other functionality. // Proc points to a virtual kernel file system exposing the process list and
// other functionality.
Proc = "/proc/" Proc = "/proc/"
// ProcSys points to a hierarchy below /proc/ that exposes a number of kernel tunables. // ProcSys points to a hierarchy below /proc/ that exposes a number of
// kernel tunables.
ProcSys = Proc + "sys/" ProcSys = Proc + "sys/"
// Sys points to a virtual kernel file system exposing discovered devices and other functionality. // ProcSelf resolves to the process's own /proc/pid directory.
ProcSelf = Proc + "self/"
// ProcSelfExe is a symbolic link to program pathname.
ProcSelfExe = ProcSelf + "exe"
// Sys points to a virtual kernel file system exposing discovered devices
// and other functionality.
Sys = "/sys/" Sys = "/sys/"
) )

View File

@@ -33,12 +33,12 @@ const (
- This path is only accessible by init and root: - This path is only accessible by init and root:
The container init sets SUID_DUMP_DISABLE and terminates if that fails. The container init sets SUID_DUMP_DISABLE and terminates if that fails.
It should be noted that none of this should become relevant at any point since the resulting It should be noted that none of this should become relevant at any point
intermediate root tmpfs should be effectively anonymous. */ since the resulting intermediate root tmpfs should be effectively anonymous. */
intermediateHostPath = fhs.Proc + "self/fd" intermediateHostPath = fhs.Proc + "self/fd"
// setupEnv is the name of the environment variable holding the string representation of // setupEnv is the name of the environment variable holding the string
// the read end file descriptor of the setup params pipe. // representation of the read end file descriptor of the setup params pipe.
setupEnv = "HAKUREI_SETUP" setupEnv = "HAKUREI_SETUP"
// exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno. // exitUnexpectedWait4 is the exit code if wait4 returns an unexpected errno.
@@ -59,7 +59,8 @@ type (
// late is called right before starting the initial process. // late is called right before starting the initial process.
late(state *setupState, k syscallDispatcher) error late(state *setupState, k syscallDispatcher) error
// prefix returns a log message prefix, and whether this Op prints no identifying message on its own. // prefix returns a log message prefix, and whether this Op prints no
// identifying message on its own.
prefix() (string, bool) prefix() (string, bool)
Is(op Op) bool Is(op Op) bool
@@ -71,9 +72,11 @@ type (
setupState struct { setupState struct {
nonrepeatable uintptr nonrepeatable uintptr
// Whether early reaping has concluded. Must only be accessed in the wait4 loop. // Whether early reaping has concluded. Must only be accessed in the
// wait4 loop.
processConcluded bool processConcluded bool
// Process to syscall.WaitStatus populated in the wait4 loop. Freed after early reaping concludes. // Process to syscall.WaitStatus populated in the wait4 loop. Freed
// after early reaping concludes.
process map[int]WaitStatus process map[int]WaitStatus
// Synchronises access to process. // Synchronises access to process.
processMu sync.RWMutex processMu sync.RWMutex
@@ -216,9 +219,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
defer cancel() defer cancel()
/* early is called right before pivot_root into intermediate root; /* early is called right before pivot_root into intermediate root;
this step is mostly for gathering information that would otherwise be difficult to obtain this step is mostly for gathering information that would otherwise be
via library functions after pivot_root, and implementations are expected to avoid changing difficult to obtain via library functions after pivot_root, and
the state of the mount namespace */ implementations are expected to avoid changing the state of the mount
namespace */
for i, op := range *params.Ops { for i, op := range *params.Ops {
if op == nil || !op.Valid() { if op == nil || !op.Valid() {
k.fatalf(msg, "invalid op at index %d", i) k.fatalf(msg, "invalid op at index %d", i)
@@ -258,10 +262,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
k.fatalf(msg, "cannot enter intermediate root: %v", err) k.fatalf(msg, "cannot enter intermediate root: %v", err)
} }
/* apply is called right after pivot_root and entering the new root; /* apply is called right after pivot_root and entering the new root. This
this step sets up the container filesystem, and implementations are expected to keep the host root step sets up the container filesystem, and implementations are expected to
and sysroot mount points intact but otherwise can do whatever they need to; keep the host root and sysroot mount points intact but otherwise can do
chdir is allowed but discouraged */ whatever they need to. Calling chdir is allowed but discouraged. */
for i, op := range *params.Ops { for i, op := range *params.Ops {
// ops already checked during early setup // ops already checked during early setup
if prefix, ok := op.prefix(); ok { if prefix, ok := op.prefix(); ok {

View File

@@ -12,14 +12,16 @@ import (
func init() { gob.Register(new(BindMountOp)) } func init() { gob.Register(new(BindMountOp)) }
// Bind appends an [Op] that bind mounts host path [BindMountOp.Source] on container path [BindMountOp.Target]. // Bind is a helper for appending [BindMountOp] to [Ops].
func (f *Ops) Bind(source, target *check.Absolute, flags int) *Ops { func (f *Ops) Bind(source, target *check.Absolute, flags int) *Ops {
*f = append(*f, &BindMountOp{nil, source, target, flags}) *f = append(*f, &BindMountOp{nil, source, target, flags})
return f return f
} }
// BindMountOp bind mounts host path Source on container path Target. // BindMountOp creates a bind mount from host path Source to container path Target.
// Note that Flags uses bits declared in this package and should not be set with constants in [syscall]. //
// Note that Flags uses bits declared in the [std] package and should not be set
// with constants in [syscall].
type BindMountOp struct { type BindMountOp struct {
sourceFinal, Source, Target *check.Absolute sourceFinal, Source, Target *check.Absolute

View File

@@ -24,8 +24,7 @@ const (
daemonTimeout = 5 * time.Second daemonTimeout = 5 * time.Second
) )
// Daemon appends an [Op] that starts a daemon in the container and blocks until // Daemon is a helper for appending [DaemonOp] to [Ops].
// [DaemonOp.Target] appears.
func (f *Ops) Daemon(target, path *check.Absolute, args ...string) *Ops { func (f *Ops) Daemon(target, path *check.Absolute, args ...string) *Ops {
*f = append(*f, &DaemonOp{target, path, args}) *f = append(*f, &DaemonOp{target, path, args})
return f return f

View File

@@ -19,7 +19,9 @@ func (f *Ops) Dev(target *check.Absolute, mqueue bool) *Ops {
} }
// DevWritable appends an [Op] that mounts a writable subset of host /dev. // DevWritable appends an [Op] that mounts a writable subset of host /dev.
// There is usually no good reason to write to /dev, so this should always be followed by a [RemountOp]. //
// There is usually no good reason to write to /dev, so this should always be
// followed by a [RemountOp].
func (f *Ops) DevWritable(target *check.Absolute, mqueue bool) *Ops { func (f *Ops) DevWritable(target *check.Absolute, mqueue bool) *Ops {
*f = append(*f, &MountDevOp{target, mqueue, true}) *f = append(*f, &MountDevOp{target, mqueue, true})
return f return f

View File

@@ -10,7 +10,7 @@ import (
func init() { gob.Register(new(MkdirOp)) } func init() { gob.Register(new(MkdirOp)) }
// Mkdir appends an [Op] that creates a directory in the container filesystem. // Mkdir is a helper for appending [MkdirOp] to [Ops].
func (f *Ops) Mkdir(name *check.Absolute, perm os.FileMode) *Ops { func (f *Ops) Mkdir(name *check.Absolute, perm os.FileMode) *Ops {
*f = append(*f, &MkdirOp{name, perm}) *f = append(*f, &MkdirOp{name, perm})
return f return f

View File

@@ -54,8 +54,11 @@ func (e *OverlayArgumentError) Error() string {
} }
} }
// Overlay appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target]. // Overlay is a helper for appending [MountOverlayOp] to [Ops].
func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Absolute) *Ops { func (f *Ops) Overlay(
target, state, work *check.Absolute,
layers ...*check.Absolute,
) *Ops {
*f = append(*f, &MountOverlayOp{ *f = append(*f, &MountOverlayOp{
Target: target, Target: target,
Lower: layers, Lower: layers,
@@ -65,13 +68,12 @@ func (f *Ops) Overlay(target, state, work *check.Absolute, layers ...*check.Abso
return f return f
} }
// OverlayEphemeral appends an [Op] that mounts the overlay pseudo filesystem on [MountOverlayOp.Target] // OverlayEphemeral appends a [MountOverlayOp] with an ephemeral upperdir and workdir.
// with an ephemeral upperdir and workdir.
func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops { func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops {
return f.Overlay(target, fhs.AbsRoot, nil, layers...) return f.Overlay(target, fhs.AbsRoot, nil, layers...)
} }
// OverlayReadonly appends an [Op] that mounts the overlay pseudo filesystem readonly on [MountOverlayOp.Target] // OverlayReadonly appends a readonly [MountOverlayOp].
func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops { func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops {
return f.Overlay(target, nil, nil, layers...) return f.Overlay(target, nil, nil, layers...)
} }
@@ -82,25 +84,34 @@ type MountOverlayOp struct {
// Any filesystem, does not need to be on a writable filesystem. // Any filesystem, does not need to be on a writable filesystem.
Lower []*check.Absolute Lower []*check.Absolute
// formatted for [OptionOverlayLowerdir], resolved, prefixed and escaped during early // Formatted for [OptionOverlayLowerdir].
//
// Resolved, prefixed and escaped during early.
lower []string lower []string
// The upperdir is normally on a writable filesystem. // The upperdir is normally on a writable filesystem.
// //
// If Work is nil and Upper holds the special value [fhs.AbsRoot], // If Work is nil and Upper holds the special value [fhs.AbsRoot], an
// an ephemeral upperdir and workdir will be set up. // ephemeral upperdir and workdir will be set up.
// //
// If both Work and Upper are nil, upperdir and workdir is omitted and the overlay is mounted readonly. // If both Work and Upper are nil, upperdir and workdir is omitted and the
// overlay is mounted readonly.
Upper *check.Absolute Upper *check.Absolute
// formatted for [OptionOverlayUpperdir], resolved, prefixed and escaped during early // Formatted for [OptionOverlayUpperdir].
//
// Resolved, prefixed and escaped during early.
upper string upper string
// The workdir needs to be an empty directory on the same filesystem as upperdir. // The workdir needs to be an empty directory on the same filesystem as upperdir.
Work *check.Absolute Work *check.Absolute
// formatted for [OptionOverlayWorkdir], resolved, prefixed and escaped during early // Formatted for [OptionOverlayWorkdir].
//
// Resolved, prefixed and escaped during early.
work string work string
ephemeral bool ephemeral bool
// used internally for mounting to the intermediate root // Used internally for mounting to the intermediate root.
noPrefix bool noPrefix bool
} }

View File

@@ -16,7 +16,7 @@ const (
func init() { gob.Register(new(TmpfileOp)) } func init() { gob.Register(new(TmpfileOp)) }
// Place appends an [Op] that places a file in container path [TmpfileOp.Path] containing [TmpfileOp.Data]. // Place is a helper for appending [TmpfileOp] to [Ops].
func (f *Ops) Place(name *check.Absolute, data []byte) *Ops { func (f *Ops) Place(name *check.Absolute, data []byte) *Ops {
*f = append(*f, &TmpfileOp{name, data}) *f = append(*f, &TmpfileOp{name, data})
return f return f

View File

@@ -21,7 +21,7 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), stub.UniqueError(5)), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, (*checkedOsFile)(nil), stub.UniqueError(5)),
}, stub.UniqueError(5)}, }, stub.UniqueError(5)},
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{ {"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
@@ -35,14 +35,14 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, stub.UniqueError(3)), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.Close", sampleDataString, stub.UniqueError(3)), nil),
}, stub.UniqueError(3)}, }, stub.UniqueError(3)},
{"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{ {"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.ensureFile", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(2)), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(2)),
}, stub.UniqueError(2)}, }, stub.UniqueError(2)},
@@ -50,29 +50,29 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.bindMount", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)), call("bindMount", stub.ExpectArgs{"tmp.bindMount", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, stub.UniqueError(1)),
}, stub.UniqueError(1)}, }, stub.UniqueError(1)},
{"remove", &Params{ParentPerm: 0700}, &TmpfileOp{ {"remove", &Params{ParentPerm: 0700}, &TmpfileOp{
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.remove", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil), call("bindMount", stub.ExpectArgs{"tmp.remove", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, stub.UniqueError(0)), call("remove", stub.ExpectArgs{"tmp.remove"}, nil, stub.UniqueError(0)),
}, stub.UniqueError(0)}, }, stub.UniqueError(0)},
{"success", &Params{ParentPerm: 0700}, &TmpfileOp{ {"success", &Params{ParentPerm: 0700}, &TmpfileOp{
Path: samplePath, Path: samplePath,
Data: sampleData, Data: sampleData,
}, nil, nil, []stub.Call{ }, nil, nil, []stub.Call{
call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil), call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.success", sampleDataString, nil), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("bindMount", stub.ExpectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil), call("bindMount", stub.ExpectArgs{"tmp.success", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil),
call("remove", stub.ExpectArgs{"tmp.32768"}, nil, nil), call("remove", stub.ExpectArgs{"tmp.success"}, nil, nil),
}, nil}, }, nil},
}) })

View File

@@ -10,7 +10,7 @@ import (
func init() { gob.Register(new(MountProcOp)) } func init() { gob.Register(new(MountProcOp)) }
// Proc appends an [Op] that mounts a private instance of proc. // Proc is a helper for appending [MountProcOp] to [Ops].
func (f *Ops) Proc(target *check.Absolute) *Ops { func (f *Ops) Proc(target *check.Absolute) *Ops {
*f = append(*f, &MountProcOp{target}) *f = append(*f, &MountProcOp{target})
return f return f

View File

@@ -9,7 +9,7 @@ import (
func init() { gob.Register(new(RemountOp)) } func init() { gob.Register(new(RemountOp)) }
// Remount appends an [Op] that applies [RemountOp.Flags] on container path [RemountOp.Target]. // Remount is a helper for appending [RemountOp] to [Ops].
func (f *Ops) Remount(target *check.Absolute, flags uintptr) *Ops { func (f *Ops) Remount(target *check.Absolute, flags uintptr) *Ops {
*f = append(*f, &RemountOp{target, flags}) *f = append(*f, &RemountOp{target, flags})
return f return f

View File

@@ -38,6 +38,7 @@ const (
_LANDLOCK_ACCESS_FS_DELIM _LANDLOCK_ACCESS_FS_DELIM
) )
// String returns a space-separated string of [LandlockAccessFS] flags.
func (f LandlockAccessFS) String() string { func (f LandlockAccessFS) String() string {
switch f { switch f {
case LANDLOCK_ACCESS_FS_EXECUTE: case LANDLOCK_ACCESS_FS_EXECUTE:
@@ -116,6 +117,7 @@ const (
_LANDLOCK_ACCESS_NET_DELIM _LANDLOCK_ACCESS_NET_DELIM
) )
// String returns a space-separated string of [LandlockAccessNet] flags.
func (f LandlockAccessNet) String() string { func (f LandlockAccessNet) String() string {
switch f { switch f {
case LANDLOCK_ACCESS_NET_BIND_TCP: case LANDLOCK_ACCESS_NET_BIND_TCP:
@@ -152,6 +154,7 @@ const (
_LANDLOCK_SCOPE_DELIM _LANDLOCK_SCOPE_DELIM
) )
// String returns a space-separated string of [LandlockScope] flags.
func (f LandlockScope) String() string { func (f LandlockScope) String() string {
switch f { switch f {
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
@@ -184,10 +187,12 @@ type RulesetAttr struct {
HandledAccessFS LandlockAccessFS HandledAccessFS LandlockAccessFS
// Bitmask of handled network actions. // Bitmask of handled network actions.
HandledAccessNet LandlockAccessNet HandledAccessNet LandlockAccessNet
// Bitmask of scopes restricting a Landlock domain from accessing outside resources (e.g. IPCs). // Bitmask of scopes restricting a Landlock domain from accessing outside
// resources (e.g. IPCs).
Scoped LandlockScope Scoped LandlockScope
} }
// String returns a user-facing description of [RulesetAttr].
func (rulesetAttr *RulesetAttr) String() string { func (rulesetAttr *RulesetAttr) String() string {
if rulesetAttr == nil { if rulesetAttr == nil {
return "NULL" return "NULL"
@@ -208,6 +213,7 @@ func (rulesetAttr *RulesetAttr) String() string {
return strings.Join(elems, ", ") return strings.Join(elems, ", ")
} }
// Create loads the ruleset into the kernel.
func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) { func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
var pointer, size uintptr var pointer, size uintptr
// NULL needed for abi version // NULL needed for abi version
@@ -216,10 +222,13 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
size = unsafe.Sizeof(*rulesetAttr) size = unsafe.Sizeof(*rulesetAttr)
} }
rulesetFd, _, errno := syscall.Syscall(std.SYS_LANDLOCK_CREATE_RULESET, pointer, size, flags) rulesetFd, _, errno := syscall.Syscall(
std.SYS_LANDLOCK_CREATE_RULESET,
pointer, size,
flags,
)
fd = int(rulesetFd) fd = int(rulesetFd)
err = errno err = errno
if fd < 0 { if fd < 0 {
return return
} }
@@ -230,12 +239,19 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
return fd, nil return fd, nil
} }
// LandlockGetABI returns the ABI version supported by the kernel.
func LandlockGetABI() (int, error) { func LandlockGetABI() (int, error) {
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION) return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
} }
// LandlockRestrictSelf applies a loaded ruleset to the calling thread.
func LandlockRestrictSelf(rulesetFd int, flags uintptr) error { func LandlockRestrictSelf(rulesetFd int, flags uintptr) error {
r, _, errno := syscall.Syscall(std.SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), flags, 0) r, _, errno := syscall.Syscall(
std.SYS_LANDLOCK_RESTRICT_SELF,
uintptr(rulesetFd),
flags,
0,
)
if r != 0 { if r != 0 {
return errno return errno
} }

View File

@@ -99,7 +99,7 @@ done:
} }
if m.Header.Type == NLMSG_ERROR { if m.Header.Type == NLMSG_ERROR {
if len(m.Data) >= 4 { if len(m.Data) >= 4 {
errno := Errno(-std.ScmpInt(binary.NativeEndian.Uint32(m.Data))) errno := Errno(-std.Int(binary.NativeEndian.Uint32(m.Data)))
if errno == 0 { if errno == 0 {
return nil return nil
} }

View File

@@ -15,7 +15,10 @@ import (
const ( const (
// Nonexistent is a path that cannot exist. // Nonexistent is a path that cannot exist.
// /proc is chosen because a system with covered /proc is unsupported by this package. //
// This path can never be presented by the kernel if proc is mounted on
// /proc/. This can only exist if parts of /proc/ is covered, or proc is not
// mounted at all. Neither configuration is supported by this package.
Nonexistent = fhs.Proc + "nonexistent" Nonexistent = fhs.Proc + "nonexistent"
hostPath = fhs.Root + hostDir hostPath = fhs.Root + hostDir

View File

@@ -88,18 +88,22 @@ var resPrefix = [...]string{
7: "seccomp_load failed", 7: "seccomp_load failed",
} }
// cbAllocateBuffer is the function signature for the function handle passed to hakurei_export_filter // cbAllocateBuffer is the function signature for the function handle passed to
// which allocates the buffer that the resulting bpf program is copied into, and writes its slice header // hakurei_scmp_make_filter which allocates the buffer that the resulting bpf
// to a value held by the caller. // program is copied into, and writes its slice header to a value held by the caller.
type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer) type cbAllocateBuffer = func(len C.size_t) (buf unsafe.Pointer)
// hakurei_scmp_allocate allocates a buffer of specified size known to the
// runtime through a callback passed in a [cgo.Handle].
//
//export hakurei_scmp_allocate //export hakurei_scmp_allocate
func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) { func hakurei_scmp_allocate(f C.uintptr_t, len C.size_t) (buf unsafe.Pointer) {
return cgo.Handle(f).Value().(cbAllocateBuffer)(len) return cgo.Handle(f).Value().(cbAllocateBuffer)(len)
} }
// makeFilter generates a bpf program from a slice of [std.NativeRule] and writes the resulting byte slice to p. // makeFilter generates a bpf program from a slice of [std.NativeRule] and
// The filter is installed to the current process if p is nil. // writes the resulting byte slice to p. The filter is installed to the current
// process if p is nil.
func makeFilter(rules []std.NativeRule, flags ExportFlag, p *[]byte) error { func makeFilter(rules []std.NativeRule, flags ExportFlag, p *[]byte) error {
if len(rules) == 0 { if len(rules) == 0 {
return ErrInvalidRules return ErrInvalidRules
@@ -170,8 +174,8 @@ func Export(rules []std.NativeRule, flags ExportFlag) (data []byte, err error) {
return return
} }
// Load generates a bpf program from a slice of [std.NativeRule] and enforces it on the current process. // Load generates a bpf program from a slice of [std.NativeRule] and enforces it
// Errors returned by libseccomp is wrapped in [LibraryError]. // on the current process. Errors returned by libseccomp is wrapped in [LibraryError].
func Load(rules []std.NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) } func Load(rules []std.NativeRule, flags ExportFlag) error { return makeFilter(rules, flags, nil) }
type ( type (

View File

@@ -24,8 +24,8 @@ func TestSyscallResolveName(t *testing.T) {
} }
func TestRuleType(t *testing.T) { func TestRuleType(t *testing.T) {
assertKind[std.ScmpUint, scmpUint](t) assertKind[std.Uint, scmpUint](t)
assertKind[std.ScmpInt, scmpInt](t) assertKind[std.Int, scmpInt](t)
assertSize[std.NativeRule, syscallRule](t) assertSize[std.NativeRule, syscallRule](t)
assertKind[std.ScmpDatum, scmpDatum](t) assertKind[std.ScmpDatum, scmpDatum](t)

View File

@@ -7,24 +7,28 @@ import (
type ( type (
// ScmpUint is equivalent to C.uint. // ScmpUint is equivalent to C.uint.
ScmpUint uint32 //
// Deprecated: This type has been renamed to Uint and will be removed in 0.4.
ScmpUint = Uint
// ScmpInt is equivalent to C.int. // ScmpInt is equivalent to C.int.
ScmpInt int32 //
// Deprecated: This type has been renamed to Int and will be removed in 0.4.
ScmpInt = Int
// ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall]. // ScmpSyscall represents a syscall number passed to libseccomp via [NativeRule.Syscall].
ScmpSyscall ScmpInt ScmpSyscall Int
// ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno]. // ScmpErrno represents an errno value passed to libseccomp via [NativeRule.Errno].
ScmpErrno ScmpInt ScmpErrno Int
// ScmpCompare is equivalent to enum scmp_compare; // ScmpCompare is equivalent to enum scmp_compare;
ScmpCompare ScmpUint ScmpCompare Uint
// ScmpDatum is equivalent to scmp_datum_t. // ScmpDatum is equivalent to scmp_datum_t.
ScmpDatum uint64 ScmpDatum uint64
// ScmpArgCmp is equivalent to struct scmp_arg_cmp. // ScmpArgCmp is equivalent to struct scmp_arg_cmp.
ScmpArgCmp struct { ScmpArgCmp struct {
// argument number, starting at 0 // argument number, starting at 0
Arg ScmpUint `json:"arg"` Arg Uint `json:"arg"`
// the comparison op, e.g. SCMP_CMP_* // the comparison op, e.g. SCMP_CMP_*
Op ScmpCompare `json:"op"` Op ScmpCompare `json:"op"`

8
container/std/types.go Normal file
View File

@@ -0,0 +1,8 @@
package std
type (
// Uint is equivalent to C.uint.
Uint uint32
// Int is equivalent to C.int.
Int int32
)

View File

@@ -3,6 +3,8 @@ package container
import ( import (
. "syscall" . "syscall"
"unsafe" "unsafe"
"hakurei.app/container/std"
) )
// Prctl manipulates various aspects of the behavior of the calling thread or process. // Prctl manipulates various aspects of the behavior of the calling thread or process.
@@ -41,6 +43,49 @@ func Isatty(fd int) bool {
return r == 0 return r == 0
} }
// include/uapi/linux/sched.h
const (
SCHED_NORMAL = iota
SCHED_FIFO
SCHED_RR
SCHED_BATCH
_ // SCHED_ISO: reserved but not implemented yet
SCHED_IDLE
SCHED_DEADLINE
SCHED_EXT
)
// schedParam is equivalent to struct sched_param from include/linux/sched.h.
type schedParam struct {
// sched_priority
priority std.Int
}
// schedSetscheduler sets both the scheduling policy and parameters for the
// thread whose ID is specified in tid. If tid equals zero, the scheduling
// policy and parameters of the calling thread will be set.
//
// This function is unexported because it is [very subtle to use correctly]. The
// function signature in libc is misleading: pid actually refers to a thread ID.
// The glibc wrapper for this system call ignores this semantic and exposes
// this counterintuitive behaviour.
//
// This function is only called from the container setup thread. Do not reuse
// this if you do not have something similar in place!
//
// [very subtle to use correctly]: https://www.openwall.com/lists/musl/2016/03/01/4
func schedSetscheduler(tid, policy int, param *schedParam) error {
if r, _, errno := Syscall(
SYS_SCHED_SETSCHEDULER,
uintptr(tid),
uintptr(policy),
uintptr(unsafe.Pointer(param)),
); r < 0 {
return errno
}
return nil
}
// IgnoringEINTR makes a function call and repeats it if it returns an // IgnoringEINTR makes a function call and repeats it if it returns an
// EINTR error. This appears to be required even though we install all // EINTR error. This appears to be required even though we install all
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.

View File

@@ -2,6 +2,8 @@ package vfs
import "strings" import "strings"
// Unmangle reverses mangling of strings done by the kernel. Its behaviour is
// consistent with the equivalent function in util-linux.
func Unmangle(s string) string { func Unmangle(s string) string {
if !strings.ContainsRune(s, '\\') { if !strings.ContainsRune(s, '\\') {
return s return s

View File

@@ -24,6 +24,7 @@ var (
ErrMountInfoSep = errors.New("bad optional fields separator") ErrMountInfoSep = errors.New("bad optional fields separator")
) )
// A DecoderError describes a nonrecoverable error decoding a mountinfo stream.
type DecoderError struct { type DecoderError struct {
Op string Op string
Line int Line int
@@ -51,7 +52,8 @@ func (e *DecoderError) Error() string {
} }
type ( type (
// A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from an input stream. // A MountInfoDecoder reads and decodes proc_pid_mountinfo(5) entries from
// an input stream.
MountInfoDecoder struct { MountInfoDecoder struct {
s *bufio.Scanner s *bufio.Scanner
m *MountInfo m *MountInfo
@@ -72,13 +74,16 @@ type (
MountInfoEntry struct { MountInfoEntry struct {
// mount ID: a unique ID for the mount (may be reused after umount(2)). // mount ID: a unique ID for the mount (may be reused after umount(2)).
ID int `json:"id"` ID int `json:"id"`
// parent ID: the ID of the parent mount (or of self for the root of this mount namespace's mount tree). // parent ID: the ID of the parent mount (or of self for the root of
// this mount namespace's mount tree).
Parent int `json:"parent"` Parent int `json:"parent"`
// major:minor: the value of st_dev for files on this filesystem (see stat(2)). // major:minor: the value of st_dev for files on this filesystem (see stat(2)).
Devno DevT `json:"devno"` Devno DevT `json:"devno"`
// root: the pathname of the directory in the filesystem which forms the root of this mount. // root: the pathname of the directory in the filesystem which forms the
// root of this mount.
Root string `json:"root"` Root string `json:"root"`
// mount point: the pathname of the mount point relative to the process's root directory. // mount point: the pathname of the mount point relative to the
// process's root directory.
Target string `json:"target"` Target string `json:"target"`
// mount options: per-mount options (see mount(2)). // mount options: per-mount options (see mount(2)).
VfsOptstr string `json:"vfs_optstr"` VfsOptstr string `json:"vfs_optstr"`
@@ -126,7 +131,8 @@ func (e *MountInfoEntry) Flags() (flags uintptr, unmatched []string) {
// NewMountInfoDecoder returns a new decoder that reads from r. // NewMountInfoDecoder returns a new decoder that reads from r.
// //
// The decoder introduces its own buffering and may read data from r beyond the mountinfo entries requested. // The decoder introduces its own buffering and may read data from r beyond the
// mountinfo entries requested.
func NewMountInfoDecoder(r io.Reader) *MountInfoDecoder { func NewMountInfoDecoder(r io.Reader) *MountInfoDecoder {
return &MountInfoDecoder{s: bufio.NewScanner(r)} return &MountInfoDecoder{s: bufio.NewScanner(r)}
} }
@@ -271,6 +277,8 @@ func parseMountInfoLine(s string, ent *MountInfoEntry) error {
return nil return nil
} }
// EqualWithIgnore compares to [MountInfoEntry] values, ignoring fields that
// compare equal to ignore.
func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bool { func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bool {
return (e.ID == want.ID || want.ID == -1) && return (e.ID == want.ID || want.ID == -1) &&
(e.Parent == want.Parent || want.Parent == -1) && (e.Parent == want.Parent || want.Parent == -1) &&
@@ -284,6 +292,8 @@ func (e *MountInfoEntry) EqualWithIgnore(want *MountInfoEntry, ignore string) bo
(e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore) (e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
} }
// String returns a user-facing representation of a [MountInfoEntry]. It fits
// roughly into the mountinfo format, but without mangling.
func (e *MountInfoEntry) String() string { func (e *MountInfoEntry) String() string {
return fmt.Sprintf("%d %d %d:%d %s %s %s %s %s %s %s", return fmt.Sprintf("%d %d %d:%d %s %s %s %s %s %s %s",
e.ID, e.Parent, e.Devno[0], e.Devno[1], e.Root, e.Target, e.VfsOptstr, e.ID, e.Parent, e.Devno[0], e.Devno[1], e.Root, e.Target, e.VfsOptstr,

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
) )
// UnfoldTargetError is a pathname that never appeared in a mount hierarchy.
type UnfoldTargetError string type UnfoldTargetError string
func (e UnfoldTargetError) Error() string { func (e UnfoldTargetError) Error() string {
@@ -27,6 +28,7 @@ func (n *MountInfoNode) Collective() iter.Seq[*MountInfoNode] {
return func(yield func(*MountInfoNode) bool) { n.visit(yield) } return func(yield func(*MountInfoNode) bool) { n.visit(yield) }
} }
// visit recursively visits all visible mountinfo nodes.
func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool { func (n *MountInfoNode) visit(yield func(*MountInfoNode) bool) bool {
if !n.Covered && !yield(n) { if !n.Covered && !yield(n) {
return false return false

4
dist/release.sh vendored
View File

@@ -13,7 +13,7 @@ echo
echo '# Building hakurei.' echo '# Building hakurei.'
go generate ./... go generate ./...
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
-buildid= -extldflags '-static' -buildid= -linkmode external -extldflags=-static
-X hakurei.app/internal/info.buildVersion=${VERSION} -X hakurei.app/internal/info.buildVersion=${VERSION}
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei -X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu -X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
@@ -21,7 +21,7 @@ go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
echo echo
echo '# Testing hakurei.' echo '# Testing hakurei.'
go test -ldflags='-buildid= -extldflags=-static' ./... go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
echo echo
echo '# Creating distribution.' echo '# Creating distribution.'

View File

@@ -29,20 +29,6 @@
{ {
nixosModules.hakurei = import ./nixos.nix self.packages; nixosModules.hakurei = import ./nixos.nix self.packages;
buildPackage = forAllSystems (
system:
nixpkgsFor.${system}.callPackage (
import ./cmd/hpkg/build.nix {
inherit
nixpkgsFor
system
nixpkgs
home-manager
;
}
)
);
checks = forAllSystems ( checks = forAllSystems (
system: system:
let let
@@ -71,8 +57,6 @@
sharefs = callPackage ./cmd/sharefs/test { inherit system self; }; sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
hpkg = callPackage ./cmd/hpkg/test { inherit system self; };
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } '' formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
cd ${./.} cd ${./.}
@@ -127,11 +111,6 @@
glibc glibc
xdg-dbus-proxy xdg-dbus-proxy
# hpkg
zstd
gnutar
coreutils
# for check # for check
util-linux util-linux
nettools nettools
@@ -219,7 +198,7 @@
./test/interactive/trace.nix ./test/interactive/trace.nix
self.nixosModules.hakurei self.nixosModules.hakurei
self.inputs.home-manager.nixosModules.home-manager home-manager.nixosModules.home-manager
]; ];
}; };
in in

View File

@@ -8,7 +8,6 @@
package filelock package filelock
import ( import (
"errors"
"io/fs" "io/fs"
) )
@@ -74,10 +73,3 @@ func (lt lockType) String() string {
return "Unlock" return "Unlock"
} }
} }
// IsNotSupported returns a boolean indicating whether the error is known to
// report that a function is not supported (possibly for a specific input).
// It is satisfied by errors.ErrUnsupported as well as some syscall errors.
func IsNotSupported(err error) bool {
return errors.Is(err, errors.ErrUnsupported)
}

View File

@@ -14,7 +14,7 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/container" "hakurei.app/container/fhs"
"hakurei.app/internal/lockedfile/internal/filelock" "hakurei.app/internal/lockedfile/internal/filelock"
"hakurei.app/internal/lockedfile/internal/testexec" "hakurei.app/internal/lockedfile/internal/testexec"
) )
@@ -197,7 +197,7 @@ func TestLockNotDroppedByExecCommand(t *testing.T) {
// Some kinds of file locks are dropped when a duplicated or forked file // Some kinds of file locks are dropped when a duplicated or forked file
// descriptor is unlocked. Double-check that the approach used by os/exec does // descriptor is unlocked. Double-check that the approach used by os/exec does
// not accidentally drop locks. // not accidentally drop locks.
cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-test.run=^$") cmd := testexec.CommandContext(t, t.Context(), fhs.ProcSelfExe, "-test.run=^$")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
t.Fatalf("exec failed: %v", err) t.Fatalf("exec failed: %v", err)
} }

View File

@@ -94,6 +94,11 @@ func (f *File) Close() error {
err := closeFile(f.osFile.File) err := closeFile(f.osFile.File)
f.cleanup.Stop() f.cleanup.Stop()
// f may be dead at the moment after we access f.cleanup,
// so the cleanup can fire before Stop completes. Keep f
// alive while we call Stop. See the documentation for
// runtime.Cleanup.Stop.
runtime.KeepAlive(f)
return err return err
} }

View File

@@ -15,7 +15,7 @@ import (
"testing" "testing"
"time" "time"
"hakurei.app/container" "hakurei.app/container/fhs"
"hakurei.app/internal/lockedfile" "hakurei.app/internal/lockedfile"
"hakurei.app/internal/lockedfile/internal/testexec" "hakurei.app/internal/lockedfile/internal/testexec"
) )
@@ -215,7 +215,7 @@ func TestSpuriousEDEADLK(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
cmd := testexec.CommandContext(t, t.Context(), container.MustExecutable(nil), "-test.run=^"+t.Name()+"$") cmd := testexec.CommandContext(t, t.Context(), fhs.ProcSelfExe, "-test.run=^"+t.Name()+"$")
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir)) cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", dirVar, dir))
qDone := make(chan struct{}) qDone := make(chan struct{})

View File

@@ -23,7 +23,7 @@ import (
"hakurei.app/message" "hakurei.app/message"
) )
// AbsWork is the container pathname [CureContext.GetWorkDir] is mounted on. // AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
var AbsWork = fhs.AbsRoot.Append("work/") var AbsWork = fhs.AbsRoot.Append("work/")
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make // ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
@@ -39,22 +39,23 @@ type ExecPath struct {
W bool W bool
} }
// layers returns pathnames collected from A deduplicated by checksum. // SchedPolicy is the [container] scheduling policy.
func (p *ExecPath) layers(f *FContext) []*check.Absolute { var SchedPolicy int
msg := f.GetMessage()
layers := make([]*check.Absolute, 0, len(p.A)) // PromoteLayers returns artifacts with identical-by-content layers promoted to
checksums := make(map[unique.Handle[Checksum]]struct{}, len(p.A)) // the highest priority instance, as if mounted via [ExecPath].
for i := range p.A { func PromoteLayers(
d := p.A[len(p.A)-1-i] artifacts []Artifact,
pathname, checksum := f.GetArtifact(d) getArtifact func(Artifact) (*check.Absolute, unique.Handle[Checksum]),
report func(i int, d Artifact),
) []*check.Absolute {
layers := make([]*check.Absolute, 0, len(artifacts))
checksums := make(map[unique.Handle[Checksum]]struct{}, len(artifacts))
for i := range artifacts {
d := artifacts[len(artifacts)-1-i]
pathname, checksum := getArtifact(d)
if _, ok := checksums[checksum]; ok { if _, ok := checksums[checksum]; ok {
if msg.IsVerbose() { report(len(artifacts)-1-i, d)
msg.Verbosef(
"promoted layer %d as %s",
len(p.A)-1-i, reportName(d, f.cache.Ident(d)),
)
}
continue continue
} }
checksums[checksum] = struct{}{} checksums[checksum] = struct{}{}
@@ -64,6 +65,19 @@ func (p *ExecPath) layers(f *FContext) []*check.Absolute {
return layers return layers
} }
// layers returns pathnames collected from A deduplicated via [PromoteLayers].
func (p *ExecPath) layers(f *FContext) []*check.Absolute {
msg := f.GetMessage()
return PromoteLayers(p.A, f.GetArtifact, func(i int, d Artifact) {
if msg.IsVerbose() {
msg.Verbosef(
"promoted layer %d as %s",
i, reportName(d, f.cache.Ident(d)),
)
}
})
}
// Path returns a populated [ExecPath]. // Path returns a populated [ExecPath].
func Path(pathname *check.Absolute, writable bool, a ...Artifact) ExecPath { func Path(pathname *check.Absolute, writable bool, a ...Artifact) ExecPath {
return ExecPath{pathname, a, writable} return ExecPath{pathname, a, writable}
@@ -347,6 +361,7 @@ const (
// scanVerbose prefixes program output for a verbose [message.Msg]. // scanVerbose prefixes program output for a verbose [message.Msg].
func scanVerbose( func scanVerbose(
msg message.Msg, msg message.Msg,
cancel context.CancelFunc,
done chan<- struct{}, done chan<- struct{},
prefix string, prefix string,
r io.Reader, r io.Reader,
@@ -361,10 +376,15 @@ func scanVerbose(
msg.Verbose(prefix, s.Text()) msg.Verbose(prefix, s.Text())
} }
if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) { if err := s.Err(); err != nil && !errors.Is(err, os.ErrClosed) {
cancel()
msg.Verbose("*"+prefix, err) msg.Verbose("*"+prefix, err)
} }
} }
// SeccompPresets is the [seccomp] presets used by exec artifacts.
const SeccompPresets = std.PresetStrict &
^(std.PresetDenyNS | std.PresetDenyDevel)
// cure is like Cure but allows optional host net namespace. This is used for // cure is like Cure but allows optional host net namespace. This is used for
// the [KnownChecksum] variant where networking is allowed. // the [KnownChecksum] variant where networking is allowed.
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) { func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
@@ -388,15 +408,22 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
z := container.New(ctx, f.GetMessage()) z := container.New(ctx, f.GetMessage())
z.WaitDelay = execWaitDelay z.WaitDelay = execWaitDelay
z.SeccompPresets |= std.PresetStrict & ^std.PresetDenyNS z.SeccompPresets = SeccompPresets
z.SeccompFlags |= seccomp.AllowMultiarch z.SeccompFlags |= seccomp.AllowMultiarch
z.ParentPerm = 0700 z.ParentPerm = 0700
z.HostNet = hostNet z.HostNet = hostNet
z.Hostname = "cure" z.Hostname = "cure"
z.SchedPolicy = SchedPolicy
if z.HostNet { if z.HostNet {
z.Hostname = "cure-net" z.Hostname = "cure-net"
} }
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1 z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
var status io.Writer
if status, err = f.GetStatusWriter(); err != nil {
return
}
if msg := f.GetMessage(); msg.IsVerbose() { if msg := f.GetMessage(); msg.IsVerbose() {
var stdout, stderr io.ReadCloser var stdout, stderr io.ReadCloser
if stdout, err = z.StdoutPipe(); err != nil { if stdout, err = z.StdoutPipe(); err != nil {
@@ -413,10 +440,26 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
} }
}() }()
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{}) stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
go scanVerbose(msg, stdoutDone, "("+a.name+":1)", stdout) go scanVerbose(
go scanVerbose(msg, stderrDone, "("+a.name+":2)", stderr) msg, cancel, stdoutDone,
defer func() { <-stdoutDone; <-stderrDone }() "("+a.name+":1)",
io.TeeReader(brStdout, status),
)
go scanVerbose(
msg, cancel, stderrDone,
"("+a.name+":2)",
io.TeeReader(brStderr, status),
)
defer func() {
<-stdoutDone
<-stderrDone
f.cache.putReader(brStdout)
f.cache.putReader(brStderr)
}()
} else {
z.Stdout, z.Stderr = status, status
} }
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args

View File

@@ -150,7 +150,7 @@ func (i *IContext) WriteUint32(v uint32) {
} }
// irMaxStringLength is the maximum acceptable wire size of [IRKindString]. // irMaxStringLength is the maximum acceptable wire size of [IRKindString].
const irMaxStringLength = 1 << 20 const irMaxStringLength = 1 << 24
// IRStringError is a string value too big to encode in IR. // IRStringError is a string value too big to encode in IR.
type IRStringError string type IRStringError string
@@ -310,6 +310,13 @@ type (
// verbose logging is enabled. Artifacts may only depend on artifacts // verbose logging is enabled. Artifacts may only depend on artifacts
// previously described in the IR stream. // previously described in the IR stream.
// //
// IRDecoder rejects an IR stream on the first decoding error, it does not
// check against nonzero reserved ancillary data or incorrectly ordered or
// redundant unstructured dependencies. An invalid IR stream as such will
// yield [Artifact] values with identifiers disagreeing with those computed
// by IRDecoder. For this reason, IRDecoder does not access the ident cache
// to avoid putting [Cache] into an inconsistent state.
//
// Methods of IRDecoder are not safe for concurrent use. // Methods of IRDecoder are not safe for concurrent use.
IRDecoder struct { IRDecoder struct {
// Address of underlying [Cache], must not be exposed directly. // Address of underlying [Cache], must not be exposed directly.

View File

@@ -28,15 +28,21 @@ import (
"unsafe" "unsafe"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/internal/info"
"hakurei.app/internal/lockedfile" "hakurei.app/internal/lockedfile"
"hakurei.app/message" "hakurei.app/message"
) )
const (
// programName is the string identifying this build system.
programName = "internal/pkg"
)
type ( type (
// A Checksum is a SHA-384 checksum computed for a cured [Artifact]. // A Checksum is a SHA-384 checksum computed for a cured [Artifact].
Checksum = [sha512.Size384]byte Checksum = [sha512.Size384]byte
// An ID is a unique identifier returned by [Artifact.ID]. This value must // An ID is a unique identifier returned by [KnownIdent.ID]. This value must
// be deterministically determined ahead of time. // be deterministically determined ahead of time.
ID Checksum ID Checksum
) )
@@ -65,18 +71,75 @@ func MustDecode(s string) (checksum Checksum) {
return return
} }
// common holds elements and receives methods shared between different contexts.
type common struct {
// Address of underlying [Cache], should be zeroed or made unusable after
// Cure returns and must not be exposed directly.
cache *Cache
}
// TContext is passed to [TrivialArtifact.Cure] and provides information and // TContext is passed to [TrivialArtifact.Cure] and provides information and
// methods required for curing the [TrivialArtifact]. // methods required for curing the [TrivialArtifact].
// //
// Methods of TContext are safe for concurrent use. TContext is valid // Methods of TContext are safe for concurrent use. TContext is valid
// until [TrivialArtifact.Cure] returns. // until [TrivialArtifact.Cure] returns.
type TContext struct { type TContext struct {
// Address of underlying [Cache], should be zeroed or made unusable after
// [TrivialArtifact.Cure] returns and must not be exposed directly.
cache *Cache
// Populated during [Cache.Cure]. // Populated during [Cache.Cure].
work, temp *check.Absolute work, temp *check.Absolute
// Target [Artifact] encoded identifier.
ids string
// Pathname status was created at.
statusPath *check.Absolute
// File statusHeader and logs are written to.
status *os.File
// Error value during prepareStatus.
statusErr error
common
}
// statusHeader is the header written to all status files in dirStatus.
var statusHeader = func() string {
s := programName
if v := info.Version(); v != info.FallbackVersion {
s += " " + v
}
s += " (" + runtime.GOARCH + ")"
if name, err := os.Hostname(); err == nil {
s += " on " + name
}
s += "\n\n"
return s
}()
// prepareStatus initialises the status file once.
func (t *TContext) prepareStatus() error {
if t.statusPath != nil || t.status != nil {
return t.statusErr
}
t.statusPath = t.cache.base.Append(
dirStatus,
t.ids,
)
if t.status, t.statusErr = os.OpenFile(
t.statusPath.String(),
syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY,
0400,
); t.statusErr != nil {
return t.statusErr
}
_, t.statusErr = t.status.WriteString(statusHeader)
return t.statusErr
}
// GetStatusWriter returns a [io.Writer] for build logs. The caller must not
// seek this writer before the position it was first returned in.
func (t *TContext) GetStatusWriter() (io.Writer, error) {
err := t.prepareStatus()
return t.status, err
} }
// destroy destroys the temporary directory and joins its errors with the error // destroy destroys the temporary directory and joins its errors with the error
@@ -84,12 +147,15 @@ type TContext struct {
// directory is removed similarly. [Cache] is responsible for making sure work // directory is removed similarly. [Cache] is responsible for making sure work
// is never left behind for a successful [Cache.Cure]. // is never left behind for a successful [Cache.Cure].
// //
// If implementation had requested status, it is closed with error joined with
// the error referred to by errP. If the error referred to by errP is non-nil,
// the status file is removed from the filesystem.
//
// destroy must be deferred by [Cache.Cure] if [TContext] is passed to any Cure // destroy must be deferred by [Cache.Cure] if [TContext] is passed to any Cure
// implementation. It should not be called prior to that point. // implementation. It should not be called prior to that point.
func (t *TContext) destroy(errP *error) { func (t *TContext) destroy(errP *error) {
if chmodErr, removeErr := removeAll(t.temp); chmodErr != nil || removeErr != nil { if chmodErr, removeErr := removeAll(t.temp); chmodErr != nil || removeErr != nil {
*errP = errors.Join(*errP, chmodErr, removeErr) *errP = errors.Join(*errP, chmodErr, removeErr)
return
} }
if *errP != nil { if *errP != nil {
@@ -97,17 +163,31 @@ func (t *TContext) destroy(errP *error) {
if chmodErr != nil || removeErr != nil { if chmodErr != nil || removeErr != nil {
*errP = errors.Join(*errP, chmodErr, removeErr) *errP = errors.Join(*errP, chmodErr, removeErr)
} else if errors.Is(*errP, os.ErrExist) { } else if errors.Is(*errP, os.ErrExist) {
// two artifacts may be backed by the same file var linkError *os.LinkError
*errP = nil if errors.As(*errP, &linkError) && linkError != nil &&
linkError.Op == "rename" {
// two artifacts may be backed by the same file
*errP = nil
}
} }
} }
if t.status != nil {
if err := t.status.Close(); err != nil {
*errP = errors.Join(*errP, err)
}
if *errP != nil {
*errP = errors.Join(*errP, os.Remove(t.statusPath.String()))
}
t.status = nil
}
} }
// Unwrap returns the underlying [context.Context]. // Unwrap returns the underlying [context.Context].
func (t *TContext) Unwrap() context.Context { return t.cache.ctx } func (c *common) Unwrap() context.Context { return c.cache.ctx }
// GetMessage returns [message.Msg] held by the underlying [Cache]. // GetMessage returns [message.Msg] held by the underlying [Cache].
func (t *TContext) GetMessage() message.Msg { return t.cache.msg } func (c *common) GetMessage() message.Msg { return c.cache.msg }
// GetWorkDir returns a pathname to a directory which [Artifact] is expected to // GetWorkDir returns a pathname to a directory which [Artifact] is expected to
// write its output to. This is not the final resting place of the [Artifact] // write its output to. This is not the final resting place of the [Artifact]
@@ -126,13 +206,13 @@ func (t *TContext) GetTempDir() *check.Absolute { return t.temp }
// If err is nil, the caller must close the resulting [io.ReadCloser] and return // If err is nil, the caller must close the resulting [io.ReadCloser] and return
// its error, if any. Failure to read r to EOF may result in a spurious // its error, if any. Failure to read r to EOF may result in a spurious
// [ChecksumMismatchError], or the underlying implementation may block on Close. // [ChecksumMismatchError], or the underlying implementation may block on Close.
func (t *TContext) Open(a Artifact) (r io.ReadCloser, err error) { func (c *common) Open(a Artifact) (r io.ReadCloser, err error) {
if f, ok := a.(FileArtifact); ok { if f, ok := a.(FileArtifact); ok {
return t.cache.openFile(f) return c.cache.openFile(f)
} }
var pathname *check.Absolute var pathname *check.Absolute
if pathname, _, err = t.cache.Cure(a); err != nil { if pathname, _, err = c.cache.Cure(a); err != nil {
return return
} }
@@ -164,7 +244,7 @@ type FContext struct {
} }
// InvalidLookupError is the identifier of non-dependency [Artifact] looked up // InvalidLookupError is the identifier of non-dependency [Artifact] looked up
// via [FContext.Pathname] by a misbehaving [Artifact] implementation. // via [FContext.GetArtifact] by a misbehaving [Artifact] implementation.
type InvalidLookupError ID type InvalidLookupError ID
func (e InvalidLookupError) Error() string { func (e InvalidLookupError) Error() string {
@@ -191,14 +271,7 @@ func (f *FContext) GetArtifact(a Artifact) (
// //
// Methods of RContext are safe for concurrent use. RContext is valid // Methods of RContext are safe for concurrent use. RContext is valid
// until [FileArtifact.Cure] returns. // until [FileArtifact.Cure] returns.
type RContext struct { type RContext struct{ common }
// Address of underlying [Cache], should be zeroed or made unusable after
// [FileArtifact.Cure] returns and must not be exposed directly.
cache *Cache
}
// Unwrap returns the underlying [context.Context].
func (r *RContext) Unwrap() context.Context { return r.cache.ctx }
// An Artifact is a read-only reference to a piece of data that may be created // An Artifact is a read-only reference to a piece of data that may be created
// deterministically but might not currently be available in memory or on the // deterministically but might not currently be available in memory or on the
@@ -377,6 +450,9 @@ const (
// dirChecksum is the directory name appended to Cache.base for storing // dirChecksum is the directory name appended to Cache.base for storing
// artifacts named after their [Checksum]. // artifacts named after their [Checksum].
dirChecksum = "checksum" dirChecksum = "checksum"
// dirStatus is the directory name appended to Cache.base for storing
// artifact metadata and logs named after their [ID].
dirStatus = "status"
// dirWork is the directory name appended to Cache.base for working // dirWork is the directory name appended to Cache.base for working
// pathnames set up during [Cache.Cure]. // pathnames set up during [Cache.Cure].
@@ -466,7 +542,7 @@ type Cache struct {
// Synchronises entry into exclusive artifacts for the cure method. // Synchronises entry into exclusive artifacts for the cure method.
exclMu sync.Mutex exclMu sync.Mutex
// Buffered I/O free list, must not be accessed directly. // Buffered I/O free list, must not be accessed directly.
bufioPool sync.Pool brPool, bwPool sync.Pool
// Unlocks the on-filesystem cache. Must only be called from Close. // Unlocks the on-filesystem cache. Must only be called from Close.
unlock func() unlock func()
@@ -548,6 +624,26 @@ func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
return return
} }
// getReader is like [bufio.NewReader] but for brPool.
func (c *Cache) getReader(r io.Reader) *bufio.Reader {
br := c.brPool.Get().(*bufio.Reader)
br.Reset(r)
return br
}
// putReader adds br to brPool.
func (c *Cache) putReader(br *bufio.Reader) { c.brPool.Put(br) }
// getWriter is like [bufio.NewWriter] but for bwPool.
func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
bw := c.bwPool.Get().(*bufio.Writer)
bw.Reset(w)
return bw
}
// putWriter adds bw to bwPool.
func (c *Cache) putWriter(bw *bufio.Writer) { c.bwPool.Put(bw) }
// A ChecksumMismatchError describes an [Artifact] with unexpected content. // A ChecksumMismatchError describes an [Artifact] with unexpected content.
type ChecksumMismatchError struct { type ChecksumMismatchError struct {
// Actual and expected checksums. // Actual and expected checksums.
@@ -569,6 +665,9 @@ type ScrubError struct {
// Dangling identifier symlinks. This can happen if the content-addressed // Dangling identifier symlinks. This can happen if the content-addressed
// entry was removed while scrubbing due to a checksum mismatch. // entry was removed while scrubbing due to a checksum mismatch.
DanglingIdentifiers []ID DanglingIdentifiers []ID
// Dangling status files. This can happen if a dangling status symlink was
// removed while scrubbing.
DanglingStatus []ID
// Miscellaneous errors, including [os.ReadDir] on checksum and identifier // Miscellaneous errors, including [os.ReadDir] on checksum and identifier
// directories, [Decode] on entry names and [os.RemoveAll] on inconsistent // directories, [Decode] on entry names and [os.RemoveAll] on inconsistent
// entries. // entries.
@@ -620,6 +719,13 @@ func (e *ScrubError) Error() string {
} }
segments = append(segments, s) segments = append(segments, s)
} }
if len(e.DanglingStatus) > 0 {
s := "dangling status:\n"
for _, id := range e.DanglingStatus {
s += Encode(id) + "\n"
}
segments = append(segments, s)
}
if len(e.Errs) > 0 { if len(e.Errs) > 0 {
s := "errors during scrub:\n" s := "errors during scrub:\n"
for pathname, errs := range e.errs { for pathname, errs := range e.errs {
@@ -798,6 +904,36 @@ func (c *Cache) Scrub(checks int) error {
wg.Wait() wg.Wait()
} }
dir = c.base.Append(dirStatus)
if entries, readdirErr := os.ReadDir(dir.String()); readdirErr != nil {
if !errors.Is(readdirErr, os.ErrNotExist) {
addErr(dir, readdirErr)
}
} else {
wg.Add(len(entries))
for _, ent := range entries {
w <- checkEntry{ent, func(ent os.DirEntry, want *Checksum) bool {
got := p.Get().(*Checksum)
defer p.Put(got)
if _, err := os.Stat(c.base.Append(
dirIdentifier,
ent.Name(),
).String()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
addErr(dir.Append(ent.Name()), err)
}
seMu.Lock()
se.DanglingStatus = append(se.DanglingStatus, *want)
seMu.Unlock()
return false
}
return true
}}
}
wg.Wait()
}
if len(c.identPending) > 0 { if len(c.identPending) > 0 {
addErr(c.base, errors.New( addErr(c.base, errors.New(
"scrub began with pending artifacts", "scrub began with pending artifacts",
@@ -828,6 +964,7 @@ func (c *Cache) Scrub(checks int) error {
if len(se.ChecksumMismatches) > 0 || if len(se.ChecksumMismatches) > 0 ||
len(se.DanglingIdentifiers) > 0 || len(se.DanglingIdentifiers) > 0 ||
len(se.DanglingStatus) > 0 ||
len(se.Errs) > 0 { len(se.Errs) > 0 {
slices.SortFunc(se.ChecksumMismatches, func(a, b ChecksumMismatchError) int { slices.SortFunc(se.ChecksumMismatches, func(a, b ChecksumMismatchError) int {
return bytes.Compare(a.Want[:], b.Want[:]) return bytes.Compare(a.Want[:], b.Want[:])
@@ -835,6 +972,9 @@ func (c *Cache) Scrub(checks int) error {
slices.SortFunc(se.DanglingIdentifiers, func(a, b ID) int { slices.SortFunc(se.DanglingIdentifiers, func(a, b ID) int {
return bytes.Compare(a[:], b[:]) return bytes.Compare(a[:], b[:])
}) })
slices.SortFunc(se.DanglingStatus, func(a, b ID) int {
return bytes.Compare(a[:], b[:])
})
return &se return &se
} else { } else {
return nil return nil
@@ -925,16 +1065,17 @@ func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return return
} }
id := c.Ident(f)
if c.msg.IsVerbose() { if c.msg.IsVerbose() {
rn := reportName(f, c.Ident(f)) rn := reportName(f, id)
c.msg.Verbosef("curing %s to memory...", rn) c.msg.Verbosef("curing %s in memory...", rn)
defer func() { defer func() {
if err == nil { if err == nil {
c.msg.Verbosef("cured %s to memory", rn) c.msg.Verbosef("opened %s for reading", rn)
} }
}() }()
} }
return f.Cure(&RContext{c}) return f.Cure(&RContext{common{c}})
} }
return return
} }
@@ -1131,6 +1272,9 @@ type DependencyCureError []*CureError
// unwrapM recursively expands underlying errors into a caller-supplied map. // unwrapM recursively expands underlying errors into a caller-supplied map.
func (e *DependencyCureError) unwrapM(me map[unique.Handle[ID]]*CureError) { func (e *DependencyCureError) unwrapM(me map[unique.Handle[ID]]*CureError) {
for _, err := range *e { for _, err := range *e {
if _, ok := me[err.Ident]; ok {
continue
}
if _e, ok := err.Err.(*DependencyCureError); ok { if _e, ok := err.Err.(*DependencyCureError); ok {
_e.unwrapM(me) _e.unwrapM(me)
continue continue
@@ -1214,13 +1358,6 @@ func (c *Cache) exitCure(a Artifact, curesExempt bool) {
<-c.cures <-c.cures
} }
// getWriter is like [bufio.NewWriter] but for bufioPool.
func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
bw := c.bufioPool.Get().(*bufio.Writer)
bw.Reset(w)
return bw
}
// measuredReader implements [io.ReadCloser] and measures the checksum during // measuredReader implements [io.ReadCloser] and measures the checksum during
// Close. If the underlying reader is not read to EOF, Close blocks until all // Close. If the underlying reader is not read to EOF, Close blocks until all
// remaining data is consumed and validated. // remaining data is consumed and validated.
@@ -1303,9 +1440,6 @@ func (r *RContext) NewMeasuredReader(
return r.cache.newMeasuredReader(rc, checksum) return r.cache.newMeasuredReader(rc, checksum)
} }
// putWriter adds bw to bufioPool.
func (c *Cache) putWriter(bw *bufio.Writer) { c.bufioPool.Put(bw) }
// cure implements Cure without checking the full dependency graph. // cure implements Cure without checking the full dependency graph.
func (c *Cache) cure(a Artifact, curesExempt bool) ( func (c *Cache) cure(a Artifact, curesExempt bool) (
pathname *check.Absolute, pathname *check.Absolute,
@@ -1437,7 +1571,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
if err = c.enterCure(a, curesExempt); err != nil { if err = c.enterCure(a, curesExempt); err != nil {
return return
} }
r, err = f.Cure(&RContext{c}) r, err = f.Cure(&RContext{common{c}})
if err == nil { if err == nil {
if checksumPathname == nil || c.IsStrict() { if checksumPathname == nil || c.IsStrict() {
h := sha512.New384() h := sha512.New384()
@@ -1513,7 +1647,12 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
return return
} }
t := TContext{c, c.base.Append(dirWork, ids), c.base.Append(dirTemp, ids)} t := TContext{
c.base.Append(dirWork, ids),
c.base.Append(dirTemp, ids),
ids, nil, nil, nil,
common{c},
}
switch ca := a.(type) { switch ca := a.(type) {
case TrivialArtifact: case TrivialArtifact:
defer t.destroy(&err) defer t.destroy(&err)
@@ -1651,6 +1790,18 @@ func (pending *pendingArtifactDep) cure(c *Cache) {
pending.errsMu.Unlock() pending.errsMu.Unlock()
} }
// OpenStatus attempts to open the status file associated to an [Artifact]. If
// err is nil, the caller must close the resulting reader.
func (c *Cache) OpenStatus(a Artifact) (r io.ReadSeekCloser, err error) {
c.identMu.RLock()
r, err = os.Open(c.base.Append(
dirStatus,
Encode(c.Ident(a).Value())).String(),
)
c.identMu.RUnlock()
return
}
// Close cancels all pending cures and waits for them to clean up. // Close cancels all pending cures and waits for them to clean up.
func (c *Cache) Close() { func (c *Cache) Close() {
c.closeOnce.Do(func() { c.closeOnce.Do(func() {
@@ -1699,6 +1850,7 @@ func open(
for _, name := range []string{ for _, name := range []string{
dirIdentifier, dirIdentifier,
dirChecksum, dirChecksum,
dirStatus,
dirWork, dirWork,
} { } {
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil && if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&
@@ -1713,13 +1865,16 @@ func open(
msg: msg, msg: msg,
base: base, base: base,
identPool: sync.Pool{New: func() any { return new(extIdent) }},
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]), ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
identErr: make(map[unique.Handle[ID]]error), identErr: make(map[unique.Handle[ID]]error),
identPending: make(map[unique.Handle[ID]]<-chan struct{}), identPending: make(map[unique.Handle[ID]]<-chan struct{}),
brPool: sync.Pool{New: func() any { return new(bufio.Reader) }},
bwPool: sync.Pool{New: func() any { return new(bufio.Writer) }},
} }
c.ctx, c.cancel = context.WithCancel(ctx) c.ctx, c.cancel = context.WithCancel(ctx)
c.identPool.New = func() any { return new(extIdent) }
c.bufioPool.New = func() any { return new(bufio.Writer) }
if lock || !testing.Testing() { if lock || !testing.Testing() {
if unlock, err := lockedfile.MutexAt( if unlock, err := lockedfile.MutexAt(

View File

@@ -314,6 +314,11 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
t.Fatal(err) t.Fatal(err)
} }
// destroy non-deterministic status files
if err := os.RemoveAll(base.Append("status").String()); err != nil {
t.Fatal(err)
}
var checksum pkg.Checksum var checksum pkg.Checksum
if err := pkg.HashDir(&checksum, base); err != nil { if err := pkg.HashDir(&checksum, base); err != nil {
t.Fatalf("HashDir: error = %v", err) t.Fatalf("HashDir: error = %v", err)
@@ -382,6 +387,9 @@ func cureMany(t *testing.T, c *pkg.Cache, steps []cureStep) {
} else if step.pathname != ignorePathname && !pathname.Is(step.pathname) { } else if step.pathname != ignorePathname && !pathname.Is(step.pathname) {
t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname) t.Fatalf("Cure: pathname = %q, want %q", pathname, step.pathname)
} else if checksum != makeChecksumH(step.checksum) { } else if checksum != makeChecksumH(step.checksum) {
if checksum == (unique.Handle[pkg.Checksum]{}) {
checksum = unique.Make(pkg.Checksum{})
}
t.Fatalf( t.Fatalf(
"Cure: checksum = %s, want %s", "Cure: checksum = %s, want %s",
pkg.Encode(checksum.Value()), pkg.Encode(step.checksum), pkg.Encode(checksum.Value()), pkg.Encode(step.checksum),

View File

@@ -10,8 +10,7 @@ import (
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"path"
"hakurei.app/container/check"
) )
const ( const (
@@ -100,7 +99,6 @@ func (e DisallowedTypeflagError) Error() string {
// Cure cures the [Artifact], producing a directory located at work. // Cure cures the [Artifact], producing a directory located at work.
func (a *tarArtifact) Cure(t *TContext) (err error) { func (a *tarArtifact) Cure(t *TContext) (err error) {
temp := t.GetTempDir()
var tr io.ReadCloser var tr io.ReadCloser
if tr, err = t.Open(a.f); err != nil { if tr, err = t.Open(a.f); err != nil {
return return
@@ -116,7 +114,9 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
err = closeErr err = closeErr
} }
}(tr) }(tr)
tr = io.NopCloser(tr) br := t.cache.getReader(tr)
defer t.cache.putReader(br)
tr = io.NopCloser(br)
switch a.compression { switch a.compression {
case TarUncompressed: case TarUncompressed:
@@ -137,14 +137,24 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
type dirTargetPerm struct { type dirTargetPerm struct {
path *check.Absolute path string
mode fs.FileMode mode fs.FileMode
} }
var madeDirectories []dirTargetPerm var madeDirectories []dirTargetPerm
if err = os.MkdirAll(temp.String(), 0700); err != nil { if err = os.MkdirAll(t.GetTempDir().String(), 0700); err != nil {
return return
} }
var root *os.Root
if root, err = os.OpenRoot(t.GetTempDir().String()); err != nil {
return
}
defer func() {
closeErr := root.Close()
if err == nil {
err = closeErr
}
}()
var header *tar.Header var header *tar.Header
r := tar.NewReader(tr) r := tar.NewReader(tr)
@@ -158,9 +168,8 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
} }
pathname := temp.Append(header.Name)
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir { if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
if err = os.MkdirAll(pathname.Dir().String(), 0700); err != nil { if err = root.MkdirAll(path.Dir(header.Name), 0700); err != nil {
return return
} }
} }
@@ -168,8 +177,8 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
switch typeflag { switch typeflag {
case tar.TypeReg: case tar.TypeReg:
var f *os.File var f *os.File
if f, err = os.OpenFile( if f, err = root.OpenFile(
pathname.String(), header.Name,
os.O_CREATE|os.O_EXCL|os.O_WRONLY, os.O_CREATE|os.O_EXCL|os.O_WRONLY,
header.FileInfo().Mode()&0500, header.FileInfo().Mode()&0500,
); err != nil { ); err != nil {
@@ -184,26 +193,29 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
break break
case tar.TypeLink: case tar.TypeLink:
if err = os.Link( if err = root.Link(
temp.Append(header.Linkname).String(), header.Linkname,
pathname.String(), header.Name,
); err != nil { ); err != nil {
return return
} }
break break
case tar.TypeSymlink: case tar.TypeSymlink:
if err = os.Symlink(header.Linkname, pathname.String()); err != nil { if err = root.Symlink(
header.Linkname,
header.Name,
); err != nil {
return return
} }
break break
case tar.TypeDir: case tar.TypeDir:
madeDirectories = append(madeDirectories, dirTargetPerm{ madeDirectories = append(madeDirectories, dirTargetPerm{
path: pathname, path: header.Name,
mode: header.FileInfo().Mode(), mode: header.FileInfo().Mode(),
}) })
if err = os.MkdirAll(pathname.String(), 0700); err != nil { if err = root.MkdirAll(header.Name, 0700); err != nil {
return return
} }
break break
@@ -220,7 +232,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
} }
if err == nil { if err == nil {
for _, e := range madeDirectories { for _, e := range madeDirectories {
if err = os.Chmod(e.path.String(), e.mode&0500); err != nil { if err = root.Chmod(e.path, e.mode&0500); err != nil {
return return
} }
} }
@@ -228,6 +240,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
return return
} }
temp := t.GetTempDir()
if err = os.Chmod(temp.String(), 0700); err != nil { if err = os.Chmod(temp.String(), 0700); err != nil {
return return
} }

View File

@@ -2,18 +2,19 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newAttr() pkg.Artifact { func (t Toolchain) newAttr() (pkg.Artifact, string) {
const ( const (
version = "2.5.2" version = "2.5.2"
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l" checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
) )
return t.NewViaMake("attr", version, t.NewPatchedSource( return t.NewPackage("attr", version, pkg.NewHTTPGetTar(
"attr", version, pkg.NewHTTPGetTar( nil, "https://download.savannah.nongnu.org/releases/attr/"+
nil, "https://download.savannah.nongnu.org/releases/attr/"+ "attr-"+version+".tar.gz",
"attr-"+version+".tar.gz", mustDecode(checksum),
mustDecode(checksum), pkg.TarGzip,
pkg.TarGzip, ), &PackageAttr{
), true, [2]string{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001 Patches: [][2]string{
{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me> From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
Date: Sat, 30 Mar 2024 10:17:10 +0100 Date: Sat, 30 Mar 2024 10:17:10 +0100
Subject: tools/attr.c: Add missing libgen.h include for basename(3) Subject: tools/attr.c: Add missing libgen.h include for basename(3)
@@ -38,53 +39,68 @@ index f12e4af..6a3c1e9 100644
#include <attr/attributes.h> #include <attr/attributes.h>
-- --
cgit v1.1`}, [2]string{"musl-errno", `diff --git a/test/attr.test b/test/attr.test cgit v1.1`},
{"musl-errno", `diff --git a/test/attr.test b/test/attr.test
index 6ce2f9b..e9bde92 100644 index 6ce2f9b..e9bde92 100644
--- a/test/attr.test --- a/test/attr.test
+++ b/test/attr.test +++ b/test/attr.test
@@ -11,7 +11,7 @@ Try various valid and invalid names @@ -11,7 +11,7 @@ Try various valid and invalid names
$ touch f $ touch f
$ setfattr -n user -v value f $ setfattr -n user -v value f
- > setfattr: f: Operation not supported - > setfattr: f: Operation not supported
+ > setfattr: f: Not supported + > setfattr: f: Not supported
$ setfattr -n user. -v value f $ setfattr -n user. -v value f
> setfattr: f: Invalid argument > setfattr: f: Invalid argument
`}, `},
), &MakeAttr{ },
ScriptEarly: ` ScriptEarly: `
ln -s ../../system/bin/perl /usr/bin ln -s ../../system/bin/perl /usr/bin
`, `,
Configure: [][2]string{ }, (*MakeHelper)(nil),
{"enable-static"}, Perl,
}, ), version
},
t.Load(Perl),
)
} }
func init() { artifactsF[Attr] = Toolchain.newAttr } func init() {
artifactsM[Attr] = Metadata{
f: Toolchain.newAttr,
func (t Toolchain) newACL() pkg.Artifact { Name: "attr",
Description: "Commands for Manipulating Filesystem Extended Attributes",
Website: "https://savannah.nongnu.org/projects/attr/",
ID: 137,
}
}
func (t Toolchain) newACL() (pkg.Artifact, string) {
const ( const (
version = "2.3.2" version = "2.3.2"
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P" checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
) )
return t.NewViaMake("acl", version, pkg.NewHTTPGetTar( return t.NewPackage("acl", version, pkg.NewHTTPGetTar(
nil, nil, "https://download.savannah.nongnu.org/releases/acl/"+
"https://download.savannah.nongnu.org/releases/acl/"+
"acl-"+version+".tar.gz", "acl-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), nil, &MakeHelper{
Configure: [][2]string{
{"enable-static"},
},
// makes assumptions about uid_map/gid_map // makes assumptions about uid_map/gid_map
SkipCheck: true, SkipCheck: true,
}, },
t.Load(Attr), Attr,
) ), version
}
func init() {
artifactsM[ACL] = Metadata{
f: Toolchain.newACL,
Name: "acl",
Description: "Commands for Manipulating POSIX Access Control Lists",
Website: "https://savannah.nongnu.org/projects/acl/",
ID: 16,
}
} }
func init() { artifactsF[ACL] = Toolchain.newACL }

View File

@@ -1,6 +1,11 @@
package rosa package rosa
import ( import (
"context"
"encoding/json"
"errors"
"net/http"
"strconv"
"sync" "sync"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
@@ -10,20 +15,44 @@ import (
type PArtifact int type PArtifact int
const ( const (
ACL PArtifact = iota LLVMCompilerRT PArtifact = iota
LLVMRuntimes
LLVMClang
// ImageInitramfs is the Rosa OS initramfs archive.
ImageInitramfs
// Kernel is the generic Rosa OS Linux kernel.
Kernel
// KernelHeaders is an installation of kernel headers for [Kernel].
KernelHeaders
// KernelSource is a writable kernel source tree installed to [AbsUsrSrc].
KernelSource
ACL
ArgpStandalone
Attr Attr
Autoconf Autoconf
Automake Automake
BC
Bash Bash
Binutils Binutils
Bison
Bzip2
CMake CMake
Coreutils Coreutils
Curl Curl
DTC
Diffutils Diffutils
Elfutils
Fakeroot
Findutils Findutils
Flex
Fuse Fuse
Gawk
GMP GMP
GLib
Gawk
GenInitCPIO
Gettext Gettext
Git Git
Go Go
@@ -32,10 +61,11 @@ const (
Gzip Gzip
Hakurei Hakurei
HakureiDist HakureiDist
IniConfig Kmod
KernelHeaders
LibXau LibXau
Libcap
Libexpat Libexpat
Libiconv
Libpsl Libpsl
Libffi Libffi
Libgd Libgd
@@ -43,31 +73,53 @@ const (
Libseccomp Libseccomp
Libucontext Libucontext
Libxml2 Libxml2
Libxslt
M4 M4
MPC MPC
MPFR MPFR
Make Make
Meson Meson
Mksh Mksh
MuslFts
MuslObstack
NSS NSS
NSSCACert NSSCACert
Ncurses
Ninja Ninja
OpenSSL OpenSSL
Packaging PCRE2
Patch Patch
Perl Perl
PerlLocaleGettext
PerlMIMECharset
PerlModuleBuild
PerlPodParser
PerlSGMLS
PerlTermReadKey
PerlTextCharWidth
PerlTextWrapI18N
PerlUnicodeGCString
PerlYAMLTiny
PkgConfig PkgConfig
Pluggy Procps
PyTest
Pygments
Python Python
PythonIniConfig
PythonPackaging
PythonPluggy
PythonPyTest
PythonPygments
QEMU
Rsync Rsync
Sed Sed
Setuptools Setuptools
SquashfsTools
TamaGo
Tar
Texinfo
Toybox Toybox
toyboxEarly toyboxEarly
Unzip Unzip
utilMacros UtilLinux
Wayland Wayland
WaylandProtocols WaylandProtocols
XCB XCB
@@ -75,101 +127,150 @@ const (
Xproto Xproto
XZ XZ
Zlib Zlib
Zstd
buildcatrust // PresetUnexportedStart is the first unexported preset.
PresetUnexportedStart
buildcatrust = iota - 1
utilMacros
// Musl is a standalone libc that does not depend on the toolchain.
Musl
// gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate // gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate
// stages only. This preset and its direct output must never be exposed. // stages only. This preset and its direct output must never be exposed.
gcc gcc
// _presetEnd is the total number of presets and does not denote a preset. // Stage0 is a tarball containing all compile-time dependencies of artifacts
_presetEnd // part of the [Std] toolchain.
Stage0
// PresetEnd is the total number of presets and does not denote a preset.
PresetEnd
) )
// Metadata is stage-agnostic information of a [PArtifact] not directly
// representable in the resulting [pkg.Artifact].
type Metadata struct {
f func(t Toolchain) (a pkg.Artifact, version string)
// Unique package name.
Name string `json:"name"`
// Short user-facing description.
Description string `json:"description"`
// Project home page.
Website string `json:"website,omitempty"`
// Project identifier on [Anitya].
//
// [Anitya]: https://release-monitoring.org/
ID int `json:"-"`
// Optional custom version checking behaviour.
latest func(v *Versions) string
}
// GetLatest returns the latest version described by v.
func (meta *Metadata) GetLatest(v *Versions) string {
if meta.latest != nil {
return meta.latest(v)
}
return v.Latest
}
// Unversioned denotes an unversioned [PArtifact].
const Unversioned = "\x00"
// UnpopulatedIDError is returned by [Metadata.GetLatest] for an instance of
// [Metadata] where ID is not populated.
type UnpopulatedIDError struct{}
func (UnpopulatedIDError) Unwrap() error { return errors.ErrUnsupported }
func (UnpopulatedIDError) Error() string { return "Anitya ID is not populated" }
// Versions are package versions returned by Anitya.
type Versions struct {
// The latest version for the project, as determined by the version sorting algorithm.
Latest string `json:"latest_version"`
// List of all versions that arent flagged as pre-release.
Stable []string `json:"stable_versions"`
// List of all versions stored, sorted from newest to oldest.
All []string `json:"versions"`
}
// getStable returns the first Stable version, or Latest if that is unavailable.
func (v *Versions) getStable() string {
if len(v.Stable) == 0 {
return v.Latest
}
return v.Stable[0]
}
// GetVersions returns versions fetched from Anitya.
func (meta *Metadata) GetVersions(ctx context.Context) (*Versions, error) {
if meta.ID == 0 {
return nil, UnpopulatedIDError{}
}
var resp *http.Response
if req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://release-monitoring.org/api/v2/versions/?project_id="+
strconv.Itoa(meta.ID),
nil,
); err != nil {
return nil, err
} else {
req.Header.Set("User-Agent", "Rosa/1.1")
if resp, err = http.DefaultClient.Do(req); err != nil {
return nil, err
}
}
var v Versions
err := json.NewDecoder(resp.Body).Decode(&v)
return &v, errors.Join(err, resp.Body.Close())
}
var ( var (
// artifactsF is an array of functions for the result of [PArtifact]. // artifactsM is an array of [PArtifact] metadata.
artifactsF [_presetEnd]func(t Toolchain) pkg.Artifact artifactsM [PresetEnd]Metadata
// artifacts stores the result of artifactsF. // artifacts stores the result of Metadata.f.
artifacts [_toolchainEnd][len(artifactsF)]pkg.Artifact artifacts [_toolchainEnd][len(artifactsM)]pkg.Artifact
// versions stores the version of [PArtifact].
versions [_toolchainEnd][len(artifactsM)]string
// artifactsOnce is for lazy initialisation of artifacts. // artifactsOnce is for lazy initialisation of artifacts.
artifactsOnce [_toolchainEnd][len(artifactsF)]sync.Once artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once
) )
// GetMetadata returns [Metadata] of a [PArtifact].
func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] }
// Load returns the resulting [pkg.Artifact] of [PArtifact]. // Load returns the resulting [pkg.Artifact] of [PArtifact].
func (t Toolchain) Load(p PArtifact) pkg.Artifact { func (t Toolchain) Load(p PArtifact) pkg.Artifact {
artifactsOnce[t][p].Do(func() { artifactsOnce[t][p].Do(func() {
artifacts[t][p] = artifactsF[p](t) artifacts[t][p], versions[t][p] = artifactsM[p].f(t)
}) })
return artifacts[t][p] return artifacts[t][p]
} }
// Version returns the version string of [PArtifact].
func (t Toolchain) Version(p PArtifact) string {
artifactsOnce[t][p].Do(func() {
artifacts[t][p], versions[t][p] = artifactsM[p].f(t)
})
return versions[t][p]
}
// ResolveName returns a [PArtifact] by name. // ResolveName returns a [PArtifact] by name.
func ResolveName(name string) (p PArtifact, ok bool) { func ResolveName(name string) (p PArtifact, ok bool) {
p, ok = map[string]PArtifact{ for i := range PresetUnexportedStart {
"acl": ACL, if name == artifactsM[i].Name {
"attr": Attr, return i, true
"autoconf": Autoconf, }
"automake": Automake, }
"bash": Bash, return 0, false
"binutils": Binutils,
"cmake": CMake,
"coreutils": Coreutils,
"curl": Curl,
"diffutils": Diffutils,
"findutils": Findutils,
"fuse": Fuse,
"gawk": Gawk,
"gmp": GMP,
"gettext": Gettext,
"git": Git,
"go": Go,
"gperf": Gperf,
"grep": Grep,
"gzip": Gzip,
"hakurei": Hakurei,
"hakurei-dist": HakureiDist,
"iniconfig": IniConfig,
"kernel-headers": KernelHeaders,
"libXau": LibXau,
"libexpat": Libexpat,
"libpsl": Libpsl,
"libseccomp": Libseccomp,
"libucontext": Libucontext,
"libxml2": Libxml2,
"libffi": Libffi,
"libgd": Libgd,
"libtool": Libtool,
"m4": M4,
"mpc": MPC,
"mpfr": MPFR,
"make": Make,
"meson": Meson,
"mksh": Mksh,
"nss": NSS,
"nss-cacert": NSSCACert,
"ninja": Ninja,
"openssl": OpenSSL,
"packaging": Packaging,
"patch": Patch,
"perl": Perl,
"pkg-config": PkgConfig,
"pluggy": Pluggy,
"pytest": PyTest,
"pygments": Pygments,
"python": Python,
"rsync": Rsync,
"sed": Sed,
"setuptools": Setuptools,
"toybox": Toybox,
"unzip": Unzip,
"wayland": Wayland,
"wayland-protocols": WaylandProtocols,
"xcb": XCB,
"xcb-proto": XCBProto,
"xproto": Xproto,
"xz": XZ,
"zlib": Zlib,
}[name]
return
} }

66
internal/rosa/all_test.go Normal file
View File

@@ -0,0 +1,66 @@
package rosa_test
import (
"testing"
"hakurei.app/internal/rosa"
)
func TestLoad(t *testing.T) {
t.Parallel()
for i := range rosa.PresetEnd {
p := rosa.PArtifact(i)
t.Run(rosa.GetMetadata(p).Name, func(t *testing.T) {
t.Parallel()
rosa.Std.Load(p)
})
}
}
func TestResolveName(t *testing.T) {
t.Parallel()
for i := range rosa.PresetUnexportedStart {
p := i
name := rosa.GetMetadata(p).Name
t.Run(name, func(t *testing.T) {
t.Parallel()
if got, ok := rosa.ResolveName(name); !ok {
t.Fatal("ResolveName: ok = false")
} else if got != p {
t.Fatalf("ResolveName: %d, want %d", got, p)
}
})
}
}
func TestResolveNameUnexported(t *testing.T) {
t.Parallel()
for i := rosa.PresetUnexportedStart; i < rosa.PresetEnd; i++ {
p := i
name := rosa.GetMetadata(p).Name
t.Run(name, func(t *testing.T) {
t.Parallel()
if got, ok := rosa.ResolveName(name); ok {
t.Fatalf("ResolveName: resolved unexported preset %d", got)
}
})
}
}
func TestUnique(t *testing.T) {
t.Parallel()
names := make(map[string]struct{})
for i := range rosa.PresetEnd {
name := rosa.GetMetadata(rosa.PArtifact(i)).Name
if _, ok := names[name]; ok {
t.Fatalf("name %s is not unique", name)
}
names[name] = struct{}{}
}
}

View File

@@ -0,0 +1,36 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newArgpStandalone() (pkg.Artifact, string) {
const (
version = "1.3"
checksum = "vtW0VyO2pJ-hPyYmDI2zwSLS8QL0sPAUKC1t3zNYbwN2TmsaE-fADhaVtNd3eNFl"
)
return t.NewPackage("argp-standalone", version, pkg.NewHTTPGetTar(
nil, "http://www.lysator.liu.se/~nisse/misc/"+
"argp-standalone-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
Env: []string{
"CC=cc -std=gnu89 -fPIC",
},
}, &MakeHelper{
Install: `
install -D -m644 /usr/src/argp-standalone/argp.h /work/system/include/argp.h
install -D -m755 libargp.a /work/system/lib/libargp.a
`,
},
Diffutils,
), version
}
func init() {
artifactsM[ArgpStandalone] = Metadata{
f: Toolchain.newArgpStandalone,
Name: "argp-standalone",
Description: "hierarchical argument parsing library broken out from glibc",
Website: "http://www.lysator.liu.se/~nisse/misc/",
}
}

38
internal/rosa/bzip2.go Normal file
View File

@@ -0,0 +1,38 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newBzip2() (pkg.Artifact, string) {
const (
version = "1.0.8"
checksum = "cTLykcco7boom-s05H1JVsQi1AtChYL84nXkg_92Dm1Xt94Ob_qlMg_-NSguIK-c"
)
return t.NewPackage("bzip2", version, pkg.NewHTTPGetTar(
nil, "https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
Writable: true,
EnterSource: true,
}, &MakeHelper{
// uses source tree as scratch space
SkipConfigure: true,
SkipCheck: true,
InPlace: true,
Make: []string{
"CC=cc",
},
Install: "make PREFIX=/work/system install",
}), version
}
func init() {
artifactsM[Bzip2] = Metadata{
f: Toolchain.newBzip2,
Name: "bzip2",
Description: "a freely available, patent free, high-quality data compressor",
Website: "https://sourceware.org/bzip2/",
ID: 237,
}
}

View File

@@ -1,82 +1,169 @@
package rosa package rosa
import ( import (
"path"
"slices" "slices"
"strings" "strings"
"hakurei.app/container/check"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
func (t Toolchain) newCMake() pkg.Artifact { func (t Toolchain) newCMake() (pkg.Artifact, string) {
const ( const (
version = "4.2.1" version = "4.2.3"
checksum = "Y3OdbMsob6Xk2y1DCME6z4Fryb5_TkFD7knRT8dTNIRtSqbiCJyyDN9AxggN_I75" checksum = "Y4uYGnLrDQX78UdzH7fMzfok46Nt_1taDIHSmqgboU1yFi6f0iAXBDegMCu4eS-J"
) )
return t.New("cmake-"+version, 0, []pkg.Artifact{ return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
t.Load(Make), nil, "https://github.com/Kitware/CMake/releases/download/"+
t.Load(KernelHeaders), "v"+version+"/cmake-"+version+".tar.gz",
}, nil, nil, ` mustDecode(checksum),
cd "$(mktemp -d)" pkg.TarGzip,
/usr/src/cmake/bootstrap \ ), &PackageAttr{
--prefix=/system \ // test suite expects writable source tree
--parallel="$(nproc)" \ Writable: true,
-- \
-DCMAKE_USE_OPENSSL=OFF // expected to be writable in the copy made during bootstrap
make "-j$(nproc)" Chmod: true,
make DESTDIR=/work install
`, pkg.Path(AbsUsrSrc.Append("cmake"), true, t.NewPatchedSource( Patches: [][2]string{
// expected to be writable in the copy made during bootstrap {"bootstrap-test-no-openssl", `diff --git a/Tests/BootstrapTest.cmake b/Tests/BootstrapTest.cmake
"cmake", version, pkg.NewHTTPGetTar( index 137de78bc1..b4da52e664 100644
nil, "https://github.com/Kitware/CMake/releases/download/"+ --- a/Tests/BootstrapTest.cmake
"v"+version+"/cmake-"+version+".tar.gz", +++ b/Tests/BootstrapTest.cmake
mustDecode(checksum), @@ -9,7 +9,7 @@ if(NOT nproc EQUAL 0)
pkg.TarGzip, endif()
), false, message(STATUS "running bootstrap: ${bootstrap} ${ninja_arg} ${parallel_arg}")
))) execute_process(
} - COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg}
func init() { artifactsF[CMake] = Toolchain.newCMake } + COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg} -- -DCMAKE_USE_OPENSSL=OFF
WORKING_DIRECTORY "${bin_dir}"
RESULT_VARIABLE result
)
`},
{"disable-broken-tests-musl", `diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 2ead810437..f85cbb8b1c 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -384,7 +384,6 @@ if(BUILD_TESTING)
add_subdirectory(CMakeLib)
endif()
add_subdirectory(CMakeOnly)
- add_subdirectory(RunCMake)
add_subdirectory(FindPackageModeMakefileTest)
@@ -528,9 +527,6 @@ if(BUILD_TESTING)
-DCMake_TEST_CUDA:BOOL=${CMake_TEST_CUDA}
-DCMake_INSTALL_NAME_TOOL_BUG:BOOL=${CMake_INSTALL_NAME_TOOL_BUG}
)
- ADD_TEST_MACRO(ExportImport ExportImport)
- set_property(TEST ExportImport APPEND
- PROPERTY LABELS "CUDA")
ADD_TEST_MACRO(Unset Unset)
ADD_TEST_MACRO(PolicyScope PolicyScope)
ADD_TEST_MACRO(EmptyLibrary EmptyLibrary)
@@ -624,7 +620,6 @@ if(BUILD_TESTING)
# run test for BundleUtilities on supported platforms/compilers
if((MSVC OR
MINGW OR
- CMAKE_SYSTEM_NAME MATCHES "Linux" OR
CMAKE_SYSTEM_NAME MATCHES "Darwin")
AND NOT CMAKE_GENERATOR STREQUAL "Watcom WMake")
@@ -3095,10 +3090,6 @@ if(BUILD_TESTING)
"${CMake_SOURCE_DIR}/Tests/CTestTestFdSetSize/test.cmake.in"
"${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake"
@ONLY ESCAPE_QUOTES)
- add_test(CTestTestFdSetSize ${CMAKE_CTEST_COMMAND}
- -S "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake" -j20 -V --timeout 120
- --output-log "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/testOutput.log"
- )
if(CMAKE_TESTS_CDASH_SERVER)
set(regex "^([^:]+)://([^/]+)(.*)$")
`},
},
}, &MakeHelper{
OmitDefaults: true,
ConfigureName: "/usr/src/cmake/bootstrap",
Configure: [][2]string{
{"prefix", "/system"},
{"parallel", `"$(nproc)"`},
{"--"},
{"-DCMAKE_USE_OPENSSL", "OFF"},
{"-DCMake_TEST_NO_NETWORK", "ON"},
},
Check: []string{
"CTEST_OUTPUT_ON_FAILURE=1",
"CTEST_PARALLEL_LEVEL=128",
"test",
},
},
KernelHeaders,
), version
}
func init() {
artifactsM[CMake] = Metadata{
f: Toolchain.newCMake,
Name: "cmake",
Description: "cross-platform, open-source build system",
Website: "https://cmake.org/",
ID: 306,
}
}
// CMakeHelper is the [CMake] build system helper.
type CMakeHelper struct {
// Joined with name with a dash if non-empty.
Variant string
// CMakeAttr holds the project-specific attributes that will be applied to a new
// [pkg.Artifact] compiled via [CMake].
type CMakeAttr struct {
// Path elements joined with source. // Path elements joined with source.
Append []string Append []string
// Use source tree as scratch space.
Writable bool
// CMake CACHE entries. // CMake CACHE entries.
Cache [][2]string Cache [][2]string
// Additional environment variables.
Env []string
// Runs before cmake.
ScriptEarly string
// Runs after cmake, replaces default.
ScriptConfigured string
// Runs after install. // Runs after install.
Script string Script string
// Override the default installation prefix [AbsSystem].
Prefix *check.Absolute
// Passed through to [Toolchain.New].
Paths []pkg.ExecPath
// Passed through to [Toolchain.New].
Flag int
} }
// NewViaCMake returns a [pkg.Artifact] for compiling and installing via [CMake]. var _ Helper = new(CMakeHelper)
func (t Toolchain) NewViaCMake(
name, version, variant string, // name returns its arguments and an optional variant string joined with '-'.
source pkg.Artifact, func (attr *CMakeHelper) name(name, version string) string {
attr *CMakeAttr, if attr != nil && attr.Variant != "" {
extra ...pkg.Artifact, name += "-" + attr.Variant
) pkg.Artifact {
if name == "" || version == "" || variant == "" {
panic("names must be non-empty")
} }
return name + "-" + version
}
// extra returns a hardcoded slice of [CMake] and [Ninja].
func (*CMakeHelper) extra(int) []PArtifact {
return []PArtifact{CMake, Ninja}
}
// wantsChmod returns false.
func (*CMakeHelper) wantsChmod() bool { return false }
// wantsWrite returns false.
func (*CMakeHelper) wantsWrite() bool { return false }
// scriptEarly returns the zero value.
func (*CMakeHelper) scriptEarly() string { return "" }
// createDir returns true.
func (*CMakeHelper) createDir() bool { return true }
// wantsDir returns a hardcoded, deterministic pathname.
func (*CMakeHelper) wantsDir() string { return "/cure/" }
// script generates the cure script.
func (attr *CMakeHelper) script(name string) string {
if attr == nil { if attr == nil {
attr = &CMakeAttr{ attr = &CMakeHelper{
Cache: [][2]string{ Cache: [][2]string{
{"CMAKE_BUILD_TYPE", "Release"}, {"CMAKE_BUILD_TYPE", "Release"},
}, },
@@ -86,40 +173,21 @@ func (t Toolchain) NewViaCMake(
panic("CACHE must be non-empty") panic("CACHE must be non-empty")
} }
scriptConfigured := "cmake --build .\ncmake --install .\n" return `
if attr.ScriptConfigured != "" {
scriptConfigured = attr.ScriptConfigured
}
prefix := attr.Prefix
if prefix == nil {
prefix = AbsSystem
}
sourcePath := AbsUsrSrc.Append(name)
return t.New(name+"-"+variant+"-"+version, attr.Flag, stage3Concat(t, extra,
t.Load(CMake),
t.Load(Ninja),
), nil, slices.Concat([]string{
"ROSA_SOURCE=" + sourcePath.String(),
"ROSA_CMAKE_SOURCE=" + sourcePath.Append(attr.Append...).String(),
"ROSA_INSTALL_PREFIX=/work" + prefix.String(),
}, attr.Env), attr.ScriptEarly+`
mkdir /cure && cd /cure
cmake -G Ninja \ cmake -G Ninja \
-DCMAKE_C_COMPILER_TARGET="${ROSA_TRIPLE}" \ -DCMAKE_C_COMPILER_TARGET="${ROSA_TRIPLE}" \
-DCMAKE_CXX_COMPILER_TARGET="${ROSA_TRIPLE}" \ -DCMAKE_CXX_COMPILER_TARGET="${ROSA_TRIPLE}" \
-DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \ -DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \
`+strings.Join(slices.Collect(func(yield func(string) bool) { ` + strings.Join(slices.Collect(func(yield func(string) bool) {
for _, v := range attr.Cache { for _, v := range attr.Cache {
if !yield("-D" + v[0] + "=" + v[1]) { if !yield("-D" + v[0] + "=" + v[1]) {
return return
} }
} }
}), " \\\n\t")+` \ }), " \\\n\t") + ` \
-DCMAKE_INSTALL_PREFIX="${ROSA_INSTALL_PREFIX}" \ -DCMAKE_INSTALL_PREFIX=/work/system \
"${ROSA_CMAKE_SOURCE}" '/usr/src/` + name + `/` + path.Join(attr.Append...) + `'
`+scriptConfigured+attr.Script, slices.Concat([]pkg.ExecPath{ cmake --build .
pkg.Path(sourcePath, attr.Writable, source), cmake --install .
}, attr.Paths)...) ` + attr.Script
} }

View File

@@ -2,31 +2,39 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newCurl() pkg.Artifact { func (t Toolchain) newCurl() (pkg.Artifact, string) {
const ( const (
version = "8.18.0" version = "8.18.0"
checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i" checksum = "YpOolP_sx1DIrCEJ3elgVAu0wTLDS-EZMZFvOP0eha7FaLueZUlEpuMwDzJNyi7i"
) )
return t.NewViaMake("curl", version, pkg.NewHTTPGetTar( return t.NewPackage("curl", version, pkg.NewHTTPGetTar(
nil, "https://curl.se/download/curl-"+version+".tar.bz2", nil, "https://curl.se/download/curl-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &MakeAttr{ ), nil, &MakeHelper{
Env: []string{
"TFLAGS=-j256",
},
Configure: [][2]string{ Configure: [][2]string{
{"with-openssl"}, {"with-openssl"},
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"}, {"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
}, },
ScriptConfigured: ` Check: []string{
make "-j$(nproc)" "TFLAGS=-j256",
`, "check",
},
}, },
t.Load(Perl), Perl,
t.Load(Libpsl), Libpsl,
t.Load(OpenSSL), OpenSSL,
) ), version
}
func init() {
artifactsM[Curl] = Metadata{
f: Toolchain.newCurl,
Name: "curl",
Description: "command line tool and library for transferring data with URLs",
Website: "https://curl.se/",
ID: 381,
}
} }
func init() { artifactsF[Curl] = Toolchain.newCurl }

43
internal/rosa/dtc.go Normal file
View File

@@ -0,0 +1,43 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newDTC() (pkg.Artifact, string) {
const (
version = "1.7.2"
checksum = "vUoiRynPyYRexTpS6USweT5p4SVHvvVJs8uqFkkVD-YnFjwf6v3elQ0-Etrh00Dt"
)
return t.NewPackage("dtc", version, pkg.NewHTTPGetTar(
nil, "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
"dtc-v"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
// works around buggy test:
// fdtdump-runtest.sh /usr/src/dtc/tests/fdtdump.dts
Writable: true,
Chmod: true,
}, &MesonHelper{
Setup: [][2]string{
{"Dyaml", "disabled"},
{"Dstatic-build", "true"},
},
},
Flex,
Bison,
M4,
Coreutils,
Diffutils,
), version
}
func init() {
artifactsM[DTC] = Metadata{
f: Toolchain.newDTC,
Name: "dtc",
Description: "The Device Tree Compiler",
Website: "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/",
ID: 16911,
}
}

51
internal/rosa/elfutils.go Normal file
View File

@@ -0,0 +1,51 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newElfutils() (pkg.Artifact, string) {
const (
version = "0.194"
checksum = "Q3XUygUPv9vR1TkWucwUsQ8Kb1_F6gzk-KMPELr3cC_4AcTrprhVPMvN0CKkiYRa"
)
return t.NewPackage("elfutils", version, pkg.NewHTTPGetTar(
nil, "https://sourceware.org/elfutils/ftp/"+
version+"/elfutils-"+version+".tar.bz2",
mustDecode(checksum),
pkg.TarBzip2,
), &PackageAttr{
Env: []string{
"CC=cc" +
// nonstandard glibc extension
" -DFNM_EXTMATCH=0",
},
}, &MakeHelper{
// nonstandard glibc extension
SkipCheck: true,
Configure: [][2]string{
{"enable-deterministic-archives"},
},
},
M4,
PkgConfig,
Zlib,
Bzip2,
Zstd,
ArgpStandalone,
MuslFts,
MuslObstack,
KernelHeaders,
), version
}
func init() {
artifactsM[Elfutils] = Metadata{
f: Toolchain.newElfutils,
Name: "elfutils",
Description: "utilities and libraries to handle ELF files and DWARF data",
Website: "https://sourceware.org/elfutils/",
ID: 5679,
}
}

61
internal/rosa/fakeroot.go Normal file
View File

@@ -0,0 +1,61 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newFakeroot() (pkg.Artifact, string) {
const (
version = "1.37.2"
checksum = "4ve-eDqVspzQ6VWDhPS0NjW3aSenBJcPAJq_BFT7OOFgUdrQzoTBxZWipDAGWxF8"
)
return t.NewPackage("fakeroot", version, pkg.NewHTTPGetTar(
nil, "https://salsa.debian.org/clint/fakeroot/-/archive/upstream/"+
version+"/fakeroot-upstream-"+version+".tar.bz2",
mustDecode(checksum),
pkg.TarBzip2,
), &PackageAttr{
Patches: [][2]string{
{"remove-broken-docs", `diff --git a/doc/Makefile.am b/doc/Makefile.am
index f135ad9..85c784c 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,5 +1,4 @@
AUTOMAKE_OPTIONS=foreign
-SUBDIRS = de es fr nl pt ro sv
man_MANS = faked.1 fakeroot.1
`},
},
Env: []string{
"CONFIG_SHELL=/bin/sh",
},
}, &MakeHelper{
Generate: "./bootstrap",
// makes assumptions about /etc/passwd
SkipCheck: true,
},
M4,
Perl,
Autoconf,
Automake,
Libtool,
PkgConfig,
Attr,
Libcap,
KernelHeaders,
), version
}
func init() {
artifactsM[Fakeroot] = Metadata{
f: Toolchain.newFakeroot,
Name: "fakeroot",
Description: "tool for simulating superuser privileges",
Website: "https://salsa.debian.org/clint/fakeroot",
ID: 12048,
}
}

31
internal/rosa/flex.go Normal file
View File

@@ -0,0 +1,31 @@
package rosa
import (
"hakurei.app/internal/pkg"
)
func (t Toolchain) newFlex() (pkg.Artifact, string) {
const (
version = "2.6.4"
checksum = "p9POjQU7VhgOf3x5iFro8fjhy0NOanvA7CTeuWS_veSNgCixIJshTrWVkc5XLZkB"
)
return t.NewPackage("flex", version, pkg.NewHTTPGetTar(
nil, "https://github.com/westes/flex/releases/download/"+
"v"+version+"/flex-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, (*MakeHelper)(nil),
M4,
), version
}
func init() {
artifactsM[Flex] = Metadata{
f: Toolchain.newFlex,
Name: "flex",
Description: "scanner generator for lexing in C and C++",
Website: "https://github.com/westes/flex/",
ID: 819,
}
}

View File

@@ -2,44 +2,45 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newFuse() pkg.Artifact { func (t Toolchain) newFuse() (pkg.Artifact, string) {
const ( const (
version = "3.18.1" version = "3.18.1"
checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6" checksum = "COb-BgJRWXLbt9XUkNeuiroQizpMifXqxgieE1SlkMXhs_WGSyJStrmyewAw2hd6"
) )
return t.New("fuse-"+version, 0, []pkg.Artifact{ return t.NewPackage("fuse", version, pkg.NewHTTPGetTar(
t.Load(Python),
t.Load(Meson),
t.Load(Ninja),
t.Load(IniConfig),
t.Load(Packaging),
t.Load(Pluggy),
t.Load(Pygments),
t.Load(PyTest),
t.Load(KernelHeaders),
}, nil, nil, `
cd "$(mktemp -d)"
meson setup \
--reconfigure \
--buildtype=release \
--prefix=/system \
--prefer-static \
-Dtests=true \
-Duseroot=false \
-Dinitscriptdir=/system/init.d \
-Ddefault_library=both \
. /usr/src/fuse
meson compile
python3 -m pytest test/
meson install \
--destdir=/work
`, pkg.Path(AbsUsrSrc.Append("fuse"), false, pkg.NewHTTPGetTar(
nil, "https://github.com/libfuse/libfuse/releases/download/"+ nil, "https://github.com/libfuse/libfuse/releases/download/"+
"fuse-"+version+"/fuse-"+version+".tar.gz", "fuse-"+version+"/fuse-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
))) ), nil, &MesonHelper{
Setup: [][2]string{
{"Ddefault_library", "both"},
{"Dtests", "true"},
{"Duseroot", "false"},
{"Dinitscriptdir", "/system/etc"},
},
ScriptCompiled: "python3 -m pytest test/",
// this project uses pytest
SkipTest: true,
},
PythonIniConfig,
PythonPackaging,
PythonPluggy,
PythonPygments,
PythonPyTest,
KernelHeaders,
), version
}
func init() {
artifactsM[Fuse] = Metadata{
f: Toolchain.newFuse,
Name: "fuse",
Description: "the reference implementation of the Linux FUSE interface",
Website: "https://github.com/libfuse/libfuse/",
ID: 861,
}
} }
func init() { artifactsF[Fuse] = Toolchain.newFuse }

View File

@@ -1,40 +1,30 @@
package rosa package rosa
import ( import "hakurei.app/internal/pkg"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newGit() pkg.Artifact { func (t Toolchain) newGit() (pkg.Artifact, string) {
const ( const (
version = "2.52.0" version = "2.53.0"
checksum = "uH3J1HAN_c6PfGNJd2OBwW4zo36n71wmkdvityYnrh8Ak0D1IifiAvEWz9Vi9DmS" checksum = "rlqSTeNgSeVKJA7nvzGqddFH8q3eFEPB4qRZft-4zth8wTHnbTbm7J90kp_obHGm"
) )
return t.NewViaMake("git", version, t.NewPatchedSource( return t.NewPackage("git", version, pkg.NewHTTPGetTar(
"git", version, pkg.NewHTTPGetTar( nil, "https://www.kernel.org/pub/software/scm/git/"+
nil, "https://www.kernel.org/pub/software/scm/git/"+ "git-"+version+".tar.gz",
"git-"+version+".tar.gz", mustDecode(checksum),
mustDecode(checksum), pkg.TarGzip,
pkg.TarGzip, ), &PackageAttr{
), false,
), &MakeAttr{
// uses source tree as scratch space
Writable: true,
InPlace: true,
// test suite in subdirectory
SkipCheck: true,
Make: []string{"all"},
ScriptEarly: ` ScriptEarly: `
cd /usr/src/git
make configure
`,
Script: `
ln -s ../../system/bin/perl /usr/bin/ || true ln -s ../../system/bin/perl /usr/bin/ || true
`,
// uses source tree as scratch space
EnterSource: true,
}, &MakeHelper{
InPlace: true,
Generate: "make configure",
ScriptMakeEarly: `
function disable_test { function disable_test {
local test=$1 pattern=$2 local test=$1 pattern=${2:-''}
if [ $# -eq 1 ]; then if [ $# -eq 1 ]; then
rm "t/${test}.sh" rm "t/${test}.sh"
else else
@@ -56,26 +46,36 @@ disable_test t9300-fast-import
disable_test t0211-trace2-perf disable_test t0211-trace2-perf
disable_test t1517-outside-repo disable_test t1517-outside-repo
disable_test t2200-add-update disable_test t2200-add-update
make \
-C t \
GIT_PROVE_OPTS="--jobs 32 --failures" \
prove
`, `,
Check: []string{
"-C t",
`GIT_PROVE_OPTS="--jobs 32 --failures"`,
"prove",
},
}, },
t.Load(Perl), Perl,
t.Load(Diffutils), Diffutils,
t.Load(M4), M4,
t.Load(Autoconf), Autoconf,
t.Load(Gettext), Gettext,
t.Load(Zlib), Zlib,
t.Load(Curl), Curl,
t.Load(OpenSSL), OpenSSL,
t.Load(Libexpat), Libexpat,
) ), version
}
func init() {
artifactsM[Git] = Metadata{
f: Toolchain.newGit,
Name: "git",
Description: "distributed version control system",
Website: "https://www.git-scm.com/",
ID: 5350,
}
} }
func init() { artifactsF[Git] = Toolchain.newGit }
// NewViaGit returns a [pkg.Artifact] for cloning a git repository. // NewViaGit returns a [pkg.Artifact] for cloning a git repository.
func (t Toolchain) NewViaGit( func (t Toolchain) NewViaGit(

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@ ln -s \
))) )))
} }
func (t Toolchain) newGoLatest() pkg.Artifact { func (t Toolchain) newGoLatest() (pkg.Artifact, string) {
var ( var (
bootstrapEnv []string bootstrapEnv []string
bootstrapExtra []pkg.Artifact bootstrapExtra []pkg.Artifact
@@ -143,7 +143,7 @@ sed -i \
go125 := t.newGo( go125 := t.newGo(
"1.25.7", "1.25.7",
"fyylHdBVRUobnBjYj3NKBaYPUw3kGmo2mEELiZonOYurPfbarNU1x77B99Fjut7Q", "fyylHdBVRUobnBjYj3NKBaYPUw3kGmo2mEELiZonOYurPfbarNU1x77B99Fjut7Q",
finalEnv, ` []string{"CGO_ENABLED=0"}, `
sed -i \ sed -i \
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \ 's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
cmd/link/internal/`+runtime.GOARCH+`/obj.go cmd/link/internal/`+runtime.GOARCH+`/obj.go
@@ -153,6 +153,31 @@ rm \
`, go123, `, go123,
) )
return go125 const (
version = "1.26.1"
checksum = "DdC5Ea-aCYPUHNObQh_09uWU0vn4e-8Ben850Vq-5OoamDRrXhuYI4YQ_BOFgaT0"
)
return t.newGo(
version,
checksum,
finalEnv, `
sed -i \
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
cmd/link/internal/`+runtime.GOARCH+`/obj.go
rm \
os/root_unix_test.go
`, go125,
), version
}
func init() {
artifactsM[Go] = Metadata{
f: Toolchain.newGoLatest,
Name: "go",
Description: "the Go programming language toolchain",
Website: "https://go.dev/",
ID: 1227,
}
} }
func init() { artifactsF[Go] = Toolchain.newGoLatest }

61
internal/rosa/gtk.go Normal file
View File

@@ -0,0 +1,61 @@
package rosa
import (
"strings"
"hakurei.app/container/fhs"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newGLib() (pkg.Artifact, string) {
const (
version = "2.87.3"
checksum = "iKSLpzZZVfmAZZmqfO1y6uHdlIks4hzPWrqeUCp4ZeQjrPFA3aAa4OmrBYMNS-Si"
)
return t.NewPackage("glib", version, pkg.NewHTTPGet(
nil, "https://download.gnome.org/sources/glib/"+
strings.Join(strings.SplitN(version, ".", 3)[:2], ".")+
"/glib-"+version+".tar.xz",
mustDecode(checksum),
), &PackageAttr{
SourceKind: sourceTarXZ,
Paths: []pkg.ExecPath{
pkg.Path(fhs.AbsEtc.Append(
"machine-id",
), false, pkg.NewFile(
"glib-machine-id",
[]byte("ffffffffffffffffffffffffffffffff\n"),
)),
pkg.Path(AbsSystem.Append(
"var/lib/dbus/machine-id",
), false, pkg.NewFile(
"glib-machine-id",
[]byte("fefefefefefefefefefefefefefefefe\n"),
)),
},
}, &MesonHelper{
Setup: [][2]string{
{"Ddefault_library", "both"},
},
},
XZ,
PythonPackaging,
Bash,
PCRE2,
Libffi,
Zlib,
), version
}
func init() {
artifactsM[GLib] = Metadata{
f: Toolchain.newGLib,
Name: "glib",
Description: "the GNU library of miscellaneous stuff",
Website: "https://developer.gnome.org/glib/",
ID: 10024,
}
}

View File

@@ -1,15 +1,9 @@
package rosa package rosa
import ( import "hakurei.app/internal/pkg"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newHakurei(suffix, script string) pkg.Artifact { func (t Toolchain) newHakurei(suffix, script string) pkg.Artifact {
const ( return t.New("hakurei"+suffix+"-"+hakureiVersion, 0, []pkg.Artifact{
version = "0.3.4"
checksum = "wVwSLo75a2OnH5tgxNWXR_YhiOJUFnYM_9-sJtxAEOKhcPE0BJafs6PU8o5JzyCT"
)
return t.New("hakurei"+suffix+"-"+version, 0, []pkg.Artifact{
t.Load(Go), t.Load(Go),
t.Load(Gzip), t.Load(Gzip),
@@ -43,214 +37,10 @@ echo
chmod -R +w /usr/src/hakurei chmod -R +w /usr/src/hakurei
cd /usr/src/hakurei cd /usr/src/hakurei
HAKUREI_VERSION='v`+version+`' HAKUREI_VERSION='v`+hakureiVersion+`'
`+script, pkg.Path(AbsUsrSrc.Append("hakurei"), true, t.NewPatchedSource("hakurei", version, pkg.NewHTTPGetTar( `+script, pkg.Path(AbsUsrSrc.Append("hakurei"), true, t.NewPatchedSource(
nil, "https://git.gensokyo.uk/security/hakurei/archive/"+ "hakurei", hakureiVersion, hakureiSource, true, hakureiPatches...,
"v"+version+".tar.gz", )), pkg.Path(AbsUsrSrc.Append("hostname", "main.go"), false, pkg.NewFile(
mustDecode(checksum),
pkg.TarGzip,
), true, [2]string{"dist-00-tests", `From 67e453f5c4de915de23ecbe5980e595758f0f2fb Mon Sep 17 00:00:00 2001
From: Ophestra <cat@gensokyo.uk>
Date: Tue, 27 Jan 2026 06:49:48 +0900
Subject: [PATCH] dist: run tests
This used to be impossible due to nix jank which has been addressed.
Signed-off-by: Ophestra <cat@gensokyo.uk>
---
dist/release.sh | 21 ++++++++++++++++-----
flake.nix | 32 ++++++++++++++++++++------------
internal/acl/acl_test.go | 2 +-
package.nix | 2 +-
4 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/dist/release.sh b/dist/release.sh
index 4dcb278..0ba9104 100755
--- a/dist/release.sh
+++ b/dist/release.sh
@@ -2,19 +2,30 @@
cd "$(dirname -- "$0")/.."
VERSION="${HAKUREI_VERSION:-untagged}"
pname="hakurei-${VERSION}"
-out="dist/${pname}"
+out="${DESTDIR:-dist}/${pname}"
+echo '# Preparing distribution files.'
mkdir -p "${out}"
cp -v "README.md" "dist/hsurc.default" "dist/install.sh" "${out}"
cp -rv "dist/comp" "${out}"
+echo
+echo '# Building hakurei.'
go generate ./...
-go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
+go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
+ -buildid= -extldflags '-static'
-X hakurei.app/internal/info.buildVersion=${VERSION}
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
-X main.hakureiPath=/usr/bin/hakurei" ./...
+echo
-rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
-rm -rf "./${out}"
-(cd dist && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512")
+echo '# Testing hakurei.'
+go test -ldflags='-buildid= -extldflags=-static' ./...
+echo
+
+echo '# Creating distribution.'
+rm -f "${out}.tar.gz" && tar -C "${out}/.." -vczf "${out}.tar.gz" "${pname}"
+rm -rf "${out}"
+(cd "${out}/.." && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512")
+echo
diff --git a/flake.nix b/flake.nix
index 9e09c61..2340b92 100644
--- a/flake.nix
+++ b/flake.nix
@@ -143,19 +143,27 @@
"bin/mount.fuse.sharefs" = "${hakurei}/libexec/sharefs";
};
- dist = pkgs.runCommand "${hakurei.name}-dist" { buildInputs = hakurei.targetPkgs ++ [ pkgs.pkgsStatic.musl ]; } ''
- # go requires XDG_CACHE_HOME for the build cache
- export XDG_CACHE_HOME="$(mktemp -d)"
+ dist =
+ pkgs.runCommand "${hakurei.name}-dist"
+ {
+ buildInputs = hakurei.targetPkgs ++ [
+ pkgs.pkgsStatic.musl
+ ];
+ }
+ ''
+ cd $(mktemp -d) \
+ && cp -r ${hakurei.src}/. . \
+ && chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \
+ && chmod -R +w .
- # get a different workdir as go does not like /build
- cd $(mktemp -d) \
- && cp -r ${hakurei.src}/. . \
- && chmod +w cmd && cp -r ${hsu.src}/. cmd/hsu/ \
- && chmod -R +w .
-
- export HAKUREI_VERSION="v${hakurei.version}"
- CC="clang -O3 -Werror" ./dist/release.sh && mkdir $out && cp -v "dist/hakurei-$HAKUREI_VERSION.tar.gz"* $out
- '';
+ CC="musl-clang -O3 -Werror -Qunused-arguments" \
+ GOCACHE="$(mktemp -d)" \
+ HAKUREI_TEST_SKIP_ACL=1 \
+ PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
+ DESTDIR="$out" \
+ HAKUREI_VERSION="v${hakurei.version}" \
+ ./dist/release.sh
+ '';
}
);
diff --git a/internal/acl/acl_test.go b/internal/acl/acl_test.go
index af6da55..19ce45a 100644
--- a/internal/acl/acl_test.go
+++ b/internal/acl/acl_test.go
@@ -24,7 +24,7 @@ var (
)
func TestUpdate(t *testing.T) {
- if os.Getenv("GO_TEST_SKIP_ACL") == "1" {
+ if os.Getenv("HAKUREI_TEST_SKIP_ACL") == "1" {
t.Skip("acl test skipped")
}
diff --git a/package.nix b/package.nix
index 00c4401..2eaa2ec 100644
--- a/package.nix
+++ b/package.nix
@@ -89,7 +89,7 @@ buildGoModule rec {
CC = "clang -O3 -Werror";
# nix build environment does not allow acls
- GO_TEST_SKIP_ACL = 1;
+ HAKUREI_TEST_SKIP_ACL = 1;
};
buildInputs = [`}, [2]string{"container-tests", `From bf14a412e47344fff2681f4b24d1ecc7415bfcb0 Mon Sep 17 00:00:00 2001
From: Ophestra <cat@gensokyo.uk>
Date: Sat, 31 Jan 2026 10:59:56 +0900
Subject: [PATCH] container: fix host-dependent test cases
These are not fully controlled by hakurei and may change depending on host configuration.
Signed-off-by: Ophestra <cat@gensokyo.uk>
---
container/container_test.go | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/container/container_test.go b/container/container_test.go
index d737a18..98713cb 100644
--- a/container/container_test.go
+++ b/container/container_test.go
@@ -275,12 +275,12 @@ var containerTestCases = []struct {
),
earlyMnt(
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
- ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
+ ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/tty", "/dev/tty", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
ent("/", "/dev/mqueue", "rw,nosuid,nodev,noexec,relatime", "mqueue", "mqueue", "rw"),
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
@@ -293,12 +293,12 @@ var containerTestCases = []struct {
),
earlyMnt(
ent("/", "/dev", "ro,nosuid,nodev,relatime", "tmpfs", "devtmpfs", ignore),
- ent("/null", "/dev/null", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/zero", "/dev/zero", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/full", "/dev/full", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/random", "/dev/random", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/urandom", "/dev/urandom", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
- ent("/tty", "/dev/tty", "rw,nosuid", "devtmpfs", "devtmpfs", ignore),
+ ent("/null", "/dev/null", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/zero", "/dev/zero", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/full", "/dev/full", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/random", "/dev/random", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/urandom", "/dev/urandom", ignore, "devtmpfs", "devtmpfs", ignore),
+ ent("/tty", "/dev/tty", ignore, "devtmpfs", "devtmpfs", ignore),
ent("/", "/dev/pts", "rw,nosuid,noexec,relatime", "devpts", "devpts", "rw,mode=620,ptmxmode=666"),
ent("/", "/dev/shm", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
),
@@ -696,6 +696,9 @@ func init() {
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",relatime")
mnt[i].VfsOptstr = strings.TrimSuffix(mnt[i].VfsOptstr, ",noatime")
+ cur.FsOptstr = strings.Replace(cur.FsOptstr, ",seclabel", "", 1)
+ mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ",seclabel", "", 1)
+
if !cur.EqualWithIgnore(mnt[i], "\x00") {
fail = true
log.Printf("[FAIL] %s", cur)`}, [2]string{"dist-01-tarball-name", `diff --git a/dist/release.sh b/dist/release.sh
index 0ba9104..2990ee1 100755
--- a/dist/release.sh
+++ b/dist/release.sh
@@ -1,7 +1,7 @@
#!/bin/sh -e
cd "$(dirname -- "$0")/.."
VERSION="${HAKUREI_VERSION:-untagged}"
-pname="hakurei-${VERSION}"
+pname="hakurei-${VERSION}-$(go env GOARCH)"
out="${DESTDIR:-dist}/${pname}"
echo '# Preparing distribution files.'
`}),
), pkg.Path(AbsUsrSrc.Append("hostname", "main.go"), false, pkg.NewFile(
"hostname.go", "hostname.go",
[]byte(` []byte(`
package main package main
@@ -268,14 +58,16 @@ func main() {
))) )))
} }
func init() { func init() {
artifactsF[Hakurei] = func(t Toolchain) pkg.Artifact { artifactsM[Hakurei] = Metadata{
return t.newHakurei("", ` f: func(t Toolchain) (pkg.Artifact, string) {
return t.newHakurei("", `
mkdir -p /work/system/libexec/hakurei/ mkdir -p /work/system/libexec/hakurei/
echo '# Building hakurei.' echo '# Building hakurei.'
go generate -v ./... go generate -v ./...
go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w
-buildid= -buildid=
-linkmode external
-extldflags=-static -extldflags=-static
-X hakurei.app/internal/info.buildVersion="$HAKUREI_VERSION" -X hakurei.app/internal/info.buildVersion="$HAKUREI_VERSION"
-X hakurei.app/internal/info.hakureiPath=/system/bin/hakurei -X hakurei.app/internal/info.hakureiPath=/system/bin/hakurei
@@ -284,7 +76,7 @@ go build -trimpath -v -o /work/system/libexec/hakurei -ldflags="-s -w
echo echo
echo '# Testing hakurei.' echo '# Testing hakurei.'
go test -ldflags='-buildid= -extldflags=-static' ./... go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
echo echo
mkdir -p /work/system/bin/ mkdir -p /work/system/bin/
@@ -292,12 +84,25 @@ mkdir -p /work/system/bin/
hakurei \ hakurei \
sharefs \ sharefs \
../../bin/) ../../bin/)
`) `), hakureiVersion
},
Name: "hakurei",
Description: "low-level userspace tooling for Rosa OS",
Website: "https://hakurei.app/",
ID: 388834,
} }
artifactsF[HakureiDist] = func(t Toolchain) pkg.Artifact { artifactsM[HakureiDist] = Metadata{
return t.newHakurei("-dist", ` f: func(t Toolchain) (pkg.Artifact, string) {
return t.newHakurei("-dist", `
export HAKUREI_VERSION export HAKUREI_VERSION
DESTDIR=/work /usr/src/hakurei/dist/release.sh DESTDIR=/work /usr/src/hakurei/dist/release.sh
`) `), hakureiVersion
},
Name: "hakurei-dist",
Description: "low-level userspace tooling for Rosa OS (distribution tarball)",
Website: "https://hakurei.app/",
} }
} }

View File

@@ -0,0 +1,26 @@
//go:build current
package rosa
import (
_ "embed"
"hakurei.app/internal/pkg"
)
const hakureiVersion = "1.0-current"
// hakureiSourceTarball is a compressed tarball of the hakurei source code.
//
//go:generate tar -zc -C ../.. --exclude .git --exclude *.tar.gz -f hakurei_current.tar.gz .
//go:embed hakurei_current.tar.gz
var hakureiSourceTarball []byte
// hakureiSource is the source code at the time this package is compiled.
var hakureiSource = pkg.NewTar(pkg.NewFile(
"hakurei-current.tar.gz",
hakureiSourceTarball,
), pkg.TarGzip)
// hakureiPatches are patches applied against the compile-time source tree.
var hakureiPatches [][2]string

View File

@@ -0,0 +1,51 @@
//go:build !current
package rosa
import "hakurei.app/internal/pkg"
const hakureiVersion = "0.3.5"
// hakureiSource is the source code of a hakurei release.
var hakureiSource = pkg.NewHTTPGetTar(
nil, "https://git.gensokyo.uk/security/hakurei/archive/"+
"v"+hakureiVersion+".tar.gz",
mustDecode("6Tn38NLezRD2d3aGdFg5qFfqn8_KvC6HwMKwJMPvaHmVw8xRgxn8B0PObswl2mOk"),
pkg.TarGzip,
)
// hakureiPatches are patches applied against a hakurei release.
var hakureiPatches = [][2]string{
{"createTemp-error-injection", `diff --git a/container/dispatcher_test.go b/container/dispatcher_test.go
index 5de37fc..fe0c4db 100644
--- a/container/dispatcher_test.go
+++ b/container/dispatcher_test.go
@@ -238,8 +238,11 @@ func sliceAddr[S any](s []S) *[]S { return &s }
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
- // check happens in Close, and cleanup is not guaranteed to run, so relying on it for sloppy implementations will cause sporadic test results
- f.cleanup = runtime.AddCleanup(f, func(name string) { f.t.Fatalf("checkedOsFile %s became unreachable without a call to Close", name) }, f.name)
+ // check happens in Close, and cleanup is not guaranteed to run, so relying
+ // on it for sloppy implementations will cause sporadic test results
+ f.cleanup = runtime.AddCleanup(f, func(name string) {
+ panic("checkedOsFile " + name + " became unreachable without a call to Close")
+ }, name)
return f
}
diff --git a/container/initplace_test.go b/container/initplace_test.go
index afeddbe..1c2f20b 100644
--- a/container/initplace_test.go
+++ b/container/initplace_test.go
@@ -21,7 +21,7 @@ func TestTmpfileOp(t *testing.T) {
Path: samplePath,
Data: sampleData,
}, nil, nil, []stub.Call{
- call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), stub.UniqueError(5)),
+ call("createTemp", stub.ExpectArgs{"/", "tmp.*"}, (*checkedOsFile)(nil), stub.UniqueError(5)),
}, stub.UniqueError(5)},
{"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
`},
}

26
internal/rosa/images.go Normal file
View File

@@ -0,0 +1,26 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newImageInitramfs() (pkg.Artifact, string) {
return t.New("initramfs", TNoToolchain, []pkg.Artifact{
t.Load(Zstd),
t.Load(Hakurei),
t.Load(GenInitCPIO),
}, nil, nil, `
gen_init_cpio -t 4294967295 -c /usr/src/initramfs | zstd > /work/initramfs.zst
`, pkg.Path(AbsUsrSrc.Append("initramfs"), false, pkg.NewFile("initramfs", []byte(`
dir /dev 0755 0 0
nod /dev/null 0666 0 0 c 1 3
nod /dev/console 0600 0 0 c 5 1
file /init /system/libexec/hakurei/earlyinit 0555 0 0
`)))), Unversioned
}
func init() {
artifactsM[ImageInitramfs] = Metadata{
Name: "initramfs-image",
Description: "Rosa OS initramfs image",
f: Toolchain.newImageInitramfs,
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
package rosa
import _ "embed"
//go:embed kernel_amd64.config
var kernelConfig []byte
const kernelName = "bzImage"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
package rosa
import _ "embed"
//go:embed kernel_arm64.config
var kernelConfig []byte
const kernelName = "vmlinuz.efi"

43
internal/rosa/kmod.go Normal file
View File

@@ -0,0 +1,43 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newKmod() (pkg.Artifact, string) {
const (
version = "34.2"
checksum = "0K7POeTKxMhExsaTsnKAC6LUNsRSfe6sSZxWONPbOu-GI_pXOw3toU_BIoqfBhJV"
)
return t.NewPackage("kmod", version, pkg.NewHTTPGetTar(
nil, "https://www.kernel.org/pub/linux/utils/kernel/"+
"kmod/kmod-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), nil, &MesonHelper{
Setup: [][2]string{
{"Dsysconfdir", "/system/etc"},
{"Dbashcompletiondir", "no"},
{"Dfishcompletiondir", "no"},
{"Dxz", "disabled"},
{"Dmanpages", "false"},
},
// makes assumptions about the running kernel
SkipTest: true,
},
Zlib,
Zstd,
OpenSSL,
KernelHeaders,
), version
}
func init() {
artifactsM[Kmod] = Metadata{
f: Toolchain.newKmod,
Name: "kmod",
Description: "a set of tools to handle common tasks with Linux kernel modules",
Website: "https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git",
ID: 1517,
}
}

54
internal/rosa/libcap.go Normal file
View File

@@ -0,0 +1,54 @@
package rosa
import "hakurei.app/internal/pkg"
func (t Toolchain) newLibcap() (pkg.Artifact, string) {
const (
version = "2.77"
checksum = "2GOTFU4cl2QoS7Dv5wh0c9-hxsQwIzMB9Y_gfAo5xKHqcM13fiHt1RbPkfemzjmB"
)
return t.NewPackage("libcap", version, pkg.NewHTTPGetTar(
nil, "https://git.kernel.org/pub/scm/libs/libcap/libcap.git/"+
"snapshot/libcap-"+version+".tar.gz",
mustDecode(checksum),
pkg.TarGzip,
), &PackageAttr{
// uses source tree as scratch space
Writable: true,
Chmod: true,
Env: []string{
"prefix=/system",
"lib=lib",
},
ScriptEarly: `
ln -s ../system/bin/bash /bin/
`,
}, &MakeHelper{
SkipConfigure: true,
InPlace: true,
Make: []string{
"CC=cc",
"all",
},
Check: []string{
"CC=cc",
"test",
},
},
Bash,
Diffutils,
), version
}
func init() {
artifactsM[Libcap] = Metadata{
f: Toolchain.newLibcap,
Name: "libcap",
Description: "a library for getting and setting POSIX.1e draft 15 capabilities",
Website: "https://sites.google.com/site/fullycapable/",
ID: 1569,
}
}

View File

@@ -6,23 +6,29 @@ import (
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
func (t Toolchain) newLibexpat() pkg.Artifact { func (t Toolchain) newLibexpat() (pkg.Artifact, string) {
const ( const (
version = "2.7.3" version = "2.7.4"
checksum = "GmkoD23nRi9cMT0cgG1XRMrZWD82UcOMzkkvP1gkwSFWCBgeSXMuoLpa8-v8kxW-" checksum = "W6NI2FESBjrTqRPcvs15fK5c3nwF6f9RT8U-XHKQKblXVzJB3nt_ez5B5jO0ZVDG"
) )
return t.NewViaMake("libexpat", version, pkg.NewHTTPGetTar( return t.NewPackage("libexpat", version, pkg.NewHTTPGetTar(
nil, "https://github.com/libexpat/libexpat/releases/download/"+ nil, "https://github.com/libexpat/libexpat/releases/download/"+
"R_"+strings.ReplaceAll(version, ".", "_")+"/"+ "R_"+strings.ReplaceAll(version, ".", "_")+"/"+
"expat-"+version+".tar.bz2", "expat-"+version+".tar.bz2",
mustDecode(checksum), mustDecode(checksum),
pkg.TarBzip2, pkg.TarBzip2,
), &MakeAttr{ ), nil, (*MakeHelper)(nil),
Configure: [][2]string{ Bash,
{"enable-static"}, ), version
}, }
}, func init() {
t.Load(Bash), artifactsM[Libexpat] = Metadata{
) f: Toolchain.newLibexpat,
Name: "libexpat",
Description: "a stream-oriented XML parser library",
Website: "https://libexpat.github.io/",
ID: 770,
}
} }
func init() { artifactsF[Libexpat] = Toolchain.newLibexpat }

View File

@@ -2,22 +2,28 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newLibffi() pkg.Artifact { func (t Toolchain) newLibffi() (pkg.Artifact, string) {
const ( const (
version = "3.4.5" version = "3.5.2"
checksum = "apIJzypF4rDudeRoI_n3K7N-zCeBLTbQlHRn9NSAZqdLAWA80mR0gXPTpHsL7oMl" checksum = "2_Q-ZNBBbVhltfL5zEr0wljxPegUimTK4VeMSiwJEGksls3n4gj3lV0Ly3vviSFH"
) )
return t.NewViaMake("libffi", version, pkg.NewHTTPGetTar( return t.NewPackage("libffi", version, pkg.NewHTTPGetTar(
nil, "https://github.com/libffi/libffi/releases/download/"+ nil, "https://github.com/libffi/libffi/releases/download/"+
"v"+version+"/libffi-"+version+".tar.gz", "v"+version+"/libffi-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), nil, (*MakeHelper)(nil),
Configure: [][2]string{ KernelHeaders,
{"enable-static"}, ), version
}, }
}, func init() {
t.Load(KernelHeaders), artifactsM[Libffi] = Metadata{
) f: Toolchain.newLibffi,
Name: "libffi",
Description: "a portable, high level programming interface to various calling conventions",
Website: "https://sourceware.org/libffi/",
ID: 1611,
}
} }
func init() { artifactsF[Libffi] = Toolchain.newLibffi }

View File

@@ -2,29 +2,35 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newLibgd() pkg.Artifact { func (t Toolchain) newLibgd() (pkg.Artifact, string) {
const ( const (
version = "2.3.3" version = "2.3.3"
checksum = "8T-sh1_FJT9K9aajgxzh8ot6vWIF-xxjcKAHvTak9MgGUcsFfzP8cAvvv44u2r36" checksum = "8T-sh1_FJT9K9aajgxzh8ot6vWIF-xxjcKAHvTak9MgGUcsFfzP8cAvvv44u2r36"
) )
return t.NewViaMake("libgd", version, pkg.NewHTTPGetTar( return t.NewPackage("libgd", version, pkg.NewHTTPGetTar(
nil, "https://github.com/libgd/libgd/releases/download/"+ nil, "https://github.com/libgd/libgd/releases/download/"+
"gd-"+version+"/libgd-"+version+".tar.gz", "gd-"+version+"/libgd-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
OmitDefaults: true,
Env: []string{ Env: []string{
"TMPDIR=/dev/shm/gd", "TMPDIR=/dev/shm/gd",
}, },
ScriptEarly: ` ScriptEarly: `
mkdir /dev/shm/gd mkdir /dev/shm/gd
`, `,
Configure: [][2]string{ }, (*MakeHelper)(nil),
{"enable-static"}, Zlib,
}, ), version
}, }
t.Load(Zlib), func init() {
) artifactsM[Libgd] = Metadata{
f: Toolchain.newLibgd,
Name: "libgd",
Description: "an open source code library for the dynamic creation of images",
Website: "https://libgd.github.io/",
ID: 880,
}
} }
func init() { artifactsF[Libgd] = Toolchain.newLibgd }

View File

@@ -2,27 +2,35 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newLibpsl() pkg.Artifact { func (t Toolchain) newLibpsl() (pkg.Artifact, string) {
const ( const (
version = "0.21.5" version = "0.21.5"
checksum = "XjfxSzh7peG2Vg4vJlL8z4JZJLcXqbuP6pLWkrGCmRxlnYUFTKNBqWGHCxEOlCad" checksum = "XjfxSzh7peG2Vg4vJlL8z4JZJLcXqbuP6pLWkrGCmRxlnYUFTKNBqWGHCxEOlCad"
) )
return t.NewViaMake("libpsl", version, pkg.NewHTTPGetTar( return t.NewPackage("libpsl", version, pkg.NewHTTPGetTar(
nil, "https://github.com/rockdaboot/libpsl/releases/download/"+ nil, "https://github.com/rockdaboot/libpsl/releases/download/"+
version+"/libpsl-"+version+".tar.gz", version+"/libpsl-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
Writable: true, Writable: true,
ScriptEarly: ` ScriptEarly: `
cd /usr/src/libpsl
test_disable() { chmod +w "$2" && echo "$1" > "$2"; } test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
test_disable 'int main(){return 0;}' tests/test-is-public-builtin.c test_disable 'int main(){return 0;}' tests/test-is-public-builtin.c
`, `,
}, }, (*MakeHelper)(nil),
t.Load(Python), Python,
) ), version
}
func init() {
artifactsM[Libpsl] = Metadata{
f: Toolchain.newLibpsl,
Name: "libpsl",
Description: "provides functions to work with the Mozilla Public Suffix List",
Website: "https://rockdaboot.github.io/libpsl/",
ID: 7305,
}
} }
func init() { artifactsF[Libpsl] = Toolchain.newLibpsl }

View File

@@ -1,33 +1,37 @@
package rosa package rosa
import ( import "hakurei.app/internal/pkg"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newLibseccomp() pkg.Artifact { func (t Toolchain) newLibseccomp() (pkg.Artifact, string) {
const ( const (
version = "2.6.0" version = "2.6.0"
checksum = "mMu-iR71guPjFbb31u-YexBaanKE_nYPjPux-vuBiPfS_0kbwJdfCGlkofaUm-EY" checksum = "mMu-iR71guPjFbb31u-YexBaanKE_nYPjPux-vuBiPfS_0kbwJdfCGlkofaUm-EY"
) )
return t.NewViaMake("libseccomp", version, pkg.NewHTTPGetTar( return t.NewPackage("libseccomp", version, pkg.NewHTTPGetTar(
nil, nil, "https://github.com/seccomp/libseccomp/releases/download/"+
"https://github.com/seccomp/libseccomp/releases/download/"+
"v"+version+"/libseccomp-"+version+".tar.gz", "v"+version+"/libseccomp-"+version+".tar.gz",
mustDecode(checksum), mustDecode(checksum),
pkg.TarGzip, pkg.TarGzip,
), &MakeAttr{ ), &PackageAttr{
ScriptEarly: ` ScriptEarly: `
ln -s ../system/bin/bash /bin/ ln -s ../system/bin/bash /bin/
`, `,
Configure: [][2]string{ }, (*MakeHelper)(nil),
{"enable-static"}, Bash,
}, Diffutils,
}, Gperf,
t.Load(Bash),
t.Load(Diffutils),
t.Load(Gperf),
t.Load(KernelHeaders), KernelHeaders,
) ), version
}
func init() {
artifactsM[Libseccomp] = Metadata{
f: Toolchain.newLibseccomp,
Name: "libseccomp",
Description: "an interface to the Linux Kernel's syscall filtering mechanism",
Website: "https://github.com/seccomp/libseccomp/",
ID: 13823,
}
} }
func init() { artifactsF[Libseccomp] = Toolchain.newLibseccomp }

View File

@@ -2,39 +2,39 @@ package rosa
import "hakurei.app/internal/pkg" import "hakurei.app/internal/pkg"
func (t Toolchain) newLibucontext() pkg.Artifact { func (t Toolchain) newLibucontext() (pkg.Artifact, string) {
const ( const (
version = "1.5" version = "1.5"
checksum = "Ggk7FMmDNBdCx1Z9PcNWWW6LSpjGYssn2vU0GK5BLXJYw7ZxZbA2m_eSgT9TFnIG" checksum = "Ggk7FMmDNBdCx1Z9PcNWWW6LSpjGYssn2vU0GK5BLXJYw7ZxZbA2m_eSgT9TFnIG"
) )
return t.New("libucontext", 0, []pkg.Artifact{ return t.NewPackage("libucontext", version, pkg.NewHTTPGetTar(
t.Load(Make), nil, "https://github.com/kaniini/libucontext/archive/refs/tags/"+
}, nil, []string{ "libucontext-"+version+".tar.gz",
"ARCH=" + linuxArch(), mustDecode(checksum),
}, ` pkg.TarGzip,
cd /usr/src/libucontext ), &PackageAttr{
make check // uses source tree as scratch space
make DESTDIR=/work install Writable: true,
`, pkg.Path(AbsUsrSrc.Append("libucontext"), true, Chmod: true,
t.NewPatchedSource("libucontext", version, pkg.NewHTTPGetTar( EnterSource: true,
nil, "https://github.com/kaniini/libucontext/archive/refs/tags/"+ }, &MakeHelper{
"libucontext-"+version+".tar.gz", OmitDefaults: true,
mustDecode(checksum), SkipConfigure: true,
pkg.TarGzip, InPlace: true,
), true, [2]string{"rosa-prefix", `diff --git a/Makefile b/Makefile Make: []string{
index c80e574..4a8c1d3 100644 "ARCH=" + linuxArch(),
--- a/Makefile },
+++ b/Makefile Install: "make prefix=/system DESTDIR=/work install",
@@ -17,7 +17,7 @@ ifeq ($(ARCH),$(filter $(ARCH),arm64)) }), version
override ARCH = aarch64 }
endif func init() {
artifactsM[Libucontext] = Metadata{
-prefix = /usr f: Toolchain.newLibucontext,
+prefix = /system
libdir = ${prefix}/lib Name: "libucontext",
shared_libdir = ${libdir} Description: "ucontext implementation featuring glibc-compatible ABI",
static_libdir = ${libdir} Website: "https://github.com/kaniini/libucontext/",
`}),
)) ID: 17085,
}
} }
func init() { artifactsF[Libucontext] = Toolchain.newLibucontext }

View File

@@ -6,29 +6,31 @@ import (
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
func (t Toolchain) newLibxml2() pkg.Artifact { func (t Toolchain) newLibxml2() (pkg.Artifact, string) {
const ( const (
version = "2.15.1" version = "2.15.2"
checksum = "pYzAR3cNrEHezhEMirgiq7jbboLzwMj5GD7SQp0jhSIMdgoU4G9oU9Gxun3zzUIU" checksum = "xba8VCofMsbWmQypA2__M9_RXNq9HDEuccjib6-tOni6OPngplRoAsYdY3NdYf8o"
) )
return t.NewViaMake("libxml2", version, pkg.NewHTTPGet( return t.NewPackage("libxml2", version, pkg.NewHTTPGet(
nil, "https://download.gnome.org/sources/libxml2/"+ nil, "https://download.gnome.org/sources/libxml2/"+
strings.Join(strings.Split(version, ".")[:2], ".")+ strings.Join(strings.Split(version, ".")[:2], ".")+
"/libxml2-"+version+".tar.xz", "/libxml2-"+version+".tar.xz",
mustDecode(checksum), mustDecode(checksum),
), &MakeAttr{ ), &PackageAttr{
ScriptEarly: ` SourceKind: sourceTarXZ,
cd /usr/src/ }, (*MakeHelper)(nil),
tar xf libxml2.tar.xz Diffutils,
mv libxml2-` + version + ` libxml2 XZ,
`, ), version
Configure: [][2]string{ }
{"enable-static"}, func init() {
}, artifactsM[Libxml2] = Metadata{
SourceSuffix: ".tar.xz", f: Toolchain.newLibxml2,
},
t.Load(Diffutils), Name: "libxml2",
t.Load(XZ), Description: "an XML toolkit implemented in C",
) Website: "https://gitlab.gnome.org/GNOME/libxml2/",
ID: 1783,
}
} }
func init() { artifactsF[Libxml2] = Toolchain.newLibxml2 }

42
internal/rosa/libxslt.go Normal file
View File

@@ -0,0 +1,42 @@
package rosa
import (
"strings"
"hakurei.app/internal/pkg"
)
func (t Toolchain) newLibxslt() (pkg.Artifact, string) {
const (
version = "1.1.45"
checksum = "vw72UbREQnA3YDYuZ9-93hDr9BYCaKV6oh_U4Kt4n1Js_na4E-nFj-ksZnZ0kvEK"
)
return t.NewPackage("libxslt", version, pkg.NewHTTPGet(
nil, "https://download.gnome.org/sources/libxslt/"+
strings.Join(strings.Split(version, ".")[:2], ".")+
"/libxslt-"+version+".tar.xz",
mustDecode(checksum),
), &PackageAttr{
SourceKind: sourceTarXZ,
}, &MakeHelper{
// python libxml2 cyclic dependency
SkipCheck: true,
},
XZ,
Python,
PkgConfig,
Libxml2,
), version
}
func init() {
artifactsM[Libxslt] = Metadata{
f: Toolchain.newLibxslt,
Name: "libxslt",
Description: "an XSLT processor based on libxml2",
Website: "https://gitlab.gnome.org/GNOME/libxslt/",
ID: 13301,
}
}

View File

@@ -7,33 +7,27 @@ import (
"strings" "strings"
"sync" "sync"
"hakurei.app/container/check"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
// llvmAttr holds the attributes that will be applied to a new [pkg.Artifact] // llvmAttr holds the attributes that will be applied to a new [pkg.Artifact]
// containing a LLVM variant. // containing a LLVM variant.
type llvmAttr struct { type llvmAttr struct {
// Passed through to PackageAttr.Flag.
flags int flags int
// Concatenated with default environment for CMakeAttr.Env. // Concatenated with default environment for PackageAttr.Env.
env []string env []string
// Concatenated with generated entries for CMakeAttr.Cache. // Concatenated with generated entries for CMakeHelper.Cache.
cmake [][2]string cmake [][2]string
// Override CMakeAttr.Append. // Override CMakeHelper.Append.
append []string append []string
// Concatenated with default dependencies for Toolchain.NewViaCMake. // Passed through to PackageAttr.NonStage0.
extra []pkg.Artifact nonStage0 []pkg.Artifact
// Passed through to CMakeAttr.Paths. // Passed through to PackageAttr.Paths.
paths []pkg.ExecPath paths []pkg.ExecPath
// Passed through to CMakeAttr.ScriptConfigured. // Concatenated with default fixup for CMakeHelper.Script.
scriptConfigured string
// Concatenated with default fixup for CMakeAttr.Script.
script string script string
// Passed through to CMakeAttr.Prefix.
prefix *check.Absolute
// Passed through to CMakeAttr.Writable.
writable bool
// Patch name and body pairs. // Patch name and body pairs.
patches [][2]string patches [][2]string
@@ -79,12 +73,15 @@ func llvmFlagName(flag int) string {
} }
} }
const (
llvmVersionMajor = "22"
llvmVersion = llvmVersionMajor + ".1.0"
)
// newLLVMVariant returns a [pkg.Artifact] containing a LLVM variant. // newLLVMVariant returns a [pkg.Artifact] containing a LLVM variant.
func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact { func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
const ( const checksum = "-_Tu5Lt8xkWoxm2VDVV7crh0WqZQbbblN3fYamMdPTDSy_54FAkD2ii7afSymPVV"
version = "21.1.8"
checksum = "8SUpqDkcgwOPsqHVtmf9kXfFeVmjVxl4LMn-qSE1AI_Xoeju-9HaoPNGtidyxyka"
)
if attr == nil { if attr == nil {
panic("LLVM attr must be non-nil") panic("LLVM attr must be non-nil")
} }
@@ -101,7 +98,7 @@ func (t Toolchain) newLLVMVariant(variant string, attr *llvmAttr) pkg.Artifact {
} }
} }
var script, scriptEarly string var script string
cache := [][2]string{ cache := [][2]string{
{"CMAKE_BUILD_TYPE", "Release"}, {"CMAKE_BUILD_TYPE", "Release"},
@@ -159,15 +156,6 @@ ln -s ld.lld /work/system/bin/ld
[2]string{"LIBCXX_HAS_MUSL_LIBC", "ON"}, [2]string{"LIBCXX_HAS_MUSL_LIBC", "ON"},
[2]string{"LIBCXX_USE_COMPILER_RT", "ON"}, [2]string{"LIBCXX_USE_COMPILER_RT", "ON"},
) )
if t > toolchainStage3 {
// libcxxabi fails to compile if c++ headers not prefixed in /usr
// is found by the compiler, and doing this is easier than
// overriding CXXFLAGS; not using mv here to avoid chown failures
scriptEarly += `
cp -r /system/include /usr/include && rm -rf /system/include
`
}
} }
if attr.flags&llvmRuntimeLibcxxABI != 0 { if attr.flags&llvmRuntimeLibcxxABI != 0 {
cache = append(cache, cache = append(cache,
@@ -176,41 +164,40 @@ cp -r /system/include /usr/include && rm -rf /system/include
) )
} }
return t.NewViaCMake("llvm", version, variant, t.NewPatchedSource( return t.NewPackage("llvm", llvmVersion, pkg.NewHTTPGetTar(
"llvmorg", version, pkg.NewHTTPGetTar( nil, "https://github.com/llvm/llvm-project/archive/refs/tags/"+
nil, "https://github.com/llvm/llvm-project/archive/refs/tags/"+ "llvmorg-"+llvmVersion+".tar.gz",
"llvmorg-"+version+".tar.gz", mustDecode(checksum),
mustDecode(checksum), pkg.TarGzip,
pkg.TarGzip, ), &PackageAttr{
), true, attr.patches..., Patches: attr.patches,
), &CMakeAttr{ NonStage0: attr.nonStage0,
Cache: slices.Concat(cache, attr.cmake),
Append: cmakeAppend,
Prefix: attr.prefix,
Env: slices.Concat([]string{ Env: slices.Concat([]string{
"ROSA_LLVM_PROJECTS=" + strings.Join(projects, ";"), "ROSA_LLVM_PROJECTS=" + strings.Join(projects, ";"),
"ROSA_LLVM_RUNTIMES=" + strings.Join(runtimes, ";"), "ROSA_LLVM_RUNTIMES=" + strings.Join(runtimes, ";"),
}, attr.env), }, attr.env),
ScriptEarly: scriptEarly,
ScriptConfigured: attr.scriptConfigured,
Script: script + attr.script,
Writable: attr.writable,
Paths: attr.paths, Paths: attr.paths,
Flag: TExclusive, Flag: TExclusive,
}, stage3Concat(t, attr.extra, }, &CMakeHelper{
t.Load(Libffi), Variant: variant,
t.Load(Python),
t.Load(Perl),
t.Load(Diffutils),
t.Load(Bash),
t.Load(Gawk),
t.Load(Coreutils),
t.Load(Findutils),
t.Load(KernelHeaders), Cache: slices.Concat(cache, attr.cmake),
)...) Append: cmakeAppend,
Script: script + attr.script,
},
Libffi,
Python,
Perl,
Diffutils,
Bash,
Gawk,
Coreutils,
Findutils,
KernelHeaders,
)
} }
// newLLVM returns LLVM toolchain across multiple [pkg.Artifact]. // newLLVM returns LLVM toolchain across multiple [pkg.Artifact].
@@ -232,8 +219,12 @@ func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
{"LLVM_ENABLE_LIBXML2", "OFF"}, {"LLVM_ENABLE_LIBXML2", "OFF"},
} }
muslHeaders, _ := t.newMusl(true, []string{
"CC=clang",
})
compilerRT = t.newLLVMVariant("compiler-rt", &llvmAttr{ compilerRT = t.newLLVMVariant("compiler-rt", &llvmAttr{
env: stage3ExclConcat(t, []string{}, env: stage0ExclConcat(t, []string{},
"LDFLAGS="+earlyLDFLAGS(false), "LDFLAGS="+earlyLDFLAGS(false),
), ),
cmake: [][2]string{ cmake: [][2]string{
@@ -253,42 +244,36 @@ func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
{"COMPILER_RT_BUILD_XRAY", "OFF"}, {"COMPILER_RT_BUILD_XRAY", "OFF"},
}, },
append: []string{"compiler-rt"}, append: []string{"compiler-rt"},
extra: []pkg.Artifact{t.NewMusl(&MuslAttr{ nonStage0: []pkg.Artifact{
Headers: true, muslHeaders,
Env: []string{ },
"CC=clang",
},
})},
script: ` script: `
mkdir -p "${ROSA_INSTALL_PREFIX}/lib/clang/21/lib/" mkdir -p "/work/system/lib/clang/` + llvmVersionMajor + `/lib/"
ln -s \ ln -s \
"../../../${ROSA_TRIPLE}" \ "../../../${ROSA_TRIPLE}" \
"${ROSA_INSTALL_PREFIX}/lib/clang/21/lib/" "/work/system/lib/clang/` + llvmVersionMajor + `/lib/"
ln -s \ ln -s \
"clang_rt.crtbegin-` + linuxArch() + `.o" \ "clang_rt.crtbegin-` + linuxArch() + `.o" \
"${ROSA_INSTALL_PREFIX}/lib/${ROSA_TRIPLE}/crtbeginS.o" "/work/system/lib/${ROSA_TRIPLE}/crtbeginS.o"
ln -s \ ln -s \
"clang_rt.crtend-` + linuxArch() + `.o" \ "clang_rt.crtend-` + linuxArch() + `.o" \
"${ROSA_INSTALL_PREFIX}/lib/${ROSA_TRIPLE}/crtendS.o" "/work/system/lib/${ROSA_TRIPLE}/crtendS.o"
`, `,
}) })
musl = t.NewMusl(&MuslAttr{ musl, _ = t.newMusl(false, stage0ExclConcat(t, []string{
Extra: []pkg.Artifact{compilerRT}, "CC=clang",
Env: stage3ExclConcat(t, []string{ "LIBCC=/system/lib/clang/" + llvmVersionMajor + "/lib/" +
"CC=clang", triplet() + "/libclang_rt.builtins.a",
"LIBCC=/system/lib/clang/21/lib/" + "AR=ar",
triplet() + "/libclang_rt.builtins.a", "RANLIB=ranlib",
"AR=ar", },
"RANLIB=ranlib", "LDFLAGS="+earlyLDFLAGS(false),
}, ), compilerRT)
"LDFLAGS="+earlyLDFLAGS(false),
),
})
runtimes = t.newLLVMVariant("runtimes", &llvmAttr{ runtimes = t.newLLVMVariant("runtimes", &llvmAttr{
env: stage3ExclConcat(t, []string{}, env: stage0ExclConcat(t, []string{},
"LDFLAGS="+earlyLDFLAGS(false), "LDFLAGS="+earlyLDFLAGS(false),
), ),
flags: llvmRuntimeLibunwind | llvmRuntimeLibcxx | llvmRuntimeLibcxxABI, flags: llvmRuntimeLibunwind | llvmRuntimeLibcxx | llvmRuntimeLibcxxABI,
@@ -300,7 +285,7 @@ ln -s \
{"LIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL", "OFF"}, {"LIBCXXABI_HAS_CXA_THREAD_ATEXIT_IMPL", "OFF"},
}, minimalDeps), }, minimalDeps),
append: []string{"runtimes"}, append: []string{"runtimes"},
extra: []pkg.Artifact{ nonStage0: []pkg.Artifact{
compilerRT, compilerRT,
musl, musl,
}, },
@@ -308,7 +293,7 @@ ln -s \
clang = t.newLLVMVariant("clang", &llvmAttr{ clang = t.newLLVMVariant("clang", &llvmAttr{
flags: llvmProjectClang | llvmProjectLld, flags: llvmProjectClang | llvmProjectLld,
env: stage3ExclConcat(t, []string{}, env: stage0ExclConcat(t, []string{},
"CFLAGS="+earlyCFLAGS, "CFLAGS="+earlyCFLAGS,
"CXXFLAGS="+earlyCXXFLAGS(), "CXXFLAGS="+earlyCXXFLAGS(),
"LDFLAGS="+earlyLDFLAGS(false), "LDFLAGS="+earlyLDFLAGS(false),
@@ -318,7 +303,7 @@ ln -s \
{"CMAKE_CROSSCOMPILING", "OFF"}, {"CMAKE_CROSSCOMPILING", "OFF"},
{"CXX_SUPPORTS_CUSTOM_LINKER", "ON"}, {"CXX_SUPPORTS_CUSTOM_LINKER", "ON"},
}, minimalDeps), }, minimalDeps),
extra: []pkg.Artifact{ nonStage0: []pkg.Artifact{
musl, musl,
compilerRT, compilerRT,
runtimes, runtimes,
@@ -332,11 +317,11 @@ ninja check-all
patches: [][2]string{ patches: [][2]string{
{"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h {"add-rosa-vendor", `diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index 657f4230379e..12c305756184 100644 index 9c83abeeb3b1..5acfe5836a23 100644
--- a/llvm/include/llvm/TargetParser/Triple.h --- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h +++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -185,6 +185,7 @@ public: @@ -190,6 +190,7 @@ public:
Apple, Apple,
PC, PC,
+ Rosa, + Rosa,
@@ -344,25 +329,25 @@ index 657f4230379e..12c305756184 100644
Freescale, Freescale,
IBM, IBM,
diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp
index 0584c941d2e6..e4d6ef963cc7 100644 index a4f9dd42c0fe..cb5a12387034 100644
--- a/llvm/lib/TargetParser/Triple.cpp --- a/llvm/lib/TargetParser/Triple.cpp
+++ b/llvm/lib/TargetParser/Triple.cpp +++ b/llvm/lib/TargetParser/Triple.cpp
@@ -269,6 +269,7 @@ StringRef Triple::getVendorTypeName(VendorType Kind) { @@ -279,6 +279,7 @@ StringRef Triple::getVendorTypeName(VendorType Kind) {
case NVIDIA: return "nvidia"; case NVIDIA: return "nvidia";
case OpenEmbedded: return "oe"; case OpenEmbedded: return "oe";
case PC: return "pc"; case PC: return "pc";
+ case Rosa: return "rosa"; + case Rosa: return "rosa";
case SCEI: return "scei"; case SCEI: return "scei";
case SUSE: return "suse"; case SUSE: return "suse";
} case Meta:
@@ -669,6 +670,7 @@ static Triple::VendorType parseVendor(StringRef VendorName) { @@ -689,6 +690,7 @@ static Triple::VendorType parseVendor(StringRef VendorName) {
.Case("suse", Triple::SUSE) return StringSwitch<Triple::VendorType>(VendorName)
.Case("oe", Triple::OpenEmbedded) .Case("apple", Triple::Apple)
.Case("intel", Triple::Intel) .Case("pc", Triple::PC)
+ .Case("rosa", Triple::Rosa) + .Case("rosa", Triple::Rosa)
.Default(Triple::UnknownVendor); .Case("scei", Triple::SCEI)
} .Case("sie", Triple::SCEI)
.Case("fsl", Triple::Freescale)
`}, `},
{"xfail-broken-tests", `diff --git a/clang/test/Modules/timestamps.c b/clang/test/Modules/timestamps.c {"xfail-broken-tests", `diff --git a/clang/test/Modules/timestamps.c b/clang/test/Modules/timestamps.c
@@ -374,14 +359,14 @@ index 50fdce630255..4b4465a75617 100644
+ +
/// Verify timestamps that gets embedded in the module /// Verify timestamps that gets embedded in the module
#include <c-header.h> #include <c-header.h>
`}, `},
{"path-system-include", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp {"path-system-include", `diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index cdbf21fb9026..dd052858700d 100644 index 8ac8d4eb9181..e46b04a898ca 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp --- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp +++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -773,6 +773,12 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs, @@ -671,6 +671,12 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
addExternCSystemInclude( addExternCSystemInclude(
DriverArgs, CC1Args, DriverArgs, CC1Args,
concat(SysRoot, "/usr/include", MultiarchIncludeDir)); concat(SysRoot, "/usr/include", MultiarchIncludeDir));
@@ -391,15 +376,15 @@ index cdbf21fb9026..dd052858700d 100644
+ DriverArgs, CC1Args, + DriverArgs, CC1Args,
+ concat(SysRoot, "/system/include", MultiarchIncludeDir)); + concat(SysRoot, "/system/include", MultiarchIncludeDir));
+ +
if (getTriple().getOS() == llvm::Triple::RTEMS) if (getTriple().getOS() == llvm::Triple::RTEMS)
return; return;
@@ -783,6 +789,7 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs, @@ -681,6 +687,7 @@ void Linux::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/include")); addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/include"));
addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/usr/include")); addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/usr/include"));
+ addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/system/include")); + addExternCSystemInclude(DriverArgs, CC1Args, concat(SysRoot, "/system/include"));
if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl()) if (!DriverArgs.hasArg(options::OPT_nobuiltininc) && getTriple().isMusl())
addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude); addSystemInclude(DriverArgs, CC1Args, ResourceDirInclude);
`}, `},
@@ -413,13 +398,13 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
const bool IsRISCV = Triple.isRISCV(); const bool IsRISCV = Triple.isRISCV();
const bool IsCSKY = Triple.isCSKY(); const bool IsCSKY = Triple.isCSKY();
+ const bool IsRosa = Triple.getVendor() == llvm::Triple::Rosa; + const bool IsRosa = Triple.getVendor() == llvm::Triple::Rosa;
if (IsCSKY && !SelectedMultilibs.empty()) if (IsCSKY && !SelectedMultilibs.empty())
SysRoot = SysRoot + SelectedMultilibs.back().osSuffix(); SysRoot = SysRoot + SelectedMultilibs.back().osSuffix();
@@ -318,12 +319,23 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args) @@ -318,12 +319,23 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
const std::string OSLibDir = std::string(getOSLibDir(Triple, Args)); const std::string OSLibDir = std::string(getOSLibDir(Triple, Args));
const std::string MultiarchTriple = getMultiarchTriple(D, Triple, SysRoot); const std::string MultiarchTriple = getMultiarchTriple(D, Triple, SysRoot);
+ if (IsRosa) { + if (IsRosa) {
+ ExtraOpts.push_back("-rpath"); + ExtraOpts.push_back("-rpath");
+ ExtraOpts.push_back("/system/lib"); + ExtraOpts.push_back("/system/lib");
@@ -441,11 +426,11 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ } + }
} }
Generic_GCC::AddMultilibPaths(D, SysRoot, OSLibDir, MultiarchTriple, Paths); Generic_GCC::AddMultilibPaths(D, SysRoot, OSLibDir, MultiarchTriple, Paths);
@@ -341,18 +353,30 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args) @@ -341,18 +353,30 @@ Linux::Linux(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
Paths); Paths);
} }
- addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths); - addPathIfExists(D, concat(SysRoot, "/usr/lib", MultiarchTriple), Paths);
- addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths); - addPathIfExists(D, concat(SysRoot, "/usr", OSLibDir), Paths);
+ if (!IsRosa) { + if (!IsRosa) {
@@ -464,9 +449,9 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ else + else
+ addPathIfExists(D, concat(SysRoot, "/system", OSLibDir, ABIName), Paths); + addPathIfExists(D, concat(SysRoot, "/system", OSLibDir, ABIName), Paths);
} }
Generic_GCC::AddMultiarchPaths(D, SysRoot, OSLibDir, Paths); Generic_GCC::AddMultiarchPaths(D, SysRoot, OSLibDir, Paths);
- addPathIfExists(D, concat(SysRoot, "/lib"), Paths); - addPathIfExists(D, concat(SysRoot, "/lib"), Paths);
- addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths); - addPathIfExists(D, concat(SysRoot, "/usr/lib"), Paths);
+ if (!IsRosa) { + if (!IsRosa) {
@@ -476,7 +461,7 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ addPathIfExists(D, concat(SysRoot, "/system/lib"), Paths); + addPathIfExists(D, concat(SysRoot, "/system/lib"), Paths);
+ } + }
} }
ToolChain::RuntimeLibType Linux::GetDefaultRuntimeLibType() const { ToolChain::RuntimeLibType Linux::GetDefaultRuntimeLibType() const {
@@ -457,6 +481,9 @@ std::string Linux::getDynamicLinker(const ArgList &Args) const { @@ -457,6 +481,9 @@ std::string Linux::getDynamicLinker(const ArgList &Args) const {
return Triple.isArch64Bit() ? "/system/bin/linker64" : "/system/bin/linker"; return Triple.isArch64Bit() ? "/system/bin/linker64" : "/system/bin/linker";
@@ -487,26 +472,62 @@ index 8ac8d4eb9181..f4d1347ab64d 100644
+ +
std::string ArchName; std::string ArchName;
bool IsArm = false; bool IsArm = false;
diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp
index 64324a3f8b01..15ce70b68217 100644 index 64324a3f8b01..15ce70b68217 100644
--- a/clang/tools/clang-installapi/Options.cpp --- a/clang/tools/clang-installapi/Options.cpp
+++ b/clang/tools/clang-installapi/Options.cpp +++ b/clang/tools/clang-installapi/Options.cpp
@@ -515,7 +515,7 @@ bool Options::processFrontendOptions(InputArgList &Args) { @@ -515,7 +515,7 @@ bool Options::processFrontendOptions(InputArgList &Args) {
FEOpts.FwkPaths = std::move(FrameworkPaths); FEOpts.FwkPaths = std::move(FrameworkPaths);
// Add default framework/library paths. // Add default framework/library paths.
- PathSeq DefaultLibraryPaths = {"/usr/lib", "/usr/local/lib"}; - PathSeq DefaultLibraryPaths = {"/usr/lib", "/usr/local/lib"};
+ PathSeq DefaultLibraryPaths = {"/usr/lib", "/system/lib", "/usr/local/lib"}; + PathSeq DefaultLibraryPaths = {"/usr/lib", "/system/lib", "/usr/local/lib"};
PathSeq DefaultFrameworkPaths = {"/Library/Frameworks", PathSeq DefaultFrameworkPaths = {"/Library/Frameworks",
"/System/Library/Frameworks"}; "/System/Library/Frameworks"};
`}, `},
}, },
}) })
return return
} }
func init() {
artifactsM[LLVMCompilerRT] = Metadata{
f: func(t Toolchain) (pkg.Artifact, string) {
_, compilerRT, _, _ := t.newLLVM()
return compilerRT, llvmVersion
},
Name: "llvm-compiler-rt",
Description: "LLVM runtime: compiler-rt",
Website: "https://llvm.org/",
}
artifactsM[LLVMRuntimes] = Metadata{
f: func(t Toolchain) (pkg.Artifact, string) {
_, _, runtimes, _ := t.newLLVM()
return runtimes, llvmVersion
},
Name: "llvm-runtimes",
Description: "LLVM runtimes: libunwind, libcxx, libcxxabi",
Website: "https://llvm.org/",
}
artifactsM[LLVMClang] = Metadata{
f: func(t Toolchain) (pkg.Artifact, string) {
_, _, _, clang := t.newLLVM()
return clang, llvmVersion
},
Name: "clang",
Description: `an "LLVM native" C/C++/Objective-C compiler`,
Website: "https://llvm.org/",
ID: 1830,
}
}
var ( var (
// llvm stores the result of Toolchain.newLLVM. // llvm stores the result of Toolchain.newLLVM.

Some files were not shown because too many files have changed in this diff Show More