buninu-linux 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +46 -46
- package/README.md +23 -5
- package/build-uki.sh +21 -7
- package/check_build.js +217 -0
- package/fetch-alpine.sh +3 -0
- package/fetch-bun.sh +3 -0
- package/index.js +72 -8
- package/initramfs/bin/dd +126 -0
- package/initramfs/bin/gunzip +6 -0
- package/initramfs/bin/gzip +6 -0
- package/initramfs/bin/mcopy +6 -0
- package/initramfs/bin/mdir +6 -0
- package/initramfs/bin/mkfs.fat +73 -0
- package/initramfs/bin/mmd +6 -0
- package/initramfs/bin/parted +178 -0
- package/initramfs/bin/truncate +78 -0
- package/initramfs/bin/unzip +248 -0
- package/initramfs/bin/zcat +6 -0
- package/initramfs/lib/crc32.js +16 -0
- package/initramfs/lib/fat.js +534 -0
- package/initramfs/lib/gpt.js +144 -0
- package/initramfs/lib/gzip.js +115 -0
- package/initramfs/lib/mtools.js +158 -0
- package/package.json +1 -1
- package/scripts/arch.js +156 -0
- package/scripts/arch.sh +5 -5
- package/scripts/build.js +145 -0
- package/scripts/cpio.js +107 -0
- package/scripts/fetch.js +194 -0
- package/scripts/modules.js +158 -0
- package/scripts/pack-initramfs.sh +33 -0
- package/scripts/pack.js +184 -0
- package/scripts/uki.js +136 -0
- package/test/js-tools.test.js +213 -0
package/NOTICE.md
CHANGED
|
@@ -103,83 +103,83 @@ the built image redistributes them.
|
|
|
103
103
|
- License: GPL-2.0-only WITH Linux-syscall-note
|
|
104
104
|
- See: `LICENSES/GPL-2.0.txt` and `LICENSES/Linux-syscall-note.txt`
|
|
105
105
|
|
|
106
|
-
Linux 6.18.
|
|
107
|
-
The default build uses package `linux-virt`, version 6.18.
|
|
108
|
-
`--linux-lts` and `--real` use package `linux-lts`, version 6.18.
|
|
106
|
+
Linux 6.18.54 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
|
|
107
|
+
The default build uses package `linux-virt`, version 6.18.54-r0.
|
|
108
|
+
`--linux-lts` and `--real` use package `linux-lts`, version 6.18.54-r0.
|
|
109
109
|
Each comes from the x86_64 or aarch64 repository to match `--arch`. All
|
|
110
110
|
listed modules are the selected package's `.ko.gz` files decompressed without
|
|
111
111
|
modification.
|
|
112
112
|
|
|
113
|
-
- Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.
|
|
113
|
+
- Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.54.tar.xz
|
|
114
114
|
- Alpine build recipe, configuration and patches (Corresponding Source),
|
|
115
|
-
pinned to the commit shipping 6.18.
|
|
116
|
-
https://gitlab.alpinelinux.org/alpine/aports/-/blob/
|
|
115
|
+
pinned to the commit shipping 6.18.54-r0 (the same for both architectures):
|
|
116
|
+
https://gitlab.alpinelinux.org/alpine/aports/-/blob/f8254a6d11c37b153dae6a0fd98ba378dd0d093d/main/linux-lts/APKBUILD
|
|
117
117
|
- Packages:
|
|
118
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.
|
|
119
|
-
SHA-256 `
|
|
120
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.
|
|
121
|
-
SHA-256 `
|
|
122
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.
|
|
123
|
-
SHA-256 `
|
|
124
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.
|
|
125
|
-
SHA-256 `
|
|
118
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.54-r0.apk
|
|
119
|
+
SHA-256 `57a522d2b6e9d1b6c9de6b9f7fc42aa4b8d33ebf9f2b42b4251dbee6ff2d9839`
|
|
120
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.54-r0.apk
|
|
121
|
+
SHA-256 `1d02a1b74a8a09c476a3c01782c0b8613ae2935988e65d4d31e6bef44dfb8b64`
|
|
122
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.54-r0.apk
|
|
123
|
+
SHA-256 `9a4a6fa042b60b70f453dded99762810516b35793c64d776d75986b0b110b6e0`
|
|
124
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.54-r0.apk
|
|
125
|
+
SHA-256 `f945ddcc306546c7c22cf10249f637fc6e806ecc40286fb50950558dd573e7e9`
|
|
126
126
|
|
|
127
127
|
SHA-256 of files in the default x86_64 `linux-virt` image:
|
|
128
128
|
|
|
129
129
|
```text
|
|
130
|
-
kernel/x86_64/vmlinuz-virt
|
|
131
|
-
failover.ko
|
|
132
|
-
net_failover.ko
|
|
133
|
-
virtio_net.ko
|
|
130
|
+
kernel/x86_64/vmlinuz-virt c7ce829b618d4a9d2df79c58ea0fb2a392e606f6c47bb94696ea75610a568166
|
|
131
|
+
failover.ko c02fc21655bd3a8598ddf29f1028a1029bcb19b1da8611d3cf3c8685d0412f84
|
|
132
|
+
net_failover.ko 46e0c3d3f4cdfafabffbd1a57ef59aac0bcc0f1a4e22b114d992cd46ac264145
|
|
133
|
+
virtio_net.ko 1163e07fd5391849820ede09277b0147ae7e48ef0f08f9e96da08bb2ff131c2a
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
SHA-256 of files in an x86_64 `--linux-lts` image:
|
|
137
137
|
|
|
138
138
|
```text
|
|
139
|
-
kernel/x86_64/vmlinuz-lts
|
|
140
|
-
failover.ko
|
|
141
|
-
net_failover.ko
|
|
142
|
-
virtio_net.ko
|
|
143
|
-
virtio_ring.ko
|
|
144
|
-
virtio.ko
|
|
145
|
-
virtio_pci_legacy_dev.ko
|
|
146
|
-
virtio_pci_modern_dev.ko
|
|
147
|
-
virtio_pci.ko
|
|
139
|
+
kernel/x86_64/vmlinuz-lts d3cb46d949f3d05a7e6753ba0b9a054323e6a7b6eeb231b31a1659d8f9f51a28
|
|
140
|
+
failover.ko 3c0e33c374419512c410559561e8bc5d2fac573cb449da404bbeaf94ec7641fe
|
|
141
|
+
net_failover.ko 2a5db89834e5f35ecdf114da8dbe4d4db4061371d37768d9f92eb09d479d21ab
|
|
142
|
+
virtio_net.ko 1fc0c4992e4a9ad83985fbad5a5674bacceb4c8ab5329b4509656ba3350478eb
|
|
143
|
+
virtio_ring.ko 3c14098101a9f30f034410efe7e91e369057d1709152a0419a3a9fc4b1367915
|
|
144
|
+
virtio.ko f47dac1696dcab539c5d313f13bd5e06945cf921bf406e0f709e1866de72803d
|
|
145
|
+
virtio_pci_legacy_dev.ko 662c0f09bcb88c7c8a79fb2159d4aa0b62044d1859a46de03fc9e57fc3501931
|
|
146
|
+
virtio_pci_modern_dev.ko 006e525fda5e369b6a1669ec0e8e174c784f1ecdb7dcfb6257ffc67fd5b93525
|
|
147
|
+
virtio_pci.ko 94d66ebd7f2a325ce83c9f42175432a7797969b3e3c7fcc07d2b913012f1c77f
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
`--real` additionally includes the following modules from that same
|
|
151
151
|
x86_64 `linux-lts` package for physical xHCI controllers and USB HID keyboards:
|
|
152
152
|
|
|
153
153
|
```text
|
|
154
|
-
usb-common.ko
|
|
155
|
-
usbcore.ko
|
|
156
|
-
xhci-hcd.ko
|
|
157
|
-
xhci-pci.ko
|
|
158
|
-
xhci-pci-renesas.ko
|
|
159
|
-
hid.ko
|
|
160
|
-
hid-generic.ko
|
|
161
|
-
usbhid.ko
|
|
154
|
+
usb-common.ko 471bed173d18c373c80e9cce646f75b71b034b3b398e6bd04c51fa065ac92e6b
|
|
155
|
+
usbcore.ko 99b20afa2057720f2aa36224909c37740cbe1068e23ba52872ed09e6c7966c29
|
|
156
|
+
xhci-hcd.ko b3ffac428a78858532a5114ad4f2f66de253aba32932f508d62eceb29af39313
|
|
157
|
+
xhci-pci.ko 2a924cfba63360acf3a9a688a5fb69f87c2c82a02f904e972a9f5c9cb303588f
|
|
158
|
+
xhci-pci-renesas.ko 2c8fa8dac908da5a13c8f87e520cc1d4a026aeb9ee58d1ac0380d696c8ef6a72
|
|
159
|
+
hid.ko 3aa16634ee8621291abfa1f9efca287299dea052678c8ead5af86003a7e493f3
|
|
160
|
+
hid-generic.ko 2879395175b39a47f09e81bb00cb1a4b565103ba4583ce99169b9ef7fd943d62
|
|
161
|
+
usbhid.ko e80b60c6dd99a87c95bd97434679aa3f069c9bce2f67a7311f179b3066cdea59
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
SHA-256 of files in the default aarch64 `linux-virt` image:
|
|
165
165
|
|
|
166
166
|
```text
|
|
167
|
-
kernel/aarch64/vmlinuz-virt
|
|
168
|
-
failover.ko
|
|
169
|
-
net_failover.ko
|
|
170
|
-
virtio_net.ko
|
|
167
|
+
kernel/aarch64/vmlinuz-virt 29de2da2ea7aa1d95da37c947278caca65ef3ad211dbb92ac75a6b962f96d0d2
|
|
168
|
+
failover.ko 62bfac505026e368b291e8d7e167dc90bff826c44d0db1ff4340de2201022674
|
|
169
|
+
net_failover.ko 3761f107b61ae4d11a7bed402cdab1862f3d61cacc612b2e6d9bc61ad1e20d6d
|
|
170
|
+
virtio_net.ko 22a95296a008f009de59e933cc1b9615396612ef5eb3922fac2b79ac672a1305
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
SHA-256 of files in an aarch64 `--linux-lts` image:
|
|
174
174
|
|
|
175
175
|
```text
|
|
176
|
-
kernel/aarch64/vmlinuz-lts
|
|
177
|
-
failover.ko
|
|
178
|
-
net_failover.ko
|
|
179
|
-
virtio_net.ko
|
|
180
|
-
virtio_pci_legacy_dev.ko
|
|
181
|
-
virtio_pci_modern_dev.ko
|
|
182
|
-
virtio_pci.ko
|
|
176
|
+
kernel/aarch64/vmlinuz-lts 52521d144fe344f23ffd03297fd0e0da42578e54d3da18685cbb7f613244c001
|
|
177
|
+
failover.ko 73571d1192683fa4f6346d7082920b4ac1a3b2edfb2da9e08ec13269dcb71f5e
|
|
178
|
+
net_failover.ko ba1700cf056585843488a5a8e75884d16cee45b4d40d8ace2f977e697cb5557c
|
|
179
|
+
virtio_net.ko fea67b801481f1445be419541e7a352a07019d52acbf644fe01f21c51d8d1475
|
|
180
|
+
virtio_pci_legacy_dev.ko 0cbfc93f3ef238c2565187eb8be94d1d0127bf2fe521f28b75245db12f4331ff
|
|
181
|
+
virtio_pci_modern_dev.ko 3fba75f38825df5ab616492bf3fb3329c4aed1a8b7f9a98370706e70a14fc2a3
|
|
182
|
+
virtio_pci.ko 92652ff84904b2b2dda1549d362ec97377f954da5463575f7e35a4ef5847c3ad
|
|
183
183
|
```
|
|
184
184
|
|
|
185
185
|
### linuxx64.efi.stub and linuxaa64.efi.stub
|
package/README.md
CHANGED
|
@@ -64,16 +64,23 @@
|
|
|
64
64
|
- This guide builds a bootable `buninu-linux-<version>.img`
|
|
65
65
|
- The documented build environment is Debian 13 under Termux PRoot
|
|
66
66
|
- A regular Debian installation works as well.
|
|
67
|
+
* Experimental Alpine support now!
|
|
67
68
|
- For a source checkout, clone into the Termux home so native Termux and PRoot
|
|
68
69
|
can share it; `~` differs between them, but the absolute path is the same.
|
|
69
70
|
|
|
70
71
|
### Steps
|
|
71
72
|
|
|
72
|
-
- Before anything, install Bun in Debian first:
|
|
73
|
+
- Before anything, install Bun in Debian/Alpine first:
|
|
73
74
|
|
|
74
75
|
```sh
|
|
76
|
+
# Debian/Ubuntu
|
|
75
77
|
apt update
|
|
76
78
|
apt install curl unzip
|
|
79
|
+
|
|
80
|
+
# Alpine
|
|
81
|
+
apk update
|
|
82
|
+
apk add curl unzip bash libgcc libstdc++
|
|
83
|
+
|
|
77
84
|
curl -fsSL https://bun.sh/install | bash
|
|
78
85
|
```
|
|
79
86
|
|
|
@@ -85,8 +92,12 @@ Install the build tools, enter a directory where
|
|
|
85
92
|
you want to keep the finished image, then run the published package:
|
|
86
93
|
|
|
87
94
|
```sh
|
|
95
|
+
# Debian/Ubuntu
|
|
88
96
|
apt install binutils-mingw-w64-x86-64 cpio curl dosfstools fakeroot mtools parted unzip
|
|
89
97
|
|
|
98
|
+
# Alpine
|
|
99
|
+
apk add bash coreutils tar curl unzip parted dosfstools mtools libgcc libstdc++
|
|
100
|
+
|
|
90
101
|
mkdir -p /data/data/com.termux/files/home/buninu-build
|
|
91
102
|
cd /data/data/com.termux/files/home/buninu-build
|
|
92
103
|
|
|
@@ -94,7 +105,14 @@ export PATH=$HOME/.bun/bin:$PATH
|
|
|
94
105
|
|
|
95
106
|
bun x buninu-linux --version
|
|
96
107
|
|
|
108
|
+
# Debian/Ubuntu
|
|
97
109
|
bun x buninu-linux -fb --real --export
|
|
110
|
+
|
|
111
|
+
# Alpine
|
|
112
|
+
export BUNINU_JS_CPIO=1
|
|
113
|
+
export BUNINU_JS_UKI=1
|
|
114
|
+
bun x buninu-linux -fb --real --export
|
|
115
|
+
|
|
98
116
|
```
|
|
99
117
|
|
|
100
118
|
`--export` copies the completed `vda.img` out of bunx's package directory and
|
|
@@ -890,7 +908,7 @@ initramfs/ init.js, commands, userspace: the same for every archi
|
|
|
890
908
|
native/<arch>/ musl and the GCC runtime (committed); bun and modules (fetched)
|
|
891
909
|
```
|
|
892
910
|
|
|
893
|
-
The `--real` inputs are Alpine v3.24 `linux-lts-6.18.
|
|
911
|
+
The `--real` inputs are Alpine v3.24 `linux-lts-6.18.54-r0`, musl `1.2.6-r2`,
|
|
894
912
|
`systemd-efistub-260.2-r0`, `libstdc++`/`libgcc` `15.2.0-r5`, and Bun 1.4.2
|
|
895
913
|
`linux-x64-musl-baseline`. The aarch64 guest uses the same versions from
|
|
896
914
|
Alpine's aarch64 repository and Bun's `linux-aarch64-musl`. There is no BusyBox
|
|
@@ -987,7 +1005,7 @@ The physical boot chain is firmware → the UKI stub → its embedded
|
|
|
987
1005
|
and, with the currently pinned kernel, embeds this complete command line:
|
|
988
1006
|
|
|
989
1007
|
```text
|
|
990
|
-
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1008
|
+
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.54-0-lts rdinit=/bin/bun -- -e import('/init.js')
|
|
991
1009
|
```
|
|
992
1010
|
|
|
993
1011
|
Serial kernel logging is retained, while the final console makes
|
|
@@ -1011,7 +1029,7 @@ the same way). Build without `--real`; this selects Alpine `linux-virt`, omits t
|
|
|
1011
1029
|
module set, and puts `ttyS0` last so serial stdio owns `/dev/console`:
|
|
1012
1030
|
|
|
1013
1031
|
```text
|
|
1014
|
-
console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1032
|
+
console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.54-0-virt rdinit=/bin/bun -- -e import('/init.js')
|
|
1015
1033
|
```
|
|
1016
1034
|
|
|
1017
1035
|
Build and boot it with:
|
|
@@ -1128,7 +1146,7 @@ The default `--real` image embeds this kernel command line (shown for the
|
|
|
1128
1146
|
currently pinned LTS release):
|
|
1129
1147
|
|
|
1130
1148
|
```text
|
|
1131
|
-
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1149
|
+
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.54-0-lts rdinit=/bin/bun -- -e import('/init.js')
|
|
1132
1150
|
```
|
|
1133
1151
|
|
|
1134
1152
|
The physical display (`tty0`) is the final console and therefore owns PID 1's
|
package/build-uki.sh
CHANGED
|
@@ -35,13 +35,33 @@ else
|
|
|
35
35
|
fi
|
|
36
36
|
printf '%s' "$consoles panic=0 PATH=/bin KERNEL_RELEASE=$kernel_release rdinit=/bin/bun -- -e import('/init.js')" > build/cmdline
|
|
37
37
|
|
|
38
|
+
# Only this architecture's removable-media name may remain: firmware of the
|
|
39
|
+
# other architecture ignores it, but run-qemu.sh reads the guest from it.
|
|
40
|
+
for efi in vda/EFI/BOOT/*.EFI; do
|
|
41
|
+
[ "$efi" = "vda/EFI/BOOT/$efi_boot_name" ] || rm -f "$efi"
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
stub="$kernel_dir/$efi_stub"
|
|
45
|
+
# BUNINU_JS_UKI=1 assembles the UKI with scripts/uki.js (bun or node) instead
|
|
46
|
+
# of binutils; it lays the sections out by the same rules as below.
|
|
47
|
+
if [ "${BUNINU_JS_UKI:-}" = 1 ]; then
|
|
48
|
+
js=$(command -v bun || command -v node) || {
|
|
49
|
+
echo "error: BUNINU_JS_UKI=1 needs bun or node on PATH" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
}
|
|
52
|
+
"$js" scripts/uki.js "$stub" "vda/EFI/BOOT/$efi_boot_name" \
|
|
53
|
+
.osrel=build/os-release .cmdline=build/cmdline \
|
|
54
|
+
.linux="$kernel_image" .initrd=build/initramfs.cpio.gz
|
|
55
|
+
echo "Built $arch UKI with Alpine linux-$linux_flavor at vda/EFI/BOOT/$efi_boot_name (scripts/uki.js)"
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
38
59
|
if ! command -v "$uki_objcopy" >/dev/null || ! command -v "$uki_objdump" >/dev/null; then
|
|
39
60
|
echo "error: build stage needs $uki_objcopy and $uki_objdump (Debian $uki_binutils_package)" >&2
|
|
40
61
|
echo "run inside PRoot or with ./index.js --docker; see README Build environment" >&2
|
|
41
62
|
exit 1
|
|
42
63
|
fi
|
|
43
64
|
|
|
44
|
-
stub="$kernel_dir/$efi_stub"
|
|
45
65
|
pe_header() { "$uki_objdump" -p "$stub" | awk -v key="$1" '$1 == key { print $2; exit }'; }
|
|
46
66
|
align_up() { echo $(( ($1 + $2 - 1) / $2 * $2 )); }
|
|
47
67
|
max() { echo $(( $1 > $2 ? $1 : $2 )); }
|
|
@@ -65,12 +85,6 @@ cmdline_vma=$((image_base + cmdline_offset))
|
|
|
65
85
|
linux_vma=$((image_base + linux_offset))
|
|
66
86
|
initrd_vma=$((image_base + initrd_offset))
|
|
67
87
|
|
|
68
|
-
# Only this architecture's removable-media name may remain: firmware of the
|
|
69
|
-
# other architecture ignores it, but run-qemu.sh reads the guest from it.
|
|
70
|
-
for efi in vda/EFI/BOOT/*.EFI; do
|
|
71
|
-
[ "$efi" = "vda/EFI/BOOT/$efi_boot_name" ] || rm -f "$efi"
|
|
72
|
-
done
|
|
73
|
-
|
|
74
88
|
"$uki_objcopy" \
|
|
75
89
|
--add-section .osrel=build/os-release \
|
|
76
90
|
--change-section-vma .osrel="$osrel_vma" \
|
package/check_build.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Content checksums of the build outputs that ignore what changes from build
|
|
3
|
+
// to build without changing what boots: file mtimes and inode numbers in the
|
|
4
|
+
// initramfs, the PE timestamp and checksum of the UKI, and the GPT GUIDs,
|
|
5
|
+
// FAT serial and FAT timestamps of the disk image. Two builds of the same
|
|
6
|
+
// inputs print the same sums.
|
|
7
|
+
//
|
|
8
|
+
// ./check_build.js build/initramfs.cpio.gz, the UKI(s) under vda/EFI/BOOT, vda.img
|
|
9
|
+
// ./check_build.js FILE... any of: .cpio(.gz), PE/EFI, GPT disk image or bare FAT image
|
|
10
|
+
//
|
|
11
|
+
// Written against node: APIs only, so it runs under node as well as bun.
|
|
12
|
+
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { gunzipSync } from "node:zlib";
|
|
18
|
+
|
|
19
|
+
const sha256 = (...parts) => {
|
|
20
|
+
const hasher = createHash("sha256");
|
|
21
|
+
for (const part of parts) hasher.update(part);
|
|
22
|
+
return hasher.digest("hex");
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const u16 = (b, o) => b[o] | (b[o + 1] << 8);
|
|
26
|
+
const u32 = (b, o) => (b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24)) >>> 0;
|
|
27
|
+
const u64 = (b, o) => u32(b, o) + u32(b, o + 4) * 2 ** 32;
|
|
28
|
+
const ascii = (b, o, n) => String.fromCharCode(...b.subarray(o, o + n));
|
|
29
|
+
|
|
30
|
+
// --- initramfs: newc cpio, optionally gzipped -------------------------------
|
|
31
|
+
// Kept per entry: name, mode, uid, gid, device numbers of special files, size
|
|
32
|
+
// and data. Dropped: inode, mtime, link count, the containing device, and
|
|
33
|
+
// the order of the entries (`sort` follows the locale, and the unpacked tree
|
|
34
|
+
// is the same either way): entries count sorted by name, byte by byte.
|
|
35
|
+
function cpioSum(bytes) {
|
|
36
|
+
if (bytes[0] === 0x1f && bytes[1] === 0x8b) bytes = gunzipSync(bytes);
|
|
37
|
+
const entries = [];
|
|
38
|
+
let offset = 0;
|
|
39
|
+
while (offset + 110 <= bytes.length) {
|
|
40
|
+
const magic = ascii(bytes, offset, 6);
|
|
41
|
+
if (magic !== "070701" && magic !== "070702") throw new Error(`not a newc cpio entry at ${offset}`);
|
|
42
|
+
const field = (index) => parseInt(ascii(bytes, offset + 6 + index * 8, 8), 16);
|
|
43
|
+
const [mode, uid, gid, size, rdevMajor, rdevMinor, nameSize] =
|
|
44
|
+
[field(1), field(2), field(3), field(6), field(9), field(10), field(11)];
|
|
45
|
+
const name = Buffer.from(bytes.subarray(offset + 110, offset + 110 + nameSize - 1));
|
|
46
|
+
const data = (offset + 110 + nameSize + 3) & ~3;
|
|
47
|
+
const next = (data + size + 3) & ~3;
|
|
48
|
+
if (name.toString("latin1") === "TRAILER!!!") break;
|
|
49
|
+
entries.push({ name, header: `\0${mode.toString(8)} ${uid} ${gid} ${rdevMajor}:${rdevMinor} ${size}\n`, data: bytes.subarray(data, data + size) });
|
|
50
|
+
offset = next;
|
|
51
|
+
}
|
|
52
|
+
entries.sort((a, b) => Buffer.compare(a.name, b.name));
|
|
53
|
+
const hasher = createHash("sha256");
|
|
54
|
+
for (const { name, header, data } of entries) {
|
|
55
|
+
hasher.update(name);
|
|
56
|
+
hasher.update(header);
|
|
57
|
+
hasher.update(data);
|
|
58
|
+
}
|
|
59
|
+
return { sum: hasher.digest("hex"), detail: `${entries.length} entries` };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- UKI: PE image ----------------------------------------------------------
|
|
63
|
+
// The whole file with the COFF TimeDateStamp and optional-header CheckSum
|
|
64
|
+
// zeroed, except that the .initrd section counts by its cpio content sum.
|
|
65
|
+
// The gzip output's size varies with the mtimes inside, so everything that
|
|
66
|
+
// follows it is left out too: the .initrd sizes in the section table, the
|
|
67
|
+
// file length (its raw data is cut out rather than zeroed), SizeOfImage and
|
|
68
|
+
// SizeOfInitializedData.
|
|
69
|
+
function peSum(bytes) {
|
|
70
|
+
const pe = u32(bytes, 0x3c);
|
|
71
|
+
if (ascii(bytes, pe, 4) !== "PE\0\0") throw new Error("not a PE image");
|
|
72
|
+
const coff = pe + 4;
|
|
73
|
+
const sections = u16(bytes, coff + 2);
|
|
74
|
+
const optional = coff + 20;
|
|
75
|
+
const table = optional + u16(bytes, coff + 16);
|
|
76
|
+
const copy = Uint8Array.from(bytes);
|
|
77
|
+
copy.fill(0, coff + 4, coff + 8); // TimeDateStamp
|
|
78
|
+
copy.fill(0, optional + 64, optional + 68); // CheckSum
|
|
79
|
+
let initrd = null;
|
|
80
|
+
let cut = null;
|
|
81
|
+
for (let index = 0; index < sections; index++) {
|
|
82
|
+
const header = table + index * 40;
|
|
83
|
+
const name = ascii(bytes, header, 8).replace(/\0+$/, "");
|
|
84
|
+
if (name !== ".initrd") continue;
|
|
85
|
+
const rawSize = u32(bytes, header + 16);
|
|
86
|
+
const start = u32(bytes, header + 20);
|
|
87
|
+
initrd = cpioSum(bytes.subarray(start, start + (u32(bytes, header + 8) || rawSize)));
|
|
88
|
+
cut = [start, start + rawSize];
|
|
89
|
+
copy.fill(0, header + 8, header + 12); // VirtualSize
|
|
90
|
+
copy.fill(0, header + 16, header + 20); // SizeOfRawData
|
|
91
|
+
copy.fill(0, optional + 8, optional + 12); // SizeOfInitializedData
|
|
92
|
+
copy.fill(0, optional + 56, optional + 60); // SizeOfImage
|
|
93
|
+
// Sections stored after it move with its size.
|
|
94
|
+
for (let other = 0; other < sections; other++) {
|
|
95
|
+
const otherHeader = table + other * 40;
|
|
96
|
+
if (u32(bytes, otherHeader + 20) > start) copy.fill(0, otherHeader + 20, otherHeader + 24);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const parts = cut ? [copy.subarray(0, cut[0]), copy.subarray(cut[1])] : [copy];
|
|
100
|
+
const sum = sha256(...parts, initrd?.sum ?? "");
|
|
101
|
+
return { sum, detail: initrd ? `.initrd ${initrd.sum.slice(0, 16)} (${initrd.detail})` : "no .initrd" };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- disk image: GPT with a FAT ESP, or a bare FAT filesystem ----------------
|
|
105
|
+
// GPT: each partition's start, size and type, but not the disk or partition
|
|
106
|
+
// GUIDs. FAT: the file tree (names, sizes, contents), not timestamps, serial
|
|
107
|
+
// or free-space contents. .EFI files inside count by their PE sum, not their length.
|
|
108
|
+
function diskSum(bytes) {
|
|
109
|
+
if (ascii(bytes, 512, 8) !== "EFI PART") return fatSum(bytes);
|
|
110
|
+
const entriesLba = u64(bytes, 512 + 72);
|
|
111
|
+
const count = u32(bytes, 512 + 80);
|
|
112
|
+
const entrySize = u32(bytes, 512 + 84);
|
|
113
|
+
const parts = [];
|
|
114
|
+
for (let index = 0; index < count; index++) {
|
|
115
|
+
const entry = entriesLba * 512 + index * entrySize;
|
|
116
|
+
const type = bytes.subarray(entry, entry + 16);
|
|
117
|
+
if (type.every((byte) => byte === 0)) continue;
|
|
118
|
+
const first = u64(bytes, entry + 32);
|
|
119
|
+
const last = u64(bytes, entry + 40);
|
|
120
|
+
const fs = fatSum(bytes.subarray(first * 512, (last + 1) * 512));
|
|
121
|
+
parts.push(`${Buffer.from(type).toString("hex")} ${first} ${last} ${fs.sum}\n`);
|
|
122
|
+
}
|
|
123
|
+
const lines = parts.map((line) => line.split(" ")[3].trim().slice(0, 16));
|
|
124
|
+
return { sum: sha256(...parts), detail: `${parts.length} partition(s), files ${lines.join(", ")}` };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function fatSum(bytes) {
|
|
128
|
+
const bytesPerSector = u16(bytes, 11);
|
|
129
|
+
const sectorsPerCluster = bytes[13];
|
|
130
|
+
const reserved = u16(bytes, 14);
|
|
131
|
+
const fats = bytes[16];
|
|
132
|
+
const rootEntries = u16(bytes, 17);
|
|
133
|
+
const fatSize = u16(bytes, 22) || u32(bytes, 36);
|
|
134
|
+
const totalSectors = u16(bytes, 19) || u32(bytes, 32);
|
|
135
|
+
if (bytes[510] !== 0x55 || bytes[511] !== 0xaa || !bytesPerSector) throw new Error("no FAT filesystem");
|
|
136
|
+
const rootSectors = Math.ceil((rootEntries * 32) / bytesPerSector);
|
|
137
|
+
const dataStart = (reserved + fats * fatSize + rootSectors) * bytesPerSector;
|
|
138
|
+
const clusterBytes = sectorsPerCluster * bytesPerSector;
|
|
139
|
+
const clusters = (totalSectors * bytesPerSector - dataStart) / clusterBytes;
|
|
140
|
+
const fat32 = clusters >= 65525;
|
|
141
|
+
const fat12 = clusters < 4085;
|
|
142
|
+
const fatOffset = reserved * bytesPerSector;
|
|
143
|
+
const nextCluster = (cluster) => {
|
|
144
|
+
if (fat32) return u32(bytes, fatOffset + cluster * 4) & 0x0fffffff;
|
|
145
|
+
if (!fat12) return u16(bytes, fatOffset + cluster * 2);
|
|
146
|
+
const value = u16(bytes, fatOffset + Math.floor(cluster * 1.5));
|
|
147
|
+
return cluster & 1 ? value >> 4 : value & 0xfff;
|
|
148
|
+
};
|
|
149
|
+
const end = fat32 ? 0x0ffffff8 : fat12 ? 0xff8 : 0xfff8;
|
|
150
|
+
const chain = (cluster) => {
|
|
151
|
+
const pieces = [];
|
|
152
|
+
for (; cluster >= 2 && cluster < end; cluster = nextCluster(cluster)) {
|
|
153
|
+
const start = dataStart + (cluster - 2) * clusterBytes;
|
|
154
|
+
pieces.push(bytes.subarray(start, start + clusterBytes));
|
|
155
|
+
}
|
|
156
|
+
return Buffer.concat(pieces);
|
|
157
|
+
};
|
|
158
|
+
const rootStart = (reserved + fats * fatSize) * bytesPerSector;
|
|
159
|
+
const root = fat32 ? chain(u32(bytes, 44)) : bytes.subarray(rootStart, rootStart + rootEntries * 32);
|
|
160
|
+
const files = [];
|
|
161
|
+
const walk = (directory, path) => {
|
|
162
|
+
for (let offset = 0; offset + 32 <= directory.length; offset += 32) {
|
|
163
|
+
const first = directory[offset];
|
|
164
|
+
if (first === 0) break;
|
|
165
|
+
const attributes = directory[offset + 11];
|
|
166
|
+
if (first === 0xe5 || attributes === 0x0f || attributes & 0x08) continue; // deleted, long name, label
|
|
167
|
+
const base = ascii(directory, offset, 8).trimEnd();
|
|
168
|
+
const extension = ascii(directory, offset + 8, 3).trimEnd();
|
|
169
|
+
if (base === "." || base === "..") continue;
|
|
170
|
+
const name = path + base + (extension ? "." + extension : "");
|
|
171
|
+
const cluster = (u16(directory, offset + 20) << 16) | u16(directory, offset + 26);
|
|
172
|
+
if (attributes & 0x10) {
|
|
173
|
+
files.push(`${name}/\n`);
|
|
174
|
+
walk(chain(cluster), name + "/");
|
|
175
|
+
} else {
|
|
176
|
+
const data = chain(cluster).subarray(0, u32(directory, offset + 28));
|
|
177
|
+
// A UKI's length follows its .initrd's gzip size; its PE sum covers the rest.
|
|
178
|
+
const pe = isPE(data);
|
|
179
|
+
files.push(`${name} ${pe ? "pe" : data.length} ${pe ? peSum(data).sum : sha256(data)}\n`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
walk(root, "/");
|
|
184
|
+
return { sum: sha256(...files), detail: files.map((line) => line.split(" ")[0].trim()).join(" ") };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const isPE = (bytes) => bytes[0] === 0x4d && bytes[1] === 0x5a && bytes.length > 0x40 &&
|
|
188
|
+
ascii(bytes, u32(bytes, 0x3c), 4) === "PE\0\0";
|
|
189
|
+
|
|
190
|
+
function check(path) {
|
|
191
|
+
const bytes = new Uint8Array(readFileSync(path));
|
|
192
|
+
if ((bytes[0] === 0x1f && bytes[1] === 0x8b) || ascii(bytes, 0, 5) === "07070") return cpioSum(bytes);
|
|
193
|
+
if (isPE(bytes)) return peSum(bytes);
|
|
194
|
+
return diskSum(bytes);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const root = dirname(fileURLToPath(import.meta.url));
|
|
198
|
+
let paths = process.argv.slice(2);
|
|
199
|
+
if (paths.length === 0) {
|
|
200
|
+
const boot = join(root, "vda/EFI/BOOT");
|
|
201
|
+
paths = [
|
|
202
|
+
join(root, "build/initramfs.cpio.gz"),
|
|
203
|
+
...(existsSync(boot) ? readdirSync(boot).filter((name) => /\.efi$/i.test(name)).sort().map((name) => join(boot, name)) : []),
|
|
204
|
+
join(root, "vda.img"),
|
|
205
|
+
].filter((path) => existsSync(path));
|
|
206
|
+
}
|
|
207
|
+
let failed = false;
|
|
208
|
+
for (const path of paths) {
|
|
209
|
+
try {
|
|
210
|
+
const { sum, detail } = check(path);
|
|
211
|
+
console.log(`${sum} ${path} (${detail})`);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`${path}: ${error.message}`);
|
|
214
|
+
failed = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
process.exit(failed ? 1 : 0);
|
package/fetch-alpine.sh
CHANGED
package/fetch-bun.sh
CHANGED
package/index.js
CHANGED
|
@@ -51,8 +51,12 @@ const stages = [
|
|
|
51
51
|
short: "b",
|
|
52
52
|
scripts: ["build-uki.sh", "build-image.sh"],
|
|
53
53
|
tools: (arch) => [
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// BUNINU_JS_CPIO=1: pack-initramfs.sh writes the archive with scripts/cpio.js.
|
|
55
|
+
...(process.env.BUNINU_JS_CPIO === "1" ? [] : ["fakeroot", "cpio", "gzip"]),
|
|
56
|
+
// BUNINU_JS_UKI=1: build-uki.sh assembles the UKI with scripts/uki.js.
|
|
57
|
+
...(process.env.BUNINU_JS_UKI === "1"
|
|
58
|
+
? []
|
|
59
|
+
: [`${architectures[arch].binutils}-objcopy`, `${architectures[arch].binutils}-objdump`]),
|
|
56
60
|
"parted", "mkfs.fat", "mmd", "mcopy",
|
|
57
61
|
],
|
|
58
62
|
},
|
|
@@ -75,6 +79,7 @@ function usage() {
|
|
|
75
79
|
Usage:
|
|
76
80
|
${pkg.name} [-f] [-b] [-r] [--arch ARCH] [--docker] [--export] [--linux-lts]
|
|
77
81
|
[--real] [-- qemu arguments]
|
|
82
|
+
${pkg.name} --check [FILE...]
|
|
78
83
|
|
|
79
84
|
Stages (always run in this order, whichever you pick):
|
|
80
85
|
-f, --fetch download and verify the pinned kernel, musl, GCC runtime,
|
|
@@ -100,6 +105,12 @@ Stages (always run in this order, whichever you pick):
|
|
|
100
105
|
primary console, and include USB xHCI/HID keyboard modules
|
|
101
106
|
(x86_64 only)
|
|
102
107
|
|
|
108
|
+
--check [FILE...]
|
|
109
|
+
print content checksums of the build outputs that ignore
|
|
110
|
+
mtimes, timestamps and GUIDs, and exit (check_build.js; the
|
|
111
|
+
arguments after it are its own: .img, .efi or .cpio files,
|
|
112
|
+
by default the initramfs, the UKI and vda.img)
|
|
113
|
+
|
|
103
114
|
-h, --help show this
|
|
104
115
|
-V, --version show name, version, runtime and platform
|
|
105
116
|
--readme render README.md in the terminal and exit
|
|
@@ -109,6 +120,10 @@ initramfs/init.js needs, -r just boots what is already there. Anything after
|
|
|
109
120
|
-- goes to qemu-system-x86_64 or qemu-system-aarch64 verbatim (for example:
|
|
110
121
|
-r -- -smp 4).
|
|
111
122
|
|
|
123
|
+
With BUNINU_JS_TOOLS=1 in the environment, fetch and build are done in
|
|
124
|
+
JavaScript (scripts/fetch.js, scripts/build.js) and need only node or bun:
|
|
125
|
+
no Debian toolchain and no Docker.
|
|
126
|
+
|
|
112
127
|
Fetch and build need the Debian toolchain from README "Build environment"
|
|
113
128
|
(PRoot, a Debian or Ubuntu host, or --docker); run needs QEMU (Termux
|
|
114
129
|
pkg, apt, or brew install qemu). Tools missing for the stages you picked are
|
|
@@ -149,6 +164,8 @@ function parse(argv) {
|
|
|
149
164
|
} else if (argument === "--readme") {
|
|
150
165
|
printReadme();
|
|
151
166
|
process.exit(0);
|
|
167
|
+
} else if (argument === "--check") {
|
|
168
|
+
runCheck(argv.slice(index + 1));
|
|
152
169
|
} else if (argument === "--linux-lts") {
|
|
153
170
|
linuxLts = true;
|
|
154
171
|
} else if (argument === "--real") {
|
|
@@ -217,6 +234,14 @@ function printReadme() {
|
|
|
217
234
|
process.stdout.write(rendered.endsWith("\n") ? rendered : rendered + "\n");
|
|
218
235
|
}
|
|
219
236
|
|
|
237
|
+
// --check hands everything after it to check_build.js, under the same
|
|
238
|
+
// runtime, and exits with its status.
|
|
239
|
+
function runCheck(args) {
|
|
240
|
+
const result = spawnSync(process.execPath, [resolve(rootDir, "check_build.js"), ...args], { stdio: "inherit" });
|
|
241
|
+
if (result.error) fail(`could not start check_build.js: ${result.error.message}`);
|
|
242
|
+
process.exit(result.status ?? 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
220
245
|
// `command -v` without a shell: look for an executable on PATH.
|
|
221
246
|
function hasTool(name) {
|
|
222
247
|
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
@@ -229,13 +254,18 @@ function hasTool(name) {
|
|
|
229
254
|
return false;
|
|
230
255
|
}
|
|
231
256
|
|
|
232
|
-
|
|
257
|
+
// BUNINU_JS_TOOLS=1: fetch and build run scripts/fetch.js and scripts/build.js
|
|
258
|
+
// in this process instead of the shell scripts, so they need no toolchain
|
|
259
|
+
// and no Docker; run is unchanged.
|
|
260
|
+
const jsTools = process.env.BUNINU_JS_TOOLS === "1";
|
|
261
|
+
const usesJs = (stage) => jsTools && stage.flag !== "run";
|
|
262
|
+
const inDocker = (stage) => useDocker && stage.flag !== "run" && !jsTools;
|
|
233
263
|
|
|
234
264
|
function checkTools(selectedStages) {
|
|
235
265
|
const missing = [];
|
|
236
266
|
for (const stage of selectedStages) {
|
|
237
267
|
const arch = stage.flag === "run" ? runArch : buildArch;
|
|
238
|
-
for (const tool of inDocker(stage) ? ["docker"] : stage.tools(arch)) {
|
|
268
|
+
for (const tool of inDocker(stage) ? ["docker"] : usesJs(stage) ? [] : stage.tools(arch)) {
|
|
239
269
|
if (!hasTool(tool)) missing.push(`${tool} (for --${stage.flag})`);
|
|
240
270
|
}
|
|
241
271
|
}
|
|
@@ -284,7 +314,7 @@ function dockerImage() {
|
|
|
284
314
|
// into the bind-mounted checkout stays owned by that user; fakeroot provides
|
|
285
315
|
// the root ownership the initramfs records.
|
|
286
316
|
function dockerRun(script, env) {
|
|
287
|
-
const passEnv = ["BUNINU_ARCH", "LINUX_FLAVOR", "REAL_MACHINE", "ALPINE_MIRROR"]
|
|
317
|
+
const passEnv = ["BUNINU_ARCH", "LINUX_FLAVOR", "REAL_MACHINE", "ALPINE_MIRROR", "BUNINU_JS_UKI", "BUNINU_JS_CPIO"]
|
|
288
318
|
.filter((name) => env[name] !== undefined)
|
|
289
319
|
.flatMap((name) => ["--env", `${name}=${env[name]}`]);
|
|
290
320
|
const user = typeof process.getuid === "function"
|
|
@@ -327,12 +357,46 @@ const stageEnv = {
|
|
|
327
357
|
...(real && { REAL_MACHINE: "1" }),
|
|
328
358
|
};
|
|
329
359
|
|
|
360
|
+
// The arch.js config for the JS stages, from the same settings as stageEnv.
|
|
361
|
+
const jsConfig = async (env) => (await import("./scripts/arch.js")).config({
|
|
362
|
+
arch: env.BUNINU_ARCH,
|
|
363
|
+
flavor: env.LINUX_FLAVOR,
|
|
364
|
+
real: env.REAL_MACHINE === "1",
|
|
365
|
+
mirror: env.ALPINE_MIRROR,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
async function runJs(stage, env) {
|
|
369
|
+
const module = stage.flag === "fetch" ? "scripts/fetch.js" : "scripts/build.js";
|
|
370
|
+
console.log(`\n==> [${stage.flag} js] ${module}`);
|
|
371
|
+
try {
|
|
372
|
+
const cfg = await jsConfig(env);
|
|
373
|
+
if (stage.flag === "fetch") await (await import("./scripts/fetch.js")).fetchAll(cfg);
|
|
374
|
+
else await (await import("./scripts/build.js")).buildAll(cfg);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
fail(`${module}: ${error.message}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
330
380
|
for (const stage of ordered) {
|
|
331
381
|
// run-qemu.sh builds a missing vda.img itself, which needs the build
|
|
332
|
-
// toolchain; with Docker that has to happen in the container first
|
|
333
|
-
|
|
382
|
+
// toolchain; with Docker that has to happen in the container first, and
|
|
383
|
+
// with BUNINU_JS_TOOLS=1 scripts/build.js writes it.
|
|
384
|
+
if (stage.flag === "run" && (useDocker || jsTools) && !selected.has(stages[1]) &&
|
|
334
385
|
!existsSync(resolve(rootDir, "vda.img"))) {
|
|
335
|
-
|
|
386
|
+
if (jsTools) {
|
|
387
|
+
console.log("\n==> [build js] scripts/build.js image");
|
|
388
|
+
try {
|
|
389
|
+
(await import("./scripts/build.js")).image(await jsConfig({ ...stageEnv, BUNINU_ARCH: runArch }));
|
|
390
|
+
} catch (error) {
|
|
391
|
+
fail(`scripts/build.js: ${error.message}`);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
runScript(stages[1], "build-image.sh", [], { ...stageEnv, BUNINU_ARCH: runArch });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (usesJs(stage)) {
|
|
398
|
+
await runJs(stage, stageEnv);
|
|
399
|
+
continue;
|
|
336
400
|
}
|
|
337
401
|
for (const script of stage.scripts) {
|
|
338
402
|
runScript(stage, script, stage.flag === "run" ? passthrough : [], stageEnv);
|