buninu-linux 0.5.1 → 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/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** 🐮
@@ -38,6 +41,9 @@
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)
40
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
41
47
 
42
48
  ---
43
49
 
@@ -46,6 +52,11 @@
46
52
 
47
53
  ## Quick start
48
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
+
49
60
  - This guide builds a bootable `buninu-linux-<version>.img`
50
61
  - The documented build environment is Debian 13 under Termux PRoot
51
62
  - A regular Debian installation works as well.
@@ -75,9 +86,11 @@ apt install binutils-mingw-w64-x86-64 cpio curl dosfstools fakeroot mtools parte
75
86
  mkdir -p /data/data/com.termux/files/home/buninu-build
76
87
  cd /data/data/com.termux/files/home/buninu-build
77
88
 
78
- $HOME/.bun/bin/bun x buninu-linux --version
89
+ export PATH=$HOME/.bun/bin:$PATH
90
+
91
+ bun x buninu-linux --version
79
92
 
80
- $HOME/.bun/bin/bun x buninu-linux -fb --real --export
93
+ bun x buninu-linux -fb --real --export
81
94
  ```
82
95
 
83
96
  `--export` copies the completed `vda.img` out of bunx's package directory and
@@ -113,6 +126,10 @@ this overwrites it completely.
113
126
  sudo dd if='buninu-linux-<version>.img' of=/dev/sdX bs=4M conv=fsync status=progress
114
127
  ```
115
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
+
116
133
  #### Booting from the USB drive
117
134
 
118
135
  Boot that drive as x86-64 UEFI media. The UKI is unsigned, so disable Secure
@@ -146,6 +163,110 @@ described under [Basic configuration](#basic-configuration). Run `poweroff` for
146
163
  shutdown. After editing the initramfs, `bun ./index.js -b --real --export` rebuilds the
147
164
  image; `-h` lists all flags.
148
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
+
149
270
  ## Using Buninu Linux
150
271
 
151
272
  ### Basic configuration
@@ -213,6 +334,13 @@ bun hlw.js
213
334
  If `Enter` is not recognized in a particular terminal, try `Ctrl-J` or
214
335
  `Ctrl-M`.
215
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
+
216
344
  ### Panes, terminals, and tabs
217
345
 
218
346
  `jmi` and jsmdcui can keep editors and terminal sessions open together. Press
@@ -352,9 +480,11 @@ only regular files. Creation still follows the `Bun.Archive` boundary: it
352
480
  stores regular files but not Unix metadata, links, or empty directories. It
353
481
  supports gzip and Bun's built-in zstd, not xz or bzip2.
354
482
 
355
- `--help` renders each Markdown manual in the terminal. The `bun x` examples
356
- below require network access: `bun x` downloads the requested program from the
357
- npm registry and runs it without installing it permanently. 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:
358
488
 
359
489
  ```sh
360
490
  mount /dev/sda1 /mnt --mkdir
@@ -374,6 +504,37 @@ bun x bunproot --git --readme | stripansi | jmi
374
504
 
375
505
 
376
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
+
377
538
  # Download and enter an x64 Alpine minirootfs
378
539
  bun x bunproot --download-alpine-x64
379
540
  mkdir alpine
@@ -536,10 +697,13 @@ it too). Its physical-image stages run as fetch → build:
536
697
  | --- | --- | --- |
537
698
  | `-f`, `--fetch` | `fetch-alpine.sh`, `fetch-bun.sh` | first clone, or after bumping a pinned version |
538
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 |
539
702
 
540
703
  `--export` is a post-build option: after `-b` finishes, it copies `vda.img` to
541
704
  the directory where the command was invoked as
542
- `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
543
707
  recommended way to retain an image produced through npx/bunx:
544
708
 
545
709
  ```sh
@@ -558,26 +722,33 @@ failing halfway. The shell scripts do the actual work and each still runs on
558
722
  its own.
559
723
 
560
724
  ```text
561
- 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)
562
727
  fetch-alpine.sh [fetch] kernel, EFI stub, musl, network/input modules
563
728
  fetch-bun.sh [fetch] Bun, libstdc++, libgcc
564
729
  build-uki.sh [build] UKI; calls scripts/pack-initramfs.sh
565
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
566
733
  scripts/fetch.sh hash-checked download helper, sourced by fetch-*.sh
567
734
  scripts/pack-initramfs.sh cpio archive with the device nodes
568
- hello/ hello-world EFI: hello.c and build-hello.sh
569
- test/ the retired C bootstrap and its build script
570
- 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)
571
740
  ```
572
741
 
573
- 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`,
574
743
  `systemd-efistub-260.2-r0`, `libstdc++`/`libgcc` `15.2.0-r5`, and Bun 1.4.2
575
- `linux-x64-musl-baseline`. There is no BusyBox and no userland beyond Bun
576
- 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.
577
748
 
578
749
  Every step is idempotent and `-f` re-downloads nothing: `scripts/fetch.sh`
579
- treats each pinned SHA-256 as the cache key, so a file already in `downloads/`
580
- 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
581
752
  tampered with is fetched again and has to pass the same hash before a script
582
753
  extracts from it. Bumping a version changes both the file name and the hash, so
583
754
  it always refetches.
@@ -592,11 +763,11 @@ and copies the final `vda.img` to the invocation directory.
592
763
 
593
764
  | script | reads | writes |
594
765
  | --- | --- | --- |
595
- | `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 |
596
- | `fetch-bun.sh` | GitHub, Alpine CDN | `initramfs/bin/bun`, `initramfs/lib/{libc.musl-x86_64.so.1,libstdc++.so.6,libgcc_s.so.1}` |
597
- | `scripts/pack-initramfs.sh` | `initramfs/` | `build/initramfs.cpio.gz` |
598
- | `build-uki.sh` | that archive, kernel, stub | `build/cmdline`, `build/os-release`, `vda/EFI/BOOT/BOOTX64.EFI` |
599
- | `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` |
600
771
 
601
772
  `build-uki.sh` calls `scripts/pack-initramfs.sh` itself, so that one is rarely
602
773
  run directly.
@@ -614,8 +785,11 @@ boot.
614
785
 
615
786
  #### UKI layout
616
787
 
617
- `build-uki.sh` appends sections to systemd's `linuxx64.efi.stub` with
618
- `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:
619
793
 
620
794
  ##### UKI sections
621
795
 
@@ -624,11 +798,12 @@ boot.
624
798
  | `.text` | +0x0 | the stub itself | 66 KB |
625
799
  | `.osrel` | +0x20000 | `build/os-release` | 78 B |
626
800
  | `.cmdline` | +0x30000 | `build/cmdline` | 136 B |
627
- | `.linux` | +0x2000000 | `kernel/vmlinuz-lts` | 14.5 MB |
801
+ | `.linux` | +0x2000000 | `kernel/x86_64/vmlinuz-lts` | 14.5 MB |
628
802
  | `.initrd` | +0x3000000 | `build/initramfs.cpio.gz` | 44.6 MB |
629
803
 
630
- The current `--real` result is a single 59.3 MB PE32+ file holding kernel, initramfs and command
631
- 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.
632
807
 
633
808
  #### Disk layout
634
809
 
@@ -643,13 +818,15 @@ rejects the volume.
643
818
  LBA 0 protective MBR
644
819
  LBA 1..33 primary GPT
645
820
  LBA 2048..262110 ESP, FAT32 "EFIBOOT", type GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B
646
- /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)
647
823
  LBA 262111.. backup GPT
648
824
  ```
649
825
 
650
826
  128 MiB sparse, 1 MiB-aligned, with 34 sectors reserved at the end for the
651
- backup GPT. `EFI/BOOT/BOOTX64.EFI` is the removable-media fallback path, so the
652
- 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.
653
830
 
654
831
  #### Real-hardware boot details
655
832
 
@@ -659,7 +836,7 @@ The physical boot chain is firmware → the UKI stub → its embedded
659
836
  and, with the currently pinned kernel, embeds this complete command line:
660
837
 
661
838
  ```text
662
- 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')
663
840
  ```
664
841
 
665
842
  Serial kernel logging is retained, while the final console makes
@@ -676,12 +853,14 @@ runner separately from the PRoot build dependencies:
676
853
  pkg install qemu-system-x86-64
677
854
  ```
678
855
 
679
- That package supplies OVMF at `$PREFIX/share/qemu/edk2-x86_64-code.fd`. Build
680
- 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
681
860
  module set, and puts `ttyS0` last so serial stdio owns `/dev/console`:
682
861
 
683
862
  ```text
684
- 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')
685
864
  ```
686
865
 
687
866
  Build and boot it with:
@@ -690,8 +869,7 @@ Build and boot it with:
690
869
  bun ./index.js -fb
691
870
 
692
871
  # When running inside PRoot with Termux's native qemu-system-x86_64,
693
- # expose both the native executable and its OVMF path:
694
- PREFIX=/data/data/com.termux/files/usr \
872
+ # put it on PATH; its OVMF is found next to it:
695
873
  PATH="/data/data/com.termux/files/usr/bin:$PATH" \
696
874
  bun ./index.js -r
697
875
  ```
@@ -699,9 +877,11 @@ bun ./index.js -r
699
877
  If QEMU is installed inside the current Debian environment instead, plain
700
878
  `bun ./index.js -r` is sufficient; `run-qemu.sh` will use Debian's OVMF path.
701
879
 
702
- `bun ./index.js -r` runs `run-qemu.sh`, which boots `vda.img` on q35 under TCG
703
- with 512 MiB, a virtio disk, virtio-net user networking, no display, and COM1
704
- 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
705
885
  quits QEMU. Leaving the REPL does not end the session: `init.js` restarts it,
706
886
  because a PID 1 that exits panics the kernel.
707
887
 
@@ -717,7 +897,7 @@ For an even shorter initramfs loop, bypass the UKI and disk image:
717
897
 
718
898
  ```sh
719
899
  qemu-system-x86_64 -machine q35,accel=tcg -cpu max -m 512M \
720
- -kernel kernel/vmlinuz-virt -initrd build/initramfs.cpio.gz \
900
+ -kernel kernel/x86_64/vmlinuz-virt -initrd build/initramfs.cpio.gz \
721
901
  -append "console=ttyS0,115200 panic=0 PATH=/bin rdinit=/bin/bun -- -e console.log(1+1)" \
722
902
  -nic user,model=virtio-net-pci -display none -serial stdio -no-reboot
723
903
  ```
@@ -771,10 +951,11 @@ modules in `/lib`. `--help` uses `Bun.markdown.ansi` to render the Markdown
771
951
  manual with hyperlinks, and prints the page's absolute path at the end so it
772
952
  can be read or edited directly. The shared pieces are:
773
953
 
774
- * `/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`
775
955
  (`mount`, `umount2`, `ioctl`, `socket`, `sync`, `reboot`, `syscall`, …), `errno`/`strerror`,
776
956
  a `SysError` class and the `showDocument()` helper behind every `--help`.
777
- `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`
778
959
  exists and cannot import anything.
779
960
  * `/lib/modprobe.js` — `modprobe(name)` resolves `modules.alias` and
780
961
  `modules.dep` under `/lib/modules/<release>/` and calls `finit_module`
@@ -796,7 +977,7 @@ The default `--real` image embeds this kernel command line (shown for the
796
977
  currently pinned LTS release):
797
978
 
798
979
  ```text
799
- 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')
800
981
  ```
801
982
 
802
983
  The physical display (`tty0`) is the final console and therefore owns PID 1's
@@ -863,8 +1044,8 @@ and the available `cfg` getters again. The loader and libc paths
863
1044
  contain identical bytes but are deliberately distinct inodes:
864
1045
 
865
1046
  ```text
866
- /lib/ld-musl-x86_64.so.1
867
- /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
868
1049
  ```
869
1050
 
870
1051
  Calling `dlopen` on the same musl inode that is already acting as Bun's loader
@@ -911,9 +1092,15 @@ for the userspace session.
911
1092
  + [Or build from a source checkout](#or-build-from-a-source-checkout)
912
1093
  + [Write the image to a USB drive](#write-the-image-to-a-usb-drive)
913
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)
914
1100
  - [Using Buninu Linux](#using-buninu-linux)
915
1101
  * [Basic configuration](#basic-configuration)
916
1102
  * [Editor quick start](#editor-quick-start)
1103
+ + [jsmdcui](#jsmdcui)
917
1104
  * [Panes, terminals, and tabs](#panes-terminals-and-tabs)
918
1105
  + [Pane and tab controls](#pane-and-tab-controls)
919
1106
  * [Using Bun Modern Shell](#using-bun-modern-shell)
@@ -959,15 +1146,16 @@ MIT License; see [LICENSE](LICENSE). The Buninu userspace under
959
1146
  `initramfs/buninu/` is also MIT (`initramfs/buninu/LICENSE`) and carries its
960
1147
  own third-party notices, listed at the end of [NOTICE.md](NOTICE.md).
961
1148
 
962
- The repository also commits four third-party platform binaries under
963
- `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
964
1152
  under its own terms, with the full texts in [`LICENSES/`](LICENSES/):
965
1153
 
966
1154
  ### Bundled component licenses
967
1155
 
968
1156
  | Component | License | Where |
969
1157
  |---|---|---|
970
- | 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 |
971
1159
  | GCC runtime (`libgcc_s.so.1`, `libstdc++.so.6`) | GPL-3.0-or-later with the GCC Runtime Library Exception | committed |
972
1160
  | Linux kernels and networking, storage, xHCI, USB and HID modules | GPL-2.0-only with the Linux syscall note | image only |
973
1161
  | systemd EFI stub | LGPL-2.1-or-later | image only |
@@ -986,4 +1174,5 @@ lets them ship next to MIT-licensed code, so nothing here changes Buninu
986
1174
  Linux's own terms — but if your organisation screens for GPL, these are the
987
1175
  components it will find. [NOTICE.md](NOTICE.md) records the
988
1176
  exact Alpine build of every component, the pinned aports commit that is its
989
- 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"
package/build-uki.sh CHANGED
@@ -3,14 +3,12 @@ set -euo pipefail
3
3
 
4
4
  cd "$(dirname "$0")"
5
5
 
6
- linux_flavor=${LINUX_FLAVOR:-virt}
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 "initramfs/lib/modules/$kernel_release/modules.dep" ] || \
8
+ if [ ! -s "$kernel_image" ] || [ ! -s "$kernel_dir/$efi_stub" ] || [ ! -s "$modules_root/modules.dep" ] || \
11
9
  { [ "${REAL_MACHINE:-}" = 1 ] && \
12
- { [ ! -s "initramfs/lib/modules/$kernel_release/kernel/drivers/hid/usbhid/usbhid.ko" ] || \
13
- [ ! -s "initramfs/lib/modules/$kernel_release/kernel/drivers/acpi/battery.ko" ]; }; }; then
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 tty0, is the console the kernel opens as the
35
- # init process's fd 0/1/2. `-display none` leaves tty0 with nowhere to go.
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=ttyS0,115200 console=tty0 REAL_MACHINE=1"
36
+ consoles="console=$serial_console,115200 console=tty0 REAL_MACHINE=1"
38
37
  else
39
- consoles="console=tty0 console=ttyS0,115200"
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 x86_64-w64-mingw32-objcopy >/dev/null; then
44
- objcopy_command=x86_64-w64-mingw32-objcopy
45
- objdump_command=x86_64-w64-mingw32-objdump
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
- image_base_hex="$($objdump_command -p kernel/linuxx64.efi.stub \
53
- | awk '$1 == "ImageBase" { print $2; exit }')"
54
- image_base=$((16#$image_base_hex))
55
- osrel_vma=$((image_base + 0x20000))
56
- cmdline_vma=$((image_base + 0x30000))
57
- linux_vma=$((image_base + 0x2000000))
58
- initrd_vma=$((image_base + 0x3000000))
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
- # These are the conventional non-overlapping VMAs used for x86-64 UKIs.
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
- kernel/linuxx64.efi.stub vda/EFI/BOOT/BOOTX64.EFI
91
+ "$stub" "vda/EFI/BOOT/$efi_boot_name"
76
92
 
77
- echo "Built UKI with Alpine linux-$linux_flavor at vda/EFI/BOOT/BOOTX64.EFI"
93
+ echo "Built $arch UKI with Alpine linux-$linux_flavor at vda/EFI/BOOT/$efi_boot_name"