buninu-linux 0.4.0 → 0.6.0
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/Dockerfile +23 -0
- package/NOTICE.md +111 -63
- package/README.md +292 -58
- package/build-image.sh +9 -5
- package/build-uki.sh +44 -28
- package/fetch-alpine.sh +45 -31
- package/fetch-bun.sh +14 -21
- package/graphics.md +7 -2
- package/index.js +150 -35
- package/initramfs/bin/bunterm +5 -1
- package/initramfs/bin/chroot +216 -0
- package/initramfs/bin/ip +3 -2
- package/initramfs/bin/ps +3 -0
- package/initramfs/bin/stripansi +27 -0
- package/initramfs/bin/tar +198 -14
- package/initramfs/init.js +149 -38
- package/initramfs/lib/bunterm/images.js +17 -2
- package/initramfs/lib/bunterm/mouse.js +222 -0
- package/initramfs/lib/bunterm/renderer.js +48 -3
- package/initramfs/lib/bunterm/terminal.js +123 -2
- package/initramfs/lib/dlopen.js +4 -0
- package/initramfs/lib/document.js +5 -1
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +35 -2
- package/initramfs/usr/share/doc/buninu-linux/chroot.md +105 -0
- package/initramfs/usr/share/doc/buninu-linux/tar.md +32 -6
- package/native/aarch64/lib/ld-musl-aarch64.so.1 +0 -0
- package/native/aarch64/lib/libc.musl-aarch64.so.1 +0 -0
- package/native/aarch64/lib/libgcc_s.so.1 +0 -0
- package/native/aarch64/lib/libstdc++.so.6 +0 -0
- package/package.json +2 -2
- package/run-qemu.sh +61 -12
- package/scripts/arch.sh +113 -0
- package/scripts/fetch.sh +5 -3
- package/scripts/pack-initramfs.sh +18 -4
- package/test/host/Dockerfile +13 -0
- package/test/host/boot-test.exp +70 -0
- package/test/host/host-test.sh +49 -0
- package/test/host/in-container.sh +58 -0
- /package/{initramfs → native/x86_64}/lib/ld-musl-x86_64.so.1 +0 -0
- /package/{initramfs → native/x86_64}/lib/libc.musl-x86_64.so.1 +0 -0
- /package/{initramfs → native/x86_64}/lib/libgcc_s.so.1 +0 -0
- /package/{initramfs → native/x86_64}/lib/libstdc++.so.6 +0 -0
package/scripts/fetch.sh
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
#
|
|
3
3
|
# fetch <url> <name> <sha256>
|
|
4
4
|
#
|
|
5
|
-
# Downloads to
|
|
6
|
-
#
|
|
5
|
+
# Downloads to $downloads_dir/<name> (downloads/<arch>/, set by
|
|
6
|
+
# scripts/arch.sh), unless a file with that exact SHA-256 is already sitting
|
|
7
|
+
# there. The hash is both the cache key and the integrity
|
|
7
8
|
# check, so there is no weaker "file exists" path: a truncated, stale or
|
|
8
9
|
# tampered file fails the first comparison, gets fetched again, and still has
|
|
9
10
|
# to pass the second one before any script extracts from it.
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
# a version (which changes both the name and the hash) always refetches.
|
|
13
14
|
fetch() {
|
|
14
15
|
local url=$1 name=$2 sha=$3
|
|
15
|
-
local path="
|
|
16
|
+
local path="$downloads_dir/$name"
|
|
16
17
|
|
|
17
18
|
if [ -f "$path" ] && printf '%s %s\n' "$sha" "$path" | sha256sum --check --status; then
|
|
18
19
|
echo "cached $name"
|
|
@@ -20,6 +21,7 @@ fetch() {
|
|
|
20
21
|
fi
|
|
21
22
|
|
|
22
23
|
echo "fetch $name"
|
|
24
|
+
mkdir -p "$downloads_dir"
|
|
23
25
|
curl -fL "$url" -o "$path"
|
|
24
26
|
# --quiet prints only failures; a mismatch exits non-zero under `set -e`.
|
|
25
27
|
printf '%s %s\n' "$sha" "$path" | sha256sum --check --quiet
|
|
@@ -2,13 +2,26 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")/.."
|
|
5
|
+
|
|
6
|
+
. ./scripts/arch.sh
|
|
7
|
+
|
|
8
|
+
test -x "$native_dir/bin/bun" || {
|
|
9
|
+
echo "error: no $native_dir/bin/bun yet; run the fetch stage first (./index.js -f)" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
5
12
|
mkdir -p build
|
|
6
13
|
|
|
7
14
|
staging="$(mktemp -d "$PWD/.initramfs.XXXXXX")"
|
|
8
15
|
archive="$(mktemp "$PWD/.initramfs.XXXXXX.cpio")"
|
|
9
16
|
trap 'rm -rf "$staging"; rm -f "$archive"' EXIT
|
|
10
17
|
|
|
18
|
+
# initramfs/ is the same for every architecture; native/<arch>/ adds bun, the
|
|
19
|
+
# musl loader, the GCC runtime and the kernel modules on top of it.
|
|
20
|
+
# A checkout fetched before native/ existed still has an x86_64 bun and its
|
|
21
|
+
# modules under initramfs/; they are dropped rather than packed.
|
|
11
22
|
cp -a --reflink=auto initramfs/. "$staging/"
|
|
23
|
+
rm -rf "$staging/bin/bun" "$staging/lib/modules"
|
|
24
|
+
cp -a --reflink=auto "$native_dir/." "$staging/"
|
|
12
25
|
|
|
13
26
|
# fakeroot lets cpio record the character devices without real root. Nothing
|
|
14
27
|
# has mounted devtmpfs when the kernel execs Bun as PID 1, so every device
|
|
@@ -20,10 +33,11 @@ cp -a --reflink=auto initramfs/. "$staging/"
|
|
|
20
33
|
# This is the one device that actually blocks booting Bun as PID 1.
|
|
21
34
|
#
|
|
22
35
|
# The rest are cheap to include and keep /dev usable if the devtmpfs mount in
|
|
23
|
-
# init.js ever fails.
|
|
36
|
+
# init.js ever fails. The serial port is ttyS0 (4, 64) on x86_64 and the
|
|
37
|
+
# PL011 ttyAMA0 (204, 64) on aarch64.
|
|
24
38
|
fakeroot-tcp -- sh -c '
|
|
25
39
|
mknod -m 0600 "$1/dev/console" c 5 1
|
|
26
|
-
mknod -m 0620 "$1/dev
|
|
40
|
+
mknod -m 0620 "$1/dev/$3" c "$4" 64
|
|
27
41
|
mknod -m 0666 "$1/dev/null" c 1 3
|
|
28
42
|
mknod -m 0666 "$1/dev/zero" c 1 5
|
|
29
43
|
mknod -m 0666 "$1/dev/random" c 1 8
|
|
@@ -34,8 +48,8 @@ fakeroot-tcp -- sh -c '
|
|
|
34
48
|
| sort -z \
|
|
35
49
|
| cpio --null --create --format=newc --owner=0:0 --reproducible 2>/dev/null \
|
|
36
50
|
> "$2"
|
|
37
|
-
' sh "$staging" "$archive"
|
|
51
|
+
' sh "$staging" "$archive" "$serial_console" "$serial_major"
|
|
38
52
|
|
|
39
53
|
gzip -9n < "$archive" > build/initramfs.cpio.gz
|
|
40
54
|
|
|
41
|
-
echo "Built build/initramfs.cpio.gz"
|
|
55
|
+
echo "Built build/initramfs.cpio.gz for $arch"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# The "ordinary Debian 13 machine" host-test.sh builds and boots the guests on:
|
|
2
|
+
# the README's build packages for both guests, QEMU with OVMF and AAVMF, node
|
|
3
|
+
# to run index.js, git to clone the checkout and expect to drive the serial
|
|
4
|
+
# console. Built for linux/amd64 or linux/arm64, whichever host is simulated.
|
|
5
|
+
FROM debian:13-slim
|
|
6
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
7
|
+
binutils-mingw-w64-x86-64 binutils-aarch64-linux-gnu cpio curl dosfstools fakeroot mtools parted unzip \
|
|
8
|
+
ca-certificates git nodejs expect \
|
|
9
|
+
qemu-system-x86 ovmf qemu-system-arm qemu-efi-aarch64 \
|
|
10
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
11
|
+
|
|
12
|
+
# parted asks udev to settle after writing a partition table; see ../../Dockerfile.
|
|
13
|
+
RUN ln -s /bin/true /usr/local/bin/udevadm
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/expect -f
|
|
2
|
+
# Boots vda.img with `node ./index.js -r --arch <arch>` and drives the serial
|
|
3
|
+
# console: the REPL, the Buninu shell, eth0, PID 1, a tmpfs mount, a fetch from
|
|
4
|
+
# inside the guest and poweroff. Each step waits for the output it expects, so
|
|
5
|
+
# a hang or a wrong answer fails with a message instead of racing ahead.
|
|
6
|
+
#
|
|
7
|
+
# Usage: boot-test.exp <checkout> x86_64|aarch64 [boot timeout in seconds]
|
|
8
|
+
|
|
9
|
+
set checkout [lindex $argv 0]
|
|
10
|
+
set arch [lindex $argv 1]
|
|
11
|
+
set boot_timeout [expr {[llength $argv] > 2 ? [lindex $argv 2] : 900}]
|
|
12
|
+
switch -- $arch {
|
|
13
|
+
x86_64 { set js_arch x64 }
|
|
14
|
+
aarch64 { set js_arch arm64 }
|
|
15
|
+
default {
|
|
16
|
+
puts "usage: boot-test.exp <checkout> x86_64|aarch64 \[boot timeout\]"
|
|
17
|
+
exit 2
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
proc fail {message} {
|
|
22
|
+
puts "\n### FAIL: $message"
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Sends one line to the guest, then waits for a regular expression in the
|
|
27
|
+
# output. The patterns match what the command prints, not its echo.
|
|
28
|
+
proc step {what line pattern} {
|
|
29
|
+
send -- "$line\r"
|
|
30
|
+
expect {
|
|
31
|
+
-re $pattern { puts "\n### ok: $what" }
|
|
32
|
+
timeout { fail "$what: no match for /$pattern/ within $::timeout s" }
|
|
33
|
+
eof { fail "$what: QEMU exited" }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
cd $checkout
|
|
38
|
+
set start [clock seconds]
|
|
39
|
+
spawn node ./index.js -r --arch $arch
|
|
40
|
+
|
|
41
|
+
set timeout $boot_timeout
|
|
42
|
+
expect {
|
|
43
|
+
"bun-repl>" { puts "\n### ok: REPL after [expr {[clock seconds] - $start}] s" }
|
|
44
|
+
"Kernel panic" { fail "kernel panic before the REPL" }
|
|
45
|
+
timeout { fail "no REPL within $boot_timeout s" }
|
|
46
|
+
eof { fail "QEMU exited before the REPL" }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# bunmsh switches bracketed paste off (ESC[?2004l) before a command's output
|
|
50
|
+
# and Bun colours the numbers it prints, so a line of output can carry escape
|
|
51
|
+
# sequences around the text.
|
|
52
|
+
set esc {(?:\x1b\[[0-9;?]*[a-zA-Z])*}
|
|
53
|
+
|
|
54
|
+
set timeout 180
|
|
55
|
+
step "process.arch is $js_arch" "process.arch" "'$js_arch'"
|
|
56
|
+
step "Buninu shell" "start()" {\$ }
|
|
57
|
+
step "uname reports $arch" "uname -a" "Linux \\(none\\) \[^\r\n\]* $arch"
|
|
58
|
+
step "eth0 is 10.0.2.15" "ip -br addr" {eth0 +UP +10\.0\.2\.15/24}
|
|
59
|
+
step "PID 1 is bun" "ps -eo pid,args" {\n +1 /bin/bun -e import}
|
|
60
|
+
step "tmpfs mount" \
|
|
61
|
+
"mount -t tmpfs -o size=8M tmpfs /tmp/x --mkdir && echo hi > /tmp/x/f && cat /tmp/x/f && umount /tmp/x" \
|
|
62
|
+
"\n${esc}hi${esc}\r"
|
|
63
|
+
step "fetch from the guest" \
|
|
64
|
+
{bun -e 'console.log((await fetch("http://example.com")).status)'} \
|
|
65
|
+
"\n${esc}200${esc}\r"
|
|
66
|
+
step "poweroff" "poweroff" "reboot: Power down"
|
|
67
|
+
expect {
|
|
68
|
+
eof { puts "\n### ok: QEMU exited, [expr {[clock seconds] - $start}] s in total" }
|
|
69
|
+
timeout { fail "QEMU still running after poweroff" }
|
|
70
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Builds and boots both guests on a fresh Debian 13 host of either CPU, in
|
|
3
|
+
# Docker:
|
|
4
|
+
#
|
|
5
|
+
# test/host/host-test.sh [amd64|arm64]
|
|
6
|
+
#
|
|
7
|
+
# The platform defaults to this machine's own CPU; the other one runs
|
|
8
|
+
# emulated and takes a few times longer. The container (./Dockerfile) has the
|
|
9
|
+
# README's build packages and QEMU. It clones the committed HEAD of this
|
|
10
|
+
# checkout, so uncommitted changes to the project are not tested, then builds
|
|
11
|
+
# with the host toolchain rather than --docker and boots each guest through
|
|
12
|
+
# boot-test.exp. The exit status is 0 only when every step passed.
|
|
13
|
+
#
|
|
14
|
+
# GUESTS="aarch64" which guests to test (default: x86_64 aarch64)
|
|
15
|
+
# BUILD_FLAGS=--linux-lts extra flags for the build stage
|
|
16
|
+
# BOOT_TIMEOUT=900 seconds to wait for the REPL
|
|
17
|
+
# ALPINE_MIRROR=... passed through to the fetch stage
|
|
18
|
+
#
|
|
19
|
+
# downloads/ is mounted read-only as a cache; running `./index.js -f --arch
|
|
20
|
+
# <arch>` here first saves the container from downloading.
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
cd "$(dirname "$0")/../.."
|
|
24
|
+
|
|
25
|
+
case "${1:-$(uname -m)}" in
|
|
26
|
+
amd64 | x86_64) platform=amd64 ;;
|
|
27
|
+
arm64 | aarch64) platform=arm64 ;;
|
|
28
|
+
*)
|
|
29
|
+
echo "usage: $0 [amd64|arm64]" >&2
|
|
30
|
+
exit 2
|
|
31
|
+
;;
|
|
32
|
+
esac
|
|
33
|
+
|
|
34
|
+
image="buninu-linux-hosttest:$platform"
|
|
35
|
+
echo "### building $image (the first build takes a few minutes)"
|
|
36
|
+
docker build --quiet --platform "linux/$platform" --tag "$image" test/host > /dev/null
|
|
37
|
+
|
|
38
|
+
# A Linux host's KVM reaches the container too, so a guest that matches the
|
|
39
|
+
# platform runs accelerated. Docker Desktop on macOS exposes no /dev/kvm.
|
|
40
|
+
kvm_device=
|
|
41
|
+
if [ -e /dev/kvm ]; then kvm_device=--device=/dev/kvm; fi
|
|
42
|
+
|
|
43
|
+
mkdir -p downloads
|
|
44
|
+
# $kvm_device is empty or a single word.
|
|
45
|
+
exec docker run --rm --platform "linux/$platform" $kvm_device \
|
|
46
|
+
--mount "type=bind,src=$PWD,dst=/repo,readonly" \
|
|
47
|
+
--mount "type=bind,src=$PWD/downloads,dst=/cache,readonly" \
|
|
48
|
+
--env GUESTS --env BUILD_FLAGS --env BOOT_TIMEOUT --env ALPINE_MIRROR \
|
|
49
|
+
"$image" /repo/test/host/in-container.sh
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Runs inside the container host-test.sh starts: an ordinary Debian 13 machine
|
|
3
|
+
# of the chosen CPU. Clones the committed HEAD of the checkout mounted at
|
|
4
|
+
# /repo, builds each guest with the host toolchain (no --docker), boots it
|
|
5
|
+
# through boot-test.exp, and checks that the builds leave git status clean.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
here=$(cd "$(dirname "$0")" && pwd)
|
|
9
|
+
guests=${GUESTS:-x86_64 aarch64}
|
|
10
|
+
kvm=no
|
|
11
|
+
if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then kvm=yes; fi
|
|
12
|
+
echo "### host: $(uname -sm), node $(node --version), /dev/kvm: $kvm"
|
|
13
|
+
|
|
14
|
+
git config --global --add safe.directory '*'
|
|
15
|
+
git clone --quiet /repo /work
|
|
16
|
+
echo "### testing $(git -C /work log -1 --format='%h %s')"
|
|
17
|
+
# /cache is the host's downloads/, read-only; every file in it is still
|
|
18
|
+
# checked against its pinned SHA-256, and anything missing is fetched.
|
|
19
|
+
mkdir -p /work/downloads
|
|
20
|
+
cp -R /cache/. /work/downloads/
|
|
21
|
+
cd /work
|
|
22
|
+
|
|
23
|
+
strip_ansi() { sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g' | tr -d '\r'; }
|
|
24
|
+
|
|
25
|
+
status=0
|
|
26
|
+
for guest in $guests; do
|
|
27
|
+
echo "### $guest: fetch + build ${BUILD_FLAGS:-}"
|
|
28
|
+
log=/tmp/build-$guest.log
|
|
29
|
+
# BUILD_FLAGS is split on purpose: it holds flags such as --linux-lts.
|
|
30
|
+
if node ./index.js -fb --arch "$guest" ${BUILD_FLAGS:-} > "$log" 2>&1; then
|
|
31
|
+
grep -E '^==>|^Fetched|^Built|^Packed|^note' "$log" || true
|
|
32
|
+
else
|
|
33
|
+
echo "### FAIL: $guest build; last lines:"
|
|
34
|
+
tail -n 30 "$log"
|
|
35
|
+
status=1
|
|
36
|
+
continue
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "### $guest: boot"
|
|
40
|
+
log=/tmp/boot-$guest.log
|
|
41
|
+
if expect "$here/boot-test.exp" /work "$guest" "${BOOT_TIMEOUT:-900}" > "$log" 2>&1; then
|
|
42
|
+
strip_ansi < "$log" | grep -a -E '^run-qemu.sh:|^network: eth0|^fetch example|^### ' || true
|
|
43
|
+
else
|
|
44
|
+
strip_ansi < "$log" | grep -a -E '^run-qemu.sh:|^### ok' || true
|
|
45
|
+
echo "### FAIL: $guest boot; last lines:"
|
|
46
|
+
strip_ansi < "$log" | tail -n 40
|
|
47
|
+
status=1
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
echo "### git status after the builds (should be empty):"
|
|
52
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
53
|
+
git status --short
|
|
54
|
+
status=1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [ "$status" = 0 ]; then echo "### PASS"; else echo "### FAIL"; fi
|
|
58
|
+
exit "$status"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|