buninu-linux 0.3.0 → 0.3.1
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 +179 -22
- package/fetch-alpine.sh +9 -4
- package/initramfs/bin/ip +2 -1
- package/initramfs/bin/mount +2 -1
- package/initramfs/bin/poweroff +6 -27
- package/initramfs/bin/ps +256 -0
- package/initramfs/bin/reboot +10 -0
- package/initramfs/bin/tar +169 -0
- package/initramfs/bin/top +129 -0
- package/initramfs/bin/umount +2 -1
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +36 -0
- package/initramfs/buninu/apps/bunmsh/README.md +7 -0
- package/initramfs/buninu/apps/bunmsh/package.json +1 -1
- package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +29 -14
- package/initramfs/buninu/apps/bunmsh/src/line-edit.js +43 -0
- package/initramfs/buninu/apps/bunmsh/src/main.js +15 -1
- package/initramfs/buninu/apps/bunmsh/src/mouse.js +29 -4
- package/initramfs/buninu/apps/bunmsh/src/shell.js +9 -1
- package/initramfs/buninu/apps/bunmsh/test/shell1.test.js +70 -0
- package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +10 -0
- package/initramfs/buninu/apps/jsmdcui/README.md +2 -0
- package/initramfs/buninu/apps/jsmdcui/package.json +1 -1
- package/initramfs/buninu/apps/jsmdcui/runtime/help/commands.md +4 -1
- package/initramfs/buninu/apps/jsmdcui/runtime/help/help.md +2 -0
- package/initramfs/buninu/apps/jsmdcui/src/index.js +40 -13
- package/initramfs/buninu/package.json +1 -1
- package/initramfs/lib/dlopen.js +0 -9
- package/initramfs/lib/document.js +6 -0
- package/initramfs/lib/power.js +35 -0
- package/initramfs/usr/share/doc/buninu-linux/ps.md +94 -0
- package/initramfs/usr/share/doc/buninu-linux/reboot.md +15 -0
- package/initramfs/usr/share/doc/buninu-linux/tar.md +73 -0
- package/initramfs/usr/share/doc/buninu-linux/top.md +23 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,14 +25,17 @@
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
+
- [Table of contents](#contents)
|
|
29
|
+
- .
|
|
28
30
|
- Still in the early stages
|
|
29
31
|
- [Video here](https://www.reddit.com/r/bun/comments/1wkpraj/buninu_linux_a_distro_with_bun_as_pid_1): Booted successfully on real x86-64 UEFI hardware with `--real`:
|
|
30
32
|
* Bun reaches its interactive REPL
|
|
31
33
|
* start() starts the Buninu userspace shell
|
|
32
34
|
* `cfg.disk` + shell `mount` mounts local disks
|
|
33
|
-
* `cfg.net` loads common wired NIC drivers
|
|
35
|
+
* `cfg.net` loads common wired NIC and Android USB-tethering drivers
|
|
36
|
+
* Android phone USB tethering over RNDIS has been tested successfully, allowing Buninu Linux to access the Internet through an Android phone
|
|
34
37
|
* IP addresses and routes are configured manually because the image does not yet include a DHCP client.
|
|
35
|
-
* The bundled `jmi` editor,
|
|
38
|
+
* The bundled `jmi` editor, jsmdcui editor/terminal multitasking, and local JavaScript execution also work.
|
|
36
39
|
|
|
37
40
|
---
|
|
38
41
|
|
|
@@ -130,20 +133,20 @@ whitespace-only line prints the welcome and current `cfg.*` getter list again.
|
|
|
130
133
|
Load only the subsystem needed, then enter the Buninu shell:
|
|
131
134
|
|
|
132
135
|
```js
|
|
133
|
-
cfg.net // wired NIC drivers
|
|
136
|
+
cfg.net // wired NIC and Android USB-tethering drivers
|
|
134
137
|
cfg.disk // SATA/NVMe/USB storage drivers and detected block devices
|
|
135
138
|
cfg.power // battery, AC, button and thermal drivers
|
|
136
139
|
start()
|
|
137
140
|
```
|
|
138
141
|
|
|
139
142
|
There is no DHCP client yet; configure a detected wired interface with `ip` as
|
|
140
|
-
described under [
|
|
143
|
+
described under [Basic configuration](#basic-configuration). Run `poweroff` for a synced
|
|
141
144
|
shutdown. After editing the initramfs, `bun ./index.js -b --real --export` rebuilds the
|
|
142
145
|
image; `-h` lists all flags.
|
|
143
146
|
|
|
144
|
-
##
|
|
147
|
+
## Using Buninu Linux
|
|
145
148
|
|
|
146
|
-
###
|
|
149
|
+
### Basic configuration
|
|
147
150
|
|
|
148
151
|
Real hardware is the primary target. Boot the USB drive with Secure Boot
|
|
149
152
|
disabled unless you have signed the UKI yourself. The physical display and USB
|
|
@@ -157,7 +160,7 @@ cfg.disk
|
|
|
157
160
|
cfg.power
|
|
158
161
|
```
|
|
159
162
|
|
|
160
|
-
`cfg.net` loads the packaged wired-network drivers and prints detected
|
|
163
|
+
`cfg.net` loads the packaged wired-network and Android USB-tethering drivers and prints detected
|
|
161
164
|
interfaces and MAC addresses. There is no DHCP client yet, so configure the
|
|
162
165
|
interface manually after entering the Buninu shell with `start()`:
|
|
163
166
|
|
|
@@ -178,6 +181,8 @@ mount -fv /dev/sda1 /mnt
|
|
|
178
181
|
|
|
179
182
|
Run `poweroff` for a synced shutdown.
|
|
180
183
|
|
|
184
|
+
### Editor quick start
|
|
185
|
+
|
|
181
186
|
The bundled `jmi` terminal editor works without a network connection:
|
|
182
187
|
|
|
183
188
|
```sh
|
|
@@ -203,19 +208,107 @@ After leaving the editor with `Ctrl-Q`, run it locally:
|
|
|
203
208
|
bun hlw.js
|
|
204
209
|
```
|
|
205
210
|
|
|
206
|
-
|
|
211
|
+
If `Enter` is not recognized in a particular terminal, try `Ctrl-J` or
|
|
212
|
+
`Ctrl-M`.
|
|
213
|
+
|
|
214
|
+
### Panes, terminals, and tabs
|
|
215
|
+
|
|
216
|
+
`jmi` and jsmdcui can keep editors and terminal sessions open together. Press
|
|
217
|
+
`Ctrl-E`, type `term`, and press `Enter` to open the default Buninu shell in a
|
|
218
|
+
terminal pane. `term COMMAND` starts a specific command instead. Use `vsplit`,
|
|
219
|
+
`hsplit`, or `tab` from the same `Ctrl-E` command prompt to create another
|
|
220
|
+
editor pane or tab; each command optionally accepts a filename. From an editor
|
|
221
|
+
pane, `Ctrl-T` is the direct shortcut for a new empty tab.
|
|
222
|
+
|
|
223
|
+
#### Pane and tab controls
|
|
224
|
+
|
|
225
|
+
| Input | Result |
|
|
226
|
+
| --- | --- |
|
|
227
|
+
| Click a pane | Focus that editor or terminal pane. |
|
|
228
|
+
| `Ctrl-T` in an editor pane | Open a new empty tab. |
|
|
229
|
+
| `Ctrl-W` once | Move to the next pane in the current tab, from either an editor or terminal pane. |
|
|
230
|
+
| `Alt-T` | Move to the next tab. |
|
|
231
|
+
| **`Ctrl-W` twice quickly** | **Important escape hatch:** move to the next tab like `Alt-T`, or create an editor tab when needed. See the detailed explanation below. |
|
|
232
|
+
| `Esc` in a terminal pane | Close that terminal pane and return to its previous editor buffer |
|
|
233
|
+
| `Ctrl-Q` or `Alt-Q` in an editor pane | Close the current editor UI. |
|
|
234
|
+
|
|
235
|
+
**Why this escape hatch is important:** A terminal pane must forward almost every key combination unchanged to the
|
|
236
|
+
shell or application running inside it. `Ctrl-W` is therefore the one
|
|
237
|
+
multitasking escape key that jsmdcui intercepts: it lets you leave the active
|
|
238
|
+
terminal without terminating the work inside it. Press it once to move to the
|
|
239
|
+
next pane in the current tab. Press it twice quickly to move to the next tab.
|
|
240
|
+
|
|
241
|
+
The double press is especially important in an all-terminal workspace. When
|
|
242
|
+
the current tab is the rightmost tab and every existing tab contains only
|
|
243
|
+
terminal panes, it creates a new editor tab instead of wrapping back to the
|
|
244
|
+
first terminal tab. In every other case it performs the normal next-tab cycle.
|
|
245
|
+
Use `Esc` only when you actually want to close the current terminal pane.
|
|
246
|
+
|
|
247
|
+
### Using Bun Modern Shell
|
|
248
|
+
|
|
249
|
+
bunmsh is the dependency-free, mksh-inspired shell bundled with Buninu. Its
|
|
250
|
+
main features are:
|
|
251
|
+
|
|
252
|
+
- Lines beginning with `Bun.*` run as JavaScript; returned values are printed
|
|
253
|
+
and promises are awaited automatically. Use `Bun.e;` or `Bun.e,` for
|
|
254
|
+
arbitrary JavaScript.
|
|
255
|
+
- Lightweight cwd tabs keep several working directories ready. In bunmsh,
|
|
256
|
+
`Ctrl-T` creates or cycles right through tabs, while `Alt-T` cycles left.
|
|
257
|
+
* Also useful cmds: `tab n`, `tab c`(Alt-C)
|
|
258
|
+
- Shell variables and JavaScript share a live variable table by `$.`, and `Bun.sha`
|
|
259
|
+
can retain JavaScript values across commands and cwd tabs.
|
|
260
|
+
- Saved history, Bash/Fish history import, completions, and ghost suggestions
|
|
261
|
+
are available interactively.
|
|
262
|
+
- `serve [directory]` starts a browsable HTTP file server. Together with
|
|
263
|
+
Android USB tethering, it provides a simple way to transfer files between
|
|
264
|
+
Buninu Linux and the connected phone.
|
|
265
|
+
- Cross-platform builtins and PATH-fallback commands.
|
|
266
|
+
- `Ctrl-U` removes and saves the text before the cursor; press it again at the
|
|
267
|
+
beginning of the line to restore it. `Ctrl-K` independently toggles the text
|
|
268
|
+
after the cursor when at the end of the line.
|
|
269
|
+
- Unquoted pathname patterns use `Bun.Glob` and follow directory symbolic
|
|
270
|
+
links used as intermediate path components, as traditional shells do. This
|
|
271
|
+
matters for sysfs, where class entries are normally links. For example:
|
|
207
272
|
|
|
208
|
-
|
|
273
|
+
```sh
|
|
274
|
+
ls /sys/class/net/*/device
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
For a readable process overview, run:
|
|
278
|
+
|
|
279
|
+
```sh
|
|
280
|
+
pspac
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`pspac` shows the real `PID COMMAND` process table and highlights each command
|
|
284
|
+
line as shell syntax with micro's Monokai colours. Leading directories are
|
|
285
|
+
dimmed so the program name stands out. Use `pspa` for the same table as plain
|
|
286
|
+
text.
|
|
287
|
+
|
|
288
|
+
See the [bunmsh repository](https://github.com/jjtseng93/bunmsh) for its full
|
|
289
|
+
syntax, interactive controls, builtins, and current compatibility details.
|
|
209
290
|
|
|
210
|
-
|
|
211
|
-
implemented as Bun scripts:
|
|
291
|
+
## Commands inside /bin
|
|
212
292
|
|
|
213
293
|
| command | does | manual |
|
|
214
294
|
| --- | --- | --- |
|
|
215
295
|
| `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` |
|
|
216
296
|
| `umount` | `umount2(2)` with `-l`, `-f`, `-R` | `umount --help` |
|
|
217
297
|
| `ip` | iproute2 grammar over `SIOC*` ioctls and `/proc/net`: `link`, `addr`, `route`, `neigh` | `ip --help` |
|
|
298
|
+
| `ps` | composable process fields from `/proc`; also exports its snapshot and single-row formatter for other commands | `ps --help` |
|
|
299
|
+
| `top` | periodically refreshes the process snapshot and rows supplied by `ps` | `top --help` |
|
|
218
300
|
| `poweroff` | sync pending writes and power off through the Linux reboot system call | `poweroff --help` |
|
|
301
|
+
| `reboot` | sync pending writes and restart through the shared Linux reboot logic | `reboot --help` |
|
|
302
|
+
| `tar` | create, extract, or list tar archives with gzip and zstd compression through `Bun.Archive` | `tar --help` |
|
|
303
|
+
|
|
304
|
+
Besides `bun` (and `sh`/`node` pointing at it), `/bin` includes eight commands
|
|
305
|
+
implemented as Bun scripts.
|
|
306
|
+
|
|
307
|
+
The small `tar` follows the current `Bun.Archive` boundary: extraction
|
|
308
|
+
restores directories and symbolic links, while hard links are skipped by Bun
|
|
309
|
+
1.4.3. Creation stores regular files but not Unix metadata, links, or empty
|
|
310
|
+
directories, and listing reports regular files only. It supports gzip and
|
|
311
|
+
Bun's built-in zstd, not xz or bzip2.
|
|
219
312
|
|
|
220
313
|
`--help` renders each Markdown manual in the terminal. Common examples:
|
|
221
314
|
|
|
@@ -224,6 +317,8 @@ mount /dev/sda1 /mnt --mkdir
|
|
|
224
317
|
mount -fv /dev/sda1 /mnt
|
|
225
318
|
mount -t tmpfs -o size=64M tmpfs /tmp/x
|
|
226
319
|
mount -t ntfs3 -o force /dev/sda3 /mnt/windows
|
|
320
|
+
umount /mnt
|
|
321
|
+
umount -R /mnt
|
|
227
322
|
|
|
228
323
|
ip link set eth0 up
|
|
229
324
|
ip addr add 192.168.1.50/24 dev eth0
|
|
@@ -233,7 +328,23 @@ ip -br addr && ip route
|
|
|
233
328
|
ip addr replace 192.168.1.50/24 dev eth0
|
|
234
329
|
ip route replace default via 192.168.1.1
|
|
235
330
|
|
|
331
|
+
ps -eo pid,args
|
|
332
|
+
ps -ef
|
|
333
|
+
ps aux
|
|
334
|
+
pspac
|
|
335
|
+
top
|
|
336
|
+
top -b -n 1
|
|
337
|
+
|
|
236
338
|
poweroff
|
|
339
|
+
reboot
|
|
340
|
+
|
|
341
|
+
tar cvf /tmp/buninu.tar README.md package.json
|
|
342
|
+
tar tvf /tmp/buninu.tar
|
|
343
|
+
mkdir -p /tmp/buninu-copy
|
|
344
|
+
tar xvf /tmp/buninu.tar -C /tmp/buninu-copy
|
|
345
|
+
|
|
346
|
+
# Add z to create a gzip-compressed archive
|
|
347
|
+
tar czvf /tmp/buninu.tar.gz README.md package.json
|
|
237
348
|
```
|
|
238
349
|
|
|
239
350
|
## Environment and dependencies
|
|
@@ -340,6 +451,8 @@ bun ./index.js -fb --real --export
|
|
|
340
451
|
`index.js` is the single entry point (`npm run fetch` and `npm run pack` call
|
|
341
452
|
it too). Its physical-image stages run as fetch → build:
|
|
342
453
|
|
|
454
|
+
#### Build flags
|
|
455
|
+
|
|
343
456
|
| flag | runs | use it when |
|
|
344
457
|
| --- | --- | --- |
|
|
345
458
|
| `-f`, `--fetch` | `fetch-alpine.sh`, `fetch-bun.sh` | first clone, or after bumping a pinned version |
|
|
@@ -356,8 +469,8 @@ bun x buninu-linux -fb --real --export
|
|
|
356
469
|
|
|
357
470
|
`--linux-lts` selects Alpine's general-purpose LTS kernel. `--real` implies
|
|
358
471
|
`--linux-lts` and builds for physical hardware: it makes `tty0` the primary
|
|
359
|
-
console and includes xHCI/USB HID, common wired
|
|
360
|
-
modules needed by typical laptops and desktops.
|
|
472
|
+
console and includes xHCI/USB HID, common wired NIC, Android USB-tethering,
|
|
473
|
+
storage and ACPI power modules needed by typical laptops and desktops.
|
|
361
474
|
|
|
362
475
|
For real hardware, `-fb --real` is the complete image pipeline and
|
|
363
476
|
`-b --real` is the edit-and-rebuild loop. Before starting, `index.js` checks
|
|
@@ -396,6 +509,8 @@ The primary `bun ./index.js -fb --real` pipeline runs the steps below in order;
|
|
|
396
509
|
each writes files the next one reads. When selected, `--export` runs afterward
|
|
397
510
|
and copies the final `vda.img` to the invocation directory.
|
|
398
511
|
|
|
512
|
+
##### Pipeline inputs and outputs
|
|
513
|
+
|
|
399
514
|
| script | reads | writes |
|
|
400
515
|
| --- | --- | --- |
|
|
401
516
|
| `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 |
|
|
@@ -423,6 +538,8 @@ boot.
|
|
|
423
538
|
`build-uki.sh` appends sections to systemd's `linuxx64.efi.stub` with
|
|
424
539
|
`objcopy`, at hand-picked VMAs because `objcopy` will not lay them out for you:
|
|
425
540
|
|
|
541
|
+
##### UKI sections
|
|
542
|
+
|
|
426
543
|
| section | offset from image base | contents | size |
|
|
427
544
|
| --- | --- | --- | --- |
|
|
428
545
|
| `.text` | +0x0 | the stub itself | 66 KB |
|
|
@@ -589,7 +706,7 @@ Which modules the image carries is the list in `fetch-alpine.sh`; the
|
|
|
589
706
|
`select_module` helper there pulls in dependencies from Alpine's
|
|
590
707
|
`modules.dep`, and `--real` adds common storage paths (`sd_mod`, `ahci`,
|
|
591
708
|
`ata_generic`, `pata_acpi`, `nvme`, Intel `vmd`, `usb-storage` and `uas`),
|
|
592
|
-
the wired NICs and the ACPI power drivers. Alpine's
|
|
709
|
+
the wired NICs, Android USB-tethering drivers and the ACPI power drivers. Alpine's
|
|
593
710
|
kernels build all of these as modules; a self-built kernel with them `=y`
|
|
594
711
|
works the same way, the loaders just find nothing to load.
|
|
595
712
|
|
|
@@ -685,8 +802,10 @@ loads the packaged SATA/PATA/SCSI, NVMe/VMD and USB storage stacks so
|
|
|
685
802
|
their disks and partitions appear in `/dev`, then lists `/sys/class/block`. `cfg.all`
|
|
686
803
|
unconditionally attempts every packaged module and recursively loads its
|
|
687
804
|
declared dependencies. `--real` ships Intel `e1000`/`e1000e`/`igb`/`igc`,
|
|
688
|
-
Realtek `r8169`, Atheros `alx`, Broadcom `tg3
|
|
689
|
-
`cdc_ether`
|
|
805
|
+
Realtek `r8169`, Atheros `alx`, Broadcom `tg3`, common USB dongles, and
|
|
806
|
+
`rndis_host`/`cdc_ether`/`cdc_ncm`/`cdc_eem`/`cdc_subset`/`zaurus` for Android
|
|
807
|
+
USB tethering. `cfg.net` unconditionally attempts all of these packaged
|
|
808
|
+
network modules. `cfg.power` loads the ACPI
|
|
690
809
|
`battery`, `ac`, `button` and `thermal` modules through `/lib/modprobe.js`
|
|
691
810
|
and lists `/sys/class/power_supply` (a desktop without a battery simply
|
|
692
811
|
reports nothing registered). Nothing loads them at boot. PID 1 also installs
|
|
@@ -694,12 +813,48 @@ reports nothing registered). Nothing loads them at boot. PID 1 also installs
|
|
|
694
813
|
`Promise.reject()` at the prompt makes Bun exit, which is a kernel panic
|
|
695
814
|
(`Attempted to kill init!`).
|
|
696
815
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
816
|
+
`start()` launches the bundled Buninu userspace and its bunmsh (Bun Modern Shell) with
|
|
817
|
+
the current process environment preserved, while overriding `PATH` and `HOME`
|
|
818
|
+
for the userspace session.
|
|
819
|
+
|
|
820
|
+
## Contents
|
|
821
|
+
|
|
822
|
+
- [Quick start](#quick-start)
|
|
823
|
+
* [Steps](#steps)
|
|
824
|
+
+ [Build directly with bun x](#build-directly-with-bun-x)
|
|
825
|
+
+ [Or build from a source checkout](#or-build-from-a-source-checkout)
|
|
826
|
+
+ [Write the image to a USB drive](#write-the-image-to-a-usb-drive)
|
|
827
|
+
+ [Booting from the USB drive](#booting-from-the-usb-drive)
|
|
828
|
+
- [Using Buninu Linux](#using-buninu-linux)
|
|
829
|
+
* [Basic configuration](#basic-configuration)
|
|
830
|
+
* [Editor quick start](#editor-quick-start)
|
|
831
|
+
* [Panes, terminals, and tabs](#panes-terminals-and-tabs)
|
|
832
|
+
+ [Pane and tab controls](#pane-and-tab-controls)
|
|
833
|
+
* [Using Bun Modern Shell](#using-bun-modern-shell)
|
|
834
|
+
- [Commands inside /bin](#commands-inside-bin)
|
|
835
|
+
- [Environment and dependencies](#environment-and-dependencies)
|
|
836
|
+
* [Build environment](#build-environment)
|
|
837
|
+
* [Buninu Linux (Section 2)](#buninu-linux-section-2)
|
|
838
|
+
* [Hello world (Section 1)](#hello-world-section-1)
|
|
839
|
+
* [Retired native bootstrap](#retired-native-bootstrap)
|
|
840
|
+
- [Implementation details](#implementation-details)
|
|
841
|
+
* [1. Hello world](#1-hello-world)
|
|
842
|
+
* [2. Buninu Linux – Bun as PID 1](#2-buninu-linux-bun-as-pid-1)
|
|
843
|
+
+ [Build flags](#build-flags)
|
|
844
|
+
+ [Build pipeline](#build-pipeline)
|
|
845
|
+
- [Pipeline inputs and outputs](#pipeline-inputs-and-outputs)
|
|
846
|
+
+ [UKI layout](#uki-layout)
|
|
847
|
+
- [UKI sections](#uki-sections)
|
|
848
|
+
+ [Disk layout](#disk-layout)
|
|
849
|
+
+ [Real-hardware boot details](#real-hardware-boot-details)
|
|
850
|
+
+ [QEMU development](#qemu-development)
|
|
851
|
+
- [QEMU networking](#qemu-networking)
|
|
852
|
+
+ [Command implementation](#command-implementation)
|
|
853
|
+
+ [Reaching JavaScript with nothing mounted](#reaching-javascript-with-nothing-mounted)
|
|
854
|
+
+ [What init.js does](#what-initjs-does)
|
|
855
|
+
- [Authorship](#authorship)
|
|
856
|
+
- [License](#license)
|
|
857
|
+
* [Bundled component licenses](#bundled-component-licenses)
|
|
703
858
|
|
|
704
859
|
## Authorship
|
|
705
860
|
|
|
@@ -721,6 +876,8 @@ The repository also commits four third-party platform binaries under
|
|
|
721
876
|
`initramfs/lib/`, and the built image redistributes several more. Each stays
|
|
722
877
|
under its own terms, with the full texts in [`LICENSES/`](LICENSES/):
|
|
723
878
|
|
|
879
|
+
### Bundled component licenses
|
|
880
|
+
|
|
724
881
|
| Component | License | Where |
|
|
725
882
|
|---|---|---|
|
|
726
883
|
| musl (`ld-musl-x86_64.so.1`, `libc.musl-x86_64.so.1`) | MIT | committed |
|
package/fetch-alpine.sh
CHANGED
|
@@ -79,10 +79,15 @@ if [ "${REAL_MACHINE:-}" = 1 ]; then
|
|
|
79
79
|
select_module "$module"
|
|
80
80
|
done
|
|
81
81
|
# Wired NICs: Intel (e1000/e1000e/igb/igc), Realtek (r8169 plus its PHY
|
|
82
|
-
# driver), Qualcomm Atheros (alx), Broadcom (tg3), and USB dongles
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
|
|
82
|
+
# driver), Qualcomm Atheros (alx), Broadcom (tg3), and USB dongles.
|
|
83
|
+
# Android USB tethering commonly presents RNDIS, CDC ECM/NCM/EEM, or an
|
|
84
|
+
# older CDC subset/zaurus-compatible gadget. Selecting the leaf drivers
|
|
85
|
+
# recursively includes usbnet, mii, usbcore and usb-common from
|
|
86
|
+
# modules.dep. cfg.net unconditionally attempts every packaged
|
|
87
|
+
# kernel/drivers/net module, then matches USB and PCI modaliases.
|
|
88
|
+
for module in e1000 e1000e igb igc r8169 realtek alx tg3 \
|
|
89
|
+
r8152 ax88179_178a cdc_ether rndis_host cdc_ncm cdc_eem \
|
|
90
|
+
cdc_subset zaurus; do
|
|
86
91
|
select_module "$module"
|
|
87
92
|
done
|
|
88
93
|
fi
|
package/initramfs/bin/ip
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// shown but not configured.
|
|
10
10
|
|
|
11
11
|
import { readdirSync, readFileSync } from "node:fs";
|
|
12
|
-
import { cString, ptr, withInetSocket, Errno, SysError
|
|
12
|
+
import { cString, ptr, withInetSocket, Errno, SysError } from "../lib/dlopen.js";
|
|
13
|
+
import { showDocument } from "../lib/document.js";
|
|
13
14
|
|
|
14
15
|
const fail = (message, status = 1) => {
|
|
15
16
|
console.error(`ip: ${message}`);
|
package/initramfs/bin/mount
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
// is not there yet.
|
|
11
11
|
|
|
12
12
|
import { existsSync, mkdirSync, openSync, readSync, closeSync, readdirSync, readFileSync } from "node:fs";
|
|
13
|
-
import { libc, cString, ptr, errno, strerror, Errno, SysError
|
|
13
|
+
import { libc, cString, ptr, errno, strerror, Errno, SysError } from "../lib/dlopen.js";
|
|
14
|
+
import { showDocument } from "../lib/document.js";
|
|
14
15
|
import { modprobe, tryModprobe, modulesDirectory, ModuleNotFound } from "../lib/modprobe.js";
|
|
15
16
|
|
|
16
17
|
const fail = (message, status = 32) => {
|
package/initramfs/bin/poweroff
CHANGED
|
@@ -1,31 +1,10 @@
|
|
|
1
1
|
#!/bin/bun
|
|
2
2
|
// poweroff — sync filesystems and power off through Linux reboot(2).
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { LINUX_REBOOT_CMD_POWER_OFF, runPowerCommand } from "../lib/power.js";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
process.exit(0);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (arguments_.length !== 0) {
|
|
15
|
-
console.error("Usage: poweroff [-h | --help]");
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
console.log("poweroff: syncing filesystems");
|
|
20
|
-
libc.symbols.sync();
|
|
21
|
-
console.log("poweroff: powering off");
|
|
22
|
-
try {
|
|
23
|
-
check("reboot(LINUX_REBOOT_CMD_POWER_OFF)", libc.symbols.reboot(LINUX_REBOOT_CMD_POWER_OFF));
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error(`poweroff: ${error.message}`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// A successful reboot(2) never returns.
|
|
30
|
-
console.error("poweroff: reboot returned without powering off");
|
|
31
|
-
process.exit(1);
|
|
6
|
+
await runPowerCommand({
|
|
7
|
+
name: "poweroff",
|
|
8
|
+
command: { name: "LINUX_REBOOT_CMD_POWER_OFF", value: LINUX_REBOOT_CMD_POWER_OFF },
|
|
9
|
+
action: "powering off",
|
|
10
|
+
});
|
package/initramfs/bin/ps
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/bin/bun
|
|
2
|
+
// ps — a /proc process snapshot, formatter, and command-line frontend.
|
|
3
|
+
|
|
4
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
5
|
+
import { showDocument } from "../lib/document.js";
|
|
6
|
+
|
|
7
|
+
export const CLOCK_TICKS = 100;
|
|
8
|
+
|
|
9
|
+
const read = (path) => readFileSync(path, "utf8");
|
|
10
|
+
const numberFrom = (text, pattern) => Number(pattern.exec(text)?.[1] ?? 0);
|
|
11
|
+
|
|
12
|
+
const ttyName = (number) => {
|
|
13
|
+
if (!number) return "?";
|
|
14
|
+
const device = number >>> 0;
|
|
15
|
+
const major = (device >>> 8) & 0xfff;
|
|
16
|
+
const minor = (device & 0xff) | ((device >>> 12) & 0xfff00);
|
|
17
|
+
if (major >= 136 && major <= 143) return `pts/${minor}`;
|
|
18
|
+
if (major === 4 && minor < 64) return `tty${minor}`;
|
|
19
|
+
if (major === 5 && minor === 0) return "tty";
|
|
20
|
+
return `${major}:${minor}`;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const cpuTime = (seconds) => {
|
|
24
|
+
const whole = Math.max(0, Math.floor(seconds));
|
|
25
|
+
const hours = Math.floor(whole / 3600);
|
|
26
|
+
const minutes = Math.floor(whole % 3600 / 60);
|
|
27
|
+
const secs = whole % 60;
|
|
28
|
+
return hours
|
|
29
|
+
? `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`
|
|
30
|
+
: `${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const elapsedTime = (seconds) => {
|
|
34
|
+
const whole = Math.max(0, Math.floor(seconds));
|
|
35
|
+
const days = Math.floor(whole / 86400);
|
|
36
|
+
const clock = [Math.floor(whole % 86400 / 3600), Math.floor(whole % 3600 / 60), whole % 60]
|
|
37
|
+
.map((part) => String(part).padStart(2, "0")).join(":");
|
|
38
|
+
return days ? `${days}-${clock}` : clock;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const startText = (started, short = false) => {
|
|
42
|
+
const date = new Date(started);
|
|
43
|
+
const now = new Date();
|
|
44
|
+
if (date.toDateString() === now.toDateString())
|
|
45
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
46
|
+
if (short || date.getFullYear() === now.getFullYear())
|
|
47
|
+
return date.toLocaleString("en-US", { month: "short", day: "2-digit" }).replace(" ", "");
|
|
48
|
+
return String(date.getFullYear());
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const readUsers = () => {
|
|
52
|
+
const users = new Map();
|
|
53
|
+
try {
|
|
54
|
+
for (const line of read("/etc/passwd").split("\n")) {
|
|
55
|
+
const fields = line.split(":");
|
|
56
|
+
if (/^\d+$/.test(fields[2] ?? "") && !users.has(Number(fields[2])))
|
|
57
|
+
users.set(Number(fields[2]), fields[0]);
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
return users;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const readMemory = () => {
|
|
64
|
+
const result = {};
|
|
65
|
+
try {
|
|
66
|
+
for (const match of read("/proc/meminfo").matchAll(/^(\w+):\s+(\d+)/gm))
|
|
67
|
+
result[match[1]] = Number(match[2]);
|
|
68
|
+
} catch {}
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const readCpu = () => {
|
|
73
|
+
try {
|
|
74
|
+
const values = read("/proc/stat").match(/^cpu\s+(.+)$/m)?.[1].trim().split(/\s+/).map(Number) ?? [];
|
|
75
|
+
const total = values.reduce((sum, value) => sum + value, 0);
|
|
76
|
+
return { total, idle: (values[3] ?? 0) + (values[4] ?? 0), count: Math.max(1, read("/proc/stat").match(/^cpu\d+\s/gm)?.length ?? 1) };
|
|
77
|
+
} catch { return { total: 0, idle: 0, count: 1 }; }
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export function readProcessSnapshot() {
|
|
81
|
+
const users = readUsers();
|
|
82
|
+
const memory = readMemory();
|
|
83
|
+
let uptime = 0;
|
|
84
|
+
let bootSeconds = 0;
|
|
85
|
+
let loadAverage = [0, 0, 0];
|
|
86
|
+
try { uptime = Number(read("/proc/uptime").split(/\s+/)[0]); } catch {}
|
|
87
|
+
try { bootSeconds = numberFrom(read("/proc/stat"), /^btime\s+(\d+)/m); } catch {}
|
|
88
|
+
try { loadAverage = read("/proc/loadavg").trim().split(/\s+/).slice(0, 3).map(Number); } catch {}
|
|
89
|
+
const bootTime = bootSeconds ? bootSeconds * 1000 : Date.now() - uptime * 1000;
|
|
90
|
+
let pids = [];
|
|
91
|
+
try {
|
|
92
|
+
pids = readdirSync("/proc", { withFileTypes: true })
|
|
93
|
+
.filter((entry) => entry.isDirectory() && /^\d+$/.test(entry.name))
|
|
94
|
+
.map((entry) => Number(entry.name)).sort((a, b) => a - b);
|
|
95
|
+
} catch (error) { throw new Error(`cannot read /proc: ${error.message}`); }
|
|
96
|
+
|
|
97
|
+
const processes = [];
|
|
98
|
+
for (const pid of pids) {
|
|
99
|
+
try {
|
|
100
|
+
let args = readFileSync(`/proc/${pid}/cmdline`).toString("utf8").replace(/\0/g, " ").replace(/[\r\n]+/g, " ").trimEnd();
|
|
101
|
+
if (!args) args = `[${read(`/proc/${pid}/comm`).trim().replace(/[\r\n]+/g, " ")}]`;
|
|
102
|
+
const status = read(`/proc/${pid}/status`);
|
|
103
|
+
const stat = read(`/proc/${pid}/stat`);
|
|
104
|
+
const close = stat.lastIndexOf(")");
|
|
105
|
+
if (close < 0) continue;
|
|
106
|
+
const open = stat.indexOf("(");
|
|
107
|
+
const fields = stat.slice(close + 2).trim().split(/\s+/);
|
|
108
|
+
const command = stat.slice(open + 1, close);
|
|
109
|
+
const uid = numberFrom(status, /^Uid:\s+(\d+)/m);
|
|
110
|
+
const vmSize = numberFrom(status, /^VmSize:\s+(\d+)/m);
|
|
111
|
+
const rss = numberFrom(status, /^VmRSS:\s+(\d+)/m);
|
|
112
|
+
const ppid = Number(fields[1]);
|
|
113
|
+
const session = Number(fields[3]);
|
|
114
|
+
const ttyNumber = Number(fields[4]);
|
|
115
|
+
// /proc/PID/stat field 9 is flags. `fields` starts at field 3, so
|
|
116
|
+
// index 6 contains PF_KTHREAD and does not rely on guessing from argv.
|
|
117
|
+
const kernelThread = (Number(fields[6]) & 0x00200000) !== 0;
|
|
118
|
+
const ticks = Number(fields[11]) + Number(fields[12]);
|
|
119
|
+
const nice = Number(fields[16]);
|
|
120
|
+
const threads = Number(fields[17]);
|
|
121
|
+
const started = bootTime + Number(fields[19]) / CLOCK_TICKS * 1000;
|
|
122
|
+
const elapsed = Math.max(0.01, (Date.now() - started) / 1000);
|
|
123
|
+
let state = fields[0];
|
|
124
|
+
if (nice < 0) state += "<"; else if (nice > 0) state += "N";
|
|
125
|
+
if (session === pid) state += "s";
|
|
126
|
+
if (threads > 1) state += "l";
|
|
127
|
+
if (Number(fields[5]) > 0 && Number(fields[5]) === Number(fields[2])) state += "+";
|
|
128
|
+
processes.push({
|
|
129
|
+
pid, ppid, uid, user: users.get(uid) ?? String(uid), args, command,
|
|
130
|
+
tty: ttyName(ttyNumber), ttyNumber, session, state, kernelThread,
|
|
131
|
+
processor: Number(fields[36] ?? 0), priority: Number(fields[15]), nice, threads, ticks,
|
|
132
|
+
cpu: ticks / CLOCK_TICKS / elapsed * 100,
|
|
133
|
+
memory: memory.MemTotal ? rss / memory.MemTotal * 100 : 0,
|
|
134
|
+
vmSize, rss, size: Math.ceil(Number(fields[20]) / 4096),
|
|
135
|
+
time: cpuTime(ticks / CLOCK_TICKS), elapsed, started,
|
|
136
|
+
});
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
139
|
+
return { processes, memory, cpu: readCpu(), uptime, loadAverage, at: Date.now() };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const processFieldDefinitions = {
|
|
143
|
+
pid: { header: "PID", numeric: true, minimum: 5, value: (r) => r.pid },
|
|
144
|
+
ppid: { header: "PPID", numeric: true, minimum: 5, value: (r) => r.ppid },
|
|
145
|
+
uid: { header: "UID", numeric: true, minimum: 5, value: (r) => r.uid },
|
|
146
|
+
user: { header: "USER", value: (r) => r.user }, euser: { header: "EUSER", value: (r) => r.user },
|
|
147
|
+
comm: { header: "COMMAND", value: (r) => r.command }, args: { header: "COMMAND", value: (r) => r.args },
|
|
148
|
+
cmd: { header: "CMD", value: (r) => r.args }, command: { header: "COMMAND", value: (r) => r.args },
|
|
149
|
+
tty: { header: "TT", value: (r) => r.tty }, tt: { header: "TT", value: (r) => r.tty },
|
|
150
|
+
stat: { header: "STAT", value: (r) => r.state }, state: { header: "S", value: (r) => r.state[0] },
|
|
151
|
+
c: { header: "C", numeric: true, value: (r) => Math.floor(r.cpu) },
|
|
152
|
+
"%cpu": { header: "%CPU", numeric: true, value: (r) => r.cpu.toFixed(1) }, pcpu: { header: "%CPU", numeric: true, value: (r) => r.cpu.toFixed(1) },
|
|
153
|
+
"%mem": { header: "%MEM", numeric: true, value: (r) => r.memory.toFixed(1) }, pmem: { header: "%MEM", numeric: true, value: (r) => r.memory.toFixed(1) },
|
|
154
|
+
vsz: { header: "VSZ", numeric: true, value: (r) => r.vmSize }, rss: { header: "RSS", numeric: true, value: (r) => r.rss },
|
|
155
|
+
sz: { header: "SZ", numeric: true, value: (r) => r.size }, time: { header: "TIME", numeric: true, value: (r) => r.time },
|
|
156
|
+
etime: { header: "ELAPSED", numeric: true, value: (r) => elapsedTime(r.elapsed) }, etimes: { header: "ELAPSED", numeric: true, value: (r) => Math.floor(r.elapsed) },
|
|
157
|
+
start: { header: "STARTED", value: (r) => startText(r.started) }, stime: { header: "STIME", value: (r) => startText(r.started, true) },
|
|
158
|
+
lstart: { header: "STARTED", value: (r) => new Date(r.started).toString().slice(0, 24) },
|
|
159
|
+
ni: { header: "NI", numeric: true, value: (r) => r.nice }, nice: { header: "NI", numeric: true, value: (r) => r.nice },
|
|
160
|
+
pri: { header: "PRI", numeric: true, value: (r) => r.priority }, psr: { header: "PSR", numeric: true, value: (r) => r.processor },
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export function prepareProcessFormat(rows, fields) {
|
|
164
|
+
const names = Array.isArray(fields) ? fields : fields.toLowerCase().split(/[\s,]+/).filter(Boolean);
|
|
165
|
+
if (!names.length) throw new Error("empty -o format");
|
|
166
|
+
return names.map((name) => {
|
|
167
|
+
const definition = processFieldDefinitions[name];
|
|
168
|
+
if (!definition) throw new Error(`unsupported output field '${name}'`);
|
|
169
|
+
const width = Math.max(definition.minimum ?? 0, definition.header.length,
|
|
170
|
+
...rows.map((row) => String(definition.value(row)).length));
|
|
171
|
+
return { name, ...definition, width };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const formatProcessHeader = (format) => format.map((f) =>
|
|
176
|
+
f.numeric ? f.header.padStart(f.width) : f.header.padEnd(f.width)).join(" ").trimEnd();
|
|
177
|
+
|
|
178
|
+
export const formatProcessLine = (row, format) => format.map((f, index) => {
|
|
179
|
+
const value = String(f.value(row));
|
|
180
|
+
if (index === format.length - 1 && !f.numeric) return value;
|
|
181
|
+
return f.numeric ? value.padStart(f.width) : value.padEnd(f.width);
|
|
182
|
+
}).join(" ").trimEnd();
|
|
183
|
+
|
|
184
|
+
export function formatProcessTable(rows, fields) {
|
|
185
|
+
const format = prepareProcessFormat(rows, fields);
|
|
186
|
+
return [formatProcessHeader(format), ...rows.map((row) => formatProcessLine(row, format))];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const formatPresetTable = (rows, fields, headers = {}) => {
|
|
190
|
+
const format = prepareProcessFormat(rows, fields).map((field) => {
|
|
191
|
+
const header = headers[field.name] ?? field.header;
|
|
192
|
+
return { ...field, header, width: Math.max(field.width, header.length) };
|
|
193
|
+
});
|
|
194
|
+
return [formatProcessHeader(format), ...rows.map((row) => formatProcessLine(row, format))];
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export function selectProcesses(rows, { all = false, selection = "default" } = {}) {
|
|
198
|
+
if (all) return rows;
|
|
199
|
+
const own = rows.find((row) => row.pid === process.pid);
|
|
200
|
+
const ownUid = own?.uid ?? process.geteuid?.() ?? process.getuid?.() ?? 0;
|
|
201
|
+
const ownTty = own?.ttyNumber ?? 0;
|
|
202
|
+
return rows.filter((row) => {
|
|
203
|
+
// A boot-console shell and kernel threads can all be UID 0 and have no
|
|
204
|
+
// controlling TTY. Plain ps must not accidentally behave like ps -e.
|
|
205
|
+
if (row.kernelThread) return false;
|
|
206
|
+
if (selection === "unix-a") return row.ttyNumber !== 0 && row.session !== row.pid;
|
|
207
|
+
if (selection === "any-with-tty") return row.ttyNumber !== 0;
|
|
208
|
+
if (selection === "own-any-tty") return row.uid === ownUid;
|
|
209
|
+
return row.uid === ownUid && row.ttyNumber === ownTty && row.session !== row.pid;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const usage = () => { throw new Error("Usage: ps | ps -e|-A [-o fields] | ps -ef | ps aux | ps ax\nTry 'ps --help' for the full manual."); };
|
|
214
|
+
|
|
215
|
+
async function main(argv) {
|
|
216
|
+
let fields = null, view = "default", all = false, selection = "default";
|
|
217
|
+
for (let index = 0; index < argv.length; index++) {
|
|
218
|
+
const argument = argv[index];
|
|
219
|
+
if (argument === "-h" || argument === "--help") return showDocument("ps");
|
|
220
|
+
if (["-e", "-A", "--all"].includes(argument)) { all = true; continue; }
|
|
221
|
+
if (["-f", "-ef", "-Af"].includes(argument)) { view = "full"; if (argument !== "-f") all = true; continue; }
|
|
222
|
+
if (["-F", "-eF", "-AF"].includes(argument)) { view = "extra-full"; if (argument !== "-F") all = true; continue; }
|
|
223
|
+
if (["aux", "-aux"].includes(argument)) { view = "aux"; all = true; continue; }
|
|
224
|
+
if (["ax", "-ax"].includes(argument)) { view = "bsd"; all = true; continue; }
|
|
225
|
+
if (argument === "a") { view = "bsd"; selection = "any-with-tty"; continue; }
|
|
226
|
+
if (argument === "x") { view = "bsd"; selection = "own-any-tty"; continue; }
|
|
227
|
+
if (argument === "-a") { selection = "unix-a"; continue; }
|
|
228
|
+
if (argument === "-x") { selection = "own-any-tty"; continue; }
|
|
229
|
+
if (argument === "-o" || argument === "--format") { fields = argv[++index] ?? usage(); view = "basic"; continue; }
|
|
230
|
+
if (argument.startsWith("--format=")) { fields = argument.slice(9); view = "basic"; continue; }
|
|
231
|
+
const combined = /^-([eA]*)o(.*)$/.exec(argument);
|
|
232
|
+
if (combined) { fields = combined[2] || argv[++index] || usage(); view = "basic"; if (combined[1]) all = true; continue; }
|
|
233
|
+
usage();
|
|
234
|
+
}
|
|
235
|
+
const rows = selectProcesses(readProcessSnapshot().processes, { all, selection });
|
|
236
|
+
const presets = {
|
|
237
|
+
full: ["user", "pid", "ppid", "c", "stime", "tty", "time", "args"],
|
|
238
|
+
"extra-full": ["user", "pid", "ppid", "c", "sz", "rss", "psr", "stime", "tty", "time", "args"],
|
|
239
|
+
aux: ["user", "pid", "%cpu", "%mem", "vsz", "rss", "tty", "stat", "start", "time", "args"],
|
|
240
|
+
bsd: ["pid", "tty", "stat", "time", "args"], default: ["pid", "tty", "time", "comm"],
|
|
241
|
+
};
|
|
242
|
+
try {
|
|
243
|
+
const headers = view === "default" ? { tty: "TTY", comm: "CMD" }
|
|
244
|
+
: view === "full" || view === "extra-full" ? { user: "UID", tty: "TTY", args: "CMD" }
|
|
245
|
+
: view === "aux" ? { tty: "TTY", start: "START" }
|
|
246
|
+
: view === "bsd" ? { tty: "TTY" } : {};
|
|
247
|
+
for (const line of fields
|
|
248
|
+
? formatProcessTable(rows, fields)
|
|
249
|
+
: formatPresetTable(rows, presets[view], headers)) console.log(line);
|
|
250
|
+
} catch (error) { throw new Error(`ps: ${error.message}`); }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (import.meta.main) {
|
|
254
|
+
try { await main(process.argv.slice(2)); }
|
|
255
|
+
catch (error) { console.error(error.message); process.exit(1); }
|
|
256
|
+
}
|