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/build-uki.sh
CHANGED
|
@@ -3,14 +3,12 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
kernel_release="6.18.52-0-$linux_flavor"
|
|
8
|
-
kernel_image="kernel/vmlinuz-$linux_flavor"
|
|
6
|
+
. ./scripts/arch.sh
|
|
9
7
|
|
|
10
|
-
if [ ! -s "$kernel_image" ] || [ ! -s "
|
|
8
|
+
if [ ! -s "$kernel_image" ] || [ ! -s "$kernel_dir/$efi_stub" ] || [ ! -s "$modules_root/modules.dep" ] || \
|
|
11
9
|
{ [ "${REAL_MACHINE:-}" = 1 ] && \
|
|
12
|
-
{ [ ! -s "
|
|
13
|
-
[ ! -s "
|
|
10
|
+
{ [ ! -s "$modules_root/kernel/drivers/hid/usbhid/usbhid.ko" ] || \
|
|
11
|
+
[ ! -s "$modules_root/kernel/drivers/acpi/battery.ko" ]; }; }; then
|
|
14
12
|
./fetch-alpine.sh
|
|
15
13
|
fi
|
|
16
14
|
./scripts/pack-initramfs.sh
|
|
@@ -31,35 +29,53 @@ printf '%s\n' \
|
|
|
31
29
|
# kernel does not care about the order of the parameters, so `rdinit=` goes
|
|
32
30
|
# last and the tail of the line reads as the `bun -e ...` it turns into.
|
|
33
31
|
#
|
|
34
|
-
# ttyS0 is listed last so it, not
|
|
35
|
-
# init process's fd 0/1/2.
|
|
32
|
+
# The serial port (ttyS0, or ttyAMA0 on aarch64) is listed last so it, not
|
|
33
|
+
# tty0, is the console the kernel opens as the init process's fd 0/1/2.
|
|
34
|
+
# `-display none` leaves tty0 with nowhere to go.
|
|
36
35
|
if [ "${REAL_MACHINE:-}" = 1 ]; then
|
|
37
|
-
consoles="console
|
|
36
|
+
consoles="console=$serial_console,115200 console=tty0 REAL_MACHINE=1"
|
|
38
37
|
else
|
|
39
|
-
consoles="console=tty0 console
|
|
38
|
+
consoles="console=tty0 console=$serial_console,115200"
|
|
40
39
|
fi
|
|
41
40
|
printf '%s' "$consoles panic=0 PATH=/bin KERNEL_RELEASE=$kernel_release rdinit=/bin/bun -- -e import('/init.js')" > build/cmdline
|
|
42
41
|
|
|
43
|
-
if command -v
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
else
|
|
47
|
-
echo "error: build stage needs x86_64-w64-mingw32-objcopy (Debian binutils-mingw-w64-x86-64)" >&2
|
|
48
|
-
echo "run inside PRoot; see README section 0" >&2
|
|
42
|
+
if ! command -v "$uki_objcopy" >/dev/null || ! command -v "$uki_objdump" >/dev/null; then
|
|
43
|
+
echo "error: build stage needs $uki_objcopy and $uki_objdump (Debian $uki_binutils_package)" >&2
|
|
44
|
+
echo "run inside PRoot or with ./index.js --docker; see README Build environment" >&2
|
|
49
45
|
exit 1
|
|
50
46
|
fi
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
stub="$kernel_dir/$efi_stub"
|
|
49
|
+
pe_header() { "$uki_objdump" -p "$stub" | awk -v key="$1" '$1 == key { print $2; exit }'; }
|
|
50
|
+
align_up() { echo $(( ($1 + $2 - 1) / $2 * $2 )); }
|
|
51
|
+
max() { echo $(( $1 > $2 ? $1 : $2 )); }
|
|
52
|
+
|
|
53
|
+
# The conventional x86-64 UKI offsets from the image base: .osrel +0x20000,
|
|
54
|
+
# .cmdline +0x30000, .linux +0x2000000, .initrd +0x3000000. The stub's own
|
|
55
|
+
# sections (SizeOfImage) and the kernel are measured rather than assumed, so
|
|
56
|
+
# a larger stub or a kernel of 16 MiB or more moves the later sections up
|
|
57
|
+
# instead of overlapping them. The x86_64 stub and both kernels fit the
|
|
58
|
+
# conventional layout; the aarch64 stub ends at +0x38000, which puts .osrel
|
|
59
|
+
# and .cmdline at +0x40000 and +0x50000 there.
|
|
60
|
+
image_base=$((16#$(pe_header ImageBase)))
|
|
61
|
+
stub_size=$((16#$(pe_header SizeOfImage)))
|
|
62
|
+
kernel_size=$(wc -c < "$kernel_image")
|
|
63
|
+
osrel_offset=$(max 0x20000 "$(align_up "$stub_size" 0x10000)")
|
|
64
|
+
cmdline_offset=$((osrel_offset + 0x10000))
|
|
65
|
+
linux_offset=$(max 0x2000000 "$(align_up $((cmdline_offset + 0x10000)) 0x1000000)")
|
|
66
|
+
initrd_offset=$(max 0x3000000 "$(align_up $((linux_offset + kernel_size)) 0x1000000)")
|
|
67
|
+
osrel_vma=$((image_base + osrel_offset))
|
|
68
|
+
cmdline_vma=$((image_base + cmdline_offset))
|
|
69
|
+
linux_vma=$((image_base + linux_offset))
|
|
70
|
+
initrd_vma=$((image_base + initrd_offset))
|
|
71
|
+
|
|
72
|
+
# Only this architecture's removable-media name may remain: firmware of the
|
|
73
|
+
# other architecture ignores it, but run-qemu.sh reads the guest from it.
|
|
74
|
+
for efi in vda/EFI/BOOT/*.EFI; do
|
|
75
|
+
[ "$efi" = "vda/EFI/BOOT/$efi_boot_name" ] || rm -f "$efi"
|
|
76
|
+
done
|
|
59
77
|
|
|
60
|
-
|
|
61
|
-
# Both supported Alpine kernels are below 16 MiB, so .linux ends before .initrd.
|
|
62
|
-
"$objcopy_command" \
|
|
78
|
+
"$uki_objcopy" \
|
|
63
79
|
--add-section .osrel=build/os-release \
|
|
64
80
|
--change-section-vma .osrel="$osrel_vma" \
|
|
65
81
|
--set-section-flags .osrel=contents,alloc,load,readonly,data \
|
|
@@ -72,6 +88,6 @@ initrd_vma=$((image_base + 0x3000000))
|
|
|
72
88
|
--add-section .initrd=build/initramfs.cpio.gz \
|
|
73
89
|
--change-section-vma .initrd="$initrd_vma" \
|
|
74
90
|
--set-section-flags .initrd=contents,alloc,load,readonly,data \
|
|
75
|
-
|
|
91
|
+
"$stub" "vda/EFI/BOOT/$efi_boot_name"
|
|
76
92
|
|
|
77
|
-
echo "Built UKI with Alpine linux-$linux_flavor at vda/EFI/BOOT
|
|
93
|
+
echo "Built $arch UKI with Alpine linux-$linux_flavor at vda/EFI/BOOT/$efi_boot_name"
|
package/fetch-alpine.sh
CHANGED
|
@@ -3,47 +3,34 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
linux_flavor=${LINUX_FLAVOR:-virt}
|
|
8
|
-
linux_package="linux-$linux_flavor-6.18.52-r0.apk"
|
|
9
|
-
musl_package=musl-1.2.6-r2.apk
|
|
10
|
-
stub_package=systemd-efistub-260.2-r0.apk
|
|
11
|
-
if [ "$linux_flavor" = lts ]; then
|
|
12
|
-
linux_sha256=bd81f00522a7c7deb96886811a087c453d48530c0c8f742412ad73e24236037c
|
|
13
|
-
else
|
|
14
|
-
linux_sha256=dff0c365a6d6c0fd015175af08dca0c39048fa39df6ef3150a7ea88d2cfdfa4f
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
musl_sha256=573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71
|
|
18
|
-
stub_sha256=8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9
|
|
6
|
+
. ./scripts/arch.sh
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
modules_root="initramfs/lib/modules/$kernel_release"
|
|
23
|
-
mkdir -p downloads kernel initramfs/lib "$modules_root"
|
|
8
|
+
mkdir -p "$downloads_dir" "$kernel_dir" "$native_dir/lib" "$modules_root"
|
|
24
9
|
|
|
25
10
|
. ./scripts/fetch.sh
|
|
26
11
|
|
|
27
|
-
fetch "$
|
|
28
|
-
fetch "$
|
|
29
|
-
fetch "$
|
|
12
|
+
fetch "$alpine_base/$linux_package" "$linux_package" "$linux_sha256"
|
|
13
|
+
fetch "$alpine_base/$musl_package" "$musl_package" "$musl_sha256"
|
|
14
|
+
fetch "$alpine_base/$stub_package" "$stub_package" "$stub_sha256"
|
|
30
15
|
|
|
31
|
-
extract() { tar --warning=no-unknown-keyword -xOf "
|
|
16
|
+
extract() { tar --warning=no-unknown-keyword -xOf "$downloads_dir/$1" "$2"; }
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
extract "$
|
|
18
|
+
# On aarch64 vmlinuz is an EFI zboot image: a PE executable that inflates
|
|
19
|
+
# the kernel itself, so the stub can start it exactly like the x86_64 bzImage.
|
|
20
|
+
extract "$linux_package" "boot/vmlinuz-$linux_flavor" > "$kernel_image"
|
|
21
|
+
extract "$musl_package" "lib/$musl_loader" > "$native_dir/lib/$musl_loader"
|
|
22
|
+
extract "$stub_package" "usr/lib/systemd/boot/efi/$efi_stub" > "$kernel_dir/$efi_stub"
|
|
36
23
|
|
|
37
24
|
# Kernel modules. The package's modules.dep says what each one needs, so a
|
|
38
25
|
# module is requested by name and its dependencies come along: on linux-lts
|
|
39
|
-
# virtio_net pulls in virtio
|
|
40
|
-
# has built in. The initramfs gets a modules.dep trimmed to the shipped set,
|
|
26
|
+
# virtio_net pulls in virtio and virtio_ring, which linux-virt has built in. The initramfs gets a modules.dep trimmed to the shipped set,
|
|
41
27
|
# the fs-* lines of modules.alias and modules.builtin; /lib/modprobe.js reads
|
|
42
28
|
# those three files.
|
|
43
29
|
depfile="$(mktemp)"
|
|
44
30
|
selected="$(mktemp)"
|
|
45
31
|
trap 'rm -f "$depfile" "$selected" "$selected.names"' EXIT
|
|
46
32
|
extract "$linux_package" "lib/modules/$kernel_release/modules.dep" > "$depfile"
|
|
33
|
+
extract "$linux_package" "lib/modules/$kernel_release/modules.builtin" > "$modules_root/modules.builtin"
|
|
47
34
|
|
|
48
35
|
select_module() {
|
|
49
36
|
local name=$1 line path dep
|
|
@@ -58,14 +45,42 @@ select_module() {
|
|
|
58
45
|
done
|
|
59
46
|
}
|
|
60
47
|
|
|
61
|
-
#
|
|
48
|
+
# For modules one architecture or flavor builds in or leaves out: a built-in
|
|
49
|
+
# one needs nothing, a missing one gets a note. aarch64 linux-virt has no
|
|
50
|
+
# ext2.ko: it sets CONFIG_EXT4_USE_FOR_EXT2, and modules.alias maps fs-ext2 to
|
|
51
|
+
# ext4, which is how /bin/mount looks the driver up.
|
|
52
|
+
select_optional_module() {
|
|
53
|
+
if grep -qF -m1 -- "/$1.ko.gz:" "$depfile"; then
|
|
54
|
+
select_module "$1"
|
|
55
|
+
elif ! grep -q -- "/$1\.ko$" "$modules_root/modules.builtin"; then
|
|
56
|
+
echo "note: module $1 is not in $arch $linux_package; skipped" >&2
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Network: virtio-net for QEMU (init.js loads these at boot). virtio_pci is
|
|
61
|
+
# the PCI transport for every virtio device, disk included; linux-virt builds
|
|
62
|
+
# it in, linux-lts has it as a module that nothing in modules.dep pulls in.
|
|
62
63
|
for module in failover net_failover virtio_net; do select_module "$module"; done
|
|
64
|
+
select_optional_module virtio_pci
|
|
63
65
|
# Storage: what /bin/mount loads on demand. ext4 brings jbd2, mbcache and
|
|
64
66
|
# crc16; FAT, exFAT and NTFS ask the kernel for the NLS tables at mount
|
|
65
67
|
# time, which has no /sbin/modprobe to answer, so mount preloads them.
|
|
66
|
-
for module in virtio_blk ext4
|
|
68
|
+
for module in virtio_blk ext4 vfat exfat ntfs3 nls_utf8 nls_cp437 nls_iso8859-1; do
|
|
67
69
|
select_module "$module"
|
|
68
70
|
done
|
|
71
|
+
select_optional_module ext2
|
|
72
|
+
# Pointing devices for `bunterm --mouse`, which reads /dev/input/event*.
|
|
73
|
+
# evdev is the character-device interface to the input layer; psmouse drives
|
|
74
|
+
# the PS/2 mouse every PC and QEMU's q35 machine has. QEMU's aarch64 virt
|
|
75
|
+
# machine has no PS/2 port; its keyboard and pointer are virtio-input. A USB
|
|
76
|
+
# mouse needs the usbhid stack, which only the --real image carries. init.js
|
|
77
|
+
# loads these at boot; nothing reads a pointer unless bunterm is asked to.
|
|
78
|
+
select_module evdev
|
|
79
|
+
if [ "$arch" = x86_64 ]; then
|
|
80
|
+
select_module psmouse
|
|
81
|
+
else
|
|
82
|
+
select_module virtio_input
|
|
83
|
+
fi
|
|
69
84
|
|
|
70
85
|
if [ "${REAL_MACHINE:-}" = 1 ]; then
|
|
71
86
|
# USB keyboard for the console; common storage paths (SATA/AHCI HDD and
|
|
@@ -108,8 +123,7 @@ extract "$linux_package" "lib/modules/$kernel_release/modules.alias" \
|
|
|
108
123
|
$2 ~ /^fs-/ { print; next }
|
|
109
124
|
$2 ~ /^(pci|usb|virtio):/ { m = $3; gsub("-", "_", m); if (m in shipped) print }
|
|
110
125
|
' > "$modules_root/modules.alias"
|
|
111
|
-
extract "$linux_package" "lib/modules/$kernel_release/modules.builtin" > "$modules_root/modules.builtin"
|
|
112
126
|
|
|
113
|
-
chmod 0755
|
|
127
|
+
chmod 0755 "$native_dir/lib/$musl_loader"
|
|
114
128
|
|
|
115
|
-
echo "Fetched Alpine linux-$linux_flavor kernel, $(wc -l < "$selected") modules, musl, and the
|
|
129
|
+
echo "Fetched Alpine $arch linux-$linux_flavor kernel, $(wc -l < "$selected") modules, musl, and the $efi_stub UKI stub."
|
package/fetch-bun.sh
CHANGED
|
@@ -3,17 +3,10 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
libstdcpp_package=libstdc++-15.2.0-r5.apk
|
|
11
|
-
libgcc_package=libgcc-15.2.0-r5.apk
|
|
12
|
-
bun_sha256=76e1db84e98f22f78de0a87e309bfbbf297732847f9720db36750646c85c8c18
|
|
13
|
-
libstdcpp_sha256=14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6
|
|
14
|
-
libgcc_sha256=393dcd32629f06d7d85409c272d142d0c082772d10b87ef55ee82f47de3be637
|
|
15
|
-
|
|
16
|
-
mkdir -p downloads initramfs/bin initramfs/lib initramfs/dev initramfs/proc initramfs/sys initramfs/tmp
|
|
6
|
+
. ./scripts/arch.sh
|
|
7
|
+
|
|
8
|
+
mkdir -p "$downloads_dir" "$native_dir/bin" "$native_dir/lib" \
|
|
9
|
+
initramfs/dev initramfs/proc initramfs/sys initramfs/tmp
|
|
17
10
|
|
|
18
11
|
. ./scripts/fetch.sh
|
|
19
12
|
|
|
@@ -21,21 +14,21 @@ fetch "$bun_url" "$bun_zip" "$bun_sha256"
|
|
|
21
14
|
fetch "$alpine_base/$libstdcpp_package" "$libstdcpp_package" "$libstdcpp_sha256"
|
|
22
15
|
fetch "$alpine_base/$libgcc_package" "$libgcc_package" "$libgcc_sha256"
|
|
23
16
|
|
|
24
|
-
test -s
|
|
17
|
+
test -s "$native_dir/lib/$musl_loader" || {
|
|
25
18
|
echo "error: fetch stage incomplete: run ./fetch-alpine.sh first (./index.js -f runs both)" >&2
|
|
26
19
|
exit 1
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
unzip -p "
|
|
30
|
-
|
|
31
|
-
tar --warning=no-unknown-keyword -xOf "
|
|
32
|
-
usr/lib/libstdc++.so.6.0.34 >
|
|
33
|
-
tar --warning=no-unknown-keyword -xOf "
|
|
34
|
-
usr/lib/libgcc_s.so.1 >
|
|
22
|
+
unzip -p "$downloads_dir/$bun_zip" \
|
|
23
|
+
"$bun_build/bun" > "$native_dir/bin/bun"
|
|
24
|
+
tar --warning=no-unknown-keyword -xOf "$downloads_dir/$libstdcpp_package" \
|
|
25
|
+
usr/lib/libstdc++.so.6.0.34 > "$native_dir/lib/libstdc++.so.6"
|
|
26
|
+
tar --warning=no-unknown-keyword -xOf "$downloads_dir/$libgcc_package" \
|
|
27
|
+
usr/lib/libgcc_s.so.1 > "$native_dir/lib/libgcc_s.so.1"
|
|
35
28
|
|
|
36
29
|
# Keep libc as a distinct inode from the active dynamic loader. Calling dlopen
|
|
37
30
|
# on the loader inode itself deadlocks, while this physical copy is FFI-safe.
|
|
38
|
-
cp
|
|
39
|
-
chmod 0755 initramfs/init.js
|
|
31
|
+
cp "$native_dir/lib/$musl_loader" "$native_dir/lib/$musl_libc"
|
|
32
|
+
chmod 0755 initramfs/init.js "$native_dir/bin/bun" "$native_dir"/lib/*.so*
|
|
40
33
|
|
|
41
|
-
echo "Fetched Bun $bun_version and its
|
|
34
|
+
echo "Fetched Bun $bun_version and its $arch musl runtime into $native_dir/."
|
package/graphics.md
CHANGED
|
@@ -19,6 +19,7 @@ via Bun FFI.
|
|
|
19
19
|
│ @xterm/headless Terminal ─ VT parser, screen buffer, attributes │
|
|
20
20
|
│ images.js ─ kitty placements as Skia images anchored to lines │
|
|
21
21
|
│ input.js ─ /dev/ttyN raw mode, console→xterm key sequences │
|
|
22
|
+
│ mouse.js ─ /dev/input/event* pointer, unless --no-mouse │
|
|
22
23
|
│ renderer.js ─ cells → glyphs (fonts) or vectors (glyphs.js) │
|
|
23
24
|
└───────────────────────────────────────────────┬──────────────────────┘
|
|
24
25
|
│ Skia draw calls
|
|
@@ -130,6 +131,7 @@ Around those, `/lib/bunterm/` adds:
|
|
|
130
131
|
| `glyphs.js` | Box drawing (U+2500–257F incl. double, dashed, rounded, mixed-weight), block elements and shades (U+2580–259F), Powerline (U+E0A0–E0BF) and legacy computing (U+1FB70–1FB97) drawn as Skia paths, so they meet seamlessly at any size. The shape tables are xterm.js's `CustomGlyphs.ts` (MIT); coordinates snap to pixel centres for crisp 1-px lines. |
|
|
131
132
|
| `images.js` | Kitty placements: decodes PNG (`f=100`) with Skia or raw RGB/RGBA (`f=24/32`), with `Bun.Image` as a transcoder for other encodings; anchors each placement to an xterm marker so it scrolls with its line and disappears with it (on the alternate screen, where xterm.js issues no markers, to a fixed row that is dropped when that screen ends); sizes in cells (`c,r`) or from pixel size; crops (`x,y,w,h`), cell offsets (`X,Y`), `p=` placement ids, `a=d` deletion scopes, and the cursor move after a placement. |
|
|
132
133
|
| `input.js` | Raw mode on the console (`TCGETS`/`TCSETS` through the shared libc binding) and translation of the kernel's "linux" key sequences to xterm's, honouring DECCKM. |
|
|
134
|
+
| `mouse.js` | `/dev/input/event*` through the input layer: finds pointing devices by their sysfs capability bitmaps, decodes the 24-byte `input_event` records (relative deltas, absolute axes read with `EVIOCGABS`, buttons, wheel) and reports a pointer position per `EV_SYN`. Not loaded under `--no-mouse`; otherwise the session draws an arrow and hands each report to xterm.js's `CoreMouseService`, which decides what the program enabled and encodes it. A wheel notch the program is not asking for scrolls the viewport instead — `scrollLines()` on the normal screen, arrow keys on the alternate one. |
|
|
133
135
|
| `terminal.js` | `createSession()` wires PTY → parser → emulator → renderer with a coalesced ~60 Hz frame timer and a serialized output queue, so a kitty packet always sees the cursor position left by the text before it. `runTerminal()` adds `runGraphics()` and the keyboard. |
|
|
134
136
|
|
|
135
137
|
Manual: `bunterm --help` / `initramfs/usr/share/doc/buninu-linux/bunterm.md`.
|
|
@@ -150,7 +152,9 @@ Manual: `bunterm --help` / `initramfs/usr/share/doc/buninu-linux/bunterm.md`.
|
|
|
150
152
|
4. `screen.flush()` finishes Skia's work and copies the wasm-heap frame into
|
|
151
153
|
the `/dev/fb0` mapping.
|
|
152
154
|
|
|
153
|
-
Keyboard bytes travel the other way: console → `input.js` → PTY
|
|
155
|
+
Keyboard bytes travel the other way: console → `input.js` → PTY, and with
|
|
156
|
+
the mouse on, pointer reports go `/dev/input/event*` → `mouse.js` → the arrow on
|
|
157
|
+
screen and xterm.js's `CoreMouseService` → PTY. Terminal
|
|
154
158
|
replies (cursor position, device attributes, kitty acks) go from the
|
|
155
159
|
emulator straight to the PTY.
|
|
156
160
|
|
|
@@ -204,7 +208,8 @@ through its transmit / `a=p` / `a=d` sequence exactly as in the browser.
|
|
|
204
208
|
|
|
205
209
|
## Not done yet
|
|
206
210
|
|
|
207
|
-
-
|
|
211
|
+
- Selection and scrollback viewing in `bunterm` (the pointer itself works,
|
|
212
|
+
and `--no-mouse` turns it off).
|
|
208
213
|
- Kitty `z < 0` (images behind text), animation frames, Unicode
|
|
209
214
|
placeholders, and shared-memory transmission.
|
|
210
215
|
- Text run shaping with ligatures for single-code-point cells (each cell is
|
package/index.js
CHANGED
|
@@ -8,28 +8,51 @@
|
|
|
8
8
|
|
|
9
9
|
import pkg from "./package.json" with { type: "json" };
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
|
-
import {
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { accessSync, constants, copyFileSync, existsSync, readFileSync } from "node:fs";
|
|
12
13
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
|
|
15
16
|
const rootDir = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const invocationDir = process.cwd();
|
|
17
18
|
|
|
19
|
+
// Guest architectures. The names are Alpine's and the kernel's; the aliases
|
|
20
|
+
// are what Go, Docker, Node and Apple call the same CPUs. scripts/arch.sh
|
|
21
|
+
// holds everything else that differs between them.
|
|
22
|
+
const architectures = {
|
|
23
|
+
x86_64: { aliases: ["amd64", "x64"], efi: "BOOTX64.EFI", binutils: "x86_64-w64-mingw32", apt: "binutils-mingw-w64-x86-64" },
|
|
24
|
+
aarch64: { aliases: ["arm64"], efi: "BOOTAA64.EFI", binutils: "aarch64-linux-gnu", apt: "binutils-aarch64-linux-gnu" },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function normalizeArch(name) {
|
|
28
|
+
for (const [arch, { aliases }] of Object.entries(architectures)) {
|
|
29
|
+
if (name === arch || aliases.includes(name)) return arch;
|
|
30
|
+
}
|
|
31
|
+
fail(`unknown architecture ${name} (use x86_64/amd64 or aarch64/arm64)`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// What vda/EFI/BOOT holds decides the guest for a bare -r, as in run-qemu.sh.
|
|
35
|
+
function builtArch() {
|
|
36
|
+
return existsSync(resolve(rootDir, "vda/EFI/BOOT", architectures.aarch64.efi)) ? "aarch64" : "x86_64";
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
// Pipeline order. Flags select stages; this array fixes the order they run in.
|
|
40
|
+
// The fetch and build stages can run inside the Docker image from ./Dockerfile
|
|
41
|
+
// (--docker, and always on macOS); run always uses the host's QEMU.
|
|
19
42
|
const stages = [
|
|
20
43
|
{
|
|
21
44
|
flag: "fetch",
|
|
22
45
|
short: "f",
|
|
23
46
|
scripts: ["fetch-alpine.sh", "fetch-bun.sh"],
|
|
24
|
-
tools: ["curl", "unzip", "tar", "gzip", "sha256sum"],
|
|
47
|
+
tools: () => ["curl", "unzip", "tar", "gzip", "sha256sum"],
|
|
25
48
|
},
|
|
26
49
|
{
|
|
27
50
|
flag: "build",
|
|
28
51
|
short: "b",
|
|
29
52
|
scripts: ["build-uki.sh", "build-image.sh"],
|
|
30
|
-
tools: [
|
|
53
|
+
tools: (arch) => [
|
|
31
54
|
"fakeroot", "cpio", "gzip",
|
|
32
|
-
|
|
55
|
+
`${architectures[arch].binutils}-objcopy`, `${architectures[arch].binutils}-objdump`,
|
|
33
56
|
"parted", "mkfs.fat", "mmd", "mcopy",
|
|
34
57
|
],
|
|
35
58
|
},
|
|
@@ -37,7 +60,7 @@ const stages = [
|
|
|
37
60
|
flag: "run",
|
|
38
61
|
short: "r",
|
|
39
62
|
scripts: ["run-qemu.sh"],
|
|
40
|
-
tools: [
|
|
63
|
+
tools: (arch) => [`qemu-system-${arch}`],
|
|
41
64
|
},
|
|
42
65
|
];
|
|
43
66
|
|
|
@@ -50,22 +73,32 @@ function usage() {
|
|
|
50
73
|
return `${pkg.name} - ${pkg.description}
|
|
51
74
|
|
|
52
75
|
Usage:
|
|
53
|
-
${pkg.name} [-f] [-b] [-r] [--
|
|
76
|
+
${pkg.name} [-f] [-b] [-r] [--arch ARCH] [--docker] [--export] [--linux-lts]
|
|
77
|
+
[--real] [-- qemu arguments]
|
|
54
78
|
|
|
55
79
|
Stages (always run in this order, whichever you pick):
|
|
56
80
|
-f, --fetch download and verify the pinned kernel, musl, GCC runtime,
|
|
57
81
|
EFI stub and Bun (fetch-alpine.sh, fetch-bun.sh)
|
|
58
82
|
-b, --build pack the initramfs, assemble the UKI, write vda.img
|
|
59
83
|
(build-uki.sh, build-image.sh)
|
|
60
|
-
-r, --run boot vda.img under QEMU (run-qemu.sh)
|
|
84
|
+
-r, --run boot vda.img under QEMU (run-qemu.sh), with KVM or
|
|
85
|
+
Hypervisor.framework when the guest matches the host CPU
|
|
86
|
+
|
|
87
|
+
--arch ARCH guest architecture: x86_64 (default; alias amd64, x64) or
|
|
88
|
+
aarch64 (alias arm64, native speed on Apple Silicon). A bare
|
|
89
|
+
-r boots whichever one vda.img was last built for
|
|
90
|
+
--docker run fetch and build inside a Debian container built from
|
|
91
|
+
./Dockerfile; always on for macOS, which lacks the toolchain
|
|
61
92
|
|
|
62
93
|
--export after -b/--build, copy the image to
|
|
63
|
-
./buninu-linux-${pkg.version}.img
|
|
94
|
+
./buninu-linux-${pkg.version}.img (x86_64) or
|
|
95
|
+
./buninu-linux-${pkg.version}-aarch64.img
|
|
64
96
|
|
|
65
97
|
--linux-lts use Alpine's general-purpose linux-lts kernel for fetch/build
|
|
66
98
|
(the default is the smaller linux-virt kernel)
|
|
67
99
|
--real build for a physical machine: use linux-lts, make tty0 the
|
|
68
100
|
primary console, and include USB xHCI/HID keyboard modules
|
|
101
|
+
(x86_64 only)
|
|
69
102
|
|
|
70
103
|
-h, --help show this
|
|
71
104
|
-V, --version show name, version, runtime and platform
|
|
@@ -73,11 +106,13 @@ Stages (always run in this order, whichever you pick):
|
|
|
73
106
|
|
|
74
107
|
Short flags combine: -fbr is the whole pipeline, -br is what an edit to
|
|
75
108
|
initramfs/init.js needs, -r just boots what is already there. Anything after
|
|
76
|
-
-- goes to qemu-system-x86_64 verbatim (for example:
|
|
109
|
+
-- goes to qemu-system-x86_64 or qemu-system-aarch64 verbatim (for example:
|
|
110
|
+
-r -- -smp 4).
|
|
77
111
|
|
|
78
|
-
Fetch and build need the
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
Fetch and build need the Debian toolchain from README "Build environment"
|
|
113
|
+
(PRoot, a Debian or Ubuntu host, or --docker); run needs QEMU (Termux
|
|
114
|
+
pkg, apt, or brew install qemu). Tools missing for the stages you picked are
|
|
115
|
+
reported before anything starts.
|
|
81
116
|
`;
|
|
82
117
|
}
|
|
83
118
|
|
|
@@ -87,13 +122,24 @@ function parse(argv) {
|
|
|
87
122
|
let linuxLts = false;
|
|
88
123
|
let real = false;
|
|
89
124
|
let exportImage = false;
|
|
125
|
+
let docker = false;
|
|
126
|
+
// BUNINU_ARCH is what scripts/arch.sh reads; --arch overrides it.
|
|
127
|
+
let arch = process.env.BUNINU_ARCH ? normalizeArch(process.env.BUNINU_ARCH) : null;
|
|
90
128
|
let sawSeparator = false;
|
|
91
129
|
|
|
92
|
-
for (
|
|
130
|
+
for (let index = 0; index < argv.length; index++) {
|
|
131
|
+
const argument = argv[index];
|
|
93
132
|
if (sawSeparator) {
|
|
94
133
|
passthrough.push(argument);
|
|
95
134
|
} else if (argument === "--") {
|
|
96
135
|
sawSeparator = true;
|
|
136
|
+
} else if (argument === "--arch") {
|
|
137
|
+
if (index + 1 >= argv.length) fail("--arch needs a value: x86_64 or aarch64");
|
|
138
|
+
arch = normalizeArch(argv[++index]);
|
|
139
|
+
} else if (argument.startsWith("--arch=")) {
|
|
140
|
+
arch = normalizeArch(argument.slice("--arch=".length));
|
|
141
|
+
} else if (argument === "--docker") {
|
|
142
|
+
docker = true;
|
|
97
143
|
} else if (argument === "-h" || argument === "--help") {
|
|
98
144
|
console.log(usage());
|
|
99
145
|
process.exit(0);
|
|
@@ -137,7 +183,10 @@ function parse(argv) {
|
|
|
137
183
|
if (exportImage && !selected.has(stages[1])) {
|
|
138
184
|
fail("--export requires -b/--build (use -fb --export for a clean build)");
|
|
139
185
|
}
|
|
140
|
-
|
|
186
|
+
if (real && arch === "aarch64") {
|
|
187
|
+
fail("--real builds an x86_64 PC image; it cannot be combined with --arch aarch64");
|
|
188
|
+
}
|
|
189
|
+
return { selected, passthrough, linuxLts, real, exportImage, docker, arch };
|
|
141
190
|
}
|
|
142
191
|
|
|
143
192
|
// Same shape as Buninu's --version. The runtime line tells you whether this
|
|
@@ -180,54 +229,120 @@ function hasTool(name) {
|
|
|
180
229
|
return false;
|
|
181
230
|
}
|
|
182
231
|
|
|
232
|
+
const inDocker = (stage) => useDocker && stage.flag !== "run";
|
|
233
|
+
|
|
183
234
|
function checkTools(selectedStages) {
|
|
184
235
|
const missing = [];
|
|
185
236
|
for (const stage of selectedStages) {
|
|
186
|
-
|
|
237
|
+
const arch = stage.flag === "run" ? runArch : buildArch;
|
|
238
|
+
for (const tool of inDocker(stage) ? ["docker"] : stage.tools(arch)) {
|
|
187
239
|
if (!hasTool(tool)) missing.push(`${tool} (for --${stage.flag})`);
|
|
188
240
|
}
|
|
189
241
|
}
|
|
190
242
|
if (missing.length > 0) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
243
|
+
const hint = process.platform === "darwin"
|
|
244
|
+
? `
|
|
245
|
+
Install Docker Desktop (https://www.docker.com/products/docker-desktop/) for
|
|
246
|
+
-f/-b, and QEMU with its UEFI firmware for -r:
|
|
247
|
+
brew install qemu
|
|
248
|
+
`
|
|
249
|
+
: `
|
|
250
|
+
Run this command in Debian or Ubuntu first, or pass --docker to build in a container:
|
|
251
|
+
apt install ${architectures[buildArch].apt} cpio curl dosfstools fakeroot mtools parted unzip
|
|
252
|
+
QEMU for -r:
|
|
253
|
+
apt install qemu-system ovmf qemu-efi-aarch64
|
|
254
|
+
`;
|
|
255
|
+
fail(`missing tools, nothing was run:\n ${missing.join("\n ")}\n${hint}`);
|
|
256
|
+
}
|
|
257
|
+
if (selectedStages.some(inDocker) && spawnSync("docker", ["info"], { stdio: "ignore" }).status !== 0) {
|
|
258
|
+
fail("docker is installed but its daemon is not answering; start Docker Desktop (or dockerd) and retry");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// The build image is tagged with a hash of the Dockerfile, so editing it
|
|
263
|
+
// builds a fresh image and an unchanged one is reused without a rebuild. The
|
|
264
|
+
// Dockerfile goes in on stdin: the build needs no context, and sending the
|
|
265
|
+
// checkout (downloads/ included) would only slow it down.
|
|
266
|
+
let dockerImageTag;
|
|
267
|
+
function dockerImage() {
|
|
268
|
+
if (dockerImageTag) return dockerImageTag;
|
|
269
|
+
const dockerfile = readFileSync(resolve(rootDir, "Dockerfile"));
|
|
270
|
+
const tag = `buninu-linux-build:${createHash("sha256").update(dockerfile).digest("hex").slice(0, 12)}`;
|
|
271
|
+
if (spawnSync("docker", ["image", "inspect", tag], { stdio: "ignore" }).status !== 0) {
|
|
272
|
+
console.log(`\n==> [docker] building ${tag} from Dockerfile`);
|
|
273
|
+
const result = spawnSync("docker", ["build", "--tag", tag, "-"], {
|
|
274
|
+
input: dockerfile,
|
|
275
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
276
|
+
});
|
|
277
|
+
if (result.error) fail(`could not start docker build: ${result.error.message}`);
|
|
278
|
+
if (result.status !== 0) fail(`docker build exited with status ${result.status}`);
|
|
198
279
|
}
|
|
280
|
+
return (dockerImageTag = tag);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// In Docker the script runs as the invoking user, so everything it writes
|
|
284
|
+
// into the bind-mounted checkout stays owned by that user; fakeroot provides
|
|
285
|
+
// the root ownership the initramfs records.
|
|
286
|
+
function dockerRun(script, env) {
|
|
287
|
+
const passEnv = ["BUNINU_ARCH", "LINUX_FLAVOR", "REAL_MACHINE", "ALPINE_MIRROR"]
|
|
288
|
+
.filter((name) => env[name] !== undefined)
|
|
289
|
+
.flatMap((name) => ["--env", `${name}=${env[name]}`]);
|
|
290
|
+
const user = typeof process.getuid === "function"
|
|
291
|
+
? ["--user", `${process.getuid()}:${process.getgid()}`]
|
|
292
|
+
: [];
|
|
293
|
+
return [
|
|
294
|
+
"run", "--rm", ...user,
|
|
295
|
+
"--mount", `type=bind,src=${rootDir},dst=/src`, "--workdir", "/src",
|
|
296
|
+
"--env", "HOME=/tmp", ...passEnv,
|
|
297
|
+
dockerImage(), `./${script}`,
|
|
298
|
+
];
|
|
199
299
|
}
|
|
200
300
|
|
|
201
301
|
function runScript(stage, script, args = [], env = process.env) {
|
|
202
|
-
const
|
|
203
|
-
console.log(
|
|
204
|
-
|
|
302
|
+
const docker = inDocker(stage);
|
|
303
|
+
console.log(
|
|
304
|
+
`\n==> [${stage.flag}${docker ? " in docker" : ""}] ${script}${args.length ? " " + args.join(" ") : ""}`,
|
|
305
|
+
);
|
|
306
|
+
const result = docker
|
|
307
|
+
? spawnSync("docker", dockerRun(script, env), { stdio: "inherit" })
|
|
308
|
+
: spawnSync(resolve(rootDir, script), args, { cwd: rootDir, stdio: "inherit", env });
|
|
205
309
|
if (result.error) fail(`could not start ${script}: ${result.error.message}`);
|
|
206
310
|
if (result.status !== 0) {
|
|
207
311
|
fail(`${script} exited with status ${result.status ?? "signal " + result.signal}`);
|
|
208
312
|
}
|
|
209
313
|
}
|
|
210
314
|
|
|
211
|
-
const { selected, passthrough, linuxLts, real, exportImage } = parse(process.argv.slice(2));
|
|
315
|
+
const { selected, passthrough, linuxLts, real, exportImage, docker, arch } = parse(process.argv.slice(2));
|
|
212
316
|
const ordered = stages.filter((stage) => selected.has(stage));
|
|
317
|
+
const useDocker = docker || process.platform === "darwin";
|
|
318
|
+
const buildArch = arch ?? "x86_64";
|
|
319
|
+
// A bare -r after -b boots what -b just built; otherwise what vda.img holds.
|
|
320
|
+
const runArch = arch ?? (selected.has(stages[1]) ? buildArch : builtArch());
|
|
213
321
|
checkTools(ordered);
|
|
214
322
|
|
|
323
|
+
const stageEnv = {
|
|
324
|
+
...process.env,
|
|
325
|
+
...(arch && { BUNINU_ARCH: arch }),
|
|
326
|
+
...((linuxLts || real) && { LINUX_FLAVOR: "lts" }),
|
|
327
|
+
...(real && { REAL_MACHINE: "1" }),
|
|
328
|
+
};
|
|
329
|
+
|
|
215
330
|
for (const stage of ordered) {
|
|
331
|
+
// 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
|
+
if (stage.flag === "run" && useDocker && !selected.has(stages[1]) &&
|
|
334
|
+
!existsSync(resolve(rootDir, "vda.img"))) {
|
|
335
|
+
runScript(stages[1], "build-image.sh", [], { ...stageEnv, BUNINU_ARCH: runArch });
|
|
336
|
+
}
|
|
216
337
|
for (const script of stage.scripts) {
|
|
217
|
-
runScript(
|
|
218
|
-
stage,
|
|
219
|
-
script,
|
|
220
|
-
stage.flag === "run" ? passthrough : [],
|
|
221
|
-
linuxLts || real
|
|
222
|
-
? { ...process.env, LINUX_FLAVOR: "lts", ...(real && { REAL_MACHINE: "1" }) }
|
|
223
|
-
: process.env,
|
|
224
|
-
);
|
|
338
|
+
runScript(stage, script, stage.flag === "run" ? passthrough : [], stageEnv);
|
|
225
339
|
}
|
|
226
340
|
}
|
|
227
341
|
|
|
228
342
|
if (exportImage) {
|
|
229
343
|
const source = resolve(rootDir, "vda.img");
|
|
230
|
-
const
|
|
344
|
+
const suffix = buildArch === "x86_64" ? "" : `-${buildArch}`;
|
|
345
|
+
const destination = resolve(invocationDir, `buninu-linux-${pkg.version}${suffix}.img`);
|
|
231
346
|
try {
|
|
232
347
|
accessSync(source, constants.R_OK);
|
|
233
348
|
copyFileSync(source, destination);
|
package/initramfs/bin/bunterm
CHANGED
|
@@ -6,7 +6,7 @@ import { runTerminal } from "../lib/bunterm/terminal.js";
|
|
|
6
6
|
|
|
7
7
|
const usage = () => {
|
|
8
8
|
console.error(`Usage:
|
|
9
|
-
bunterm [/dev/ttyN] [--font-size N] [--fb /dev/fb0] [-e command [argument...]]
|
|
9
|
+
bunterm [/dev/ttyN] [--font-size N] [--fb /dev/fb0] [--no-mouse] [-e command [argument...]]
|
|
10
10
|
bunterm -h | --help`);
|
|
11
11
|
};
|
|
12
12
|
|
|
@@ -46,6 +46,10 @@ const main = async () => {
|
|
|
46
46
|
options.device = valued("--fb");
|
|
47
47
|
} else if (argument === "--no-blink") {
|
|
48
48
|
options.cursorBlink = false;
|
|
49
|
+
} else if (argument === "--mouse") {
|
|
50
|
+
options.mouse = true;
|
|
51
|
+
} else if (argument === "--no-mouse") {
|
|
52
|
+
options.mouse = false;
|
|
49
53
|
} else if (argument.startsWith("/dev/")) {
|
|
50
54
|
options.console = argument;
|
|
51
55
|
} else {
|