buninu-linux 0.13.56 → 0.14.57

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/NOTICE.md CHANGED
@@ -139,83 +139,83 @@ the built image redistributes them.
139
139
  - License: GPL-2.0-only WITH Linux-syscall-note
140
140
  - See: `LICENSES/GPL-2.0.txt` and `LICENSES/Linux-syscall-note.txt`
141
141
 
142
- Linux 6.18.56 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
143
- The default build uses package `linux-virt`, version 6.18.56-r0.
144
- `--linux-lts` and `--real` use package `linux-lts`, version 6.18.56-r0.
142
+ Linux 6.18.57 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
143
+ The default build uses package `linux-virt`, version 6.18.57-r0.
144
+ `--linux-lts` and `--real` use package `linux-lts`, version 6.18.57-r0.
145
145
  Each comes from the x86_64 or aarch64 repository to match `--arch`. All
146
146
  listed modules are the selected package's `.ko.gz` files decompressed without
147
147
  modification.
148
148
 
149
- - Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.56.tar.xz
149
+ - Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.57.tar.xz
150
150
  - Alpine build recipe, configuration and patches (Corresponding Source),
151
- pinned to the commit shipping 6.18.56-r0 (the same for both architectures):
152
- https://gitlab.alpinelinux.org/alpine/aports/-/blob/efc8ddbb20c827d2bd8223c19a2625e754e5cb3c/main/linux-lts/APKBUILD
151
+ pinned to the commit shipping 6.18.57-r0 (the same for both architectures):
152
+ https://gitlab.alpinelinux.org/alpine/aports/-/blob/0c40eb4fea009af7de457fa20e186f43a88e0954/main/linux-lts/APKBUILD
153
153
  - Packages:
154
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.56-r0.apk
155
- SHA-256 `755deb045cfeb8c2bcaa2b2febe01ab2101d747921e64d4cd54d0312321e8d5d`
156
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.56-r0.apk
157
- SHA-256 `b0d8cc4d7e031b5619221e7978920d051bf1dd1e335f6d05d03964b2285fc3b4`
158
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.56-r0.apk
159
- SHA-256 `e3839bfd751ff0a4421484f712b9b29efb514024523736a0ef62521521571b12`
160
- https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.56-r0.apk
161
- SHA-256 `795bcd49840be7f4ff9566f5f669201bd41202376a455b720a98eb814f0e72f8`
154
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.57-r0.apk
155
+ SHA-256 `414aa2572ef88164b1af9905fa6dfbe863bf50f6130d895d57282dea3e15f7b9`
156
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.57-r0.apk
157
+ SHA-256 `7614556471859956a436b95813daf409b444aa849ec899178b404e828c5a9d98`
158
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.57-r0.apk
159
+ SHA-256 `b3820fee22e0366a15f7c139e5bc00348e333f3e7f77802271df7ded795aee4f`
160
+ https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.57-r0.apk
161
+ SHA-256 `67b8f61c5cfb6ac9b5a65cb1610b67d467e6c4ffc17e7d29dbea4bd8c447ff32`
162
162
 
163
163
  SHA-256 of files in the default x86_64 `linux-virt` image:
164
164
 
165
165
  ```text
166
- kernel/x86_64/vmlinuz-virt dc99c86bb8e58cb4eda5f01eac44c4d972105945a46dfe76a9e86e8529915c72
167
- failover.ko ddf94fe8a89b24e839f21866f828adc3eb91e62fa46dd977c74d7c2b6f92fe21
168
- net_failover.ko fb710ef0450417dea2f6dede919d14f5a4a94b1be73f338cfe2ed05e32f0cc30
169
- virtio_net.ko 2fb98ab7d06cfb7a1e1a07dcc5098da44316360097d75e288a30ad4855c449f7
166
+ kernel/x86_64/vmlinuz-virt 3725536e1c015345d781430b728849c2f090cf2040af00d7fe825920a106a08e
167
+ failover.ko f373917790e5e17b8c0c278efdd6fef7545ee8362510efaa80efb821e553f0b3
168
+ net_failover.ko 78fce63b3625b11bc65d4f0eb8f844796cad472ef14302016458ac60f898ac95
169
+ virtio_net.ko 18addca2d17e0b01b9adc64111b0409c08da6a196215e68bfda067309ff323a8
170
170
  ```
171
171
 
172
172
  SHA-256 of files in an x86_64 `--linux-lts` image:
173
173
 
174
174
  ```text
175
- kernel/x86_64/vmlinuz-lts e893ce0f7aecbdf2dcf012659ec5a0b35740780ae5c3af5ad188029f5cd4aa8f
176
- failover.ko 59e4e4808dca2d18d849c2da850e4ea659485007b09a690c7e57fb0e623cd1df
177
- net_failover.ko 20dd06a6f6f1528079a438630d741f125d11cc9bae58d618e0e6381f8bff6f5c
178
- virtio_net.ko 39bcd12bfc3149351fa226dabdbb1f82137c53f3b1ccbacbf30661bf8ff99ad4
179
- virtio_ring.ko e8fdebd89345ef39a54d3b23e6f2a57c6c4b201708b35113705b56d0fa373596
180
- virtio.ko d4ab148e31f5d4d5a1dd3c3f30bb52ba9a13e670929c9f7f1668396edf8049b7
181
- virtio_pci_legacy_dev.ko 15c35c8875b68dcaed3ac58d463d1b3f2c3e52dcf9a3840366d3fa6da7cd4dd7
182
- virtio_pci_modern_dev.ko 59bd6e34e6cf3c0ce54bab1eca1d7ac0325112280a6718613460002d2cde70d0
183
- virtio_pci.ko cc3ad8eaffa321bd58dbd663afba074dee81a5ad6f283d50cd5b1b340672430e
175
+ kernel/x86_64/vmlinuz-lts 674c615f1f0887621e6609becd11a7db40cc71b7616644fcc260b4b226a52103
176
+ failover.ko 00d409746a8033e5acf0d4d0b8db4589d48168f2965e4de0c05374bc4e3a4025
177
+ net_failover.ko afbe1a6cd279381ade52943158c62931d79f38dcd293e31ef522878ff82c17ed
178
+ virtio_net.ko 84b658a64416094f3bc793ac43ccd987e087898d0c0510ddf5f9e53e0f9b36ff
179
+ virtio_ring.ko db497b3c23133f48b5a48800695b9f7068244dfa9d52cd1d10b5d4618e68fb60
180
+ virtio.ko c867c11e61db4c9c4a7af54f29ba9d127759706854ba84bf42ca6f2ae8b4f71a
181
+ virtio_pci_legacy_dev.ko 8825df8124c86800b3bf3d3403f6aba47d527d02565f91baddebe40ea0f66f88
182
+ virtio_pci_modern_dev.ko 3ba582ad90247b04fe2afe289d2b51a39e3efc1cc40e58f55e7701062141e061
183
+ virtio_pci.ko 5f6baef35e1536d735628e14fa3172c27228cd26f7c80b7465d2afbed8ee3876
184
184
  ```
185
185
 
186
186
  `--real` additionally includes the following modules from that same
187
187
  x86_64 `linux-lts` package for physical xHCI controllers and USB HID keyboards:
188
188
 
189
189
  ```text
190
- usb-common.ko 37feaa14670d4bbd3dca77d701344d2fb02e2cb139d21c5af8b2c140aab5c6fe
191
- usbcore.ko 75efddfd7d1bf996ad99f0d9be790be767ca72ebdb53a00194b449c3588faa16
192
- xhci-hcd.ko bd5772b2ba908dff16c74ae10211a1e5cfd72910d861dbdf30b8f71c5995066a
193
- xhci-pci.ko 6c688038c9b45297fce752c8f0443cd0f398f31e0b4efffd7f869325ce9884fa
194
- xhci-pci-renesas.ko aa08808c9e609fe570a1c5b90d0f0ba33f2ca2e7d84e8ccbb3a2856fe65a7b3f
195
- hid.ko 161160bf12d18a24c25aef6355e265142fc4fb2122b09923a6e184fabedf6246
196
- hid-generic.ko 6d5ae3cfd8f8b034cbe444869485ac9012376c4ca604a3cd039f0cae442005f9
197
- usbhid.ko 6ed1e4891b242f64520621d689a24828dfc24a96236c71b86d9af9361248aa5a
190
+ usb-common.ko eb9d7b52b5c8f74b062ea3efbd8112c1460e3d34c0338e538602c648b958a8ef
191
+ usbcore.ko 30286b4e42c985e56903f41fd35233b3b35700b96ab99b088b1d91735c8e6e94
192
+ xhci-hcd.ko f0988c511d5692265f8c5221d77c468c9650151f3d69b5b68988eba9d1fab7f1
193
+ xhci-pci.ko a9145f467b6bf9b099e52eb5ac41b0c1164d9400e5b2f5f9d595ff58b4d776fb
194
+ xhci-pci-renesas.ko 2d88a4e72512944b62a4394172702db8dc211ccc7b99a93778f2d42ed8a23caf
195
+ hid.ko 7994f6b42f1df68a62312cf4de50a22b1a9756c3da91966a634630a62b18ae10
196
+ hid-generic.ko ea2eb7491e30bef769d2c419a2ccc9d55db030edddfe531c0d4e4723e170a4c3
197
+ usbhid.ko 899f357db5ba74182cf9161a577b080f8a6a9b0b015f6a322a5c31bf8754188b
198
198
  ```
199
199
 
200
200
  SHA-256 of files in the default aarch64 `linux-virt` image:
201
201
 
202
202
  ```text
203
- kernel/aarch64/vmlinuz-virt 039c532c2fa8f49896a679065d79a03b8a444c9d54fa80ac9bc4d0ce1ecd0ca6
204
- failover.ko ce65a98acce98720753c4a9a7e339f6c70d22eae70b65cc1b4211543a7c6c3a3
205
- net_failover.ko 39c9222d800ae95d8a82ff797e408187411ac2ff47bec2c9d7ddbfe99537dbe3
206
- virtio_net.ko 762f8e362ae2e2bbf200ab9bc742524999e8bce12c72671bf8560fd9bb7907d5
203
+ kernel/aarch64/vmlinuz-virt 60603568b5589c730e9183e5d6d4f0b8575ddac31e9187222decc92416329b26
204
+ failover.ko 792943de0975a4111d2040ab35cc214e5b1b4ec8c12dbc9725fd0602a56f1576
205
+ net_failover.ko ba3ab71ef38aa3a957c5b0fe7ddd908e31635e408f5937bccb50f36bb78761ba
206
+ virtio_net.ko 716e32b4425aa8dd4ebfeb6808e58c125e6b125c71b00ba8288851363f72fa60
207
207
  ```
208
208
 
209
209
  SHA-256 of files in an aarch64 `--linux-lts` image:
210
210
 
211
211
  ```text
212
- kernel/aarch64/vmlinuz-lts de5ff95fd663a30e53765cb0794707656bb80749d77f16bbf94225d17c512447
213
- failover.ko 7f3d40067603c9916d22e9d85fea1512bd95c087aaae774e5ea70f8bacb7ae04
214
- net_failover.ko 085e4fd53a40704408b6bf914ee464d16007296fa3615940fa54688afe84466c
215
- virtio_net.ko 4ea86521819063df91cfd18191e8bc2bd2551392e8f7a33126df0aba675ee409
216
- virtio_pci_legacy_dev.ko baf8033c183819a8bccacf36677247740a5cff9eec9b403b94840af258b60b6e
217
- virtio_pci_modern_dev.ko 2905133de7c9baceff9fc6a27dba9b8469744019545c19cf1456e64f7e50f062
218
- virtio_pci.ko 3b506947c25866e08a311f124cd143bd0f501179f0b556aed7ca4d6632143d8d
212
+ kernel/aarch64/vmlinuz-lts 19fc9f974bb3cb666b8a216f18ca9f9df8e1658da4e9e55ed464c3fcaf87a303
213
+ failover.ko d243de2d9c2b2c34094c125719fa784b2e476986bfcd5766b96e7f7fbcb658ac
214
+ net_failover.ko 675cdf971cbb2ab15f8db6295afadcfc852172f38d289e9935ed28386e3e38f6
215
+ virtio_net.ko 34ea985ebf0bef82731f398aa3aca9030d6d7882d4bcb1542fd17d8637cad70e
216
+ virtio_pci_legacy_dev.ko 17f1487bf62ac3b2961f5f959fb28a2e1135db565e8a77296372c5101e00aae1
217
+ virtio_pci_modern_dev.ko e7a76945ae0b9c75af78a77950a4f22c95758fe507db2b77fbc168a4d6f842d8
218
+ virtio_pci.ko 76efc4e0428eb0413727247f41972d72c3f2ed1373f7e94f88052357a097b6a8
219
219
  ```
220
220
 
221
221
  ### linuxx64.efi.stub and linuxaa64.efi.stub
package/README.md CHANGED
@@ -35,10 +35,10 @@
35
35
  * Bun reaches its interactive REPL
36
36
  * `start` starts the Buninu userspace shell
37
37
  * `cfg.disk` + shell `mount` mounts local disks
38
- * `cfg.net` loads common wired NIC and Android USB-tethering drivers
38
+ * `cfg.net` loads common wired NIC and Android USB-tethering drivers and automatically attempts DHCP configuration
39
39
  * `cfg.sound` loads every packaged ALSA sound module
40
40
  * Android phone USB tethering over RNDIS has been tested successfully, allowing Buninu Linux to access the Internet through an Android phone
41
- * IP addresses and routes are configured manually because the image does not yet include a DHCP client.
41
+ * After a DHCP lease expires or when you need fresh network settings, run `dhclient usb0` (using your interface name). Manual configuration with `ip` remains available.
42
42
  * The bundled `jmi` editor, jsmdcui App runtime, and local JavaScript execution also work.
43
43
  * Multi-tasking with kernel VT or jsmdcui
44
44
  + See [Multitasking modes](#multitasking-modes)
@@ -227,8 +227,7 @@ It supports Chinese text, emoji and the browser's terminal graphics. Use
227
227
  `Ctrl+Alt+F1` to return to the original console and `Ctrl+Alt+F2` to return
228
228
  to this terminal.
229
229
 
230
- There is no DHCP client yet; configure a detected wired interface with `ip` as
231
- described under [Basic configuration](#basic-configuration). Run `poweroff` for a synced
230
+ Run `poweroff` for a synced
232
231
  shutdown. After editing the initramfs, `bun ./index.js -b --real --export` rebuilds the
233
232
  image; `-h` lists all flags.
234
233
 
@@ -384,9 +383,16 @@ cfg.power
384
383
  cfg.sound
385
384
  ```
386
385
 
387
- `cfg.net` loads the packaged wired-network and Android USB-tethering drivers and prints detected
388
- interfaces and MAC addresses. There is no DHCP client yet, so configure the
389
- interface manually after entering the Buninu shell with `start()`:
386
+ `cfg.net` loads the packaged wired-network and Android USB-tethering drivers,
387
+ prints detected interfaces and MAC addresses, and automatically attempts DHCP
388
+ configuration. If it succeeds, no additional DHCP command is needed.
389
+
390
+ After a lease expires or when you need to obtain network settings again,
391
+ enter the Buninu shell with `start()` and run `dhclient usb0` (replace `usb0`
392
+ with your interface name). It configures the interface once and exits;
393
+ there is no automatic renewal.
394
+
395
+ For a manually configured static address, use:
390
396
 
391
397
  ```sh
392
398
  ip link set eth0 up
@@ -695,6 +701,8 @@ If there is no sound, check step 4's terminal for errors, and make sure
695
701
  | `mount` | `mount(2)` with type detection, `-o` parsing, `LABEL=`/`UUID=`, bind/move/remount; loads required filesystem and disk modules | `mount --help` → `/usr/share/doc/buninu-linux/mount.md` |
696
702
  | `umount` | `umount2(2)` with `-l`, `-f`, `-R` | `umount --help` |
697
703
  | `ip` | iproute2 grammar over `SIOC*` ioctls and `/proc/net`: `link`, `addr`, `route`, `neigh` | `ip --help` |
704
+ | `dhclient` | Reacquire DHCPv4 settings after expiry or when needed (`dhclient usb0`), then exit; also supports release | `dhclient --help` |
705
+ | `ping` | IPv4 ICMP / IPv6 ICMPv6 echo via Bun FFI; count, interval, timeout, deadline, payload size, TTL and source selection | `ping --help` |
698
706
  | `ps` | composable process fields from `/proc`; also exports its snapshot and single-row formatter for other commands | `ps --help` |
699
707
  | `top` | periodically refreshes the process snapshot and rows supplied by `ps` | `top --help` |
700
708
  | `poweroff` | sync pending writes and power off through the Linux reboot system call | `poweroff --help` |
@@ -717,6 +725,14 @@ above, implemented as Bun scripts or thin launchers for bundled packages.
717
725
  For Debian/Alpine rootfs and Chromium examples, see
718
726
  [Optional chroot and native binaries](#optional-chroot-and-native-binaries).
719
727
 
728
+ `ping -c 4 127.0.0.1` checks IPv4 connectivity; `ping -6 -c 4 ::1` checks
729
+ IPv6. `ping -c 10 -i 0.2 -W 1 HOST` adjusts count, interval and reply timeout.
730
+ The command uses ICMP sockets directly, with a raw-socket fallback when needed.
731
+ It supports Linux glibc/musl and native Android bionic through the shared libc
732
+ loader; `BUNINU_LIBC` remains an explicit override. Run
733
+ `bun test test/ping.test.js test/libc.test.js` to compare against the installed
734
+ system ping. See `ping --help` for permission requirements and supported flags.
735
+
720
736
  ### Buninu Browser (early-stage, experimental)
721
737
 
722
738
  `buninu-browser` is still in an early stage. Websites may render incorrectly
@@ -1179,7 +1195,7 @@ initramfs/ init.js, commands, userspace: the same for every archi
1179
1195
  native/<arch>/ musl and the GCC runtime (committed); bun and modules (fetched)
1180
1196
  ```
1181
1197
 
1182
- The `--real` inputs are Alpine v3.24 `linux-lts-6.18.56-r0`, musl `1.2.6-r2`,
1198
+ The `--real` inputs are Alpine v3.24 `linux-lts-6.18.57-r0`, musl `1.2.6-r2`,
1183
1199
  `systemd-efistub-260.2-r0`, `libstdc++`/`libgcc` `15.2.0-r5`, and Bun 1.4.2
1184
1200
  `linux-x64-musl-baseline`. The aarch64 guest uses the same versions from
1185
1201
  Alpine's aarch64 repository and Bun's `linux-aarch64-musl`. There is no BusyBox
@@ -1194,7 +1210,7 @@ extracts from it. Bumping a version changes both the file name and the hash, so
1194
1210
  it always refetches.
1195
1211
 
1196
1212
  The five application packages are pinned separately in
1197
- `scripts/npm-packages.md`: `buninu-browser@0.2.3`, `buninu-land@0.1.3`,
1213
+ `scripts/npm-packages.md`: `buninu-browser@0.2.4`, `buninu-land@0.1.4`,
1198
1214
  `@drxiaozhi/casty@1.3.7`, `@drxiaozhi/jspulse@0.2.1`, and
1199
1215
  `@drxiaozhi/jspinyin@0.1.1`. These were npm's `latest` versions when checked
1200
1216
  on 2026-10-11, with buninu-browser updated on 2026-10-12, and
@@ -1343,7 +1359,7 @@ The physical boot chain is firmware → the UKI stub → its embedded
1343
1359
  and, with the currently pinned kernel, embeds this complete command line:
1344
1360
 
1345
1361
  ```text
1346
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.56-0-lts rdinit=/bin/bun -- -e import('/init.js')
1362
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.57-0-lts rdinit=/bin/bun -- -e import('/init.js')
1347
1363
  ```
1348
1364
 
1349
1365
  Serial kernel logging is retained, while the final console makes
@@ -1367,7 +1383,7 @@ the same way). Build without `--real`; this selects Alpine `linux-virt`, omits t
1367
1383
  module set, and puts `ttyS0` last so serial stdio owns `/dev/console`:
1368
1384
 
1369
1385
  ```text
1370
- console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.56-0-virt rdinit=/bin/bun -- -e import('/init.js')
1386
+ console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.57-0-virt rdinit=/bin/bun -- -e import('/init.js')
1371
1387
  ```
1372
1388
 
1373
1389
  Build and boot it with:
@@ -1484,7 +1500,7 @@ The default `--real` image embeds this kernel command line (shown for the
1484
1500
  currently pinned LTS release):
1485
1501
 
1486
1502
  ```text
1487
- console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.56-0-lts rdinit=/bin/bun -- -e import('/init.js')
1503
+ console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.57-0-lts rdinit=/bin/bun -- -e import('/init.js')
1488
1504
  ```
1489
1505
 
1490
1506
  The physical display (`tty0`) is the final console and therefore owns PID 1's
@@ -1563,8 +1579,22 @@ custom `libinit.so` is needed.
1563
1579
  On a `--real` boot, init starts `/etc/resolv.conf` with `1.1.1.1` and
1564
1580
  `8.8.8.8`; these are initial public resolvers until the user replaces them
1565
1581
  with DNS appropriate for the local network. Hardware networking remains down
1566
- until `cfg.net` loads the packaged drivers and the user assigns an address and
1567
- route.
1582
+ until `cfg.net` loads the packaged drivers and attempts DHCP configuration.
1583
+ Manual address and route configuration with `ip` remains available.
1584
+
1585
+ `cfg.net` starts DHCP in the background for every detected non-loopback interface.
1586
+ It returns immediately; failures and acquisition timeouts are logged without
1587
+ interrupting init. Each client exits after configuring its lease; no renewal process stays running.
1588
+ Repeated `cfg.net` calls do not launch duplicate active clients. Route metrics
1589
+ start at 100; only the first interface updates DNS, leaving the init fallback
1590
+ resolver available if it cannot acquire a lease.
1591
+
1592
+ After a lease expires or when network settings need to be obtained again,
1593
+ run `dhclient usb0` (or `dhclient eth0`, depending on the interface name).
1594
+ This is not an extra step after successful automatic configuration by `cfg.net`.
1595
+ `dhclient` exits after configuration; `-timeout 20` limits acquisition to 20
1596
+ seconds. Leases do not renew automatically. For manual static configuration,
1597
+ see [Basic configuration](#basic-configuration).
1568
1598
 
1569
1599
  On a `--real` image the REPL also offers the getter-based `cfg` namespace:
1570
1600
  `cfg.net`, `cfg.disk`, `cfg.power`, `cfg.sound` and `cfg.all` run as soon as the property is read, without
package/fetch-alpine.sh CHANGED
@@ -63,7 +63,7 @@ select_optional_module() {
63
63
  # Network: virtio-net for QEMU (init.js loads these at boot). virtio_pci is
64
64
  # the PCI transport for every virtio device, disk included; linux-virt builds
65
65
  # it in, linux-lts has it as a module that nothing in modules.dep pulls in.
66
- for module in failover net_failover virtio_net; do select_module "$module"; done
66
+ for module in failover net_failover virtio_net af_packet; do select_module "$module"; done
67
67
  select_optional_module virtio_pci
68
68
  # Storage: what /bin/mount loads on demand. ext4 brings jbd2, mbcache and
69
69
  # crc16; FAT, exFAT and NTFS ask the kernel for the NLS tables at mount
@@ -0,0 +1,8 @@
1
+ #!/bin/bun
2
+ import {parseDhclient,runDhclient} from '../lib/dhclient.js';
3
+ import {showDocument} from '../lib/document.js';
4
+ try {
5
+ const options=parseDhclient(Bun.argv.slice(2));
6
+ if(options.help)await showDocument('dhclient');
7
+ else process.exitCode=await runDhclient(options);
8
+ }catch(error){console.error(`dhclient: ${error.message}`);process.exitCode=1;}
@@ -0,0 +1,8 @@
1
+ #!/bin/bun
2
+ import {parsePing,runPing} from '../lib/ping.js';
3
+ import {showDocument} from '../lib/document.js';
4
+ try {
5
+ const options=parsePing(Bun.argv.slice(2));
6
+ if(options.help)await showDocument('ping');
7
+ else process.exitCode=await runPing(options);
8
+ } catch(error) {console.error(`ping: ${error.message}`);process.exitCode=2;}
package/initramfs/init.js CHANGED
@@ -495,7 +495,7 @@ globalThis.start = launchBuninu;
495
495
  // cfg.net loads every packaged network module, including PHY and bus support
496
496
  // that a kernel userspace modprobe helper would normally load on demand. It
497
497
  // then reports the device matches and lists the resulting interfaces. The
498
- // address and route are still yours to set with `ip`.
498
+ // DHCP runs independently afterward; errors and timeouts never block init.
499
499
  const configureEthernet = () => {
500
500
  const { autoload, modprobeAll } = require("/lib/modprobe.js");
501
501
  const { readdirSync, readFileSync } = require("node:fs");
@@ -529,12 +529,13 @@ const configureEthernet = () => {
529
529
  const carrier = read(`/sys/class/net/${name}/carrier`);
530
530
  console.log(`eth: ${name} ${read(`/sys/class/net/${name}/address`)} ${read(`/sys/class/net/${name}/operstate`)}${carrier === "1" ? " carrier" : ""}`);
531
531
  }
532
- if (interfaces.length) console.log(`eth:
533
- Next steps in shell( after start() ):
534
- ip link set ${interfaces[0]} up
535
- ip addr add A.B.C.D/24 dev ${interfaces[0]};
536
- ip route add default via GATEWAY
537
- `);
532
+ try {
533
+ const { startDhcp } = require("/lib/dhcp-start.js");
534
+ startDhcp(interfaces);
535
+ } catch (error) {
536
+ console.log(`[dhcp setup] unavailable: ${error?.message ?? error}; init continues`);
537
+ }
538
+ console.log("[dhcp] If automatic DHCP fails, run start() to enter the shell, then try dhclient usb0 (replace usb0 with your interface) or configure the network manually with ip commands. Use ip link to list network interfaces.");
538
539
  return interfaces;
539
540
  };
540
541
 
@@ -58,7 +58,7 @@ const isSingleCodePoint = (text) => {
58
58
  };
59
59
 
60
60
  export class Renderer {
61
- constructor({ CanvasKit, screen, fonts, fontSize = 16, lineHeight = 1, theme = defaultTheme }) {
61
+ constructor({ CanvasKit, screen, fonts, fontSize = 13, lineHeight = 1, theme = defaultTheme }) {
62
62
  this.CanvasKit = CanvasKit;
63
63
  this.screen = screen;
64
64
  this.canvas = screen.canvas;
@@ -49,7 +49,7 @@ export const createSession = ({
49
49
  fonts,
50
50
  command = "/bin/sh",
51
51
  argv = [],
52
- fontSize = 16,
52
+ fontSize = 13,
53
53
  lineHeight = 1,
54
54
  theme = defaultTheme,
55
55
  scrollback = 1000,
@@ -0,0 +1,135 @@
1
+ import {readFileSync,writeFileSync,mkdirSync,renameSync,unlinkSync,openSync,closeSync} from 'node:fs';
2
+ import {dirname,resolve} from 'node:path';
3
+ import {DhcpClient} from './dhcp.js';
4
+ import {interfaceInfo,openTransport} from './dhcp-transport.js';
5
+ export function parseDhclient(args) {
6
+ const o={timeout:60,dns:true,resolv:'/etc/resolv.conf',once:true,release:false,verbose:false};
7
+ for(let i=0;i<args.length;i++){
8
+ const a=args[i];
9
+ if(['--help','-h','-help'].includes(a))return {...o,help:true};
10
+ if(a==='-4'||a==='-d')continue;
11
+ if(a==='-1')o.once=true;
12
+ else if(a==='-r')o.release=true;
13
+ else if(a==='-v')o.verbose=true;
14
+ else if(a==='-q')o.quiet=true;
15
+ else if(a==='--no-dns')o.dns=false;
16
+ else if(['-timeout','-lf','-H','--resolv-conf','--metric'].includes(a)){
17
+ const value=args[++i];if(!value||value.startsWith('-'))throw new Error(`${a} requires a value`);
18
+ if(a==='-timeout'){o.timeout=Number(value);if(!Number.isFinite(o.timeout)||o.timeout<=0)throw new Error('timeout must be positive');}
19
+ if(a==='--metric'){o.metric=Number(value);if(!Number.isInteger(o.metric)||o.metric<0||o.metric>32766)throw new Error('metric must be between 0 and 32766');}
20
+ if(a==='-lf')o.leaseFile=resolve(value);
21
+ if(a==='-H'){if(!/^[a-zA-Z0-9][a-zA-Z0-9.-]{0,252}$/.test(value))throw new Error('invalid hostname');o.hostname=value;}
22
+ if(a==='--resolv-conf')o.resolv=resolve(value);
23
+ }else if(a.startsWith('-'))throw new Error(`unsupported option: ${a} (DHCPv4 only; see --help)`);
24
+ else if(o.interface)throw new Error('specify one interface per process');
25
+ else o.interface=a;
26
+ }
27
+ if(!o.interface||!/^[a-zA-Z0-9_.:-]{1,15}$/.test(o.interface))throw new Error('specify an Ethernet interface, e.g. dhclient usb0. Use ip link to list network interfaces; use dhclient -h or dhclient --help for help.');
28
+ o.leaseFile??=`/var/lib/dhclient/${o.interface}.json`;
29
+ if(args.includes('-1')&&o.release)throw new Error('-1 and -r cannot be combined');
30
+ return o;
31
+ }
32
+ export async function runIp(args) {
33
+ const p=Bun.spawn([process.execPath,`${import.meta.dir}/../bin/ip`,...args],{stdout:'pipe',stderr:'pipe'});
34
+ const [status,out,err]=await Promise.all([p.exited,new Response(p.stdout).text(),new Response(p.stderr).text()]);
35
+ if(status)throw new Error(`ip ${args.join(' ')}: ${err.trim()||out.trim()}`);
36
+ return out;
37
+ }
38
+ const readOptional=path=>{try{return readFileSync(path,'utf8');}catch(e){if(e.code==='ENOENT')return null;throw e;}};
39
+ export function saveLease(path,lease) {
40
+ mkdirSync(dirname(path),{recursive:true});const tmp=`${path}.${process.pid}.tmp`;
41
+ writeFileSync(tmp,JSON.stringify(lease,null,2)+'\n',{mode:0o600});renameSync(tmp,path);
42
+ }
43
+ export class Configuration {
44
+ constructor(options,ip=runIp){this.options=options;this.ip=ip;}
45
+ metric(lease){return lease.metric===undefined?[]:['metric',String(lease.metric)];}
46
+ async apply(lease,previous) {
47
+ const o=this.options;
48
+ lease.metric=o.metric;
49
+ if(previous&&previous.routers[0]!==lease.routers[0])await this.removeRoute(previous);
50
+ await this.ip(['addr','replace',`${lease.address}/${lease.prefix}`,'brd','+','dev',o.interface]);
51
+ if(lease.routers[0])await this.ip(['route','replace','default','via',lease.routers[0],'dev',o.interface,...this.metric(lease)]);
52
+ if(previous?.resolver)lease.resolver=previous.resolver;
53
+ if(o.dns&&lease.dns.length){
54
+ const content=`# dhclient ${o.interface}\n`+lease.dns.map(ip=>`nameserver ${ip}\n`).join('');
55
+ const old=lease.resolver;
56
+ // Keep the original resolver across repeated invocations, unless another writer took over.
57
+ const current=readOptional(o.resolv);
58
+ lease.resolver={path:o.resolv,before:old?.path===o.resolv&&current===old.written?old.before:current,written:content};
59
+ writeFileSync(o.resolv,content);
60
+ }
61
+ }
62
+ async removeRoute(lease) {
63
+ if(!lease.routers[0])return;
64
+ try{await this.ip(['route','del','default','via',lease.routers[0],'dev',this.options.interface,...this.metric(lease)]);}
65
+ catch(e){if(!/No such process|No such device|Cannot find device/.test(e.message))throw e;}
66
+ }
67
+ async clear(lease) {
68
+ const errors=[];
69
+ try{await this.removeRoute(lease);}catch(e){errors.push(e);}
70
+ // ip addr del checks the current address before deleting it.
71
+ try{await this.ip(['addr','del',`${lease.address}/${lease.prefix}`,'dev',this.options.interface]);}
72
+ catch(e){if(!/Cannot assign requested address|Cannot find device/.test(e.message))errors.push(e);}
73
+ try{
74
+ const r=lease.resolver;
75
+ if(r&&readOptional(r.path)===r.written){if(r.before===null)unlinkSync(r.path);else writeFileSync(r.path,r.before);}
76
+ }catch(e){errors.push(e);}
77
+ if(errors.length)throw new AggregateError(errors,errors.map(e=>e.message).join('; '));
78
+ }
79
+ }
80
+ // A command invocation acquires once and exits. There is no renewal daemon.
81
+ export async function manageLease(client,config,{previous,signal,log=console.log,save=()=>{},forget=()=>{}}={}) {
82
+ let lease,kept=false;
83
+ try {
84
+ lease=await client.acquire();
85
+ if(!lease){if(signal?.aborted)return 0;throw new Error('no DHCP lease received before timeout');}
86
+ if(signal?.aborted)return 0;
87
+ await config.apply(lease,previous);await save(lease);
88
+ log(`bound to ${lease.address}/${lease.prefix}, lease ${lease.seconds}s; configured and exiting (no automatic renewal)`);
89
+ kept=true;return 0;
90
+ }finally{
91
+ if(lease&&!kept){
92
+ try{client.release(lease);}catch(e){log(`release: ${e.message}`);}
93
+ await config.clear(lease);await forget();
94
+ }
95
+ }
96
+ }
97
+ export async function runDhclient(o) {
98
+ if(!['linux','android'].includes(process.platform))throw new Error('dhclient requires Linux/Android networking');
99
+ // Serialize this interface even when callers choose different lease files.
100
+ const lock=`/run/dhclient-${o.interface}.lock`;mkdirSync(dirname(lock),{recursive:true});
101
+ let handle;
102
+ try{handle=openSync(lock,'wx',0o600);}catch(e){if(e.code==='EEXIST')throw new Error(`${lock} exists; stop the other client, or remove this lock if its process died`);throw e;}
103
+ writeFileSync(handle,`${process.pid}\n`);
104
+ let transport;
105
+ const controller=new AbortController(),stop=()=>controller.abort();
106
+ process.on('SIGINT',stop);process.on('SIGTERM',stop);
107
+ const log=message=>{if(!o.quiet)console.log(`dhclient: ${message}`);};
108
+ const forget=()=>{try{unlinkSync(o.leaseFile);}catch(e){if(e.code!=='ENOENT')throw e;}};
109
+ try{
110
+ const info=interfaceInfo(o.interface);
111
+ await runIp(['link','set','dev',o.interface,'up']);
112
+ try { transport=openTransport(o.interface,info); }
113
+ catch(error) {
114
+ if(error.errno!==97)throw error; // EAFNOSUPPORT: Alpine ships AF_PACKET as a module.
115
+ const {modprobe}=await import('./modprobe.js');
116
+ modprobe('af_packet');transport=openTransport(o.interface,info);
117
+ }
118
+ const client=new DhcpClient({transport,...info,...o,signal:controller.signal,log:o.verbose?log:()=>{}});
119
+ const config=new Configuration(o);
120
+ if(o.release){
121
+ const text=readOptional(o.leaseFile);if(!text)throw new Error(`no saved lease: ${o.leaseFile}`);
122
+ const lease=JSON.parse(text);if(lease.interface!==o.interface||lease.mac!==info.mac.toString('hex'))throw new Error('saved lease belongs to another interface');
123
+ try{client.release(lease);}finally{await config.clear(lease);forget();}log(`released ${lease.address}`);return 0;
124
+ }
125
+ let previous;
126
+ const saved=readOptional(o.leaseFile);
127
+ if(saved){
128
+ const candidate=JSON.parse(saved);
129
+ if(candidate.interface===o.interface&&candidate.mac===info.mac.toString('hex'))previous=candidate;
130
+ }
131
+ return await manageLease(client,config,{...o,previous,signal:controller.signal,log,save:lease=>saveLease(o.leaseFile,{...lease,interface:o.interface,mac:info.mac.toString('hex')}),forget});
132
+ }finally{
133
+ transport?.close();process.off('SIGINT',stop);process.off('SIGTERM',stop);closeSync(handle);unlinkSync(lock);
134
+ }
135
+ }
@@ -0,0 +1,85 @@
1
+ // Best-effort one-shot DHCP for cfg.net. Each interface owns its result and
2
+ // bounded output buffer; a failed link never changes another link's status.
3
+ export function createDhcpLauncher({spawn=(argv,options)=>Bun.spawn(argv,options),log=console.log,
4
+ acquisitionSeconds=20,watchdogMs=25000,killGraceMs=2000,outputGraceMs=250}={}) {
5
+ const active=new Map();
6
+ const messageOf=error=>String(error?.message??error);
7
+ const clean=text=>String(text).replace(/\x1b\[[0-?]*[ -/]*[@-~]/g,'').replace(/[\x00-\x1f\x7f]/g,' ').trim();
8
+ const report=(name,message)=>{try{log(`[dhcp ${clean(name)}] ${clean(message)}`);}catch{}};
9
+ return function startDhcp(interfaces) {
10
+ const started=[];
11
+ try {
12
+ for(const [index,name] of interfaces.entries()) {
13
+ if(name==='lo'||active.has(name))continue;
14
+ let child,state;
15
+ try {
16
+ if(!/^[a-zA-Z0-9_.:-]{1,15}$/.test(name))throw new Error('invalid interface name');
17
+ const args=[process.execPath,`${import.meta.dir}/../bin/dhclient`,'-1','-timeout',String(acquisitionSeconds),'--metric',String(100+index)];
18
+ // Keep a single resolver owner; init already supplies fallback DNS.
19
+ if(index>0)args.push('--no-dns');
20
+ args.push(name);
21
+ child=spawn(args,{stdin:'ignore',stdout:'pipe',stderr:'pipe'});
22
+ state={child,done:false,timer:null,killTimer:null,timedOut:false,address:null,errors:[],notes:[],readers:[]};active.set(name,state);
23
+ const remember=(list,text)=>{list.push(clean(text).slice(0,512));if(list.length>4)list.shift();};
24
+ const kill=signal=>{try{child.kill(signal);}catch(e){remember(state.errors,`${signal}: ${messageOf(e)}`);}};
25
+ state.timer=setTimeout(()=>{
26
+ if(state.done)return;
27
+ state.timedOut=true;kill('SIGTERM');
28
+ state.killTimer=setTimeout(()=>{if(!state.done)kill('SIGKILL');},killGraceMs);
29
+ state.killTimer.unref?.();
30
+ },watchdogMs);state.timer.unref?.();
31
+ const line=(text,isError)=>{
32
+ text=clean(text);if(!text)return;
33
+ const bound=!isError&&/^dhclient: bound to ([^,\s]+)/.exec(text);
34
+ if(bound)state.address=bound[1];
35
+ else remember(isError?state.errors:state.notes,text.replace(/^dhclient:\s*/,''));
36
+ };
37
+ const drain=async(stream,isError)=>{
38
+ if(!stream)return;
39
+ const reader=stream.getReader();state.readers.push(reader);
40
+ const decoder=new TextDecoder();let pending='';
41
+ try {
42
+ for(;;){
43
+ const {done,value}=await reader.read();if(done)break;
44
+ pending+=decoder.decode(value,{stream:true});
45
+ let at;while((at=pending.indexOf('\n'))>=0){line(pending.slice(0,at),isError);pending=pending.slice(at+1);}
46
+ if(pending.length>4096){line(pending.slice(0,4096),isError);pending='';}
47
+ }
48
+ if(pending)line(pending,isError);
49
+ }finally{reader.releaseLock();}
50
+ };
51
+ const drains=[drain(child.stdout,false),drain(child.stderr,true)].map(p=>p.catch(e=>remember(state.errors,`output: ${messageOf(e)}`)));
52
+ const finish=async(code,error)=>{
53
+ state.done=true;clearTimeout(state.timer);clearTimeout(state.killTimer);
54
+ // exited can resolve before the last stderr/stdout chunk arrives.
55
+ // Bound the wait even if an inherited pipe never closes.
56
+ let outputTimer;
57
+ try {
58
+ await Promise.race([Promise.all(drains),new Promise(resolve=>{outputTimer=setTimeout(resolve,outputGraceMs);})]);
59
+ }finally{
60
+ clearTimeout(outputTimer);
61
+ for(const reader of state.readers){try{void reader.cancel().catch(()=>{});}catch{}}
62
+ if(active.get(name)===state)active.delete(name);
63
+ }
64
+ if(state.timedOut)report(name,`failed: timed out; other interfaces are unaffected`);
65
+ else if(error!==undefined)report(name,`failed: ${messageOf(error)}`);
66
+ else if(code===0&&state.address)report(name,`configured ${state.address}; command finished`);
67
+ else if(code===0)report(name,'finished without configuring an address');
68
+ else {
69
+ const detail=state.errors.at(-1)??state.notes.at(-1)??'no error details';
70
+ report(name,`failed: ${detail} (exit ${code}); other interfaces are unaffected`);
71
+ }
72
+ };
73
+ void Promise.resolve(child.exited).then(code=>finish(code),error=>finish(null,error??'unknown exit error')).catch(e=>report(name,`result unavailable: ${messageOf(e)}`));
74
+ started.push(name);report(name,'requesting configuration');
75
+ }catch(error){
76
+ if(state){clearTimeout(state.timer);clearTimeout(state.killTimer);active.delete(name);}
77
+ if(child){try{child.kill('SIGKILL');}catch{}}
78
+ report(name,`failed to start: ${messageOf(error)}; other interfaces are unaffected`);
79
+ }
80
+ }
81
+ }catch(error){report('setup',`failed: ${messageOf(error)}; init continues`);}
82
+ return started;
83
+ };
84
+ }
85
+ export const startDhcp=createDhcpLauncher();
@@ -0,0 +1,44 @@
1
+ import {libc,ptr,check,errno,SysError,cString,withInetSocket} from './dlopen.js';
2
+ import {ipv4,wrapIPv4,unwrapIPv4} from './dhcp.js';
3
+ export function interfaceInfo(name) {
4
+ if(!/^[a-zA-Z0-9_.:-]{1,15}$/.test(name))throw new Error('invalid interface name');
5
+ return withInetSocket(ioctl=>{
6
+ const req=Buffer.alloc(40);req.write(name);ioctl(0x8933,req,'SIOCGIFINDEX');const index=req.readInt32LE(16);
7
+ ioctl(0x8927,req,'SIOCGIFHWADDR');if(req.readUInt16LE(16)!==1)throw new Error('DHCP requires an Ethernet-compatible interface');
8
+ return {index,mac:Buffer.from(req.subarray(18,24))};
9
+ });
10
+ }
11
+ const sockaddr=(ip,port)=>{const b=Buffer.alloc(16);b.writeUInt16LE(2);b.writeUInt16BE(port,2);ipv4(ip).copy(b,4);return b;};
12
+ export function openTransport(name,{index}) {
13
+ const f=libc.symbols, descriptors=[];
14
+ try {
15
+ // Packet sockets work before the interface has any IPv4 address or route.
16
+ const raw=check('socket(AF_PACKET): requires CAP_NET_RAW',f.socket(17,2|0x800|0x80000,0x0008));descriptors.push(raw);
17
+ const link=Buffer.alloc(20);link.writeUInt16LE(17);link.writeUInt16BE(0x0800,2);link.writeInt32LE(index,4);link[11]=6;link.fill(255,12,18);
18
+ check('bind(AF_PACKET)',f.bind(raw,ptr(link),link.length));
19
+ // Reserve the client UDP port and use kernel routing/ARP for unicast renew
20
+ // and release. The packet socket receives both broadcast and unicast replies.
21
+ const udp=check('socket(UDP)',f.socket(2,2|0x800|0x80000,17));descriptors.push(udp);
22
+ const device=cString(name);check('SO_BINDTODEVICE',f.setsockopt(udp,1,25,ptr(device),device.length));
23
+ const local=sockaddr('0.0.0.0',68);check('bind UDP port 68 (another DHCP client may be running)',f.bind(udp,ptr(local),16));
24
+ const buffer=Buffer.alloc(65535);
25
+ return {
26
+ send(payload,server){
27
+ const destination=server?sockaddr(server,67):link;
28
+ const bytes=server?payload:wrapIPv4(payload,payload.subarray(12,16).join('.'));
29
+ check('sendto(DHCP)',Number(f.sendto(server?udp:raw,ptr(bytes),bytes.length,0,ptr(destination),destination.length)));
30
+ },
31
+ receive(){
32
+ // Drain the UDP copy so its receive buffer does not remain full.
33
+ for(let i=0;i<64;i++)if(f.recv(udp,ptr(buffer),buffer.length,0)<0)break;
34
+ for(let i=0;i<64;i++){
35
+ const n=f.recv(raw,ptr(buffer),buffer.length,0);
36
+ if(n<0){const code=errno();if(code===11||code===4)return null;throw new SysError('recv(DHCP)',code);}
37
+ const payload=unwrapIPv4(buffer.subarray(0,Number(n)));if(payload)return Buffer.from(payload);
38
+ }
39
+ return null;
40
+ },
41
+ close(){for(const fd of descriptors.splice(0))f.close(fd);},
42
+ };
43
+ }catch(error){for(const fd of descriptors)f.close(fd);throw error;}
44
+ }