buninu-linux 0.7.0 → 0.7.2

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
@@ -36,6 +36,7 @@
36
36
  * `start` starts the Buninu userspace shell
37
37
  * `cfg.disk` + shell `mount` mounts local disks
38
38
  * `cfg.net` loads common wired NIC and Android USB-tethering drivers
39
+ * `cfg.sound` loads every packaged ALSA sound module
39
40
  * Android phone USB tethering over RNDIS has been tested successfully, allowing Buninu Linux to access the Internet through an Android phone
40
41
  * IP addresses and routes are configured manually because the image does not yet include a DHCP client.
41
42
  * The bundled `jmi` editor, jsmdcui App runtime, and local JavaScript execution also work.
@@ -45,6 +46,7 @@
45
46
  + Also has mouse click, wheel, and cursor drawing
46
47
  + Simplified browser works: @sanohiro/casty in chroot debian
47
48
  + (Use at your own risk: We haven't fully examined its code)
49
+ + Playing YouTube with sound (experimental); see [Playing YouTube with sound](#playing-youtube-with-sound-experimental)
48
50
  + See [Commands inside `/bin`](#commands-inside-bin) for browser usage instructions
49
51
 
50
52
  ---
@@ -142,7 +144,7 @@ display and USB keyboard with a welcome ending like this:
142
144
  Welcome to Buninu Linux!
143
145
  Bun 1.4.2 is now PID 1
144
146
  Type start() to run buninu --local
145
- Configuration getters: cfg.all, cfg.disk, cfg.net, cfg.power
147
+ Configuration getters: cfg.all, cfg.disk, cfg.net, cfg.power, cfg.sound
146
148
 
147
149
  bun-repl>
148
150
  ```
@@ -157,6 +159,7 @@ Load only the subsystem needed, then enter the Buninu shell:
157
159
  cfg.net // wired NIC and Android USB-tethering drivers
158
160
  cfg.disk // SATA/NVMe/USB storage drivers and detected block devices
159
161
  cfg.power // battery, AC, button and thermal drivers
162
+ cfg.sound // all packaged ALSA sound modules and detected sound devices
160
163
  start()
161
164
  ```
162
165
 
@@ -208,6 +211,8 @@ bun ./index.js -fbr
208
211
  The first boot line from `run-qemu.sh` names the guest, machine, accelerator
209
212
  and firmware it picked. A bare `-r` boots whichever architecture `vda.img`
210
213
  was last built for; `-br --arch arm64` is the aarch64 edit-and-boot loop.
214
+ To run `bunterm` in a QEMU window on the aarch64 guest, see
215
+ [What differs on aarch64](#what-differs-on-aarch64).
211
216
 
212
217
  #### amd64 Linux
213
218
 
@@ -231,9 +236,32 @@ qemu-efi-aarch64` to run.
231
236
  Alpine's aarch64 packages and `bun-linux-aarch64-musl`.
232
237
  * The serial console is `ttyAMA0` (QEMU `virt`'s PL011) instead of `ttyS0`.
233
238
  * `--real` is x86_64 only: its module set is PC hardware.
234
- * The current aarch64 QEMU configuration supplies no framebuffer or PS/2
235
- devices, so `bunterm` has nothing to draw on yet; the serial REPL and the
236
- Buninu shell work as on x86_64.
239
+ * QEMU's `virt` machine has no display or PS/2 devices, so by default
240
+ `bunterm` has nothing to draw on; the serial REPL and the Buninu shell work
241
+ as on x86_64. To run `bunterm`, build with `--linux-lts` and give QEMU a
242
+ `ramfb` display and virtio input devices. On a Mac, `-display cocoa` opens
243
+ the window:
244
+
245
+ ```sh
246
+ bun ./index.js -fbr --arch arm64 --linux-lts -- \
247
+ -device ramfb -device virtio-keyboard-pci -device virtio-mouse-pci -display cocoa
248
+ ```
249
+
250
+ Once that image is built, boot it again without rebuilding (and without
251
+ Docker). `--linux-lts` belongs to the build, so leave it off `-r`:
252
+
253
+ ```sh
254
+ bun ./index.js -r --arch arm64 -- \
255
+ -device ramfb -device virtio-keyboard-pci -device virtio-mouse-pci -display cocoa
256
+ ```
257
+
258
+ `start()` in the serial REPL, then `bunterm 2` opens bunterm on VT 2 in the
259
+ QEMU window and returns to the serial shell; type into the window. The
260
+ serial console is not a VT, so a bare `bunterm` there takes the active VT 1
261
+ and holds the serial shell until it exits; naming another VT starts it
262
+ detached (see [Multiple virtual consoles with bunterm](#multiple-virtual-consoles-with-bunterm)). The
263
+ firmware sets up ramfb at 800×600, and `init.js` loads `simpledrm` so it
264
+ becomes `/dev/fb0` (see [graphics.md](graphics.md#trying-it)).
237
265
  * `--export` writes `buninu-linux-<version>-aarch64.img`.
238
266
  * Before the kernel starts, Homebrew's aarch64 EDK2 prints a few
239
267
  `Error: Image at … start failed` lines (its own drivers for hardware `virt`
@@ -283,6 +311,7 @@ Load only the hardware subsystem you need:
283
311
  cfg.net
284
312
  cfg.disk
285
313
  cfg.power
314
+ cfg.sound
286
315
  ```
287
316
 
288
317
  `cfg.net` loads the packaged wired-network and Android USB-tethering drivers and prints detected
@@ -298,7 +327,10 @@ ip route add default via 192.168.1.1
298
327
  The real image initializes `/etc/resolv.conf` with `1.1.1.1` and `8.8.8.8`;
299
328
  replace them if your network requires different DNS servers. `cfg.disk` loads
300
329
  common SATA/PATA/SCSI, NVMe/VMD and USB-storage drivers so detected disks and
301
- partitions appear in `/dev`. To inspect a filesystem without mounting it:
330
+ partitions appear in `/dev`. `cfg.sound` unconditionally loads every sound
331
+ module packaged in the image, then lists the resulting `/dev/snd` devices; it
332
+ does not probe the hardware before loading modules. To inspect a filesystem
333
+ without mounting it:
302
334
 
303
335
  ```sh
304
336
  mount -fv /dev/sda1 /mnt
@@ -341,7 +373,7 @@ If `Enter` is not recognized in a particular terminal, try `Ctrl-J` or
341
373
  - Be careful not to run markdown files from strangers
342
374
  * Otherwise it can run any command on your PC
343
375
  - `jsmdcui --cdp-maze` runs a self-solving maze game
344
- * Run `bunterm` first to show its Emojis
376
+ * Run `bunterm 2` first to show its Emojis
345
377
 
346
378
  ### Multitasking modes
347
379
 
@@ -448,11 +480,13 @@ syntax, interactive controls, builtins, and current compatibility details.
448
480
  ### Showing images
449
481
 
450
482
  The Linux text console cannot draw pictures, so first start `bunterm`, the
451
- graphical terminal on the framebuffer, and run the image commands inside it:
483
+ graphical terminal on the framebuffer, and run the image commands inside it.
484
+ Start it on another virtual console so tty1 keeps the `bun-repl>` prompt for
485
+ hardware setup (`Ctrl+Alt+F1` goes back to it):
452
486
 
453
487
  ```sh
454
- # Start the graphical terminal on this virtual console (or name one: bunterm /dev/tty1)
455
- bunterm
488
+ # Start the graphical terminal on VT 2 (same as bunterm /dev/tty2)
489
+ bunterm 2
456
490
 
457
491
  # Show an image with the kitty graphics protocol (jsgotty --viu)
458
492
  showimg /buninu/icon.png
@@ -465,7 +499,8 @@ buninu-help
465
499
  jsmdcui --allow-url README.md
466
500
 
467
501
  # The mouse works in bunterm; --no-mouse leaves /dev/input alone
468
- bunterm -e jsmdcui --allow-url README.md
502
+ # (VT 3, since VT 2 already has the bunterm started above)
503
+ bunterm 3 -e jsmdcui --allow-url README.md
469
504
  ```
470
505
 
471
506
  `bunterm` draws with Skia (CanvasKit) and understands the kitty graphics
@@ -475,7 +510,93 @@ below a shown image, and images scroll with the text. When the program you
475
510
  started exits, the console returns to text mode. `bunterm --help` has the
476
511
  options; [graphics.md](graphics.md) describes how it is built. The image
477
512
  needs a kernel with a framebuffer: build with `--linux-lts` or `--real`
478
- (the default `linux-virt` builds one only as modules the image omits).
513
+ (the default `linux-virt` builds one only as modules the image omits). The
514
+ aarch64 guest also needs a QEMU display; see
515
+ [What differs on aarch64](#what-differs-on-aarch64).
516
+
517
+ ### Clipboard
518
+
519
+ `bunterm` supports copy and paste through OSC 52, so programs such as
520
+ `casty` and `jsmdcui` can use the clipboard. bunterm keeps nothing itself; it
521
+ passes every copy and paste to Buninu's `xclip -selection clipboard`. For
522
+ now that clipboard is a file, `$HOME/.xclip.clipboard`, read and written in
523
+ the ramdisk, so all bunterm sessions share it and it is gone after a reboot.
524
+ `bunterm --no-clipboard` turns OSC 52 off.
525
+
526
+ For Chinese input, run the pinyin input method
527
+ [jspinyin](https://www.npmjs.com/package/@drxiaozhi/jspinyin) in a bunterm
528
+ that is not inside a chroot, so it uses Buninu's `xclip`:
529
+
530
+ ```sh
531
+ bun x @drxiaozhi/jspinyin
532
+ ```
533
+
534
+ - jspinyin → casty: press `Ctrl-C` in jspinyin to copy its result box, then
535
+ `Ctrl-V` in casty pastes it through OSC 52.
536
+ - casty → jsmdcui / jspinyin: `Alt-C` in casty copies the selection through
537
+ OSC 52, and `Ctrl-V` pastes it in jsmdcui or jspinyin, since jsmdcui reads
538
+ from `xclip` first by default.
539
+
540
+ ### Playing YouTube with sound (experimental)
541
+
542
+ [jspulse](https://www.npmjs.com/package/@drxiaozhi/jspulse) is a
543
+ PulseAudio-compatible sound server written for Bun. It runs natively in
544
+ Buninu, outside any chroot, and plays through ALSA. Chromium-based programs
545
+ such as `casty` inside the Debian chroot connect to it over `127.0.0.1`,
546
+ because a chroot shares Buninu's network. Several programs can play at once.
547
+
548
+ This assumes the Debian chroot and `casty` from
549
+ [Commands inside /bin](#commands-inside-bin) are already set up, with the
550
+ chroot shell on tty2. `bun x` needs network access; see
551
+ [Basic configuration](#basic-configuration).
552
+
553
+ 1. **Load the sound drivers.** Press `Ctrl+Alt+F1` to go to the `bun-repl>`
554
+ prompt on tty1 and run:
555
+
556
+ ```js
557
+ cfg.sound
558
+ ```
559
+
560
+ 2. **Open a native Buninu terminal.** Still on tty1, enter the Buninu shell
561
+ with `start()`, then open another virtual console from that shell (see
562
+ [Multitasking modes](#multitasking-modes)). Run the next two steps there,
563
+ *not* inside the chroot:
564
+
565
+ ```js
566
+ start()
567
+ ```
568
+
569
+ ```sh
570
+ bunterm 3
571
+ ```
572
+
573
+ 3. **Set the volume, then test the speakers.** Setting a volume also unmutes
574
+ the output, so the 3-second test tone is guaranteed to be audible. Adjust
575
+ the percentage afterwards if it is too quiet or too loud:
576
+
577
+ ```sh
578
+ bun x @drxiaozhi/jspulse --volume 50
579
+ bun x @drxiaozhi/jspulse --play
580
+ ```
581
+
582
+ 4. **Start the sound server.** It keeps running in the foreground, so leave
583
+ this terminal as it is; `Ctrl+C` stops it:
584
+
585
+ ```sh
586
+ bun x @drxiaozhi/jspulse --alsa
587
+ ```
588
+
589
+ 5. **Start casty with sound.** Press `Ctrl+Alt+F2` to return to the chroot
590
+ shell, point PulseAudio clients at jspulse, and start casty:
591
+
592
+ ```sh
593
+ export PULSE_SERVER=127.0.0.1
594
+ cd ~/casty/bin
595
+ bun casty.js https://www.youtube.com
596
+ ```
597
+
598
+ If there is no sound, check step 4's terminal for errors, and make sure
599
+ `PULSE_SERVER` was exported in the same shell that started casty.
479
600
 
480
601
  ## Commands inside /bin
481
602
 
@@ -524,14 +645,14 @@ umount -R /mnt
524
645
  bun x bunproot --git clone https://github.com/jjtseng93/bunproot
525
646
 
526
647
  # Read bunproot's Git manual without ANSI formatting in jmi
527
- bunterm
648
+ bunterm 2
528
649
  bun x bunproot --git --readme | stripansi | jmi
529
650
 
530
651
 
531
652
 
532
653
  # Download and enter an x64 Debian rootfs
533
654
  # Make sure you have run this already:
534
- # bunterm --font-size 13
655
+ # bunterm 2 --font-size 13
535
656
  bun x bunproot --git --yes clone https://github.com/jjtseng93/js-udocker
536
657
  cd js-udocker
537
658
  bun udocker.js pull --platform=linux/amd64 debian:13
@@ -561,7 +682,8 @@ git clone https://github.com/jjtseng93/casty
561
682
  cd casty/bin
562
683
  # When clicking around, don’t release the mouse button immediately after pressing it, to make sure the mouse-down event is triggered
563
684
  bun casty.js buninu.org
564
-
685
+ # For sound in casty (YouTube), start jspulse first:
686
+ # see "Playing YouTube with sound (experimental)" under "Using Buninu Linux"
565
687
 
566
688
 
567
689
  # Download and enter an x64 Alpine minirootfs
@@ -608,9 +730,9 @@ tar xvf /tmp/buninu.tar -C /tmp/buninu-copy
608
730
  # Add z to create a gzip-compressed archive
609
731
  tar czvf /tmp/buninu.tar.gz README.md package.json
610
732
 
611
- # On a virtual console: a graphical terminal with CJK, emoji and images
612
- bunterm
613
- bunterm /dev/tty1 --font-size 13 -e bun /buninu/apps/jsmdcui/src/index.js --demo
733
+ # On another virtual console: a graphical terminal with CJK, emoji and images
734
+ bunterm 2
735
+ bunterm /dev/tty3 --font-size 13 -e bun /buninu/apps/jsmdcui/src/index.js --demo
614
736
  ```
615
737
 
616
738
  The graphics stack behind `bunterm` — the framebuffer module, CanvasKit,
@@ -753,7 +875,7 @@ its own.
753
875
  ```text
754
876
  index.js entry point: -f / -b / -r / --arch / --docker / --export / --linux-lts / --real
755
877
  Dockerfile the Debian 13 build toolchain for --docker (and macOS)
756
- fetch-alpine.sh [fetch] kernel, EFI stub, musl, network/input modules
878
+ fetch-alpine.sh [fetch] kernel, EFI stub, musl, network/input/display modules
757
879
  fetch-bun.sh [fetch] Bun, libstdc++, libgcc
758
880
  build-uki.sh [build] UKI; calls scripts/pack-initramfs.sh
759
881
  build-image.sh [build] GPT disk image; shared with the hello-world EFI
@@ -1089,7 +1211,7 @@ until `cfg.net` loads the packaged drivers and the user assigns an address and
1089
1211
  route.
1090
1212
 
1091
1213
  On a `--real` image the REPL also offers the getter-based `cfg` namespace:
1092
- `cfg.net`, `cfg.disk`, `cfg.power` and `cfg.all` run as soon as the property is read, without
1214
+ `cfg.net`, `cfg.disk`, `cfg.power`, `cfg.sound` and `cfg.all` run as soon as the property is read, without
1093
1215
  parentheses. `cfg.net` loads every packaged network module (including PHY and bus support),
1094
1216
  then reads the `modalias` of every PCI and USB network device, matches it
1095
1217
  against the `pci:`/`usb:` lines
@@ -1101,7 +1223,10 @@ declared dependencies. `--real` ships Intel `e1000`/`e1000e`/`igb`/`igc`,
1101
1223
  Realtek `r8169`, Atheros `alx`, Broadcom `tg3`, common USB dongles, and
1102
1224
  `rndis_host`/`cdc_ether`/`cdc_ncm`/`cdc_eem`/`cdc_subset`/`zaurus` for Android
1103
1225
  USB tethering. `cfg.net` unconditionally attempts all of these packaged
1104
- network modules. `cfg.power` loads the ACPI
1226
+ network modules. `cfg.sound` likewise unconditionally attempts every packaged
1227
+ module below `kernel/sound/`, then lists `/dev/snd`; it does no hardware probe.
1228
+ The packaged sound set covers QEMU ES1371/HDA and common PC HDA/HDMI and USB
1229
+ audio where the selected kernel flavor provides them. `cfg.power` loads the ACPI
1105
1230
  `battery`, `ac`, `button` and `thermal` modules through `/lib/modprobe.js`
1106
1231
  and lists `/sys/class/power_supply` (a desktop without a battery simply
1107
1232
  reports nothing registered). Nothing loads them at boot. PID 1 also installs
@@ -1136,6 +1261,8 @@ for the userspace session.
1136
1261
  - [Pane and tab controls](#pane-and-tab-controls)
1137
1262
  * [Using Bun Modern Shell](#using-bun-modern-shell)
1138
1263
  * [Showing images](#showing-images)
1264
+ * [Clipboard](#clipboard)
1265
+ * [Playing YouTube with sound (experimental)](#playing-youtube-with-sound-experimental)
1139
1266
  - [Commands inside /bin](#commands-inside-bin)
1140
1267
  - [Environment and dependencies](#environment-and-dependencies)
1141
1268
  * [Build environment](#build-environment)
package/build-uki.sh CHANGED
@@ -14,11 +14,7 @@ fi
14
14
  ./scripts/pack-initramfs.sh
15
15
 
16
16
  mkdir -p build vda/EFI/BOOT
17
- printf '%s\n' \
18
- 'NAME="Buninu Linux"' \
19
- 'ID=buninu-linux' \
20
- 'PRETTY_NAME="Buninu Linux 1"' \
21
- 'VERSION_ID=1' > build/os-release
17
+ # build/os-release is written by pack-initramfs.sh from package.json.
22
18
  # Bun itself is PID 1: no native bootstrap. `-e import('/init.js')` avoids the
23
19
  # entry-file open that would readlink /proc/self/fd/N before /proc exists;
24
20
  # init.js mounts /proc, /sys, /dev and /tmp as its first action.
package/fetch-alpine.sh CHANGED
@@ -81,6 +81,22 @@ if [ "$arch" = x86_64 ]; then
81
81
  else
82
82
  select_module virtio_input
83
83
  fi
84
+ # The framebuffer for bunterm. Both lts kernels set CONFIG_SYSFB_SIMPLEFB, so
85
+ # the UEFI GOP (QEMU's ramfb on aarch64 virt) becomes a simple-framebuffer
86
+ # device that only simpledrm drives: x86_64 lts builds it in, aarch64 lts has
87
+ # it as a module. init.js loads it at boot, which is what makes /dev/fb0.
88
+ if [ "$linux_flavor" = lts ]; then
89
+ select_optional_module simpledrm
90
+ fi
91
+
92
+ # Sound: a compact set covering QEMU's emulated AC97/HDA devices and the
93
+ # usual PC HDA/HDMI and USB-audio paths. Dependencies pull in the ALSA core,
94
+ # PCM, timer, codec and USB support needed by each available leaf driver.
95
+ # Some kernel flavors build fewer of these, so keep every leaf optional.
96
+ for module in snd-ens1371 snd-hda-intel snd-hda-codec-generic \
97
+ snd-hda-codec-hdmi snd-usb-audio; do
98
+ select_optional_module "$module"
99
+ done
84
100
 
85
101
  if [ "${REAL_MACHINE:-}" = 1 ]; then
86
102
  # USB keyboard for the console; common storage paths (SATA/AHCI HDD and
package/graphics.md CHANGED
@@ -174,13 +174,22 @@ needs about 1 GB.
174
174
 
175
175
  ## Trying it
176
176
 
177
- The framebuffer needs a kernel that provides one. Alpine's `linux-lts` has
178
- `CONFIG_FB`, `CONFIG_FB_EFI` and `CONFIG_DRM_SIMPLEDRM` built in; the default
179
- `linux-virt` builds them as modules, and the image ships only the network,
180
- storage, USB and HID modules, so there `/dev/fb0` never appears. Build with
181
- `./index.js -b --linux-lts` (or `--real`); under QEMU the lts kernel gets a
182
- 1280×800 `simpledrm` framebuffer from the UEFI GOP even with `-display none`,
183
- and the monitor's `screendump` shows it. From a virtual console in the guest:
177
+ The framebuffer needs a kernel that provides one. Both `linux-lts` kernels
178
+ set `CONFIG_SYSFB_SIMPLEFB`, so the UEFI GOP becomes a `simple-framebuffer`
179
+ device, which only `simpledrm` drives (`efifb` never sees it). The x86_64
180
+ `linux-lts` builds `simpledrm` in. The aarch64 one has it as a module:
181
+ `fetch-alpine.sh` ships it and its DRM dependencies for lts builds, and
182
+ `init.js` loads it at boot. The default `linux-virt` builds the framebuffer
183
+ drivers as modules, and the image ships only the network, storage, USB and
184
+ HID modules, so there `/dev/fb0` never appears. Build with
185
+ `./index.js -b --linux-lts` (or `--real` on x86_64). Under QEMU, the x86_64
186
+ lts kernel gets a 1280×800 `simpledrm` framebuffer from the UEFI GOP even
187
+ with `-display none`, and the monitor's `screendump` shows it. QEMU's aarch64
188
+ `virt` machine has no display until you add `-device ramfb`; EDK2 then
189
+ provides an 800×600 GOP, and `virtio-keyboard-pci` gives the VTs a keyboard
190
+ (see the README's
191
+ [What differs on aarch64](README.md#what-differs-on-aarch64)). From a virtual
192
+ console in the guest:
184
193
 
185
194
  ```sh
186
195
  bunterm # /bin/sh on this console
@@ -206,6 +215,13 @@ host, `jsmdcui --tui` on a Markdown file with a JPEG (bunterm sets
206
215
  `JSMDCUI_KITTY_MODE=extended` for the program it runs) places the image
207
216
  through its transmit / `a=p` / `a=d` sequence exactly as in the browser.
208
217
 
218
+ Verified on aarch64 (lts kernel, Hypervisor.framework, `-device ramfb
219
+ -device virtio-keyboard-pci -device virtio-mouse-pci`, `-display none`):
220
+ `/proc/fb` lists `simpledrmdrmfb`; `bunterm 2` from the serial shell returns
221
+ at once and draws on VT 2; and keys sent with the monitor's `sendkey` reach
222
+ it: `showimg /buninu/icon.png` places the kitty image, and the prompt's
223
+ colour emoji and the pointer render, all checked with `screendump`.
224
+
209
225
  ## Not done yet
210
226
 
211
227
  - Selection and scrollback viewing in `bunterm` (the pointer itself works,
@@ -11,7 +11,7 @@ const CANONICAL_BUNTERM = "/bin/bunterm";
11
11
 
12
12
  const usage = () => {
13
13
  console.error(`Usage:
14
- bunterm [N | /dev/ttyN] [-s N | --font-size N] [--fb /dev/fb0] [--no-mouse] [-e command [argument...]]
14
+ bunterm [N | /dev/ttyN] [-s N | --font-size N] [--fb /dev/fb0] [--no-mouse] [--no-clipboard] [-e command [argument...]]
15
15
  bunterm -h | --help`);
16
16
  };
17
17
 
@@ -58,6 +58,8 @@ const main = async () => {
58
58
  options.mouse = true;
59
59
  } else if (argument === "--no-mouse") {
60
60
  options.mouse = false;
61
+ } else if (argument === "--no-clipboard") {
62
+ options.clipboard = false;
61
63
  } else if (/^[1-9][0-9]*$/.test(argument) || argument.startsWith("/dev/")) {
62
64
  const number = /^[1-9][0-9]*$/.test(argument) ? Number(argument) : virtualConsoleNumber(argument);
63
65
  if (number === null) throw new Error(`expected N or /dev/ttyN, got ${argument}`);
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.17 - 2026-09-26
4
+
5
+ - readme: Add homepage URL
6
+
7
+ ## 0.4.16 - 2026-09-22
8
+
9
+ - Update homepage to <https://buninu.org>
10
+
3
11
  ## 0.4.15 - 2026-09-22
4
12
 
5
13
  ### Changed
@@ -23,6 +23,7 @@ npx buninu
23
23
  > (`--local`, bunmsh) has no job control yet, so use the browser terminal when
24
24
  > you need to background a job.
25
25
 
26
+ - Homepage: <https://buninu.org>
26
27
  - Source: [github.com/jjtseng93/buninu](https://github.com/jjtseng93/buninu)
27
28
 
28
29
  - Core components:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
4
4
  "description": "Buninu is a portable, self-bootstrapping Unix-like userspace built on Bun that runs across operating systems",
5
5
  "type": "module",
6
6
  "main": "./bin/init.js",
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/jjtseng93/buninu.git"
12
12
  },
13
- "homepage": "https://github.com/jjtseng93/buninu#readme",
13
+ "homepage": "https://buninu.org",
14
14
  "bugs": {
15
15
  "url": "https://github.com/jjtseng93/buninu/issues"
16
16
  },
package/initramfs/init.js CHANGED
@@ -179,6 +179,16 @@ try {
179
179
  console.error(`init.js: input modules: ${error?.message ?? error}`);
180
180
  }
181
181
 
182
+ // The framebuffer for bunterm: the firmware's GOP is a simple-framebuffer
183
+ // device, and simpledrm turns it into /dev/fb0. x86_64 linux-lts builds it
184
+ // in and linux-virt images do not ship it; tryModprobe skips both.
185
+ try {
186
+ const { tryModprobe } = await import("/lib/modprobe.js");
187
+ if (tryModprobe("simpledrm")?.length) console.log("init.js: loaded simpledrm");
188
+ } catch (error) {
189
+ console.error(`init.js: simpledrm: ${error?.message ?? error}`);
190
+ }
191
+
182
192
  const { default: repl } = await import("node:repl");
183
193
  const { mkdirSync, writeFileSync } = await import("node:fs");
184
194
 
@@ -549,11 +559,26 @@ const configureDisks = () => {
549
559
  return devices;
550
560
  };
551
561
 
562
+ // Load the complete sound stack packaged in the image. Like cfg.net this is
563
+ // deliberately unconditional: it does not probe modaliases first, so codec
564
+ // and helper modules without their own discoverable device are loaded too.
565
+ const configureSound = () => {
566
+ const { modprobeAll } = require("/lib/modprobe.js");
567
+ const { readdirSync } = require("node:fs");
568
+ const report = modprobeAll({ accept: (_module, path) => path.startsWith("kernel/sound/") });
569
+ console.log(`sound: loaded all packaged sound modules (${report.inserted.length} newly loaded, ${report.errors.length} failed)`);
570
+ for (const { module, error } of report.errors) console.log(`sound: ${module}: ${error}`);
571
+ const devices = (() => { try { return readdirSync("/dev/snd"); } catch { return []; } })();
572
+ console.log(`sound: ${devices.length ? devices.map((name) => `/dev/snd/${name}`).join(" ") : "no sound devices registered"}`);
573
+ return devices;
574
+ };
575
+
552
576
  globalThis.cfg = Object.freeze(Object.defineProperties({}, {
553
577
  all: { enumerable: true, get: configureModules },
554
578
  disk: { enumerable: true, get: configureDisks },
555
579
  net: { enumerable: true, get: configureEthernet },
556
580
  power: { enumerable: true, get: configureBattery },
581
+ sound: { enumerable: true, get: configureSound },
557
582
  }));
558
583
 
559
584
  // Compatibility aliases for images and notes that used the original API.
@@ -562,6 +587,7 @@ globalThis.cfgNet = configureEthernet;
562
587
  globalThis.cfgDisk = configureDisks;
563
588
  globalThis.cfgMod = configureModules;
564
589
  globalThis.cfgBat = configureBattery;
590
+ globalThis.cfgSound = configureSound;
565
591
 
566
592
  const showWelcome = () => {
567
593
  console.log(`
@@ -0,0 +1,51 @@
1
+ // /lib/bunterm/clipboard.js — OSC 52 clipboard for bunterm, backed by xclip.
2
+ //
3
+ // program ── ESC ] 52 ; c ; <base64> BEL ──▶ xclip -selection clipboard
4
+ // program ── ESC ] 52 ; c ; ? BEL ─────────▶ xclip -selection clipboard -o
5
+ // ◀── ESC ] 52 ; c ; <base64> BEL ──
6
+ //
7
+ // bunterm keeps nothing itself: every copy and paste goes through xclip, so
8
+ // all bunterm sessions (and anything else using xclip) share one clipboard.
9
+ // Buninu's xclip prints status lines on stderr, which must not reach the
10
+ // console under the framebuffer, so stderr is discarded.
11
+
12
+ const runXclip = async (args, input) => {
13
+ const command = ["xclip", "-selection", "clipboard", ...args];
14
+ const proc = Bun.spawn(command, {
15
+ stdin: input === undefined ? "ignore" : input,
16
+ stdout: input === undefined ? "pipe" : "ignore",
17
+ stderr: "ignore",
18
+ });
19
+ const [output, exitCode] = await Promise.all([
20
+ input === undefined ? new Response(proc.stdout).arrayBuffer() : null,
21
+ proc.exited,
22
+ ]);
23
+ if (exitCode !== 0) throw new Error(`${command.join(" ")} exited with ${exitCode}`);
24
+ return output;
25
+ };
26
+
27
+ // Registers the OSC 52 handler on an xterm.js terminal. `reply` sends bytes
28
+ // back to the program, as the terminal's other replies do. A read the xclip
29
+ // call cannot answer is still answered, with empty contents, so the program
30
+ // does not wait out its own timeout.
31
+ export const attachClipboard = ({ term, reply, onError = () => {} }) =>
32
+ term.parser.registerOscHandler(52, (data) => {
33
+ const separator = data.indexOf(";");
34
+ if (separator < 0) return true;
35
+ const target = data.slice(0, separator);
36
+ const payload = data.slice(separator + 1);
37
+
38
+ if (payload === "?") {
39
+ // The reply is sent after xclip returns, when the program may have exited
40
+ // and the PTY closed; writing then throws, and an unhandled rejection
41
+ // would end bunterm before it restores the console.
42
+ runXclip(["-o"])
43
+ .catch((error) => { onError(error); return new ArrayBuffer(0); })
44
+ .then((bytes) => reply(`\u001b]52;${target};${Buffer.from(bytes).toString("base64")}\u0007`))
45
+ .catch(onError);
46
+ return true;
47
+ }
48
+
49
+ runXclip([], Buffer.from(payload, "base64")).catch(onError);
50
+ return true;
51
+ });
@@ -21,6 +21,7 @@ import { Renderer, defaultTheme } from "./renderer.js";
21
21
  import { ImageStore } from "./images.js";
22
22
  import { openKeyboard, translateKeys } from "./input.js";
23
23
  import { claimVirtualConsole } from "./vt.js";
24
+ import { attachClipboard } from "./clipboard.js";
24
25
 
25
26
  const require = createRequire(import.meta.url);
26
27
 
@@ -52,6 +53,7 @@ export const createSession = ({
52
53
  scrollback = 1000,
53
54
  cursorBlink = true,
54
55
  mouse = true,
56
+ clipboard = true,
55
57
  env = {},
56
58
  onError = (error) => console.error(`bunterm: ${error?.stack ?? error}`),
57
59
  }) => {
@@ -158,6 +160,9 @@ export const createSession = ({
158
160
  });
159
161
  backend.onData(pump);
160
162
  term.onData((data) => backend.write(Buffer.from(data, "utf8")));
163
+ // OSC 52 copy and paste go straight to xclip; replies return to the program.
164
+ // Without it (--no-clipboard) xterm.js ignores OSC 52 and nothing reaches xclip.
165
+ if (clipboard) attachClipboard({ term, reply: (text) => backend.write(Buffer.from(text, "utf8")), onError });
161
166
 
162
167
  // Keyboard bytes from the console, rewritten for the program's key mode.
163
168
  const input = (bytes) => {
@@ -6,12 +6,13 @@ xterm-compatible terminal with anti-aliased text, CJK, colour emoji, seamless
6
6
  box drawing and kitty graphics protocol images — no X11, Wayland or GPU.
7
7
 
8
8
  ```sh
9
- bunterm # run /bin/sh on the console you are on
10
9
  bunterm 2 # start a detached bunterm on /dev/tty2
11
- bunterm /dev/tty1 # the full virtual-console spelling also works
10
+ bunterm /dev/tty2 # the full virtual-console spelling also works
11
+ bunterm # take over the console you are on (on tty1 that hides the bun-repl)
12
12
  bunterm -s 20 # short for --font-size 20
13
13
  bunterm -e bun /buninu/apps/jsmdcui/src/index.js --demo
14
14
  bunterm --no-mouse # keyboard only
15
+ bunterm --no-clipboard # ignore OSC 52 copy and paste
15
16
  bunterm -h
16
17
  ```
17
18
 
@@ -39,6 +40,7 @@ identified from the process task name and its open console descriptor under
39
40
  | `--fb /dev/fbN` | The framebuffer device (default `/dev/fb0`). |
40
41
  | `--no-blink` | A steady cursor. |
41
42
  | `--no-mouse` | Do not attach a pointing device; nothing then reads `/dev/input`. |
43
+ | `--no-clipboard` | Ignore OSC 52: programs can neither copy to nor read from the clipboard, and `xclip` is never run. |
42
44
 
43
45
  ## What is drawn
44
46
 
@@ -101,6 +103,16 @@ mouse also needs the `usbhid` stack, which a `--real` image carries and
101
103
  ones (a tablet or touchscreen) alike; a device that cannot be opened is
102
104
  reported and the session continues without it.
103
105
 
106
+ ## Clipboard
107
+
108
+ Programs copy and paste through OSC 52, as `casty` and `jsmdcui` do.
109
+ bunterm keeps no clipboard of its own: `ESC ] 52 ; c ; <base64>` is passed to
110
+ `xclip -selection clipboard`, and a query (`ESC ] 52 ; c ; ?`) is answered
111
+ with the output of `xclip -selection clipboard -o`, so every bunterm session
112
+ shares the same clipboard as `xclip`. A query xclip cannot answer is replied
113
+ to with empty contents. `--no-clipboard` turns this off, so a program can
114
+ neither set nor read the clipboard through the terminal.
115
+
104
116
  ## Limits
105
117
 
106
118
  - Needs a framebuffer: `linux-lts` has one built in, while the default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu-linux",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "A Linux distribution running Bun as PID 1 with the Buninu userspace.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -27,6 +27,29 @@ cp -a --reflink=auto "$native_dir/." "$staging/"
27
27
  cp README.md "$staging/usr/share/doc/buninu-linux/README.md"
28
28
  cp -a LICENSE NOTICE.md LICENSES "$staging/usr/share/licenses/"
29
29
 
30
+ # os-release(5): the canonical file lives in /usr/lib and /etc/os-release links
31
+ # to it. build-uki.sh embeds the same file as the UKI's .osrel section.
32
+ version="$(sed -n 's/^ *"version": *"\([^"]*\)".*/\1/p' package.json | head -n 1)"
33
+ test -n "$version" || { echo "error: no version in package.json" >&2; exit 1; }
34
+ printf '%s\n' \
35
+ 'NAME="Buninu Linux"' \
36
+ 'ID=buninu-linux' \
37
+ "VERSION=\"$version\"" \
38
+ "VERSION_ID=$version" \
39
+ "PRETTY_NAME=\"Buninu Linux $version\"" \
40
+ 'HOME_URL="https://buninu.org"' \
41
+ 'BUG_REPORT_URL="https://github.com/jjtseng93/buninu-linux/issues"' \
42
+ 'LOGO=buninu-linux' \
43
+ 'DEFAULT_HOSTNAME=buninu' > build/os-release
44
+ mkdir -p "$staging/usr/lib" "$staging/etc"
45
+ cp build/os-release "$staging/usr/lib/os-release"
46
+ ln -sfn ../usr/lib/os-release "$staging/etc/os-release"
47
+ # LOGO names an icon-theme icon; point the standard lookup paths at the Buninu
48
+ # icon that /buninu already ships instead of packing a second copy.
49
+ mkdir -p "$staging/usr/share/icons/hicolor/512x512/apps" "$staging/usr/share/pixmaps"
50
+ ln -sfn ../../../../../../buninu/icon.png "$staging/usr/share/icons/hicolor/512x512/apps/buninu-linux.png"
51
+ ln -sfn ../../../buninu/icon.png "$staging/usr/share/pixmaps/buninu-linux.png"
52
+
30
53
  # fakeroot lets cpio record the character devices without real root. Nothing
31
54
  # has mounted devtmpfs when the kernel execs Bun as PID 1, so every device
32
55
  # Bun's startup needs has to be in the archive itself: