buninu-linux 0.5.1 → 0.7.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 +280 -60
- package/build-image.sh +9 -5
- package/build-uki.sh +44 -28
- package/fetch-alpine.sh +43 -35
- package/fetch-bun.sh +14 -21
- package/index.js +150 -35
- package/initramfs/bin/buninu-linux-help +12 -0
- package/initramfs/bin/bunterm +64 -5
- package/initramfs/bin/chroot +4 -1
- package/initramfs/bin/ip +3 -2
- package/initramfs/bin/ps +3 -0
- package/initramfs/bin/tar +9 -0
- package/initramfs/init.js +79 -41
- package/initramfs/lib/bunterm/images.js +17 -2
- package/initramfs/lib/bunterm/terminal.js +25 -9
- package/initramfs/lib/bunterm/vt.js +91 -0
- package/initramfs/lib/dlopen.js +4 -0
- package/initramfs/lib/fbdev.js +33 -10
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +25 -7
- package/initramfs/usr/share/doc/buninu-linux/fbdev.md +1 -1
- package/initramfs/usr/share/doc/buninu-linux/tar.md +2 -0
- package/initramfs/usr/share/licenses/README.md +4 -0
- 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 +22 -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/initramfs/lib/fbdev.js
CHANGED
|
@@ -355,8 +355,12 @@ const setConsoleSwitchMode = (consoleDevice, mode) => {
|
|
|
355
355
|
// when it comes back (answered with VT_ACKACQ), at which point `onAcquire`
|
|
356
356
|
// runs so the caller can repaint what fbcon left on the screen. Returns a
|
|
357
357
|
// function that puts the console back into automatic switching.
|
|
358
|
-
export const watchConsoleSwitches = (consoleDevice,
|
|
358
|
+
export const watchConsoleSwitches = (consoleDevice, callbacks) => {
|
|
359
|
+
// Keep the original (console, onAcquire) public form working.
|
|
360
|
+
const onRelease = typeof callbacks === "function" ? () => {} : callbacks.onRelease;
|
|
361
|
+
const onAcquire = typeof callbacks === "function" ? callbacks : callbacks.onAcquire;
|
|
359
362
|
const release = () => {
|
|
363
|
+
try { onRelease(); } catch {}
|
|
360
364
|
try { ioctlValue(consoleDevice.fd, VT_RELDISP, 1, "VT_RELDISP"); } catch {}
|
|
361
365
|
};
|
|
362
366
|
const acquire = () => {
|
|
@@ -429,6 +433,7 @@ const defaultTriangle = (display) => [
|
|
|
429
433
|
// before text mode returns, such as putting a keyboard back into cooked mode.
|
|
430
434
|
export const runGraphics = async (draw, {
|
|
431
435
|
device = "/dev/fb0", console: consolePaths, switchConsole = true,
|
|
436
|
+
prepareConsole = null,
|
|
432
437
|
} = {}) => {
|
|
433
438
|
const display = openFramebuffer(device);
|
|
434
439
|
let consoleDevice;
|
|
@@ -442,6 +447,7 @@ export const runGraphics = async (draw, {
|
|
|
442
447
|
let previousConsole = null;
|
|
443
448
|
let unwatch = null;
|
|
444
449
|
const restoreCallbacks = [];
|
|
450
|
+
const releaseCallbacks = [];
|
|
445
451
|
const acquireCallbacks = [];
|
|
446
452
|
const restore = () => {
|
|
447
453
|
while (restoreCallbacks.length) {
|
|
@@ -470,6 +476,10 @@ export const runGraphics = async (draw, {
|
|
|
470
476
|
for (const [signal, handler] of signalHandlers) process.on(signal, handler);
|
|
471
477
|
process.on("exit", restore);
|
|
472
478
|
try {
|
|
479
|
+
if (prepareConsole) {
|
|
480
|
+
const cleanup = await prepareConsole(consoleDevice);
|
|
481
|
+
if (typeof cleanup === "function") restoreCallbacks.push(cleanup);
|
|
482
|
+
}
|
|
473
483
|
const target = switchConsole ? consoleNumber(consoleDevice.path) : null;
|
|
474
484
|
if (target !== null) {
|
|
475
485
|
const active = activeConsole(consoleDevice);
|
|
@@ -480,22 +490,35 @@ export const runGraphics = async (draw, {
|
|
|
480
490
|
}
|
|
481
491
|
setConsoleGraphics(consoleDevice);
|
|
482
492
|
graphics = true;
|
|
493
|
+
// An async draw callback runs synchronously until its first await. Start
|
|
494
|
+
// it before enabling VT_PROCESS so it can register release/acquire hooks;
|
|
495
|
+
// otherwise a switch in that small window could be acknowledged without
|
|
496
|
+
// giving the renderer a chance to stop.
|
|
497
|
+
const drawing = draw(display, {
|
|
498
|
+
console: consoleDevice,
|
|
499
|
+
onRestore: (callback) => { restoreCallbacks.push(callback); },
|
|
500
|
+
onRelease: (callback) => { releaseCallbacks.push(callback); },
|
|
501
|
+
onAcquire: (callback) => { acquireCallbacks.push(callback); },
|
|
502
|
+
});
|
|
483
503
|
if (switchConsole) {
|
|
484
504
|
try {
|
|
485
|
-
unwatch = watchConsoleSwitches(consoleDevice,
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
505
|
+
unwatch = watchConsoleSwitches(consoleDevice, {
|
|
506
|
+
onRelease: () => {
|
|
507
|
+
for (const callback of releaseCallbacks) {
|
|
508
|
+
try { callback(); } catch {}
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
onAcquire: () => {
|
|
512
|
+
for (const callback of acquireCallbacks) {
|
|
513
|
+
try { callback(); } catch {}
|
|
514
|
+
}
|
|
515
|
+
},
|
|
489
516
|
});
|
|
490
517
|
} catch {
|
|
491
518
|
// Not a virtual console (or no permission): switches stay automatic.
|
|
492
519
|
}
|
|
493
520
|
}
|
|
494
|
-
return await
|
|
495
|
-
console: consoleDevice,
|
|
496
|
-
onRestore: (callback) => { restoreCallbacks.push(callback); },
|
|
497
|
-
onAcquire: (callback) => { acquireCallbacks.push(callback); },
|
|
498
|
-
});
|
|
521
|
+
return await drawing;
|
|
499
522
|
} finally {
|
|
500
523
|
restore();
|
|
501
524
|
process.off("exit", restore);
|
|
@@ -7,8 +7,9 @@ box drawing and kitty graphics protocol images — no X11, Wayland or GPU.
|
|
|
7
7
|
|
|
8
8
|
```sh
|
|
9
9
|
bunterm # run /bin/sh on the console you are on
|
|
10
|
-
bunterm
|
|
11
|
-
bunterm
|
|
10
|
+
bunterm 2 # start a detached bunterm on /dev/tty2
|
|
11
|
+
bunterm /dev/tty1 # the full virtual-console spelling also works
|
|
12
|
+
bunterm -s 20 # short for --font-size 20
|
|
12
13
|
bunterm -e bun /buninu/apps/jsmdcui/src/index.js --demo
|
|
13
14
|
bunterm --no-mouse # keyboard only
|
|
14
15
|
bunterm -h
|
|
@@ -19,13 +20,21 @@ The program's `TERM` is `xterm-256color`, `COLORTERM=truecolor`,
|
|
|
19
20
|
Markdown images without `--kitty`). When it exits, the console returns to
|
|
20
21
|
text mode and `bunterm` exits with its status.
|
|
21
22
|
|
|
23
|
+
Every live session has a canonical `/bin/bunterm /dev/ttyN ...` command line.
|
|
24
|
+
When no console is named, the currently active VT is used in the foreground.
|
|
25
|
+
Naming a different VT starts bunterm as a detached session and returns
|
|
26
|
+
immediately to the calling shell. Only one bunterm may control a given VT;
|
|
27
|
+
ownership is
|
|
28
|
+
identified from the process task name and its open console descriptor under
|
|
29
|
+
`/proc`, so it disappears automatically when the process exits.
|
|
30
|
+
|
|
22
31
|
## Options
|
|
23
32
|
|
|
24
33
|
| Option | Meaning |
|
|
25
34
|
| --- | --- |
|
|
26
|
-
| `/dev/ttyN` | The virtual console to use for graphics and the keyboard
|
|
35
|
+
| `N`, `/dev/ttyN` | The virtual console to use for graphics and the keyboard; for example, `2` means `/dev/tty2`. Naming a VT other than the active one starts a detached bunterm there, switches the display to it, and immediately returns control to the calling shell. Without this argument, bunterm uses the active VT in the foreground. |
|
|
27
36
|
| `-e command [argument...]` | The program to run; everything after `-e` belongs to it. Default `/bin/sh`. |
|
|
28
|
-
| `--font-size N` | Font size in pixels (default 16). The cell is 0.6 × N wide and 1.16 × N tall with DejaVu Sans Mono, so 1024×768 at 16 px gives 106 × 41 cells. |
|
|
37
|
+
| `-s N`, `--font-size N` | Font size in pixels (default 16). The cell is 0.6 × N wide and 1.16 × N tall with DejaVu Sans Mono, so 1024×768 at 16 px gives 106 × 41 cells. |
|
|
29
38
|
| `--line-height F` | Multiplies the cell height (default 1). |
|
|
30
39
|
| `--fb /dev/fbN` | The framebuffer device (default `/dev/fb0`). |
|
|
31
40
|
| `--no-blink` | A steady cursor. |
|
|
@@ -57,7 +66,11 @@ text mode and `bunterm` exits with its status.
|
|
|
57
66
|
|
|
58
67
|
The console stays in the kernel's translated keyboard mode, so
|
|
59
68
|
`Ctrl-Alt-F2` and friends still switch consoles; coming back repaints the
|
|
60
|
-
screen.
|
|
69
|
+
screen. While its VT is inactive, bunterm continues consuming PTY output and
|
|
70
|
+
updating its scrollback and kitty images, but neither writes the framebuffer
|
|
71
|
+
nor forwards global `/dev/input` mouse events. This allows separate bunterm
|
|
72
|
+
processes on `/dev/tty1`, `/dev/tty2`, and so on to hand the display back and
|
|
73
|
+
forth through the normal Linux VT switch. A few console-specific
|
|
61
74
|
sequences are rewritten to xterm's (`F1`–`F5`, `Home`, `End`), and arrow keys
|
|
62
75
|
follow the program's application cursor mode.
|
|
63
76
|
|
|
@@ -80,7 +93,9 @@ hidden while it is scrolled out of view.
|
|
|
80
93
|
`/dev/input` and never loads the mouse module.
|
|
81
94
|
|
|
82
95
|
`init.js` loads `evdev` and `psmouse` at boot, so a PS/2 mouse — which is
|
|
83
|
-
what QEMU's q35 machine and most PCs present — has a device node ready.
|
|
96
|
+
what QEMU's q35 machine and most PCs present — has a device node ready. The
|
|
97
|
+
aarch64 image loads `virtio_input` in place of `psmouse`, for QEMU's
|
|
98
|
+
`virtio-tablet-pci` and `virtio-keyboard-pci`. A USB
|
|
84
99
|
mouse also needs the `usbhid` stack, which a `--real` image carries and
|
|
85
100
|
`cfg.all` loads. The pointer follows relative devices (a mouse) and absolute
|
|
86
101
|
ones (a tablet or touchscreen) alike; a device that cannot be opened is
|
|
@@ -91,6 +106,8 @@ reported and the session continues without it.
|
|
|
91
106
|
- Needs a framebuffer: `linux-lts` has one built in, while the default
|
|
92
107
|
`linux-virt` builds it as modules the image does not ship, so build with
|
|
93
108
|
`--linux-lts` or `--real` (efifb/simpledrm from UEFI).
|
|
109
|
+
- The aarch64 image has no framebuffer yet: `run-qemu.sh` gives QEMU's
|
|
110
|
+
`virt` machine no display device, so there is no `/dev/fb0` to draw on.
|
|
94
111
|
- No selection, and no keyboard way to scroll back: the wheel is it. The
|
|
95
112
|
buffer keeps 1000 lines. A program that does not enable mouse reporting
|
|
96
113
|
sees no clicks — the arrow still moves, so the pointer is visibly alive.
|
|
@@ -100,7 +117,8 @@ reported and the session continues without it.
|
|
|
100
117
|
- Kitty images are drawn above text; `z` ordering below text and animation
|
|
101
118
|
frames are not implemented, and file transmission (`t=f`/`t=t`) works only
|
|
102
119
|
for files under `/tmp`, `/dev/shm` or `$TMPDIR`.
|
|
103
|
-
- One
|
|
120
|
+
- One bunterm per virtual console may use the framebuffer; only the active VT
|
|
121
|
+
draws. For panes and tabs within one VT, run `jsmdcui` inside it.
|
|
104
122
|
|
|
105
123
|
## See also
|
|
106
124
|
|
|
@@ -84,7 +84,7 @@ than an X11 `Display`. Colours are `{ r, g, b, a }` objects.
|
|
|
84
84
|
| `openConsole`, `setConsoleGraphics`, `setConsoleText` | Explicit Linux virtual-console control for callers that need it. |
|
|
85
85
|
| `activeConsole(console)`, `activateConsole(console, n)`, `consoleNumber(path)` | `VT_GETSTATE` / `VT_ACTIVATE` + `VT_WAITACTIVE`, and the number in a `/dev/ttyN` path. |
|
|
86
86
|
| `watchConsoleSwitches(console, onAcquire)` | `VT_SETMODE` with `VT_PROCESS`: answers the kernel's switch-away and switch-back signals and calls `onAcquire` when the display returns. Returns a function restoring automatic switching. |
|
|
87
|
-
| `runGraphics(draw, { device, console, switchConsole }?)` | Open the framebuffer, put the console in graphics mode, run `await draw(display, { console, onRestore, onAcquire })`, and restore text mode however the call ends (return, throw, `process.exit()`, `SIGHUP`/`SIGINT`/`SIGQUIT`/`SIGTERM`). `console` lists console devices to try, such as `["/dev/tty1"]`; when the one opened is a `/dev/ttyN` other than the console on screen, the display is switched to it first and back afterwards (`switchConsole: false` disables this). Console switches with Ctrl-Alt-Fn keep working
|
|
87
|
+
| `runGraphics(draw, { device, console, switchConsole, prepareConsole }?)` | Open the framebuffer, optionally call `await prepareConsole(console)`, put the console in graphics mode, run `await draw(display, { console, onRestore, onRelease, onAcquire })`, and restore text mode however the call ends (return, throw, `process.exit()`, `SIGHUP`/`SIGINT`/`SIGQUIT`/`SIGTERM`). `console` lists console devices to try, such as `["/dev/tty1"]`; when the one opened is a `/dev/ttyN` other than the console on screen, the display is switched to it first and back afterwards (`switchConsole: false` disables this). Console switches with Ctrl-Alt-Fn keep working: `onRelease(callback)` runs before the switch is acknowledged so drawing can stop, `onAcquire(callback)` runs when the display comes back, and `onRestore(callback)` registers cleanup to run before text mode returns. A cleanup function returned by `prepareConsole` is registered the same way. |
|
|
88
88
|
| `runDemo({ polygons, device }?)` | Run the CLI demo through `runGraphics()`. Each item in `polygons` is drawn separately. |
|
|
89
89
|
|
|
90
90
|
The module supports 16-, 24-, and 32-bit packed-pixel framebuffers and uses
|
|
@@ -28,6 +28,8 @@ Use `-` as ARCHIVE to read from standard input or write to standard output.
|
|
|
28
28
|
| `-z`, `--gzip` | Create gzip-compressed tar, or explicitly identify gzip input. |
|
|
29
29
|
| `--zstd` | Create or read a zstd-compressed tar. A `.zst` or `.tzst` input is also detected by name. |
|
|
30
30
|
| `-v` | Print names while creating/extracting; with `-t`, also show each member's size, modification time, kind and link target. |
|
|
31
|
+
| `-o`, `--no-same-owner`, `--no-same-permissions`, `--overwrite` | GNU tar compatibility for extraction; these already match Bun.Archive's behaviour. |
|
|
32
|
+
| `--exclude=PATTERN` | Accepted for js-udocker compatibility. Device nodes are not extracted and js-udocker removes whiteout files separately. |
|
|
31
33
|
|
|
32
34
|
Short options can be combined, for example `-xzvf` or `-czf`.
|
|
33
35
|
For the traditional first option word, the leading hyphen is optional, so
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buninu-linux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A Linux distribution running Bun as PID 1 with the Buninu userspace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "git+https://github.com/jjtseng93/buninu-linux.git"
|
|
15
15
|
},
|
|
16
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://buninu.org",
|
|
17
17
|
"bugs": {
|
|
18
18
|
"url": "https://github.com/jjtseng93/buninu-linux/issues"
|
|
19
19
|
},
|
package/run-qemu.sh
CHANGED
|
@@ -2,19 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
cd "$(dirname "$0")"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if [ -z "$
|
|
8
|
-
|
|
5
|
+
# The guest is whatever build-uki.sh last put in vda/EFI/BOOT (it leaves only
|
|
6
|
+
# one architecture there), unless BUNINU_ARCH (index.js --arch) names one.
|
|
7
|
+
if [ -z "$BUNINU_ARCH" ] && [ -f vda/EFI/BOOT/BOOTAA64.EFI ]; then
|
|
8
|
+
BUNINU_ARCH=aarch64
|
|
9
9
|
fi
|
|
10
|
+
. ./scripts/arch.sh
|
|
11
|
+
|
|
12
|
+
test -f vda.img || ./build-image.sh || exit 1
|
|
13
|
+
test -f "vda/EFI/BOOT/$efi_boot_name" || {
|
|
14
|
+
echo "error: vda.img holds no $arch UKI; build one with ./index.js -b --arch $arch" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
qemu="qemu-system-$arch"
|
|
19
|
+
command -v "$qemu" >/dev/null || {
|
|
20
|
+
echo "error: $qemu not found (macOS: brew install qemu; Debian: apt install qemu-system)" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
}
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
case $arch in
|
|
25
|
+
x86_64) machine=q35 distro_firmware=/usr/share/OVMF/OVMF_CODE_4M.fd ;;
|
|
26
|
+
aarch64) machine=virt distro_firmware=/usr/share/AAVMF/AAVMF_CODE.fd ;;
|
|
27
|
+
esac
|
|
28
|
+
|
|
29
|
+
# UEFI firmware: OVMF_FILE if set; then the edk2 build QEMU ships next to its
|
|
30
|
+
# own binary (Termux's $PREFIX/share/qemu, Homebrew's share/qemu, ...); then
|
|
31
|
+
# the distribution packages (Debian ovmf / qemu-efi-aarch64).
|
|
32
|
+
qemu_share="$(dirname "$(command -v "$qemu")")/../share/qemu"
|
|
33
|
+
ovmf_filepath=
|
|
34
|
+
for candidate in "$OVMF_FILE" "$distro_firmware" "$qemu_share/edk2-$arch-code.fd" \
|
|
35
|
+
"/usr/share/qemu/edk2-$arch-code.fd"; do
|
|
36
|
+
if [ -n "$candidate" ] && [ -f "$candidate" ]; then
|
|
37
|
+
ovmf_filepath=$candidate
|
|
38
|
+
break
|
|
39
|
+
fi
|
|
40
|
+
done
|
|
41
|
+
test -n "$ovmf_filepath" || {
|
|
42
|
+
echo "error: no $arch UEFI firmware found; set OVMF_FILE to an edk2-$arch-code.fd" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
}
|
|
12
45
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
46
|
+
# Hardware acceleration needs the guest to match the host CPU: KVM on Linux,
|
|
47
|
+
# Hypervisor.framework on macOS (Apple Silicon runs the aarch64 guest natively,
|
|
48
|
+
# an Intel Mac the x86_64 one). Anything else, such as the x86_64 guest on
|
|
49
|
+
# Apple Silicon or on an Android phone, is emulated with TCG. ACCEL overrides
|
|
50
|
+
# the choice, e.g. ACCEL=tcg to rule acceleration out while debugging.
|
|
51
|
+
case "$(uname -m)" in
|
|
52
|
+
arm64 | aarch64) host_arch=aarch64 ;;
|
|
53
|
+
*) host_arch=$(uname -m) ;;
|
|
54
|
+
esac
|
|
55
|
+
if [ -z "$ACCEL" ]; then
|
|
56
|
+
ACCEL=tcg
|
|
57
|
+
if [ "$host_arch" = "$arch" ]; then
|
|
58
|
+
case "$(uname -s)" in
|
|
59
|
+
Linux) [ -r /dev/kvm ] && [ -w /dev/kvm ] && ACCEL=kvm ;;
|
|
60
|
+
Darwin) [ "$(sysctl -n kern.hv_support 2>/dev/null)" = 1 ] && ACCEL=hvf ;;
|
|
61
|
+
esac
|
|
62
|
+
fi
|
|
17
63
|
fi
|
|
64
|
+
# TCG emulates every feature it knows; KVM and HVF pass the host CPU through.
|
|
65
|
+
if [ "$ACCEL" = tcg ]; then cpu=max; else cpu=host; fi
|
|
66
|
+
echo "run-qemu.sh: $arch guest on $machine, accel=$ACCEL, firmware $ovmf_filepath" >&2
|
|
18
67
|
|
|
19
68
|
# User-mode networking is NAT: the guest can reach out, nothing reaches in
|
|
20
69
|
# unless a port is forwarded. PORTS lists them, space-separated, as
|
|
@@ -31,9 +80,9 @@ for port in ${PORTS:-}; do
|
|
|
31
80
|
nic="$nic,hostfwd=tcp:127.0.0.1:$host-:$guest"
|
|
32
81
|
done
|
|
33
82
|
|
|
34
|
-
exec qemu
|
|
35
|
-
-machine
|
|
36
|
-
-cpu
|
|
83
|
+
exec "$qemu" \
|
|
84
|
+
-machine "$machine,accel=$ACCEL" \
|
|
85
|
+
-cpu "$cpu" \
|
|
37
86
|
-m 1G \
|
|
38
87
|
-drive if=pflash,format=raw,readonly=on,file="$ovmf_filepath" \
|
|
39
88
|
-drive if=virtio,format=raw,file=vda.img \
|
package/scripts/arch.sh
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Sourced by the fetch, build and run scripts; not executable on its own.
|
|
2
|
+
#
|
|
3
|
+
# Everything that differs between the two guest architectures lives here:
|
|
4
|
+
# the pinned packages and their SHA-256, the file names inside them, the EFI
|
|
5
|
+
# removable-media name, the PE-aware objcopy that assembles the UKI and the
|
|
6
|
+
# serial console. BUNINU_ARCH (index.js --arch) picks one; x86_64 is the
|
|
7
|
+
# default. LINUX_FLAVOR (virt or lts) and REAL_MACHINE are read here too.
|
|
8
|
+
#
|
|
9
|
+
# Fetched and extracted files are kept per architecture, so both guests can
|
|
10
|
+
# be built from one checkout without refetching:
|
|
11
|
+
#
|
|
12
|
+
# downloads/<arch>/ the pinned archives (both repos use the same names)
|
|
13
|
+
# kernel/<arch>/ vmlinuz-<flavor> and the UKI stub
|
|
14
|
+
# native/<arch>/ bun, musl, libgcc/libstdc++ and kernel modules;
|
|
15
|
+
# scripts/pack-initramfs.sh lays it over initramfs/
|
|
16
|
+
|
|
17
|
+
case "${BUNINU_ARCH:-x86_64}" in
|
|
18
|
+
x86_64 | amd64 | x64) arch=x86_64 ;;
|
|
19
|
+
aarch64 | arm64) arch=aarch64 ;;
|
|
20
|
+
*)
|
|
21
|
+
echo "error: unsupported BUNINU_ARCH=$BUNINU_ARCH (use x86_64 or aarch64)" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
linux_flavor=${LINUX_FLAVOR:-virt}
|
|
27
|
+
case $linux_flavor in
|
|
28
|
+
virt | lts) ;;
|
|
29
|
+
*)
|
|
30
|
+
echo "error: unsupported LINUX_FLAVOR=$linux_flavor (use virt or lts)" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
if [ "${REAL_MACHINE:-}" = 1 ] && [ "$arch" != x86_64 ]; then
|
|
36
|
+
echo "error: --real images are x86_64 only; its module set is PC hardware" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# The Alpine repository keeps only the newest build of a package, so a pin
|
|
41
|
+
# stops downloading when Alpine bumps it; the hashes below then fail loudly
|
|
42
|
+
# rather than silently taking the new build. ALPINE_MIRROR swaps the host for
|
|
43
|
+
# a closer mirror; the SHA-256 check is the same either way.
|
|
44
|
+
alpine_mirror=${ALPINE_MIRROR:-https://dl-cdn.alpinelinux.org/alpine}
|
|
45
|
+
alpine_base="$alpine_mirror/v3.24/main/$arch"
|
|
46
|
+
|
|
47
|
+
kernel_version=6.18.53
|
|
48
|
+
kernel_release="$kernel_version-0-$linux_flavor"
|
|
49
|
+
linux_package="linux-$linux_flavor-$kernel_version-r0.apk"
|
|
50
|
+
musl_package=musl-1.2.6-r2.apk
|
|
51
|
+
stub_package=systemd-efistub-260.2-r0.apk
|
|
52
|
+
libstdcpp_package=libstdc++-15.2.0-r5.apk
|
|
53
|
+
libgcc_package=libgcc-15.2.0-r5.apk
|
|
54
|
+
bun_version=1.4.2
|
|
55
|
+
|
|
56
|
+
downloads_dir="downloads/$arch"
|
|
57
|
+
kernel_dir="kernel/$arch"
|
|
58
|
+
kernel_image="$kernel_dir/vmlinuz-$linux_flavor"
|
|
59
|
+
native_dir="native/$arch"
|
|
60
|
+
modules_root="$native_dir/lib/modules/$kernel_release"
|
|
61
|
+
musl_loader="ld-musl-$arch.so.1"
|
|
62
|
+
musl_libc="libc.musl-$arch.so.1"
|
|
63
|
+
|
|
64
|
+
case $arch in
|
|
65
|
+
x86_64)
|
|
66
|
+
linux_virt_sha256=cad859cc46342e18002621fdde166bf2cd520dfec5e781d241de7d13e53970d6
|
|
67
|
+
linux_lts_sha256=8e3cfdd1d98e0e70c2e70a8c299ed3cd1e0939d60ad7bbd1da1cf241d794490b
|
|
68
|
+
musl_sha256=573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71
|
|
69
|
+
stub_sha256=8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9
|
|
70
|
+
libstdcpp_sha256=14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6
|
|
71
|
+
libgcc_sha256=393dcd32629f06d7d85409c272d142d0c082772d10b87ef55ee82f47de3be637
|
|
72
|
+
# Bun ships one x64 musl build; -baseline is an alias (NOTICE.md).
|
|
73
|
+
bun_build=bun-linux-x64-musl-baseline
|
|
74
|
+
bun_sha256=76e1db84e98f22f78de0a87e309bfbbf297732847f9720db36750646c85c8c18
|
|
75
|
+
efi_stub=linuxx64.efi.stub
|
|
76
|
+
efi_boot_name=BOOTX64.EFI
|
|
77
|
+
uki_objcopy=x86_64-w64-mingw32-objcopy
|
|
78
|
+
uki_objdump=x86_64-w64-mingw32-objdump
|
|
79
|
+
uki_binutils_package=binutils-mingw-w64-x86-64
|
|
80
|
+
# COM1 on q35 and on PCs.
|
|
81
|
+
serial_console=ttyS0
|
|
82
|
+
serial_major=4
|
|
83
|
+
;;
|
|
84
|
+
aarch64)
|
|
85
|
+
linux_virt_sha256=fea61b7fd5e72e626d771f9798df56c83db658d0e221044f06fc28aacf5a118b
|
|
86
|
+
linux_lts_sha256=46885041022ea11afa1e07c40199c74146bf72d362368a844338af0c162e365c
|
|
87
|
+
musl_sha256=5e9674b7f41152fe2119093b5cb4c13eaaadb19c2d5422b2d7267913e663ee6e
|
|
88
|
+
stub_sha256=a1823d2d7082db555d528f82c1809f276c818aeb5b40f198f4565f104e055c38
|
|
89
|
+
libstdcpp_sha256=2302e766d4e4926038ec166ecb85837ee884576115236ddb565e3a5fca4a11d7
|
|
90
|
+
libgcc_sha256=369aaa6e9d099a737bad6dd3e6c2fe7bb1547ca26d22b94ee0411228f709b403
|
|
91
|
+
bun_build=bun-linux-aarch64-musl
|
|
92
|
+
bun_sha256=71760b6c8ea30623b81a4907cb815d48e2ea266f2e73e751534a44a0607950df
|
|
93
|
+
efi_stub=linuxaa64.efi.stub
|
|
94
|
+
efi_boot_name=BOOTAA64.EFI
|
|
95
|
+
# The aarch64 ELF binutils also read and write pei-aarch64-little:
|
|
96
|
+
# native binutils on an arm64 Debian, the cross package on amd64.
|
|
97
|
+
uki_objcopy=aarch64-linux-gnu-objcopy
|
|
98
|
+
uki_objdump=aarch64-linux-gnu-objdump
|
|
99
|
+
uki_binutils_package=binutils-aarch64-linux-gnu
|
|
100
|
+
# The PL011 UART of QEMU's virt machine.
|
|
101
|
+
serial_console=ttyAMA0
|
|
102
|
+
serial_major=204
|
|
103
|
+
;;
|
|
104
|
+
esac
|
|
105
|
+
|
|
106
|
+
if [ "$linux_flavor" = lts ]; then
|
|
107
|
+
linux_sha256=$linux_lts_sha256
|
|
108
|
+
else
|
|
109
|
+
linux_sha256=$linux_virt_sha256
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
bun_zip="$bun_build-$bun_version.zip"
|
|
113
|
+
bun_url="https://github.com/oven-sh/bun/releases/download/bun-v$bun_version/$bun_build.zip"
|
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,30 @@
|
|
|
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/"
|
|
25
|
+
# Keep the project README canonical while making it available to
|
|
26
|
+
# buninu-linux-help from the conventional installed-document path.
|
|
27
|
+
cp README.md "$staging/usr/share/doc/buninu-linux/README.md"
|
|
28
|
+
cp -a LICENSE NOTICE.md LICENSES "$staging/usr/share/licenses/"
|
|
12
29
|
|
|
13
30
|
# fakeroot lets cpio record the character devices without real root. Nothing
|
|
14
31
|
# has mounted devtmpfs when the kernel execs Bun as PID 1, so every device
|
|
@@ -20,10 +37,11 @@ cp -a --reflink=auto initramfs/. "$staging/"
|
|
|
20
37
|
# This is the one device that actually blocks booting Bun as PID 1.
|
|
21
38
|
#
|
|
22
39
|
# The rest are cheap to include and keep /dev usable if the devtmpfs mount in
|
|
23
|
-
# init.js ever fails.
|
|
40
|
+
# init.js ever fails. The serial port is ttyS0 (4, 64) on x86_64 and the
|
|
41
|
+
# PL011 ttyAMA0 (204, 64) on aarch64.
|
|
24
42
|
fakeroot-tcp -- sh -c '
|
|
25
43
|
mknod -m 0600 "$1/dev/console" c 5 1
|
|
26
|
-
mknod -m 0620 "$1/dev
|
|
44
|
+
mknod -m 0620 "$1/dev/$3" c "$4" 64
|
|
27
45
|
mknod -m 0666 "$1/dev/null" c 1 3
|
|
28
46
|
mknod -m 0666 "$1/dev/zero" c 1 5
|
|
29
47
|
mknod -m 0666 "$1/dev/random" c 1 8
|
|
@@ -34,8 +52,8 @@ fakeroot-tcp -- sh -c '
|
|
|
34
52
|
| sort -z \
|
|
35
53
|
| cpio --null --create --format=newc --owner=0:0 --reproducible 2>/dev/null \
|
|
36
54
|
> "$2"
|
|
37
|
-
' sh "$staging" "$archive"
|
|
55
|
+
' sh "$staging" "$archive" "$serial_console" "$serial_major"
|
|
38
56
|
|
|
39
57
|
gzip -9n < "$archive" > build/initramfs.cpio.gz
|
|
40
58
|
|
|
41
|
-
echo "Built build/initramfs.cpio.gz"
|
|
59
|
+
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
|