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.
Files changed (42) hide show
  1. package/Dockerfile +23 -0
  2. package/NOTICE.md +111 -63
  3. package/README.md +292 -58
  4. package/build-image.sh +9 -5
  5. package/build-uki.sh +44 -28
  6. package/fetch-alpine.sh +45 -31
  7. package/fetch-bun.sh +14 -21
  8. package/graphics.md +7 -2
  9. package/index.js +150 -35
  10. package/initramfs/bin/bunterm +5 -1
  11. package/initramfs/bin/chroot +216 -0
  12. package/initramfs/bin/ip +3 -2
  13. package/initramfs/bin/ps +3 -0
  14. package/initramfs/bin/stripansi +27 -0
  15. package/initramfs/bin/tar +198 -14
  16. package/initramfs/init.js +149 -38
  17. package/initramfs/lib/bunterm/images.js +17 -2
  18. package/initramfs/lib/bunterm/mouse.js +222 -0
  19. package/initramfs/lib/bunterm/renderer.js +48 -3
  20. package/initramfs/lib/bunterm/terminal.js +123 -2
  21. package/initramfs/lib/dlopen.js +4 -0
  22. package/initramfs/lib/document.js +5 -1
  23. package/initramfs/usr/share/doc/buninu-linux/bunterm.md +35 -2
  24. package/initramfs/usr/share/doc/buninu-linux/chroot.md +105 -0
  25. package/initramfs/usr/share/doc/buninu-linux/tar.md +32 -6
  26. package/native/aarch64/lib/ld-musl-aarch64.so.1 +0 -0
  27. package/native/aarch64/lib/libc.musl-aarch64.so.1 +0 -0
  28. package/native/aarch64/lib/libgcc_s.so.1 +0 -0
  29. package/native/aarch64/lib/libstdc++.so.6 +0 -0
  30. package/package.json +2 -2
  31. package/run-qemu.sh +61 -12
  32. package/scripts/arch.sh +113 -0
  33. package/scripts/fetch.sh +5 -3
  34. package/scripts/pack-initramfs.sh +18 -4
  35. package/test/host/Dockerfile +13 -0
  36. package/test/host/boot-test.exp +70 -0
  37. package/test/host/host-test.sh +49 -0
  38. package/test/host/in-container.sh +58 -0
  39. /package/{initramfs → native/x86_64}/lib/ld-musl-x86_64.so.1 +0 -0
  40. /package/{initramfs → native/x86_64}/lib/libc.musl-x86_64.so.1 +0 -0
  41. /package/{initramfs → native/x86_64}/lib/libgcc_s.so.1 +0 -0
  42. /package/{initramfs → native/x86_64}/lib/libstdc++.so.6 +0 -0
package/README.md CHANGED
@@ -5,18 +5,21 @@
5
5
  * a prebuilt kernel from Alpine
6
6
  * a prebuilt systemd EFI stub from Alpine
7
7
  * initramfs JavaScript components:
8
- * `/init.js` and other modules
8
+ * `/init.js`, Buninu userspace, and other modules
9
9
  * initramfs Native components:
10
- * Official `bun-linux-x64-musl` and its dependencies
11
- + ld-musl-x86_64.so.1
12
- + libc.musl-x86_64.so.1 (intentional copy)
10
+ * Official `bun-linux-x64-musl` (or `bun-linux-aarch64-musl`) and its dependencies
11
+ + ld-musl-x86_64.so.1 (ld-musl-aarch64.so.1)
12
+ + libc.musl-x86_64.so.1 (libc.musl-aarch64.so.1; intentional copy)
13
13
  + libgcc_s.so.1
14
14
  + libstdc++.so.6
15
+ - Boots on real x86-64 UEFI hardware; QEMU system testing supports both x86_64
16
+ and aarch64 guests.
15
17
  - The kernel runs `bun` as PID 1
16
18
  * Everything an init would normally do — mounting filesystems, loading modules, configuring the network, reaping children
17
19
  * happens in JavaScript through `bun:ffi`
18
20
  * There is no BusyBox and no C bootstrap.
19
21
 
22
+ - Homepage: <https://buninu.org>
20
23
  - Souce: [github.com/jjtseng93/buninu-linux](https://github.com/jjtseng93/buninu-linux)
21
24
  - Buninu userspace source: [github.com/jjtseng93/buninu](https://github.com/jjtseng93/buninu)
22
25
  + **BUNinu Is Not Unix** 🐮
@@ -37,6 +40,10 @@
37
40
  * IP addresses and routes are configured manually because the image does not yet include a DHCP client.
38
41
  * The bundled `jmi` editor, jsmdcui editor/terminal multitasking, and local JavaScript execution also work.
39
42
  * The graphical terminal `bunterm` draws on the framebuffer with CJK, colour emoji and kitty images; see [Showing images](#showing-images)
43
+ + Also has mouse click, wheel, and cursor drawing
44
+ + Simplified browser works: @sanohiro/casty in chroot debian
45
+ + (Use at your own risk: We haven't fully examined its code)
46
+ + See [Commands inside `/bin`](#commands-inside-bin) for browser usage instructions
40
47
 
41
48
  ---
42
49
 
@@ -45,6 +52,11 @@
45
52
 
46
53
  ## Quick start
47
54
 
55
+ > [!IMPORTANT]
56
+ > This section builds an image for real x86-64 UEFI hardware. To build and
57
+ > test either an x86_64 or aarch64 guest with `qemu-system` instead, see
58
+ > [Build and test with QEMU on macOS or Linux](#build-and-test-with-qemu-on-macos-or-linux).
59
+
48
60
  - This guide builds a bootable `buninu-linux-<version>.img`
49
61
  - The documented build environment is Debian 13 under Termux PRoot
50
62
  - A regular Debian installation works as well.
@@ -74,9 +86,11 @@ apt install binutils-mingw-w64-x86-64 cpio curl dosfstools fakeroot mtools parte
74
86
  mkdir -p /data/data/com.termux/files/home/buninu-build
75
87
  cd /data/data/com.termux/files/home/buninu-build
76
88
 
77
- $HOME/.bun/bin/bun x buninu-linux --version
89
+ export PATH=$HOME/.bun/bin:$PATH
90
+
91
+ bun x buninu-linux --version
78
92
 
79
- $HOME/.bun/bin/bun x buninu-linux -fb --real --export
93
+ bun x buninu-linux -fb --real --export
80
94
  ```
81
95
 
82
96
  `--export` copies the completed `vda.img` out of bunx's package directory and
@@ -112,6 +126,10 @@ this overwrites it completely.
112
126
  sudo dd if='buninu-linux-<version>.img' of=/dev/sdX bs=4M conv=fsync status=progress
113
127
  ```
114
128
 
129
+ Alternatively, copy the exported `.img` file to a
130
+ [Ventoy](https://www.ventoy.net/en/index.html) USB drive and select it from
131
+ Ventoy's boot menu.
132
+
115
133
  #### Booting from the USB drive
116
134
 
117
135
  Boot that drive as x86-64 UEFI media. The UKI is unsigned, so disable Secure
@@ -145,6 +163,110 @@ described under [Basic configuration](#basic-configuration). Run `poweroff` for
145
163
  shutdown. After editing the initramfs, `bun ./index.js -b --real --export` rebuilds the
146
164
  image; `-h` lists all flags.
147
165
 
166
+ ### Build and test with QEMU on macOS or Linux
167
+
168
+ > [!IMPORTANT]
169
+ > This section covers guests built and run with `qemu-system` only. To build
170
+ > an image for real x86-64 UEFI hardware, follow [Quick start](#quick-start)
171
+ > above and use `-fb --real --export`.
172
+
173
+ The same `index.js` builds and boots Buninu Linux in QEMU on a Mac or Linux
174
+ host. `--arch` picks the guest:
175
+
176
+ | host | `--arch x86_64` (default) | `--arch aarch64` (alias `arm64`) |
177
+ | --- | --- | --- |
178
+ | Apple Silicon Mac | q35, emulated (TCG) | `virt`, hardware-accelerated (Hypervisor.framework) |
179
+ | Intel Mac | q35, hardware-accelerated (Hypervisor.framework) | `virt`, emulated (TCG) |
180
+ | x86-64 Linux | q35, hardware-accelerated (KVM) when `/dev/kvm` is usable | `virt`, emulated (TCG) |
181
+ | arm64 Linux | q35, emulated (TCG) | `virt`, hardware-accelerated (KVM) when `/dev/kvm` is usable |
182
+ | Android Termux | q35, emulated (TCG) | `virt`, KVM when `/dev/kvm` is usable, otherwise TCG |
183
+
184
+ `run-qemu.sh` chooses the accelerator itself; `ACCEL=tcg` forces emulation.
185
+
186
+ #### macOS
187
+
188
+ This project does not currently support a native macOS build toolchain (GNU
189
+ cpio and tar, `fakeroot`, `parted`, PE-aware binutils), so on a Mac `index.js`
190
+ always runs the fetch and build stages in a Debian 13 container built from
191
+ [`Dockerfile`](Dockerfile), with the checkout bind-mounted. The image is built
192
+ on first use and tagged with a hash of the Dockerfile. Only the run stage uses
193
+ the Mac itself:
194
+
195
+ ```sh
196
+ # Docker Desktop (running) for -f/-b, QEMU and its UEFI firmware for -r
197
+ brew install qemu
198
+
199
+ # Apple Silicon: Hypervisor.framework accelerates the aarch64 guest
200
+ bun ./index.js -fbr --arch arm64
201
+
202
+ # The x86_64 guest works too, emulated
203
+ bun ./index.js -fbr
204
+ ```
205
+
206
+ The first boot line from `run-qemu.sh` names the guest, machine, accelerator
207
+ and firmware it picked. A bare `-r` boots whichever architecture `vda.img`
208
+ was last built for; `-br --arch arm64` is the aarch64 edit-and-boot loop.
209
+
210
+ #### amd64 Linux
211
+
212
+ On Debian or Ubuntu the build runs on the host exactly as in PRoot, and QEMU
213
+ uses KVM for the x86_64 guest when the user can open `/dev/kvm`:
214
+
215
+ ```sh
216
+ apt install binutils-mingw-w64-x86-64 cpio curl dosfstools fakeroot mtools parted unzip
217
+ apt install qemu-system-x86 ovmf
218
+ bun ./index.js -fbr
219
+ ```
220
+
221
+ Pass `--docker` to build in the same container as macOS instead of installing
222
+ the build packages; the aarch64 guest additionally needs
223
+ `binutils-aarch64-linux-gnu` for a host build and `qemu-system-arm
224
+ qemu-efi-aarch64` to run.
225
+
226
+ #### What differs on aarch64
227
+
228
+ * The image is `vda/EFI/BOOT/BOOTAA64.EFI` on the same GPT disk, built from
229
+ Alpine's aarch64 packages and `bun-linux-aarch64-musl`.
230
+ * The serial console is `ttyAMA0` (QEMU `virt`'s PL011) instead of `ttyS0`.
231
+ * `--real` is x86_64 only: its module set is PC hardware.
232
+ * The current aarch64 QEMU configuration supplies no framebuffer or PS/2
233
+ devices, so `bunterm` has nothing to draw on yet; the serial REPL and the
234
+ Buninu shell work as on x86_64.
235
+ * `--export` writes `buninu-linux-<version>-aarch64.img`.
236
+ * Before the kernel starts, Homebrew's aarch64 EDK2 prints a few
237
+ `Error: Image at … start failed` lines (its own drivers for hardware `virt`
238
+ does not have) and `ConvertPages: failed to find range …` (it cannot place
239
+ the UKI at the stub's preferred address and relocates it). Both are
240
+ harmless; `BdsDxe: starting Boot0001` follows and the boot continues.
241
+
242
+ #### Testing on a Linux host in Docker
243
+
244
+ `test/host/host-test.sh` checks the Linux-host path from any machine with
245
+ Docker: it starts a fresh Debian 13 container of the chosen CPU with the
246
+ build packages above and QEMU, clones the committed `HEAD` of this checkout,
247
+ builds both guests with that toolchain (not `--docker`) and boots each one,
248
+ driving the serial console through `test/host/boot-test.exp`:
249
+
250
+ ```sh
251
+ test/host/host-test.sh arm64 # a Linux arm64 host (native on Apple Silicon)
252
+ test/host/host-test.sh amd64 # a Linux x86-64 host (emulated there)
253
+ ```
254
+
255
+ Every step — REPL, `start()`, `uname`, eth0, PID 1, a tmpfs mount, a fetch
256
+ from the guest, `poweroff` — waits for its expected output, and the run ends
257
+ with `### PASS` and exit status 0 or `### FAIL` and 1. Uncommitted changes to
258
+ the project are not tested; commit first. `GUESTS=aarch64` limits the guests,
259
+ `BUILD_FLAGS=--linux-lts` adds build flags, and `downloads/` is used read-only
260
+ as the download cache. Docker Desktop exposes no `/dev/kvm`, so there both
261
+ guests run under TCG; on a Linux host with KVM the script passes it through
262
+ and the matching guest boots with `accel=kvm`. On macOS the same
263
+ `boot-test.exp` also drives a local boot: `expect test/host/boot-test.exp
264
+ "$PWD" aarch64` after `bun ./index.js -b --arch arm64`.
265
+
266
+ `ALPINE_MIRROR` swaps the Alpine CDN for a mirror, e.g.
267
+ `ALPINE_MIRROR=https://mirrors.edge.kernel.org/alpine`; every download is still
268
+ checked against its pinned SHA-256.
269
+
148
270
  ## Using Buninu Linux
149
271
 
150
272
  ### Basic configuration
@@ -212,6 +334,13 @@ bun hlw.js
212
334
  If `Enter` is not recognized in a particular terminal, try `Ctrl-J` or
213
335
  `Ctrl-M`.
214
336
 
337
+ #### jsmdcui
338
+ - `jsmdcui` is the same editor, but by default executes markdown files as interactive Apps
339
+ - Be careful not to run markdown files from strangers
340
+ * Otherwise it can run any command on your PC
341
+ - `jsmdcui --cdp-maze` runs a self-solving maze game
342
+ * Run `bunterm` first to show its Emojis
343
+
215
344
  ### Panes, terminals, and tabs
216
345
 
217
346
  `jmi` and jsmdcui can keep editors and terminal sessions open together. Press
@@ -310,6 +439,9 @@ buninu-help
310
439
  # Markdown view with its images in place
311
440
  # cfg.net first to show that GitHub image
312
441
  jsmdcui --allow-url README.md
442
+
443
+ # The mouse works in bunterm; --no-mouse leaves /dev/input alone
444
+ bunterm -e jsmdcui --allow-url README.md
313
445
  ```
314
446
 
315
447
  `bunterm` draws with Skia (CanvasKit) and understands the kitty graphics
@@ -333,18 +465,26 @@ needs a kernel with a framebuffer: build with `--linux-lts` or `--real`
333
465
  | `poweroff` | sync pending writes and power off through the Linux reboot system call | `poweroff --help` |
334
466
  | `reboot` | sync pending writes and restart through the shared Linux reboot logic | `reboot --help` |
335
467
  | `tar` | create, extract, or list tar archives with gzip and zstd compression through `Bun.Archive` | `tar --help` |
336
- | `bunterm` | a graphical terminal on the framebuffer: Skia (CanvasKit) text with CJK, colour emoji, seamless box drawing, kitty graphics images; xterm.js's emulator core over Bun's built-in PTY | `bunterm --help` |
468
+ | `stripansi` | remove ANSI escape sequences from stdin, `-`, or one or more files and concatenate the results | — |
469
+ | `chroot` | `chroot(2)` into another root directory, mounting `/proc`, `/sys`, `/dev`, `/dev/pts`, `/run`, `/tmp` and `/etc/resolv.conf` for you and unmounting them afterwards | `chroot --help` |
470
+ | `bunterm` | a graphical terminal on the framebuffer: Skia (CanvasKit) text with CJK, colour emoji, seamless box drawing, kitty graphics images, with the mouse working, wheel scrollback included (`--no-mouse` turns it off); xterm.js's emulator core over Bun's built-in PTY | `bunterm --help` |
337
471
 
338
- Besides `bun` (and `sh`/`node` pointing at it), `/bin` includes nine commands
472
+ Besides `bun` (and `sh`/`node` pointing at it), `/bin` includes eleven commands
339
473
  implemented as Bun scripts.
340
474
 
341
- The small `tar` follows the current `Bun.Archive` boundary: extraction
342
- restores directories and symbolic links, while hard links are skipped by Bun
343
- 1.4.3. Creation stores regular files but not Unix metadata, links, or empty
344
- directories, and listing reports regular files only. It supports gzip and
345
- Bun's built-in zstd, not xz or bzip2.
475
+ The small `tar` reads the archive's own headers for links and listing, so
476
+ extraction restores directories, hard links and every symbolic link —
477
+ including the absolute targets `Bun.Archive` refuses, which is what makes an
478
+ unpacked Alpine root filesystem usable — and `-t` lists every member, not
479
+ only regular files. Creation still follows the `Bun.Archive` boundary: it
480
+ stores regular files but not Unix metadata, links, or empty directories. It
481
+ supports gzip and Bun's built-in zstd, not xz or bzip2.
346
482
 
347
- `--help` renders each Markdown manual in the terminal. Common examples:
483
+ - `--help` renders each Markdown manual in the terminal.
484
+ - The `bun x` examples below require network access:
485
+ * `bun x` downloads the requested program from the npm registry and runs it without installing it permanently.
486
+ * If they fail the 1st time, try `bun pm cache rm`
487
+ - Common examples:
348
488
 
349
489
  ```sh
350
490
  mount /dev/sda1 /mnt --mkdir
@@ -354,6 +494,65 @@ mount -t ntfs3 -o force /dev/sda3 /mnt/windows
354
494
  umount /mnt
355
495
  umount -R /mnt
356
496
 
497
+
498
+ # Clone a Git repository through bunproot
499
+ bun x bunproot --git clone https://github.com/jjtseng93/bunproot
500
+
501
+ # Read bunproot's Git manual without ANSI formatting in jmi
502
+ bunterm
503
+ bun x bunproot --git --readme | stripansi | jmi
504
+
505
+
506
+
507
+ # Download and enter an x64 Debian rootfs
508
+ # Make sure you have run this already:
509
+ # bunterm --font-size 13
510
+ bun x bunproot --git --yes clone https://github.com/jjtseng93/js-udocker
511
+ cd js-udocker
512
+ bun udocker.js pull --platform=linux/amd64 debian:13
513
+ bun udocker.js create --name db debian:13
514
+ cd ~/.udocker/containers/db/ROOT
515
+ chroot . /bin/bash
516
+ apt update
517
+ apt install curl unzip chromium fonts-noto-cjk fonts-noto-color-emoji npm
518
+ curl -fsSL https://bun.sh/install | bash
519
+ bun x bunmsh
520
+ # We haven't fully reviewed
521
+ # @sanohiro/casty's safety
522
+ # !!!Use at your own risk!!!
523
+ # !!!Use at your own risk!!!
524
+ # !!!Use at your own risk!!!
525
+ # !!!Use at your own risk!!!
526
+ # !!!Use at your own risk!!!
527
+ # !!!Use at your own risk!!!
528
+ # !!!Use at your own risk!!!
529
+ # !!!Use at your own risk!!!
530
+ # !!!Use at your own risk!!!
531
+ # !!!Use at your own risk!!!
532
+ bun i -g @sanohiro/casty
533
+ # When clicking around, don’t release the mouse button immediately after pressing it, to make sure the mouse-down event is triggered
534
+ casty https://github.com/jjtseng93/buninu-linux
535
+
536
+
537
+
538
+ # Download and enter an x64 Alpine minirootfs
539
+ bun x bunproot --download-alpine-x64
540
+ mkdir alpine
541
+ cd alpine
542
+ tar xvf ../alpine-minirootfs-*-x86_64.tar.gz
543
+ cp $(which bun) bin
544
+ chroot .
545
+ apk add libgcc libstdc++
546
+
547
+
548
+ # Enter a root filesystem on a disk. /proc, /sys, /dev, /dev/pts, /run, /tmp
549
+ # and /etc/resolv.conf are mounted for you and removed again on exit
550
+ chroot /mnt
551
+ chroot /mnt /bin/bun --version
552
+ chroot -n /mnt /bin/sh # traditional: mount nothing
553
+
554
+
555
+
357
556
  ip link set eth0 up
358
557
  ip addr add 192.168.1.50/24 dev eth0
359
558
  ip route add default via 192.168.1.1
@@ -498,10 +697,13 @@ it too). Its physical-image stages run as fetch → build:
498
697
  | --- | --- | --- |
499
698
  | `-f`, `--fetch` | `fetch-alpine.sh`, `fetch-bun.sh` | first clone, or after bumping a pinned version |
500
699
  | `-b`, `--build` | `build-uki.sh`, `build-image.sh` | after editing `initramfs/init.js` or the kernel command line |
700
+ | `--arch ARCH` | explicitly selects `x86_64` (default for fetch/build) or `aarch64`; a bare `-r` detects the last built UKI | building or running a particular guest architecture |
701
+ | `--docker` | `-f`/`-b` inside the container from `Dockerfile` (always on macOS) | no Debian toolchain on the host |
501
702
 
502
703
  `--export` is a post-build option: after `-b` finishes, it copies `vda.img` to
503
704
  the directory where the command was invoked as
504
- `buninu-linux-<version>.img`. It therefore requires `-b`/`--build` and is the
705
+ `buninu-linux-<version>.img` (`buninu-linux-<version>-aarch64.img` for
706
+ `--arch aarch64`). It therefore requires `-b`/`--build` and is the
505
707
  recommended way to retain an image produced through npx/bunx:
506
708
 
507
709
  ```sh
@@ -520,26 +722,33 @@ failing halfway. The shell scripts do the actual work and each still runs on
520
722
  its own.
521
723
 
522
724
  ```text
523
- index.js entry point: -f / -b / --export / --linux-lts / --real
725
+ index.js entry point: -f / -b / -r / --arch / --docker / --export / --linux-lts / --real
726
+ Dockerfile the Debian 13 build toolchain for --docker (and macOS)
524
727
  fetch-alpine.sh [fetch] kernel, EFI stub, musl, network/input modules
525
728
  fetch-bun.sh [fetch] Bun, libstdc++, libgcc
526
729
  build-uki.sh [build] UKI; calls scripts/pack-initramfs.sh
527
730
  build-image.sh [build] GPT disk image; shared with the hello-world EFI
731
+ run-qemu.sh [run] QEMU q35 (x86_64) or virt (aarch64), KVM/HVF/TCG
732
+ scripts/arch.sh per-architecture pins, hashes and names, sourced by all of the above
528
733
  scripts/fetch.sh hash-checked download helper, sourced by fetch-*.sh
529
734
  scripts/pack-initramfs.sh cpio archive with the device nodes
530
- hello/ hello-world EFI: hello.c and build-hello.sh
531
- test/ the retired C bootstrap and its build script
532
- initramfs/ init.js and the committed libraries; rest fetched
735
+ hello/ hello-world EFI: hello.c and build-hello.sh (x86_64 only)
736
+ test/ the retired C bootstrap and its build script (x86_64 only)
737
+ test/host/ build-and-boot test on a fresh Linux amd64/arm64 host, in Docker
738
+ initramfs/ init.js, commands, userspace: the same for every architecture
739
+ native/<arch>/ musl and the GCC runtime (committed); bun and modules (fetched)
533
740
  ```
534
741
 
535
- The `--real` inputs are Alpine v3.24 `linux-lts-6.18.52-r0`, musl `1.2.6-r2`,
742
+ The `--real` inputs are Alpine v3.24 `linux-lts-6.18.53-r0`, musl `1.2.6-r2`,
536
743
  `systemd-efistub-260.2-r0`, `libstdc++`/`libgcc` `15.2.0-r5`, and Bun 1.4.2
537
- `linux-x64-musl-baseline`. There is no BusyBox and no userland beyond Bun
538
- itself.
744
+ `linux-x64-musl-baseline`. The aarch64 guest uses the same versions from
745
+ Alpine's aarch64 repository and Bun's `linux-aarch64-musl`. There is no BusyBox
746
+ or conventional native userland; Bun and the JavaScript-based Buninu userspace
747
+ provide the commands.
539
748
 
540
749
  Every step is idempotent and `-f` re-downloads nothing: `scripts/fetch.sh`
541
- treats each pinned SHA-256 as the cache key, so a file already in `downloads/`
542
- with the right hash is used as-is, and anything missing, truncated, stale or
750
+ treats each pinned SHA-256 as the cache key, so a file already in
751
+ `downloads/<arch>/` with the right hash is used as-is, and anything missing, truncated, stale or
543
752
  tampered with is fetched again and has to pass the same hash before a script
544
753
  extracts from it. Bumping a version changes both the file name and the hash, so
545
754
  it always refetches.
@@ -554,11 +763,11 @@ and copies the final `vda.img` to the invocation directory.
554
763
 
555
764
  | script | reads | writes |
556
765
  | --- | --- | --- |
557
- | `fetch-alpine.sh` | Alpine CDN | `kernel/vmlinuz-lts`, `kernel/linuxx64.efi.stub`, musl, and the selected network, storage, input, power and filesystem modules with trimmed module indexes |
558
- | `fetch-bun.sh` | GitHub, Alpine CDN | `initramfs/bin/bun`, `initramfs/lib/{libc.musl-x86_64.so.1,libstdc++.so.6,libgcc_s.so.1}` |
559
- | `scripts/pack-initramfs.sh` | `initramfs/` | `build/initramfs.cpio.gz` |
560
- | `build-uki.sh` | that archive, kernel, stub | `build/cmdline`, `build/os-release`, `vda/EFI/BOOT/BOOTX64.EFI` |
561
- | `build-image.sh` | `vda/EFI/BOOT/BOOTX64.EFI` | `vda.img` |
766
+ | `fetch-alpine.sh` | Alpine CDN | `kernel/<arch>/vmlinuz-<flavor>`, `kernel/<arch>/linuxx64.efi.stub` (`linuxaa64.efi.stub`), `native/<arch>/lib/ld-musl-<arch>.so.1`, and the selected network, storage, input, power and filesystem modules under `native/<arch>/lib/modules/` with trimmed module indexes |
767
+ | `fetch-bun.sh` | GitHub, Alpine CDN | `native/<arch>/bin/bun`, `native/<arch>/lib/{libc.musl-<arch>.so.1,libstdc++.so.6,libgcc_s.so.1}` |
768
+ | `scripts/pack-initramfs.sh` | `initramfs/`, then `native/<arch>/` over it | `build/initramfs.cpio.gz` |
769
+ | `build-uki.sh` | that archive, kernel, stub | `build/cmdline`, `build/os-release`, `vda/EFI/BOOT/BOOTX64.EFI` (`BOOTAA64.EFI`) |
770
+ | `build-image.sh` | `vda/EFI/BOOT/BOOTX64.EFI` (`BOOTAA64.EFI`) | `vda.img` |
562
771
 
563
772
  `build-uki.sh` calls `scripts/pack-initramfs.sh` itself, so that one is rarely
564
773
  run directly.
@@ -576,8 +785,11 @@ boot.
576
785
 
577
786
  #### UKI layout
578
787
 
579
- `build-uki.sh` appends sections to systemd's `linuxx64.efi.stub` with
580
- `objcopy`, at hand-picked VMAs because `objcopy` will not lay them out for you:
788
+ `build-uki.sh` appends sections to systemd's architecture-specific EFI stub
789
+ with `objcopy`, at calculated VMAs because `objcopy` will not lay them out for
790
+ you. The following sizes and offsets describe the current x86_64 `--real`
791
+ image; aarch64 uses `linuxaa64.efi.stub`, whose larger stub moves `.osrel` and
792
+ `.cmdline`, and every build measures the inputs to prevent overlap:
581
793
 
582
794
  ##### UKI sections
583
795
 
@@ -586,11 +798,12 @@ boot.
586
798
  | `.text` | +0x0 | the stub itself | 66 KB |
587
799
  | `.osrel` | +0x20000 | `build/os-release` | 78 B |
588
800
  | `.cmdline` | +0x30000 | `build/cmdline` | 136 B |
589
- | `.linux` | +0x2000000 | `kernel/vmlinuz-lts` | 14.5 MB |
801
+ | `.linux` | +0x2000000 | `kernel/x86_64/vmlinuz-lts` | 14.5 MB |
590
802
  | `.initrd` | +0x3000000 | `build/initramfs.cpio.gz` | 44.6 MB |
591
803
 
592
- The current `--real` result is a single 59.3 MB PE32+ file holding kernel, initramfs and command
593
- line.
804
+ The current x86_64 `--real` result is a single 59.3 MB PE32+ file holding the
805
+ kernel, initramfs and command line. Other architectures, flavors and source
806
+ revisions can produce different sizes.
594
807
 
595
808
  #### Disk layout
596
809
 
@@ -605,13 +818,15 @@ rejects the volume.
605
818
  LBA 0 protective MBR
606
819
  LBA 1..33 primary GPT
607
820
  LBA 2048..262110 ESP, FAT32 "EFIBOOT", type GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B
608
- /EFI/BOOT/BOOTX64.EFI the UKI
821
+ /EFI/BOOT/BOOTX64.EFI x86_64 UKI
822
+ /EFI/BOOT/BOOTAA64.EFI aarch64 UKI (in an aarch64 image)
609
823
  LBA 262111.. backup GPT
610
824
  ```
611
825
 
612
826
  128 MiB sparse, 1 MiB-aligned, with 34 sectors reserved at the end for the
613
- backup GPT. `EFI/BOOT/BOOTX64.EFI` is the removable-media fallback path, so the
614
- firmware runs it without any NVRAM boot entry.
827
+ backup GPT. `EFI/BOOT/BOOTX64.EFI` and `EFI/BOOT/BOOTAA64.EFI` are the
828
+ architecture-specific removable-media fallback paths, so matching firmware
829
+ runs the one present in the image without any NVRAM boot entry.
615
830
 
616
831
  #### Real-hardware boot details
617
832
 
@@ -621,7 +836,7 @@ The physical boot chain is firmware → the UKI stub → its embedded
621
836
  and, with the currently pinned kernel, embeds this complete command line:
622
837
 
623
838
  ```text
624
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.52-0-lts rdinit=/bin/bun -- -e import('/init.js')
839
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.53-0-lts rdinit=/bin/bun -- -e import('/init.js')
625
840
  ```
626
841
 
627
842
  Serial kernel logging is retained, while the final console makes
@@ -638,12 +853,14 @@ runner separately from the PRoot build dependencies:
638
853
  pkg install qemu-system-x86-64
639
854
  ```
640
855
 
641
- That package supplies OVMF at `$PREFIX/share/qemu/edk2-x86_64-code.fd`. Build
642
- without `--real`; this selects Alpine `linux-virt`, omits the physical-hardware
856
+ That package supplies OVMF at `$PREFIX/share/qemu/edk2-x86_64-code.fd`, next to
857
+ QEMU's own binary, which is where `run-qemu.sh` looks after `OVMF_FILE` and
858
+ Debian's `/usr/share/OVMF/OVMF_CODE_4M.fd` (Homebrew's `share/qemu` is found
859
+ the same way). Build without `--real`; this selects Alpine `linux-virt`, omits the physical-hardware
643
860
  module set, and puts `ttyS0` last so serial stdio owns `/dev/console`:
644
861
 
645
862
  ```text
646
- console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.52-0-virt rdinit=/bin/bun -- -e import('/init.js')
863
+ console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.53-0-virt rdinit=/bin/bun -- -e import('/init.js')
647
864
  ```
648
865
 
649
866
  Build and boot it with:
@@ -652,8 +869,7 @@ Build and boot it with:
652
869
  bun ./index.js -fb
653
870
 
654
871
  # When running inside PRoot with Termux's native qemu-system-x86_64,
655
- # expose both the native executable and its OVMF path:
656
- PREFIX=/data/data/com.termux/files/usr \
872
+ # put it on PATH; its OVMF is found next to it:
657
873
  PATH="/data/data/com.termux/files/usr/bin:$PATH" \
658
874
  bun ./index.js -r
659
875
  ```
@@ -661,9 +877,11 @@ bun ./index.js -r
661
877
  If QEMU is installed inside the current Debian environment instead, plain
662
878
  `bun ./index.js -r` is sufficient; `run-qemu.sh` will use Debian's OVMF path.
663
879
 
664
- `bun ./index.js -r` runs `run-qemu.sh`, which boots `vda.img` on q35 under TCG
665
- with 512 MiB, a virtio disk, virtio-net user networking, no display, and COM1
666
- on stdio. Anything after `--` is appended to the QEMU command line. Ctrl-C
880
+ `bun ./index.js -r` runs `run-qemu.sh`, which boots `vda.img` on q35 (the
881
+ aarch64 image on `virt`) with 1 GiB, a virtio disk, virtio-net user
882
+ networking, no display, and the first serial port on stdio. It uses KVM or
883
+ Hypervisor.framework when the guest matches the host CPU and TCG otherwise;
884
+ see [Build and test with QEMU on macOS or Linux](#build-and-test-with-qemu-on-macos-or-linux). Anything after `--` is appended to the QEMU command line. Ctrl-C
667
885
  quits QEMU. Leaving the REPL does not end the session: `init.js` restarts it,
668
886
  because a PID 1 that exits panics the kernel.
669
887
 
@@ -679,7 +897,7 @@ For an even shorter initramfs loop, bypass the UKI and disk image:
679
897
 
680
898
  ```sh
681
899
  qemu-system-x86_64 -machine q35,accel=tcg -cpu max -m 512M \
682
- -kernel kernel/vmlinuz-virt -initrd build/initramfs.cpio.gz \
900
+ -kernel kernel/x86_64/vmlinuz-virt -initrd build/initramfs.cpio.gz \
683
901
  -append "console=ttyS0,115200 panic=0 PATH=/bin rdinit=/bin/bun -- -e console.log(1+1)" \
684
902
  -nic user,model=virtio-net-pci -display none -serial stdio -no-reboot
685
903
  ```
@@ -730,12 +948,14 @@ Two things to expect:
730
948
 
731
949
  The `/bin` commands described earlier are Bun scripts built on two shared
732
950
  modules in `/lib`. `--help` uses `Bun.markdown.ansi` to render the Markdown
733
- manual with hyperlinks. The shared pieces are:
951
+ manual with hyperlinks, and prints the page's absolute path at the end so it
952
+ can be read or edited directly. The shared pieces are:
734
953
 
735
- * `/lib/dlopen.js` — one `bun:ffi` binding to `/lib/libc.musl-x86_64.so.1`
954
+ * `/lib/dlopen.js` — one `bun:ffi` binding to `/lib/libc.musl-<arch>.so.1`
736
955
  (`mount`, `umount2`, `ioctl`, `socket`, `sync`, `reboot`, `syscall`, …), `errno`/`strerror`,
737
956
  a `SysError` class and the `showDocument()` helper behind every `--help`.
738
- `init.js` keeps its own copy of the binding because it runs before `/proc`
957
+ `process.arch` picks the libc file name and the `finit_module` syscall
958
+ number, the only raw syscall the commands make. `init.js` keeps its own copy of the binding because it runs before `/proc`
739
959
  exists and cannot import anything.
740
960
  * `/lib/modprobe.js` — `modprobe(name)` resolves `modules.alias` and
741
961
  `modules.dep` under `/lib/modules/<release>/` and calls `finit_module`
@@ -757,7 +977,7 @@ The default `--real` image embeds this kernel command line (shown for the
757
977
  currently pinned LTS release):
758
978
 
759
979
  ```text
760
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.52-0-lts rdinit=/bin/bun -- -e import('/init.js')
980
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.53-0-lts rdinit=/bin/bun -- -e import('/init.js')
761
981
  ```
762
982
 
763
983
  The physical display (`tty0`) is the final console and therefore owns PID 1's
@@ -812,14 +1032,20 @@ was found.
812
1032
  It installs signal handlers, loads a separate physical copy of musl through
813
1033
  `bun:ffi`, mounts `/proc`, `/sys`, `/dev`, `/tmp` and `/dev/pts` (without
814
1034
  the devpts mount every pty open fails with `ENODEV`, since `/dev/ptmx`
815
- resolves through `/dev/pts/ptmx`), prints a greeting, and starts `node:repl`.
1035
+ resolves through `/dev/pts/ptmx`), turns Num Lock on, prints a greeting, and
1036
+ starts `node:repl`. The kernel starts every virtual console with Num Lock
1037
+ off, so the keypad would type arrows instead of digits; init sets it through
1038
+ `KDSKBLED` on each console that exists, as the current state and as the state
1039
+ a console is reset to. A console first activated later — `Ctrl-Alt-F3` on a
1040
+ fresh boot — starts from the kernel default again, and `numlock()` in the REPL
1041
+ (or `numlock(false)`) applies the setting to every console again.
816
1042
  Submitting an empty or whitespace-only REPL line prints the welcome message
817
1043
  and the available `cfg` getters again. The loader and libc paths
818
1044
  contain identical bytes but are deliberately distinct inodes:
819
1045
 
820
1046
  ```text
821
- /lib/ld-musl-x86_64.so.1
822
- /lib/libc.musl-x86_64.so.1
1047
+ /lib/ld-musl-x86_64.so.1 /lib/ld-musl-aarch64.so.1
1048
+ /lib/libc.musl-x86_64.so.1 /lib/libc.musl-aarch64.so.1
823
1049
  ```
824
1050
 
825
1051
  Calling `dlopen` on the same musl inode that is already acting as Bun's loader
@@ -866,9 +1092,15 @@ for the userspace session.
866
1092
  + [Or build from a source checkout](#or-build-from-a-source-checkout)
867
1093
  + [Write the image to a USB drive](#write-the-image-to-a-usb-drive)
868
1094
  + [Booting from the USB drive](#booting-from-the-usb-drive)
1095
+ * [Build and test with QEMU on macOS or Linux](#build-and-test-with-qemu-on-macos-or-linux)
1096
+ + [macOS](#macos)
1097
+ + [amd64 Linux](#amd64-linux)
1098
+ + [What differs on aarch64](#what-differs-on-aarch64)
1099
+ + [Testing on a Linux host in Docker](#testing-on-a-linux-host-in-docker)
869
1100
  - [Using Buninu Linux](#using-buninu-linux)
870
1101
  * [Basic configuration](#basic-configuration)
871
1102
  * [Editor quick start](#editor-quick-start)
1103
+ + [jsmdcui](#jsmdcui)
872
1104
  * [Panes, terminals, and tabs](#panes-terminals-and-tabs)
873
1105
  + [Pane and tab controls](#pane-and-tab-controls)
874
1106
  * [Using Bun Modern Shell](#using-bun-modern-shell)
@@ -914,15 +1146,16 @@ MIT License; see [LICENSE](LICENSE). The Buninu userspace under
914
1146
  `initramfs/buninu/` is also MIT (`initramfs/buninu/LICENSE`) and carries its
915
1147
  own third-party notices, listed at the end of [NOTICE.md](NOTICE.md).
916
1148
 
917
- The repository also commits four third-party platform binaries under
918
- `initramfs/lib/`, and the built image redistributes several more. Each stays
1149
+ The repository also commits four third-party platform binaries per guest
1150
+ architecture under `native/<arch>/lib/`, and the built image redistributes
1151
+ several more. Each stays
919
1152
  under its own terms, with the full texts in [`LICENSES/`](LICENSES/):
920
1153
 
921
1154
  ### Bundled component licenses
922
1155
 
923
1156
  | Component | License | Where |
924
1157
  |---|---|---|
925
- | musl (`ld-musl-x86_64.so.1`, `libc.musl-x86_64.so.1`) | MIT | committed |
1158
+ | musl (`ld-musl-x86_64.so.1`, `libc.musl-x86_64.so.1`, and the `aarch64` pair) | MIT | committed |
926
1159
  | GCC runtime (`libgcc_s.so.1`, `libstdc++.so.6`) | GPL-3.0-or-later with the GCC Runtime Library Exception | committed |
927
1160
  | Linux kernels and networking, storage, xHCI, USB and HID modules | GPL-2.0-only with the Linux syscall note | image only |
928
1161
  | systemd EFI stub | LGPL-2.1-or-later | image only |
@@ -941,4 +1174,5 @@ lets them ship next to MIT-licensed code, so nothing here changes Buninu
941
1174
  Linux's own terms — but if your organisation screens for GPL, these are the
942
1175
  components it will find. [NOTICE.md](NOTICE.md) records the
943
1176
  exact Alpine build of every component, the pinned aports commit that is its
944
- Corresponding Source, and SHA-256 sums for every binary.
1177
+ Corresponding Source, package SHA-256 sums and selected extracted-file
1178
+ SHA-256 sums.
package/build-image.sh CHANGED
@@ -3,9 +3,13 @@ set -euo pipefail
3
3
 
4
4
  cd "$(dirname "$0")"
5
5
 
6
+ . ./scripts/arch.sh
7
+
6
8
  mkdir -p vda/EFI/BOOT
7
- test -s vda/EFI/BOOT/BOOTX64.EFI || {
8
- echo "error: build stage: no EFI payload yet; run ./build-uki.sh (./index.js -b) or ./hello/build-hello.sh first" >&2
9
+ test -s "vda/EFI/BOOT/$efi_boot_name" || {
10
+ hint="./build-uki.sh (./index.js -b)"
11
+ [ "$arch" = x86_64 ] && hint="$hint or ./hello/build-hello.sh"
12
+ echo "error: build stage: no $arch EFI payload vda/EFI/BOOT/$efi_boot_name yet; run $hint first" >&2
9
13
  exit 1
10
14
  }
11
15
 
@@ -13,7 +17,7 @@ test -s vda/EFI/BOOT/BOOTX64.EFI || {
13
17
  # it into a GPT disk. No mount, loop device, or root privilege is required.
14
18
  for required_tool in parted mkfs.fat mmd mcopy; do
15
19
  command -v "$required_tool" >/dev/null || {
16
- echo "error: build stage needs $required_tool; run inside PRoot, see README section 0" >&2
20
+ echo "error: build stage needs $required_tool; run inside PRoot or with ./index.js --docker, see README Build environment" >&2
17
21
  exit 1
18
22
  }
19
23
  done
@@ -29,7 +33,7 @@ esp_sectors=$((esp_end - esp_start + 1))
29
33
  truncate -s $((esp_sectors * 512)) "$temporary_esp"
30
34
  mkfs.fat -F 32 -h "$esp_start" -n EFIBOOT "$temporary_esp" >/dev/null
31
35
  mmd -i "$temporary_esp" ::/EFI ::/EFI/BOOT
32
- mcopy -i "$temporary_esp" vda/EFI/BOOT/BOOTX64.EFI ::/EFI/BOOT/BOOTX64.EFI
36
+ mcopy -i "$temporary_esp" "vda/EFI/BOOT/$efi_boot_name" "::/EFI/BOOT/$efi_boot_name"
33
37
 
34
38
  rm -f vda.img
35
39
  truncate -s $((disk_sectors * 512)) vda.img
@@ -41,4 +45,4 @@ dd if="$temporary_esp" of=vda.img bs=512 \
41
45
  seek="$esp_start" count="$esp_sectors" \
42
46
  conv=notrunc,sparse status=none
43
47
 
44
- echo "Packed vda/EFI/BOOT/BOOTX64.EFI into vda.img"
48
+ echo "Packed vda/EFI/BOOT/$efi_boot_name into vda.img"