buninu-linux 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +23 -0
- package/NOTICE.md +111 -63
- package/README.md +292 -58
- package/build-image.sh +9 -5
- package/build-uki.sh +44 -28
- package/fetch-alpine.sh +45 -31
- package/fetch-bun.sh +14 -21
- package/graphics.md +7 -2
- package/index.js +150 -35
- package/initramfs/bin/bunterm +5 -1
- package/initramfs/bin/chroot +216 -0
- package/initramfs/bin/ip +3 -2
- package/initramfs/bin/ps +3 -0
- package/initramfs/bin/stripansi +27 -0
- package/initramfs/bin/tar +198 -14
- package/initramfs/init.js +149 -38
- package/initramfs/lib/bunterm/images.js +17 -2
- package/initramfs/lib/bunterm/mouse.js +222 -0
- package/initramfs/lib/bunterm/renderer.js +48 -3
- package/initramfs/lib/bunterm/terminal.js +123 -2
- package/initramfs/lib/dlopen.js +4 -0
- package/initramfs/lib/document.js +5 -1
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +35 -2
- package/initramfs/usr/share/doc/buninu-linux/chroot.md +105 -0
- package/initramfs/usr/share/doc/buninu-linux/tar.md +32 -6
- package/native/aarch64/lib/ld-musl-aarch64.so.1 +0 -0
- package/native/aarch64/lib/libc.musl-aarch64.so.1 +0 -0
- package/native/aarch64/lib/libgcc_s.so.1 +0 -0
- package/native/aarch64/lib/libstdc++.so.6 +0 -0
- package/package.json +2 -2
- package/run-qemu.sh +61 -12
- package/scripts/arch.sh +113 -0
- package/scripts/fetch.sh +5 -3
- package/scripts/pack-initramfs.sh +18 -4
- package/test/host/Dockerfile +13 -0
- package/test/host/boot-test.exp +70 -0
- package/test/host/host-test.sh +49 -0
- package/test/host/in-container.sh +58 -0
- /package/{initramfs → native/x86_64}/lib/ld-musl-x86_64.so.1 +0 -0
- /package/{initramfs → native/x86_64}/lib/libc.musl-x86_64.so.1 +0 -0
- /package/{initramfs → native/x86_64}/lib/libgcc_s.so.1 +0 -0
- /package/{initramfs → native/x86_64}/lib/libstdc++.so.6 +0 -0
|
@@ -50,6 +50,7 @@ export const createSession = ({
|
|
|
50
50
|
theme = defaultTheme,
|
|
51
51
|
scrollback = 1000,
|
|
52
52
|
cursorBlink = true,
|
|
53
|
+
mouse = true,
|
|
53
54
|
env = {},
|
|
54
55
|
onError = (error) => console.error(`bunterm: ${error?.stack ?? error}`),
|
|
55
56
|
}) => {
|
|
@@ -82,13 +83,24 @@ export const createSession = ({
|
|
|
82
83
|
let frameTimer = null;
|
|
83
84
|
let blinkOn = true;
|
|
84
85
|
const cursorHidden = () => Boolean(term._core?.coreService?.isCursorHidden);
|
|
86
|
+
// Only set when a pointer is attached; without one the renderer is called
|
|
87
|
+
// exactly as before.
|
|
88
|
+
let pointer = null;
|
|
85
89
|
const paint = () => {
|
|
86
90
|
if (frameTimer) clearTimeout(frameTimer);
|
|
87
91
|
frameTimer = null;
|
|
88
92
|
const buffer = term.buffer.active;
|
|
93
|
+
// cursorY counts from the buffer's base, so a scrolled-back viewport
|
|
94
|
+
// moves the cursor down the screen and eventually off it.
|
|
95
|
+
const row = buffer.baseY + buffer.cursorY - buffer.viewportY;
|
|
89
96
|
return renderer.render(term, {
|
|
90
|
-
cursor: {
|
|
97
|
+
cursor: {
|
|
98
|
+
x: buffer.cursorX,
|
|
99
|
+
y: row,
|
|
100
|
+
visible: blinkOn && !cursorHidden() && row >= 0 && row < renderer.rows,
|
|
101
|
+
},
|
|
91
102
|
images: images.visible(),
|
|
103
|
+
pointer,
|
|
92
104
|
});
|
|
93
105
|
};
|
|
94
106
|
const requestFrame = () => {
|
|
@@ -137,10 +149,102 @@ export const createSession = ({
|
|
|
137
149
|
const applicationCursorKeys = Boolean(term._core?.coreService?.decPrivateModes?.applicationCursorKeys);
|
|
138
150
|
const text = translateKeys(bytes.toString("latin1"), { applicationCursorKeys });
|
|
139
151
|
backend.write(Buffer.from(text, "latin1"));
|
|
152
|
+
// Typing returns to the live screen, as every terminal does.
|
|
153
|
+
if (term.buffer.active.viewportY !== term.buffer.active.baseY) {
|
|
154
|
+
term.scrollToBottom();
|
|
155
|
+
renderer.invalidate();
|
|
156
|
+
}
|
|
140
157
|
resetBlink();
|
|
141
158
|
requestFrame();
|
|
142
159
|
};
|
|
143
160
|
|
|
161
|
+
// Turns a pointer report from mouse.js into the mouse sequences the
|
|
162
|
+
// program asked for. xterm.js's CoreMouseService owns the protocol: it
|
|
163
|
+
// knows which events the program enabled (DECSET 1000/1002/1003), encodes
|
|
164
|
+
// them (SGR 1006 or the default form) and sends them through onData, so
|
|
165
|
+
// nothing here builds an escape sequence. Reports arrive only while a
|
|
166
|
+
// pointer is open, and this is the only place the terminal sees one.
|
|
167
|
+
const CoreMouseButton = { LEFT: 0, MIDDLE: 1, RIGHT: 2, NONE: 3, WHEEL: 4 };
|
|
168
|
+
const CoreMouseAction = { UP: 0, DOWN: 1, LEFT: 2, RIGHT: 3, MOVE: 32 };
|
|
169
|
+
const heldButton = (buttons) => (buttons.left ? CoreMouseButton.LEFT
|
|
170
|
+
: buttons.middle ? CoreMouseButton.MIDDLE
|
|
171
|
+
: buttons.right ? CoreMouseButton.RIGHT
|
|
172
|
+
: CoreMouseButton.NONE);
|
|
173
|
+
|
|
174
|
+
// Scrolls the viewport when the program is not reading the mouse itself:
|
|
175
|
+
// through the scrollback on the normal screen, and as arrow keys on the
|
|
176
|
+
// alternate screen, which has no scrollback and is what a pager expects.
|
|
177
|
+
const wheelScroll = (notches) => {
|
|
178
|
+
const lines = notches * 3;
|
|
179
|
+
if (term.buffer.active.type === "alternate") {
|
|
180
|
+
const application = Boolean(term._core?.coreService?.decPrivateModes?.applicationCursorKeys);
|
|
181
|
+
const key = `\u001b${application ? "O" : "["}${lines > 0 ? "A" : "B"}`;
|
|
182
|
+
backend.write(Buffer.from(key.repeat(Math.abs(lines)), "latin1"));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const before = term.buffer.active.viewportY;
|
|
186
|
+
term.scrollLines(-lines);
|
|
187
|
+
if (term.buffer.active.viewportY !== before) {
|
|
188
|
+
// Every row on screen is different now, and identical text at the same
|
|
189
|
+
// row would otherwise hash the same as before.
|
|
190
|
+
renderer.invalidate();
|
|
191
|
+
requestFrame();
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const reportMouse = (report) => {
|
|
196
|
+
pointer = { x: report.x, y: report.y };
|
|
197
|
+
requestFrame();
|
|
198
|
+
const service = term._core?.coreMouseService;
|
|
199
|
+
if (!service?.areMouseEventsActive) {
|
|
200
|
+
if (report.wheel) wheelScroll(report.wheel);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const column = Math.floor((report.x - renderer.offsetX) / renderer.cellWidth);
|
|
204
|
+
const row = Math.floor((report.y - renderer.offsetY) / renderer.cellHeight);
|
|
205
|
+
const send = (button, action) => {
|
|
206
|
+
try {
|
|
207
|
+
service.triggerMouseEvent({ col: column, row, button, action, ctrl: false, alt: false, shift: false });
|
|
208
|
+
} catch (error) {
|
|
209
|
+
onError(error);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
for (const name of report.released) {
|
|
213
|
+
send(CoreMouseButton[name.toUpperCase()], CoreMouseAction.UP);
|
|
214
|
+
}
|
|
215
|
+
for (const name of report.pressed) {
|
|
216
|
+
send(CoreMouseButton[name.toUpperCase()], CoreMouseAction.DOWN);
|
|
217
|
+
}
|
|
218
|
+
if (report.moved) send(heldButton(report.buttons), CoreMouseAction.MOVE);
|
|
219
|
+
for (let notch = 0; notch < Math.abs(report.wheel); notch++) {
|
|
220
|
+
send(CoreMouseButton.WHEEL, report.wheel > 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN);
|
|
221
|
+
}
|
|
222
|
+
for (let notch = 0; notch < Math.abs(report.hwheel); notch++) {
|
|
223
|
+
send(CoreMouseButton.WHEEL, report.hwheel > 0 ? CoreMouseAction.RIGHT : CoreMouseAction.LEFT);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// The pointer is opened only when asked for, by a module that is imported
|
|
228
|
+
// only then, so a machine without one — or a broken input layer — cannot
|
|
229
|
+
// affect a plain session.
|
|
230
|
+
let pointerDevice = null;
|
|
231
|
+
const attachPointer = async () => {
|
|
232
|
+
if (!mouse) return null;
|
|
233
|
+
const { openPointer, findPointers } = await import("./mouse.js");
|
|
234
|
+
const devices = findPointers();
|
|
235
|
+
if (!devices.length) throw new Error("no pointing device in /dev/input (is the evdev module loaded?)");
|
|
236
|
+
pointerDevice = openPointer({
|
|
237
|
+
devices,
|
|
238
|
+
width: screen.width,
|
|
239
|
+
height: screen.height,
|
|
240
|
+
onSync: reportMouse,
|
|
241
|
+
onError,
|
|
242
|
+
});
|
|
243
|
+
pointer = { x: pointerDevice.state.x, y: pointerDevice.state.y };
|
|
244
|
+
paint();
|
|
245
|
+
return pointerDevice;
|
|
246
|
+
};
|
|
247
|
+
|
|
144
248
|
// Waits until every byte received so far has been parsed, then paints.
|
|
145
249
|
const settle = async () => {
|
|
146
250
|
await queue;
|
|
@@ -154,13 +258,18 @@ export const createSession = ({
|
|
|
154
258
|
if (frameTimer) clearTimeout(frameTimer);
|
|
155
259
|
frameTimer = null;
|
|
156
260
|
backend.close();
|
|
261
|
+
pointerDevice?.close();
|
|
262
|
+
pointerDevice = null;
|
|
157
263
|
images.delete();
|
|
158
264
|
renderer.delete();
|
|
159
265
|
term.dispose();
|
|
160
266
|
};
|
|
161
267
|
|
|
162
268
|
paint();
|
|
163
|
-
return {
|
|
269
|
+
return {
|
|
270
|
+
term, backend, renderer, images, kitty,
|
|
271
|
+
paint, requestFrame, settle, input, exited, close, attachPointer, reportMouse, wheelScroll,
|
|
272
|
+
};
|
|
164
273
|
};
|
|
165
274
|
|
|
166
275
|
// Runs a session on the framebuffer with the console in graphics mode and
|
|
@@ -193,6 +302,18 @@ export const runTerminal = async ({ console: consoleDevice = null, device = "/de
|
|
|
193
302
|
},
|
|
194
303
|
});
|
|
195
304
|
onRestore(() => keyboard.close());
|
|
305
|
+
if (options.mouse !== false) {
|
|
306
|
+
// A pointer that cannot be opened is reported and the session
|
|
307
|
+
// continues as a keyboard-only terminal.
|
|
308
|
+
try {
|
|
309
|
+
const attached = await session.attachPointer();
|
|
310
|
+
if (attached?.devices.length) {
|
|
311
|
+
console.error(`bunterm: mouse: ${attached.devices.map((device) => device.name).join(", ")}`);
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error(`bunterm: mouse: ${error.message}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
196
317
|
return await session.exited;
|
|
197
318
|
} finally {
|
|
198
319
|
keyboard?.close();
|
package/initramfs/lib/dlopen.js
CHANGED
|
@@ -85,6 +85,10 @@ export const libc = dlopen(libcPath, {
|
|
|
85
85
|
args: [FFIType.i32, FFIType.ptr, FFIType.i32],
|
|
86
86
|
returns: FFIType.i32,
|
|
87
87
|
},
|
|
88
|
+
chroot: {
|
|
89
|
+
args: [FFIType.ptr],
|
|
90
|
+
returns: FFIType.i32,
|
|
91
|
+
},
|
|
88
92
|
});
|
|
89
93
|
|
|
90
94
|
export const cString = (value) => new TextEncoder().encode(`${value}\0`);
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// Shared renderer for /usr/share/doc/buninu-linux/<name>.md command manuals.
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
2
4
|
export const showDocument = async (name) => {
|
|
3
|
-
const path = `${import.meta.dir}/../usr/share/doc/buninu-linux/${name}.md
|
|
5
|
+
const path = resolve(`${import.meta.dir}/../usr/share/doc/buninu-linux/${name}.md`);
|
|
4
6
|
const rendered = Bun.markdown.ansi(await Bun.file(path).text(), { hyperlinks: true });
|
|
5
7
|
process.stdout.write(rendered.endsWith("\n") ? rendered : `${rendered}\n`);
|
|
8
|
+
// Where the page lives, for anyone who wants to read or edit the source.
|
|
9
|
+
process.stdout.write(`\u001b[2m${path}\u001b[0m\n`);
|
|
6
10
|
};
|
|
@@ -10,6 +10,7 @@ bunterm # run /bin/sh on the console you are on
|
|
|
10
10
|
bunterm /dev/tty1 # use virtual console 1 for graphics and keys
|
|
11
11
|
bunterm --font-size 20
|
|
12
12
|
bunterm -e bun /buninu/apps/jsmdcui/src/index.js --demo
|
|
13
|
+
bunterm --no-mouse # keyboard only
|
|
13
14
|
bunterm -h
|
|
14
15
|
```
|
|
15
16
|
|
|
@@ -28,6 +29,7 @@ text mode and `bunterm` exits with its status.
|
|
|
28
29
|
| `--line-height F` | Multiplies the cell height (default 1). |
|
|
29
30
|
| `--fb /dev/fbN` | The framebuffer device (default `/dev/fb0`). |
|
|
30
31
|
| `--no-blink` | A steady cursor. |
|
|
32
|
+
| `--no-mouse` | Do not attach a pointing device; nothing then reads `/dev/input`. |
|
|
31
33
|
|
|
32
34
|
## What is drawn
|
|
33
35
|
|
|
@@ -59,13 +61,44 @@ screen. A few console-specific
|
|
|
59
61
|
sequences are rewritten to xterm's (`F1`–`F5`, `Home`, `End`), and arrow keys
|
|
60
62
|
follow the program's application cursor mode.
|
|
61
63
|
|
|
64
|
+
## Mouse
|
|
65
|
+
|
|
66
|
+
Every pointing device under `/dev/input/event*` is opened and an arrow is
|
|
67
|
+
drawn on the screen. Clicks, drags and wheel notches reach the program as
|
|
68
|
+
the xterm mouse sequences it asked for: nothing is sent unless the program
|
|
69
|
+
enabled reporting (`DECSET 1000`, `1002` or `1003`), and what is sent follows
|
|
70
|
+
the mode it chose, SGR (`1006`) included. `jsmdcui`, `micro`, `htop`, `vim`
|
|
71
|
+
and anything else that speaks the protocol work with no configuration.
|
|
72
|
+
|
|
73
|
+
When the program is not reading the mouse, the wheel scrolls instead: back
|
|
74
|
+
through the scrollback on the normal screen, three lines per notch, and as
|
|
75
|
+
arrow keys on the alternate screen, which has no scrollback and is what a
|
|
76
|
+
pager expects. Typing returns to the live screen, and the text cursor is
|
|
77
|
+
hidden while it is scrolled out of view.
|
|
78
|
+
|
|
79
|
+
`--no-mouse` turns all of this off: the terminal then never looks at
|
|
80
|
+
`/dev/input` and never loads the mouse module.
|
|
81
|
+
|
|
82
|
+
`init.js` loads `evdev` and `psmouse` at boot, so a PS/2 mouse — which is
|
|
83
|
+
what QEMU's q35 machine and most PCs present — has a device node ready. The
|
|
84
|
+
aarch64 image loads `virtio_input` in place of `psmouse`, for QEMU's
|
|
85
|
+
`virtio-tablet-pci` and `virtio-keyboard-pci`. A USB
|
|
86
|
+
mouse also needs the `usbhid` stack, which a `--real` image carries and
|
|
87
|
+
`cfg.all` loads. The pointer follows relative devices (a mouse) and absolute
|
|
88
|
+
ones (a tablet or touchscreen) alike; a device that cannot be opened is
|
|
89
|
+
reported and the session continues without it.
|
|
90
|
+
|
|
62
91
|
## Limits
|
|
63
92
|
|
|
64
93
|
- Needs a framebuffer: `linux-lts` has one built in, while the default
|
|
65
94
|
`linux-virt` builds it as modules the image does not ship, so build with
|
|
66
95
|
`--linux-lts` or `--real` (efifb/simpledrm from UEFI).
|
|
67
|
-
-
|
|
68
|
-
|
|
96
|
+
- The aarch64 image has no framebuffer yet: `run-qemu.sh` gives QEMU's
|
|
97
|
+
`virt` machine no display device, so there is no `/dev/fb0` to draw on.
|
|
98
|
+
- No selection, and no keyboard way to scroll back: the wheel is it. The
|
|
99
|
+
buffer keeps 1000 lines. A program that does not enable mouse reporting
|
|
100
|
+
sees no clicks — the arrow still moves, so the pointer is visibly alive.
|
|
101
|
+
`--no-mouse` removes the pointer entirely.
|
|
69
102
|
- A program that prints a terminal reply while the tty echoes (that is, one
|
|
70
103
|
not in raw mode) will see the reply echoed, as on any terminal.
|
|
71
104
|
- Kitty images are drawn above text; `z` ordering below text and animation
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# chroot — run a command inside another root directory
|
|
2
|
+
|
|
3
|
+
`/bin/chroot` is a Bun script that calls `chroot(2)` and `mount(2)` through
|
|
4
|
+
`/lib/dlopen.js`.
|
|
5
|
+
|
|
6
|
+
Unlike the traditional `chroot`, it prepares the new root first, so a
|
|
7
|
+
freshly unpacked root filesystem is usable straight away, and it undoes
|
|
8
|
+
every mount it made when the command exits.
|
|
9
|
+
|
|
10
|
+
## Synopsis
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
chroot NEWROOT [COMMAND [ARGUMENT...]]
|
|
14
|
+
chroot [-n] [-k] [-v] NEWROOT [COMMAND [ARGUMENT...]]
|
|
15
|
+
chroot -h | --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With no COMMAND it runs `$SHELL` if that exists inside NEWROOT, otherwise
|
|
19
|
+
`/bin/sh`, interactively when the terminal is one.
|
|
20
|
+
|
|
21
|
+
## What it mounts
|
|
22
|
+
|
|
23
|
+
| Inside NEWROOT | What | Why |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `/proc` | a fresh `proc` | path resolution, `ps`, anything reading `/proc/self` |
|
|
26
|
+
| `/sys` | a fresh `sysfs` | device and network information |
|
|
27
|
+
| `/dev` | recursive bind of the host's `/dev`, made a slave mount | every device node, including `/dev/ptmx`, without a second devtmpfs |
|
|
28
|
+
| `/dev/pts` | `devpts`, only if the bind brought none | pseudo-terminals for shells and editors |
|
|
29
|
+
| `/run` | `tmpfs` | runtime state |
|
|
30
|
+
| `/tmp` | `tmpfs` | scratch space that does not touch the root filesystem |
|
|
31
|
+
| `/etc/resolv.conf` | bind of the host's file | name resolution, without editing anything inside |
|
|
32
|
+
|
|
33
|
+
A mount point that already exists as a mount is left alone. A directory that
|
|
34
|
+
is missing is created; if the root filesystem is read-only and that fails,
|
|
35
|
+
that one mount is skipped and the rest still happen — use `-v` to see which.
|
|
36
|
+
|
|
37
|
+
Making `/dev` a slave mount means mounts appearing inside the root do not
|
|
38
|
+
propagate back to the host, and unmounting it afterwards cannot take the
|
|
39
|
+
host's `/dev` with it.
|
|
40
|
+
|
|
41
|
+
## Options
|
|
42
|
+
|
|
43
|
+
| Option | Meaning |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `-n`, `--no-mount` | Do not mount anything: the behaviour of the traditional `chroot`. Note that Bun itself cannot start without `/proc` and `/dev/urandom`, so `-n` suits statically linked programs. |
|
|
46
|
+
| `-k`, `--keep-mounts` | Leave the mounts in place after the command exits, to enter the same root again cheaply. Clean up later with `umount -R`. |
|
|
47
|
+
| `-v`, `--verbose` | Report every mount and unmount. |
|
|
48
|
+
| `--` | End of options; everything after it is NEWROOT and COMMAND. |
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
# Mount a disk and enter the system on it
|
|
54
|
+
mount /dev/vda2 /mnt --mkdir
|
|
55
|
+
chroot /mnt
|
|
56
|
+
|
|
57
|
+
# Run one command and leave
|
|
58
|
+
chroot /mnt /bin/bun --version
|
|
59
|
+
|
|
60
|
+
# Watch what is set up and taken down
|
|
61
|
+
chroot -v /mnt /bin/sh -c 'ls /proc | head -3'
|
|
62
|
+
|
|
63
|
+
# Traditional behaviour: nothing is mounted for you
|
|
64
|
+
chroot -n /mnt /bin/sh
|
|
65
|
+
|
|
66
|
+
# Keep the mounts for repeated visits
|
|
67
|
+
chroot -k /mnt /bin/sh -c 'true'
|
|
68
|
+
chroot -k /mnt
|
|
69
|
+
umount -R /mnt
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Cleanup
|
|
73
|
+
|
|
74
|
+
When the command exits, every mount this invocation made is unmounted,
|
|
75
|
+
deepest first, including anything that appeared under the recursive `/dev`
|
|
76
|
+
bind. A mount that is still busy — a process left running inside, a shell
|
|
77
|
+
with its working directory there — is detached instead (`MNT_DETACH`), so
|
|
78
|
+
the kernel releases it once its last user goes away. The same cleanup runs
|
|
79
|
+
if `chroot(2)` itself fails or the command cannot be started, and on
|
|
80
|
+
`SIGTERM` or `SIGHUP`.
|
|
81
|
+
|
|
82
|
+
`Ctrl-C` goes to the command, not to `chroot`, so the mounts are still
|
|
83
|
+
removed after an interrupted program.
|
|
84
|
+
|
|
85
|
+
## Limits
|
|
86
|
+
|
|
87
|
+
- Needs to be superuser: `chroot(2)` requires `CAP_SYS_CHROOT`, and the
|
|
88
|
+
mounts require `CAP_SYS_ADMIN`. Buninu normally runs as root.
|
|
89
|
+
- The new root must contain the program you ask for and whatever it links
|
|
90
|
+
against; `chroot /mnt /bin/sh` fails with "not found" if `/mnt/bin/sh`
|
|
91
|
+
is not there.
|
|
92
|
+
- Only the filesystem view changes. Processes, network, users and the
|
|
93
|
+
hostname are shared with the host; this is not a container.
|
|
94
|
+
- `-k` leaves mounts behind on purpose. `umount -R NEWROOT` removes them.
|
|
95
|
+
|
|
96
|
+
## Exit status
|
|
97
|
+
|
|
98
|
+
The command's own status, or `125` if `chroot` itself failed, `126` if the
|
|
99
|
+
command was found but could not be run, and `127` if it was not found.
|
|
100
|
+
|
|
101
|
+
## See also
|
|
102
|
+
|
|
103
|
+
- Source: `/bin/chroot`
|
|
104
|
+
- `mount --help`, `umount --help`
|
|
105
|
+
- Shared native binding: `/lib/dlopen.js`
|
|
@@ -27,7 +27,9 @@ Use `-` as ARCHIVE to read from standard input or write to standard output.
|
|
|
27
27
|
| `-C DIR`, `--directory=DIR` | Read input files from DIR, or extract into DIR. |
|
|
28
28
|
| `-z`, `--gzip` | Create gzip-compressed tar, or explicitly identify gzip input. |
|
|
29
29
|
| `--zstd` | Create or read a zstd-compressed tar. A `.zst` or `.tzst` input is also detected by name. |
|
|
30
|
-
| `-v` | Print names while creating/extracting; with `-t`, also show each
|
|
30
|
+
| `-v` | Print names while creating/extracting; with `-t`, also show each member's size, modification time, kind and link target. |
|
|
31
|
+
| `-o`, `--no-same-owner`, `--no-same-permissions`, `--overwrite` | GNU tar compatibility for extraction; these already match Bun.Archive's behaviour. |
|
|
32
|
+
| `--exclude=PATTERN` | Accepted for js-udocker compatibility. Device nodes are not extracted and js-udocker removes whiteout files separately. |
|
|
31
33
|
|
|
32
34
|
Short options can be combined, for example `-xzvf` or `-czf`.
|
|
33
35
|
For the traditional first option word, the leading hyphen is optional, so
|
|
@@ -42,8 +44,34 @@ tar -xzf source.tar.gz -C /tmp/source
|
|
|
42
44
|
tar -tf source.tar.gz
|
|
43
45
|
tar --zstd -cf backup.tar.zst home
|
|
44
46
|
tar --zstd -xf backup.tar.zst -C /mnt/restore
|
|
47
|
+
|
|
48
|
+
# An Alpine root filesystem, symbolic links and all; then enter it
|
|
49
|
+
tar -xzf alpine-minirootfs-3.24.1-x86_64.tar.gz -C /mnt/alpine
|
|
50
|
+
chroot /mnt/alpine
|
|
45
51
|
```
|
|
46
52
|
|
|
53
|
+
## Links
|
|
54
|
+
|
|
55
|
+
Extraction restores symbolic links exactly as the archive records them,
|
|
56
|
+
including absolute targets such as `/bin/busybox` and targets that do not
|
|
57
|
+
exist yet, and it restores hard links. This is what GNU tar does, and it is
|
|
58
|
+
what an unpacked root filesystem needs: an Alpine minirootfs is 335 symbolic
|
|
59
|
+
links, 306 of them absolute, and without them the result has no `/bin/sh`.
|
|
60
|
+
|
|
61
|
+
`Bun.Archive` creates only the symbolic links whose target is relative and
|
|
62
|
+
stays inside the extraction directory, and skips hard links entirely, so
|
|
63
|
+
this command reads the archive's own headers (ustar, GNU `L`/`K` long names,
|
|
64
|
+
pax `x` records) and creates the rest itself once every file is in place.
|
|
65
|
+
Nothing is ever written through a link, because the links are made last.
|
|
66
|
+
|
|
67
|
+
A member's own name still follows GNU tar's rule: a leading `/` is dropped
|
|
68
|
+
and a name containing `..` is refused, so an archive cannot write outside
|
|
69
|
+
the extraction directory.
|
|
70
|
+
|
|
71
|
+
The same header scan backs listing, so `-t` reports every member —
|
|
72
|
+
directories, links, devices — and not just regular files. A symbolic link
|
|
73
|
+
shows its target, a hard link the file it points at.
|
|
74
|
+
|
|
47
75
|
## Current Bun.Archive limits
|
|
48
76
|
|
|
49
77
|
Creation stores regular files only. Symbolic links are rejected rather than
|
|
@@ -51,11 +79,9 @@ silently archived as the wrong type; modes, ownership, hard links, and empty
|
|
|
51
79
|
directories are not preserved. Bun 1.4.3 also requires file contents to be
|
|
52
80
|
materialized in memory before archive creation.
|
|
53
81
|
|
|
54
|
-
Extraction
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
but not the unavailable mode or owner. Bun provides gzip and zstd codecs but no xz or bzip2 codec,
|
|
58
|
-
so this command does not accept `.tar.xz` or `.tar.bz2`.
|
|
82
|
+
Extraction does not restore modes, ownership or timestamps. Bun provides gzip
|
|
83
|
+
and zstd codecs but no xz or bzip2 codec, so this command does not accept
|
|
84
|
+
`.tar.xz` or `.tar.bz2`.
|
|
59
85
|
|
|
60
86
|
Appending with `-r`/`rvf` is not supported. `Bun.Archive` has no append API;
|
|
61
87
|
rebuilding an existing archive through `Archive.files()` would silently lose
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buninu-linux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A Linux distribution running Bun as PID 1 with the Buninu userspace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"fetch": "./index.js -f",
|
|
38
38
|
"pack": "./index.js -b",
|
|
39
39
|
"start": "./index.js -r",
|
|
40
|
-
"check": "for f in ./index.js ./initramfs/init.js ./initramfs/lib/document.js ./initramfs/lib/dlopen.js ./initramfs/lib/fbdev.js ./initramfs/lib/canvas.js ./initramfs/lib/bunterm/glyphs.js ./initramfs/lib/bunterm/renderer.js ./initramfs/lib/bunterm/images.js ./initramfs/lib/bunterm/input.js ./initramfs/lib/bunterm/terminal.js ./initramfs/lib/modprobe.js ./initramfs/lib/power.js ./initramfs/bin/bunterm ./initramfs/bin/mount ./initramfs/bin/umount ./initramfs/bin/ip ./initramfs/bin/ps ./initramfs/bin/top ./initramfs/bin/poweroff ./initramfs/bin/reboot ./initramfs/bin/tar; do node --check \"$f\" || exit 1; done"
|
|
40
|
+
"check": "for f in ./index.js ./initramfs/init.js ./initramfs/lib/document.js ./initramfs/lib/dlopen.js ./initramfs/lib/fbdev.js ./initramfs/lib/canvas.js ./initramfs/lib/bunterm/glyphs.js ./initramfs/lib/bunterm/renderer.js ./initramfs/lib/bunterm/images.js ./initramfs/lib/bunterm/input.js ./initramfs/lib/bunterm/mouse.js ./initramfs/lib/bunterm/terminal.js ./initramfs/lib/modprobe.js ./initramfs/lib/power.js ./initramfs/bin/bunterm ./initramfs/bin/chroot ./initramfs/bin/mount ./initramfs/bin/umount ./initramfs/bin/ip ./initramfs/bin/ps ./initramfs/bin/top ./initramfs/bin/poweroff ./initramfs/bin/reboot ./initramfs/bin/tar ./initramfs/bin/stripansi; do node --check \"$f\" || exit 1; done"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/run-qemu.sh
CHANGED
|
@@ -2,19 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
cd "$(dirname "$0")"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if [ -z "$
|
|
8
|
-
|
|
5
|
+
# The guest is whatever build-uki.sh last put in vda/EFI/BOOT (it leaves only
|
|
6
|
+
# one architecture there), unless BUNINU_ARCH (index.js --arch) names one.
|
|
7
|
+
if [ -z "$BUNINU_ARCH" ] && [ -f vda/EFI/BOOT/BOOTAA64.EFI ]; then
|
|
8
|
+
BUNINU_ARCH=aarch64
|
|
9
9
|
fi
|
|
10
|
+
. ./scripts/arch.sh
|
|
11
|
+
|
|
12
|
+
test -f vda.img || ./build-image.sh || exit 1
|
|
13
|
+
test -f "vda/EFI/BOOT/$efi_boot_name" || {
|
|
14
|
+
echo "error: vda.img holds no $arch UKI; build one with ./index.js -b --arch $arch" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
qemu="qemu-system-$arch"
|
|
19
|
+
command -v "$qemu" >/dev/null || {
|
|
20
|
+
echo "error: $qemu not found (macOS: brew install qemu; Debian: apt install qemu-system)" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
}
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
case $arch in
|
|
25
|
+
x86_64) machine=q35 distro_firmware=/usr/share/OVMF/OVMF_CODE_4M.fd ;;
|
|
26
|
+
aarch64) machine=virt distro_firmware=/usr/share/AAVMF/AAVMF_CODE.fd ;;
|
|
27
|
+
esac
|
|
28
|
+
|
|
29
|
+
# UEFI firmware: OVMF_FILE if set; then the edk2 build QEMU ships next to its
|
|
30
|
+
# own binary (Termux's $PREFIX/share/qemu, Homebrew's share/qemu, ...); then
|
|
31
|
+
# the distribution packages (Debian ovmf / qemu-efi-aarch64).
|
|
32
|
+
qemu_share="$(dirname "$(command -v "$qemu")")/../share/qemu"
|
|
33
|
+
ovmf_filepath=
|
|
34
|
+
for candidate in "$OVMF_FILE" "$distro_firmware" "$qemu_share/edk2-$arch-code.fd" \
|
|
35
|
+
"/usr/share/qemu/edk2-$arch-code.fd"; do
|
|
36
|
+
if [ -n "$candidate" ] && [ -f "$candidate" ]; then
|
|
37
|
+
ovmf_filepath=$candidate
|
|
38
|
+
break
|
|
39
|
+
fi
|
|
40
|
+
done
|
|
41
|
+
test -n "$ovmf_filepath" || {
|
|
42
|
+
echo "error: no $arch UEFI firmware found; set OVMF_FILE to an edk2-$arch-code.fd" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
}
|
|
12
45
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
46
|
+
# Hardware acceleration needs the guest to match the host CPU: KVM on Linux,
|
|
47
|
+
# Hypervisor.framework on macOS (Apple Silicon runs the aarch64 guest natively,
|
|
48
|
+
# an Intel Mac the x86_64 one). Anything else, such as the x86_64 guest on
|
|
49
|
+
# Apple Silicon or on an Android phone, is emulated with TCG. ACCEL overrides
|
|
50
|
+
# the choice, e.g. ACCEL=tcg to rule acceleration out while debugging.
|
|
51
|
+
case "$(uname -m)" in
|
|
52
|
+
arm64 | aarch64) host_arch=aarch64 ;;
|
|
53
|
+
*) host_arch=$(uname -m) ;;
|
|
54
|
+
esac
|
|
55
|
+
if [ -z "$ACCEL" ]; then
|
|
56
|
+
ACCEL=tcg
|
|
57
|
+
if [ "$host_arch" = "$arch" ]; then
|
|
58
|
+
case "$(uname -s)" in
|
|
59
|
+
Linux) [ -r /dev/kvm ] && [ -w /dev/kvm ] && ACCEL=kvm ;;
|
|
60
|
+
Darwin) [ "$(sysctl -n kern.hv_support 2>/dev/null)" = 1 ] && ACCEL=hvf ;;
|
|
61
|
+
esac
|
|
62
|
+
fi
|
|
17
63
|
fi
|
|
64
|
+
# TCG emulates every feature it knows; KVM and HVF pass the host CPU through.
|
|
65
|
+
if [ "$ACCEL" = tcg ]; then cpu=max; else cpu=host; fi
|
|
66
|
+
echo "run-qemu.sh: $arch guest on $machine, accel=$ACCEL, firmware $ovmf_filepath" >&2
|
|
18
67
|
|
|
19
68
|
# User-mode networking is NAT: the guest can reach out, nothing reaches in
|
|
20
69
|
# unless a port is forwarded. PORTS lists them, space-separated, as
|
|
@@ -31,9 +80,9 @@ for port in ${PORTS:-}; do
|
|
|
31
80
|
nic="$nic,hostfwd=tcp:127.0.0.1:$host-:$guest"
|
|
32
81
|
done
|
|
33
82
|
|
|
34
|
-
exec qemu
|
|
35
|
-
-machine
|
|
36
|
-
-cpu
|
|
83
|
+
exec "$qemu" \
|
|
84
|
+
-machine "$machine,accel=$ACCEL" \
|
|
85
|
+
-cpu "$cpu" \
|
|
37
86
|
-m 1G \
|
|
38
87
|
-drive if=pflash,format=raw,readonly=on,file="$ovmf_filepath" \
|
|
39
88
|
-drive if=virtio,format=raw,file=vda.img \
|
package/scripts/arch.sh
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Sourced by the fetch, build and run scripts; not executable on its own.
|
|
2
|
+
#
|
|
3
|
+
# Everything that differs between the two guest architectures lives here:
|
|
4
|
+
# the pinned packages and their SHA-256, the file names inside them, the EFI
|
|
5
|
+
# removable-media name, the PE-aware objcopy that assembles the UKI and the
|
|
6
|
+
# serial console. BUNINU_ARCH (index.js --arch) picks one; x86_64 is the
|
|
7
|
+
# default. LINUX_FLAVOR (virt or lts) and REAL_MACHINE are read here too.
|
|
8
|
+
#
|
|
9
|
+
# Fetched and extracted files are kept per architecture, so both guests can
|
|
10
|
+
# be built from one checkout without refetching:
|
|
11
|
+
#
|
|
12
|
+
# downloads/<arch>/ the pinned archives (both repos use the same names)
|
|
13
|
+
# kernel/<arch>/ vmlinuz-<flavor> and the UKI stub
|
|
14
|
+
# native/<arch>/ bun, musl, libgcc/libstdc++ and kernel modules;
|
|
15
|
+
# scripts/pack-initramfs.sh lays it over initramfs/
|
|
16
|
+
|
|
17
|
+
case "${BUNINU_ARCH:-x86_64}" in
|
|
18
|
+
x86_64 | amd64 | x64) arch=x86_64 ;;
|
|
19
|
+
aarch64 | arm64) arch=aarch64 ;;
|
|
20
|
+
*)
|
|
21
|
+
echo "error: unsupported BUNINU_ARCH=$BUNINU_ARCH (use x86_64 or aarch64)" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
linux_flavor=${LINUX_FLAVOR:-virt}
|
|
27
|
+
case $linux_flavor in
|
|
28
|
+
virt | lts) ;;
|
|
29
|
+
*)
|
|
30
|
+
echo "error: unsupported LINUX_FLAVOR=$linux_flavor (use virt or lts)" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
if [ "${REAL_MACHINE:-}" = 1 ] && [ "$arch" != x86_64 ]; then
|
|
36
|
+
echo "error: --real images are x86_64 only; its module set is PC hardware" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# The Alpine repository keeps only the newest build of a package, so a pin
|
|
41
|
+
# stops downloading when Alpine bumps it; the hashes below then fail loudly
|
|
42
|
+
# rather than silently taking the new build. ALPINE_MIRROR swaps the host for
|
|
43
|
+
# a closer mirror; the SHA-256 check is the same either way.
|
|
44
|
+
alpine_mirror=${ALPINE_MIRROR:-https://dl-cdn.alpinelinux.org/alpine}
|
|
45
|
+
alpine_base="$alpine_mirror/v3.24/main/$arch"
|
|
46
|
+
|
|
47
|
+
kernel_version=6.18.53
|
|
48
|
+
kernel_release="$kernel_version-0-$linux_flavor"
|
|
49
|
+
linux_package="linux-$linux_flavor-$kernel_version-r0.apk"
|
|
50
|
+
musl_package=musl-1.2.6-r2.apk
|
|
51
|
+
stub_package=systemd-efistub-260.2-r0.apk
|
|
52
|
+
libstdcpp_package=libstdc++-15.2.0-r5.apk
|
|
53
|
+
libgcc_package=libgcc-15.2.0-r5.apk
|
|
54
|
+
bun_version=1.4.2
|
|
55
|
+
|
|
56
|
+
downloads_dir="downloads/$arch"
|
|
57
|
+
kernel_dir="kernel/$arch"
|
|
58
|
+
kernel_image="$kernel_dir/vmlinuz-$linux_flavor"
|
|
59
|
+
native_dir="native/$arch"
|
|
60
|
+
modules_root="$native_dir/lib/modules/$kernel_release"
|
|
61
|
+
musl_loader="ld-musl-$arch.so.1"
|
|
62
|
+
musl_libc="libc.musl-$arch.so.1"
|
|
63
|
+
|
|
64
|
+
case $arch in
|
|
65
|
+
x86_64)
|
|
66
|
+
linux_virt_sha256=cad859cc46342e18002621fdde166bf2cd520dfec5e781d241de7d13e53970d6
|
|
67
|
+
linux_lts_sha256=8e3cfdd1d98e0e70c2e70a8c299ed3cd1e0939d60ad7bbd1da1cf241d794490b
|
|
68
|
+
musl_sha256=573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71
|
|
69
|
+
stub_sha256=8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9
|
|
70
|
+
libstdcpp_sha256=14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6
|
|
71
|
+
libgcc_sha256=393dcd32629f06d7d85409c272d142d0c082772d10b87ef55ee82f47de3be637
|
|
72
|
+
# Bun ships one x64 musl build; -baseline is an alias (NOTICE.md).
|
|
73
|
+
bun_build=bun-linux-x64-musl-baseline
|
|
74
|
+
bun_sha256=76e1db84e98f22f78de0a87e309bfbbf297732847f9720db36750646c85c8c18
|
|
75
|
+
efi_stub=linuxx64.efi.stub
|
|
76
|
+
efi_boot_name=BOOTX64.EFI
|
|
77
|
+
uki_objcopy=x86_64-w64-mingw32-objcopy
|
|
78
|
+
uki_objdump=x86_64-w64-mingw32-objdump
|
|
79
|
+
uki_binutils_package=binutils-mingw-w64-x86-64
|
|
80
|
+
# COM1 on q35 and on PCs.
|
|
81
|
+
serial_console=ttyS0
|
|
82
|
+
serial_major=4
|
|
83
|
+
;;
|
|
84
|
+
aarch64)
|
|
85
|
+
linux_virt_sha256=fea61b7fd5e72e626d771f9798df56c83db658d0e221044f06fc28aacf5a118b
|
|
86
|
+
linux_lts_sha256=46885041022ea11afa1e07c40199c74146bf72d362368a844338af0c162e365c
|
|
87
|
+
musl_sha256=5e9674b7f41152fe2119093b5cb4c13eaaadb19c2d5422b2d7267913e663ee6e
|
|
88
|
+
stub_sha256=a1823d2d7082db555d528f82c1809f276c818aeb5b40f198f4565f104e055c38
|
|
89
|
+
libstdcpp_sha256=2302e766d4e4926038ec166ecb85837ee884576115236ddb565e3a5fca4a11d7
|
|
90
|
+
libgcc_sha256=369aaa6e9d099a737bad6dd3e6c2fe7bb1547ca26d22b94ee0411228f709b403
|
|
91
|
+
bun_build=bun-linux-aarch64-musl
|
|
92
|
+
bun_sha256=71760b6c8ea30623b81a4907cb815d48e2ea266f2e73e751534a44a0607950df
|
|
93
|
+
efi_stub=linuxaa64.efi.stub
|
|
94
|
+
efi_boot_name=BOOTAA64.EFI
|
|
95
|
+
# The aarch64 ELF binutils also read and write pei-aarch64-little:
|
|
96
|
+
# native binutils on an arm64 Debian, the cross package on amd64.
|
|
97
|
+
uki_objcopy=aarch64-linux-gnu-objcopy
|
|
98
|
+
uki_objdump=aarch64-linux-gnu-objdump
|
|
99
|
+
uki_binutils_package=binutils-aarch64-linux-gnu
|
|
100
|
+
# The PL011 UART of QEMU's virt machine.
|
|
101
|
+
serial_console=ttyAMA0
|
|
102
|
+
serial_major=204
|
|
103
|
+
;;
|
|
104
|
+
esac
|
|
105
|
+
|
|
106
|
+
if [ "$linux_flavor" = lts ]; then
|
|
107
|
+
linux_sha256=$linux_lts_sha256
|
|
108
|
+
else
|
|
109
|
+
linux_sha256=$linux_virt_sha256
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
bun_zip="$bun_build-$bun_version.zip"
|
|
113
|
+
bun_url="https://github.com/oven-sh/bun/releases/download/bun-v$bun_version/$bun_build.zip"
|