buninu-linux 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +23 -0
- package/NOTICE.md +111 -63
- package/README.md +240 -51
- package/build-image.sh +9 -5
- package/build-uki.sh +44 -28
- package/fetch-alpine.sh +43 -35
- package/fetch-bun.sh +14 -21
- package/index.js +150 -35
- package/initramfs/bin/chroot +4 -1
- package/initramfs/bin/ip +3 -2
- package/initramfs/bin/ps +3 -0
- package/initramfs/bin/tar +9 -0
- package/initramfs/init.js +79 -41
- package/initramfs/lib/bunterm/images.js +17 -2
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +5 -1
- package/initramfs/usr/share/doc/buninu-linux/tar.md +2 -0
- 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 +1 -1
- 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
package/fetch-alpine.sh
CHANGED
|
@@ -3,47 +3,34 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
linux_flavor=${LINUX_FLAVOR:-virt}
|
|
8
|
-
linux_package="linux-$linux_flavor-6.18.52-r0.apk"
|
|
9
|
-
musl_package=musl-1.2.6-r2.apk
|
|
10
|
-
stub_package=systemd-efistub-260.2-r0.apk
|
|
11
|
-
if [ "$linux_flavor" = lts ]; then
|
|
12
|
-
linux_sha256=bd81f00522a7c7deb96886811a087c453d48530c0c8f742412ad73e24236037c
|
|
13
|
-
else
|
|
14
|
-
linux_sha256=dff0c365a6d6c0fd015175af08dca0c39048fa39df6ef3150a7ea88d2cfdfa4f
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
musl_sha256=573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71
|
|
18
|
-
stub_sha256=8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9
|
|
6
|
+
. ./scripts/arch.sh
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
modules_root="initramfs/lib/modules/$kernel_release"
|
|
23
|
-
mkdir -p downloads kernel initramfs/lib "$modules_root"
|
|
8
|
+
mkdir -p "$downloads_dir" "$kernel_dir" "$native_dir/lib" "$modules_root"
|
|
24
9
|
|
|
25
10
|
. ./scripts/fetch.sh
|
|
26
11
|
|
|
27
|
-
fetch "$
|
|
28
|
-
fetch "$
|
|
29
|
-
fetch "$
|
|
12
|
+
fetch "$alpine_base/$linux_package" "$linux_package" "$linux_sha256"
|
|
13
|
+
fetch "$alpine_base/$musl_package" "$musl_package" "$musl_sha256"
|
|
14
|
+
fetch "$alpine_base/$stub_package" "$stub_package" "$stub_sha256"
|
|
30
15
|
|
|
31
|
-
extract() { tar --warning=no-unknown-keyword -xOf "
|
|
16
|
+
extract() { tar --warning=no-unknown-keyword -xOf "$downloads_dir/$1" "$2"; }
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
extract "$
|
|
18
|
+
# On aarch64 vmlinuz is an EFI zboot image: a PE executable that inflates
|
|
19
|
+
# the kernel itself, so the stub can start it exactly like the x86_64 bzImage.
|
|
20
|
+
extract "$linux_package" "boot/vmlinuz-$linux_flavor" > "$kernel_image"
|
|
21
|
+
extract "$musl_package" "lib/$musl_loader" > "$native_dir/lib/$musl_loader"
|
|
22
|
+
extract "$stub_package" "usr/lib/systemd/boot/efi/$efi_stub" > "$kernel_dir/$efi_stub"
|
|
36
23
|
|
|
37
24
|
# Kernel modules. The package's modules.dep says what each one needs, so a
|
|
38
25
|
# module is requested by name and its dependencies come along: on linux-lts
|
|
39
|
-
# virtio_net pulls in virtio
|
|
40
|
-
# has built in. The initramfs gets a modules.dep trimmed to the shipped set,
|
|
26
|
+
# virtio_net pulls in virtio and virtio_ring, which linux-virt has built in. The initramfs gets a modules.dep trimmed to the shipped set,
|
|
41
27
|
# the fs-* lines of modules.alias and modules.builtin; /lib/modprobe.js reads
|
|
42
28
|
# those three files.
|
|
43
29
|
depfile="$(mktemp)"
|
|
44
30
|
selected="$(mktemp)"
|
|
45
31
|
trap 'rm -f "$depfile" "$selected" "$selected.names"' EXIT
|
|
46
32
|
extract "$linux_package" "lib/modules/$kernel_release/modules.dep" > "$depfile"
|
|
33
|
+
extract "$linux_package" "lib/modules/$kernel_release/modules.builtin" > "$modules_root/modules.builtin"
|
|
47
34
|
|
|
48
35
|
select_module() {
|
|
49
36
|
local name=$1 line path dep
|
|
@@ -58,20 +45,42 @@ select_module() {
|
|
|
58
45
|
done
|
|
59
46
|
}
|
|
60
47
|
|
|
61
|
-
#
|
|
48
|
+
# For modules one architecture or flavor builds in or leaves out: a built-in
|
|
49
|
+
# one needs nothing, a missing one gets a note. aarch64 linux-virt has no
|
|
50
|
+
# ext2.ko: it sets CONFIG_EXT4_USE_FOR_EXT2, and modules.alias maps fs-ext2 to
|
|
51
|
+
# ext4, which is how /bin/mount looks the driver up.
|
|
52
|
+
select_optional_module() {
|
|
53
|
+
if grep -qF -m1 -- "/$1.ko.gz:" "$depfile"; then
|
|
54
|
+
select_module "$1"
|
|
55
|
+
elif ! grep -q -- "/$1\.ko$" "$modules_root/modules.builtin"; then
|
|
56
|
+
echo "note: module $1 is not in $arch $linux_package; skipped" >&2
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Network: virtio-net for QEMU (init.js loads these at boot). virtio_pci is
|
|
61
|
+
# the PCI transport for every virtio device, disk included; linux-virt builds
|
|
62
|
+
# it in, linux-lts has it as a module that nothing in modules.dep pulls in.
|
|
62
63
|
for module in failover net_failover virtio_net; do select_module "$module"; done
|
|
64
|
+
select_optional_module virtio_pci
|
|
63
65
|
# Storage: what /bin/mount loads on demand. ext4 brings jbd2, mbcache and
|
|
64
66
|
# crc16; FAT, exFAT and NTFS ask the kernel for the NLS tables at mount
|
|
65
67
|
# time, which has no /sbin/modprobe to answer, so mount preloads them.
|
|
66
|
-
for module in virtio_blk ext4
|
|
68
|
+
for module in virtio_blk ext4 vfat exfat ntfs3 nls_utf8 nls_cp437 nls_iso8859-1; do
|
|
67
69
|
select_module "$module"
|
|
68
70
|
done
|
|
71
|
+
select_optional_module ext2
|
|
69
72
|
# Pointing devices for `bunterm --mouse`, which reads /dev/input/event*.
|
|
70
73
|
# evdev is the character-device interface to the input layer; psmouse drives
|
|
71
|
-
# the PS/2 mouse every PC and QEMU's q35 machine has.
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
|
|
74
|
+
# the PS/2 mouse every PC and QEMU's q35 machine has. QEMU's aarch64 virt
|
|
75
|
+
# machine has no PS/2 port; its keyboard and pointer are virtio-input. A USB
|
|
76
|
+
# mouse needs the usbhid stack, which only the --real image carries. init.js
|
|
77
|
+
# loads these at boot; nothing reads a pointer unless bunterm is asked to.
|
|
78
|
+
select_module evdev
|
|
79
|
+
if [ "$arch" = x86_64 ]; then
|
|
80
|
+
select_module psmouse
|
|
81
|
+
else
|
|
82
|
+
select_module virtio_input
|
|
83
|
+
fi
|
|
75
84
|
|
|
76
85
|
if [ "${REAL_MACHINE:-}" = 1 ]; then
|
|
77
86
|
# USB keyboard for the console; common storage paths (SATA/AHCI HDD and
|
|
@@ -114,8 +123,7 @@ extract "$linux_package" "lib/modules/$kernel_release/modules.alias" \
|
|
|
114
123
|
$2 ~ /^fs-/ { print; next }
|
|
115
124
|
$2 ~ /^(pci|usb|virtio):/ { m = $3; gsub("-", "_", m); if (m in shipped) print }
|
|
116
125
|
' > "$modules_root/modules.alias"
|
|
117
|
-
extract "$linux_package" "lib/modules/$kernel_release/modules.builtin" > "$modules_root/modules.builtin"
|
|
118
126
|
|
|
119
|
-
chmod 0755
|
|
127
|
+
chmod 0755 "$native_dir/lib/$musl_loader"
|
|
120
128
|
|
|
121
|
-
echo "Fetched Alpine linux-$linux_flavor kernel, $(wc -l < "$selected") modules, musl, and the
|
|
129
|
+
echo "Fetched Alpine $arch linux-$linux_flavor kernel, $(wc -l < "$selected") modules, musl, and the $efi_stub UKI stub."
|
package/fetch-bun.sh
CHANGED
|
@@ -3,17 +3,10 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
libstdcpp_package=libstdc++-15.2.0-r5.apk
|
|
11
|
-
libgcc_package=libgcc-15.2.0-r5.apk
|
|
12
|
-
bun_sha256=76e1db84e98f22f78de0a87e309bfbbf297732847f9720db36750646c85c8c18
|
|
13
|
-
libstdcpp_sha256=14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6
|
|
14
|
-
libgcc_sha256=393dcd32629f06d7d85409c272d142d0c082772d10b87ef55ee82f47de3be637
|
|
15
|
-
|
|
16
|
-
mkdir -p downloads initramfs/bin initramfs/lib initramfs/dev initramfs/proc initramfs/sys initramfs/tmp
|
|
6
|
+
. ./scripts/arch.sh
|
|
7
|
+
|
|
8
|
+
mkdir -p "$downloads_dir" "$native_dir/bin" "$native_dir/lib" \
|
|
9
|
+
initramfs/dev initramfs/proc initramfs/sys initramfs/tmp
|
|
17
10
|
|
|
18
11
|
. ./scripts/fetch.sh
|
|
19
12
|
|
|
@@ -21,21 +14,21 @@ fetch "$bun_url" "$bun_zip" "$bun_sha256"
|
|
|
21
14
|
fetch "$alpine_base/$libstdcpp_package" "$libstdcpp_package" "$libstdcpp_sha256"
|
|
22
15
|
fetch "$alpine_base/$libgcc_package" "$libgcc_package" "$libgcc_sha256"
|
|
23
16
|
|
|
24
|
-
test -s
|
|
17
|
+
test -s "$native_dir/lib/$musl_loader" || {
|
|
25
18
|
echo "error: fetch stage incomplete: run ./fetch-alpine.sh first (./index.js -f runs both)" >&2
|
|
26
19
|
exit 1
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
unzip -p "
|
|
30
|
-
|
|
31
|
-
tar --warning=no-unknown-keyword -xOf "
|
|
32
|
-
usr/lib/libstdc++.so.6.0.34 >
|
|
33
|
-
tar --warning=no-unknown-keyword -xOf "
|
|
34
|
-
usr/lib/libgcc_s.so.1 >
|
|
22
|
+
unzip -p "$downloads_dir/$bun_zip" \
|
|
23
|
+
"$bun_build/bun" > "$native_dir/bin/bun"
|
|
24
|
+
tar --warning=no-unknown-keyword -xOf "$downloads_dir/$libstdcpp_package" \
|
|
25
|
+
usr/lib/libstdc++.so.6.0.34 > "$native_dir/lib/libstdc++.so.6"
|
|
26
|
+
tar --warning=no-unknown-keyword -xOf "$downloads_dir/$libgcc_package" \
|
|
27
|
+
usr/lib/libgcc_s.so.1 > "$native_dir/lib/libgcc_s.so.1"
|
|
35
28
|
|
|
36
29
|
# Keep libc as a distinct inode from the active dynamic loader. Calling dlopen
|
|
37
30
|
# on the loader inode itself deadlocks, while this physical copy is FFI-safe.
|
|
38
|
-
cp
|
|
39
|
-
chmod 0755 initramfs/init.js
|
|
31
|
+
cp "$native_dir/lib/$musl_loader" "$native_dir/lib/$musl_libc"
|
|
32
|
+
chmod 0755 initramfs/init.js "$native_dir/bin/bun" "$native_dir"/lib/*.so*
|
|
40
33
|
|
|
41
|
-
echo "Fetched Bun $bun_version and its
|
|
34
|
+
echo "Fetched Bun $bun_version and its $arch musl runtime into $native_dir/."
|
package/index.js
CHANGED
|
@@ -8,28 +8,51 @@
|
|
|
8
8
|
|
|
9
9
|
import pkg from "./package.json" with { type: "json" };
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
|
-
import {
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { accessSync, constants, copyFileSync, existsSync, readFileSync } from "node:fs";
|
|
12
13
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
|
|
15
16
|
const rootDir = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const invocationDir = process.cwd();
|
|
17
18
|
|
|
19
|
+
// Guest architectures. The names are Alpine's and the kernel's; the aliases
|
|
20
|
+
// are what Go, Docker, Node and Apple call the same CPUs. scripts/arch.sh
|
|
21
|
+
// holds everything else that differs between them.
|
|
22
|
+
const architectures = {
|
|
23
|
+
x86_64: { aliases: ["amd64", "x64"], efi: "BOOTX64.EFI", binutils: "x86_64-w64-mingw32", apt: "binutils-mingw-w64-x86-64" },
|
|
24
|
+
aarch64: { aliases: ["arm64"], efi: "BOOTAA64.EFI", binutils: "aarch64-linux-gnu", apt: "binutils-aarch64-linux-gnu" },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function normalizeArch(name) {
|
|
28
|
+
for (const [arch, { aliases }] of Object.entries(architectures)) {
|
|
29
|
+
if (name === arch || aliases.includes(name)) return arch;
|
|
30
|
+
}
|
|
31
|
+
fail(`unknown architecture ${name} (use x86_64/amd64 or aarch64/arm64)`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// What vda/EFI/BOOT holds decides the guest for a bare -r, as in run-qemu.sh.
|
|
35
|
+
function builtArch() {
|
|
36
|
+
return existsSync(resolve(rootDir, "vda/EFI/BOOT", architectures.aarch64.efi)) ? "aarch64" : "x86_64";
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
// Pipeline order. Flags select stages; this array fixes the order they run in.
|
|
40
|
+
// The fetch and build stages can run inside the Docker image from ./Dockerfile
|
|
41
|
+
// (--docker, and always on macOS); run always uses the host's QEMU.
|
|
19
42
|
const stages = [
|
|
20
43
|
{
|
|
21
44
|
flag: "fetch",
|
|
22
45
|
short: "f",
|
|
23
46
|
scripts: ["fetch-alpine.sh", "fetch-bun.sh"],
|
|
24
|
-
tools: ["curl", "unzip", "tar", "gzip", "sha256sum"],
|
|
47
|
+
tools: () => ["curl", "unzip", "tar", "gzip", "sha256sum"],
|
|
25
48
|
},
|
|
26
49
|
{
|
|
27
50
|
flag: "build",
|
|
28
51
|
short: "b",
|
|
29
52
|
scripts: ["build-uki.sh", "build-image.sh"],
|
|
30
|
-
tools: [
|
|
53
|
+
tools: (arch) => [
|
|
31
54
|
"fakeroot", "cpio", "gzip",
|
|
32
|
-
|
|
55
|
+
`${architectures[arch].binutils}-objcopy`, `${architectures[arch].binutils}-objdump`,
|
|
33
56
|
"parted", "mkfs.fat", "mmd", "mcopy",
|
|
34
57
|
],
|
|
35
58
|
},
|
|
@@ -37,7 +60,7 @@ const stages = [
|
|
|
37
60
|
flag: "run",
|
|
38
61
|
short: "r",
|
|
39
62
|
scripts: ["run-qemu.sh"],
|
|
40
|
-
tools: [
|
|
63
|
+
tools: (arch) => [`qemu-system-${arch}`],
|
|
41
64
|
},
|
|
42
65
|
];
|
|
43
66
|
|
|
@@ -50,22 +73,32 @@ function usage() {
|
|
|
50
73
|
return `${pkg.name} - ${pkg.description}
|
|
51
74
|
|
|
52
75
|
Usage:
|
|
53
|
-
${pkg.name} [-f] [-b] [-r] [--
|
|
76
|
+
${pkg.name} [-f] [-b] [-r] [--arch ARCH] [--docker] [--export] [--linux-lts]
|
|
77
|
+
[--real] [-- qemu arguments]
|
|
54
78
|
|
|
55
79
|
Stages (always run in this order, whichever you pick):
|
|
56
80
|
-f, --fetch download and verify the pinned kernel, musl, GCC runtime,
|
|
57
81
|
EFI stub and Bun (fetch-alpine.sh, fetch-bun.sh)
|
|
58
82
|
-b, --build pack the initramfs, assemble the UKI, write vda.img
|
|
59
83
|
(build-uki.sh, build-image.sh)
|
|
60
|
-
-r, --run boot vda.img under QEMU (run-qemu.sh)
|
|
84
|
+
-r, --run boot vda.img under QEMU (run-qemu.sh), with KVM or
|
|
85
|
+
Hypervisor.framework when the guest matches the host CPU
|
|
86
|
+
|
|
87
|
+
--arch ARCH guest architecture: x86_64 (default; alias amd64, x64) or
|
|
88
|
+
aarch64 (alias arm64, native speed on Apple Silicon). A bare
|
|
89
|
+
-r boots whichever one vda.img was last built for
|
|
90
|
+
--docker run fetch and build inside a Debian container built from
|
|
91
|
+
./Dockerfile; always on for macOS, which lacks the toolchain
|
|
61
92
|
|
|
62
93
|
--export after -b/--build, copy the image to
|
|
63
|
-
./buninu-linux-${pkg.version}.img
|
|
94
|
+
./buninu-linux-${pkg.version}.img (x86_64) or
|
|
95
|
+
./buninu-linux-${pkg.version}-aarch64.img
|
|
64
96
|
|
|
65
97
|
--linux-lts use Alpine's general-purpose linux-lts kernel for fetch/build
|
|
66
98
|
(the default is the smaller linux-virt kernel)
|
|
67
99
|
--real build for a physical machine: use linux-lts, make tty0 the
|
|
68
100
|
primary console, and include USB xHCI/HID keyboard modules
|
|
101
|
+
(x86_64 only)
|
|
69
102
|
|
|
70
103
|
-h, --help show this
|
|
71
104
|
-V, --version show name, version, runtime and platform
|
|
@@ -73,11 +106,13 @@ Stages (always run in this order, whichever you pick):
|
|
|
73
106
|
|
|
74
107
|
Short flags combine: -fbr is the whole pipeline, -br is what an edit to
|
|
75
108
|
initramfs/init.js needs, -r just boots what is already there. Anything after
|
|
76
|
-
-- goes to qemu-system-x86_64 verbatim (for example:
|
|
109
|
+
-- goes to qemu-system-x86_64 or qemu-system-aarch64 verbatim (for example:
|
|
110
|
+
-r -- -smp 4).
|
|
77
111
|
|
|
78
|
-
Fetch and build need the
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
Fetch and build need the Debian toolchain from README "Build environment"
|
|
113
|
+
(PRoot, a Debian or Ubuntu host, or --docker); run needs QEMU (Termux
|
|
114
|
+
pkg, apt, or brew install qemu). Tools missing for the stages you picked are
|
|
115
|
+
reported before anything starts.
|
|
81
116
|
`;
|
|
82
117
|
}
|
|
83
118
|
|
|
@@ -87,13 +122,24 @@ function parse(argv) {
|
|
|
87
122
|
let linuxLts = false;
|
|
88
123
|
let real = false;
|
|
89
124
|
let exportImage = false;
|
|
125
|
+
let docker = false;
|
|
126
|
+
// BUNINU_ARCH is what scripts/arch.sh reads; --arch overrides it.
|
|
127
|
+
let arch = process.env.BUNINU_ARCH ? normalizeArch(process.env.BUNINU_ARCH) : null;
|
|
90
128
|
let sawSeparator = false;
|
|
91
129
|
|
|
92
|
-
for (
|
|
130
|
+
for (let index = 0; index < argv.length; index++) {
|
|
131
|
+
const argument = argv[index];
|
|
93
132
|
if (sawSeparator) {
|
|
94
133
|
passthrough.push(argument);
|
|
95
134
|
} else if (argument === "--") {
|
|
96
135
|
sawSeparator = true;
|
|
136
|
+
} else if (argument === "--arch") {
|
|
137
|
+
if (index + 1 >= argv.length) fail("--arch needs a value: x86_64 or aarch64");
|
|
138
|
+
arch = normalizeArch(argv[++index]);
|
|
139
|
+
} else if (argument.startsWith("--arch=")) {
|
|
140
|
+
arch = normalizeArch(argument.slice("--arch=".length));
|
|
141
|
+
} else if (argument === "--docker") {
|
|
142
|
+
docker = true;
|
|
97
143
|
} else if (argument === "-h" || argument === "--help") {
|
|
98
144
|
console.log(usage());
|
|
99
145
|
process.exit(0);
|
|
@@ -137,7 +183,10 @@ function parse(argv) {
|
|
|
137
183
|
if (exportImage && !selected.has(stages[1])) {
|
|
138
184
|
fail("--export requires -b/--build (use -fb --export for a clean build)");
|
|
139
185
|
}
|
|
140
|
-
|
|
186
|
+
if (real && arch === "aarch64") {
|
|
187
|
+
fail("--real builds an x86_64 PC image; it cannot be combined with --arch aarch64");
|
|
188
|
+
}
|
|
189
|
+
return { selected, passthrough, linuxLts, real, exportImage, docker, arch };
|
|
141
190
|
}
|
|
142
191
|
|
|
143
192
|
// Same shape as Buninu's --version. The runtime line tells you whether this
|
|
@@ -180,54 +229,120 @@ function hasTool(name) {
|
|
|
180
229
|
return false;
|
|
181
230
|
}
|
|
182
231
|
|
|
232
|
+
const inDocker = (stage) => useDocker && stage.flag !== "run";
|
|
233
|
+
|
|
183
234
|
function checkTools(selectedStages) {
|
|
184
235
|
const missing = [];
|
|
185
236
|
for (const stage of selectedStages) {
|
|
186
|
-
|
|
237
|
+
const arch = stage.flag === "run" ? runArch : buildArch;
|
|
238
|
+
for (const tool of inDocker(stage) ? ["docker"] : stage.tools(arch)) {
|
|
187
239
|
if (!hasTool(tool)) missing.push(`${tool} (for --${stage.flag})`);
|
|
188
240
|
}
|
|
189
241
|
}
|
|
190
242
|
if (missing.length > 0) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
243
|
+
const hint = process.platform === "darwin"
|
|
244
|
+
? `
|
|
245
|
+
Install Docker Desktop (https://www.docker.com/products/docker-desktop/) for
|
|
246
|
+
-f/-b, and QEMU with its UEFI firmware for -r:
|
|
247
|
+
brew install qemu
|
|
248
|
+
`
|
|
249
|
+
: `
|
|
250
|
+
Run this command in Debian or Ubuntu first, or pass --docker to build in a container:
|
|
251
|
+
apt install ${architectures[buildArch].apt} cpio curl dosfstools fakeroot mtools parted unzip
|
|
252
|
+
QEMU for -r:
|
|
253
|
+
apt install qemu-system ovmf qemu-efi-aarch64
|
|
254
|
+
`;
|
|
255
|
+
fail(`missing tools, nothing was run:\n ${missing.join("\n ")}\n${hint}`);
|
|
256
|
+
}
|
|
257
|
+
if (selectedStages.some(inDocker) && spawnSync("docker", ["info"], { stdio: "ignore" }).status !== 0) {
|
|
258
|
+
fail("docker is installed but its daemon is not answering; start Docker Desktop (or dockerd) and retry");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// The build image is tagged with a hash of the Dockerfile, so editing it
|
|
263
|
+
// builds a fresh image and an unchanged one is reused without a rebuild. The
|
|
264
|
+
// Dockerfile goes in on stdin: the build needs no context, and sending the
|
|
265
|
+
// checkout (downloads/ included) would only slow it down.
|
|
266
|
+
let dockerImageTag;
|
|
267
|
+
function dockerImage() {
|
|
268
|
+
if (dockerImageTag) return dockerImageTag;
|
|
269
|
+
const dockerfile = readFileSync(resolve(rootDir, "Dockerfile"));
|
|
270
|
+
const tag = `buninu-linux-build:${createHash("sha256").update(dockerfile).digest("hex").slice(0, 12)}`;
|
|
271
|
+
if (spawnSync("docker", ["image", "inspect", tag], { stdio: "ignore" }).status !== 0) {
|
|
272
|
+
console.log(`\n==> [docker] building ${tag} from Dockerfile`);
|
|
273
|
+
const result = spawnSync("docker", ["build", "--tag", tag, "-"], {
|
|
274
|
+
input: dockerfile,
|
|
275
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
276
|
+
});
|
|
277
|
+
if (result.error) fail(`could not start docker build: ${result.error.message}`);
|
|
278
|
+
if (result.status !== 0) fail(`docker build exited with status ${result.status}`);
|
|
198
279
|
}
|
|
280
|
+
return (dockerImageTag = tag);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// In Docker the script runs as the invoking user, so everything it writes
|
|
284
|
+
// into the bind-mounted checkout stays owned by that user; fakeroot provides
|
|
285
|
+
// the root ownership the initramfs records.
|
|
286
|
+
function dockerRun(script, env) {
|
|
287
|
+
const passEnv = ["BUNINU_ARCH", "LINUX_FLAVOR", "REAL_MACHINE", "ALPINE_MIRROR"]
|
|
288
|
+
.filter((name) => env[name] !== undefined)
|
|
289
|
+
.flatMap((name) => ["--env", `${name}=${env[name]}`]);
|
|
290
|
+
const user = typeof process.getuid === "function"
|
|
291
|
+
? ["--user", `${process.getuid()}:${process.getgid()}`]
|
|
292
|
+
: [];
|
|
293
|
+
return [
|
|
294
|
+
"run", "--rm", ...user,
|
|
295
|
+
"--mount", `type=bind,src=${rootDir},dst=/src`, "--workdir", "/src",
|
|
296
|
+
"--env", "HOME=/tmp", ...passEnv,
|
|
297
|
+
dockerImage(), `./${script}`,
|
|
298
|
+
];
|
|
199
299
|
}
|
|
200
300
|
|
|
201
301
|
function runScript(stage, script, args = [], env = process.env) {
|
|
202
|
-
const
|
|
203
|
-
console.log(
|
|
204
|
-
|
|
302
|
+
const docker = inDocker(stage);
|
|
303
|
+
console.log(
|
|
304
|
+
`\n==> [${stage.flag}${docker ? " in docker" : ""}] ${script}${args.length ? " " + args.join(" ") : ""}`,
|
|
305
|
+
);
|
|
306
|
+
const result = docker
|
|
307
|
+
? spawnSync("docker", dockerRun(script, env), { stdio: "inherit" })
|
|
308
|
+
: spawnSync(resolve(rootDir, script), args, { cwd: rootDir, stdio: "inherit", env });
|
|
205
309
|
if (result.error) fail(`could not start ${script}: ${result.error.message}`);
|
|
206
310
|
if (result.status !== 0) {
|
|
207
311
|
fail(`${script} exited with status ${result.status ?? "signal " + result.signal}`);
|
|
208
312
|
}
|
|
209
313
|
}
|
|
210
314
|
|
|
211
|
-
const { selected, passthrough, linuxLts, real, exportImage } = parse(process.argv.slice(2));
|
|
315
|
+
const { selected, passthrough, linuxLts, real, exportImage, docker, arch } = parse(process.argv.slice(2));
|
|
212
316
|
const ordered = stages.filter((stage) => selected.has(stage));
|
|
317
|
+
const useDocker = docker || process.platform === "darwin";
|
|
318
|
+
const buildArch = arch ?? "x86_64";
|
|
319
|
+
// A bare -r after -b boots what -b just built; otherwise what vda.img holds.
|
|
320
|
+
const runArch = arch ?? (selected.has(stages[1]) ? buildArch : builtArch());
|
|
213
321
|
checkTools(ordered);
|
|
214
322
|
|
|
323
|
+
const stageEnv = {
|
|
324
|
+
...process.env,
|
|
325
|
+
...(arch && { BUNINU_ARCH: arch }),
|
|
326
|
+
...((linuxLts || real) && { LINUX_FLAVOR: "lts" }),
|
|
327
|
+
...(real && { REAL_MACHINE: "1" }),
|
|
328
|
+
};
|
|
329
|
+
|
|
215
330
|
for (const stage of ordered) {
|
|
331
|
+
// run-qemu.sh builds a missing vda.img itself, which needs the build
|
|
332
|
+
// toolchain; with Docker that has to happen in the container first.
|
|
333
|
+
if (stage.flag === "run" && useDocker && !selected.has(stages[1]) &&
|
|
334
|
+
!existsSync(resolve(rootDir, "vda.img"))) {
|
|
335
|
+
runScript(stages[1], "build-image.sh", [], { ...stageEnv, BUNINU_ARCH: runArch });
|
|
336
|
+
}
|
|
216
337
|
for (const script of stage.scripts) {
|
|
217
|
-
runScript(
|
|
218
|
-
stage,
|
|
219
|
-
script,
|
|
220
|
-
stage.flag === "run" ? passthrough : [],
|
|
221
|
-
linuxLts || real
|
|
222
|
-
? { ...process.env, LINUX_FLAVOR: "lts", ...(real && { REAL_MACHINE: "1" }) }
|
|
223
|
-
: process.env,
|
|
224
|
-
);
|
|
338
|
+
runScript(stage, script, stage.flag === "run" ? passthrough : [], stageEnv);
|
|
225
339
|
}
|
|
226
340
|
}
|
|
227
341
|
|
|
228
342
|
if (exportImage) {
|
|
229
343
|
const source = resolve(rootDir, "vda.img");
|
|
230
|
-
const
|
|
344
|
+
const suffix = buildArch === "x86_64" ? "" : `-${buildArch}`;
|
|
345
|
+
const destination = resolve(invocationDir, `buninu-linux-${pkg.version}${suffix}.img`);
|
|
231
346
|
try {
|
|
232
347
|
accessSync(source, constants.R_OK);
|
|
233
348
|
copyFileSync(source, destination);
|
package/initramfs/bin/chroot
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// /etc/resolv.conf, so `chroot /mnt/rootfs` lands in a usable system. Every
|
|
9
9
|
// mount it makes is undone when the command exits.
|
|
10
10
|
|
|
11
|
-
import { existsSync, mkdirSync, realpathSync, statSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { chmodSync, existsSync, mkdirSync, realpathSync, statSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { libc, cString, ptr, errno, strerror, Errno } from "../lib/dlopen.js";
|
|
13
13
|
import { showDocument } from "../lib/document.js";
|
|
14
14
|
|
|
@@ -125,6 +125,9 @@ if (options.mount) {
|
|
|
125
125
|
// A bound file, so name resolution inside follows the host's configuration
|
|
126
126
|
// and nothing inside the root is modified.
|
|
127
127
|
if (existsSync("/etc/resolv.conf") && existsSync(`${root}/etc`)) {
|
|
128
|
+
|
|
129
|
+
chmodSync(`/etc/resolv.conf`, 0o644);
|
|
130
|
+
|
|
128
131
|
prepare({ inside: "/etc/resolv.conf", source: "/etc/resolv.conf", type: "none", flags: MS.BIND, file: true });
|
|
129
132
|
}
|
|
130
133
|
}
|
package/initramfs/bin/ip
CHANGED
|
@@ -498,8 +498,9 @@ const commandAddress = (words, options) => {
|
|
|
498
498
|
});
|
|
499
499
|
};
|
|
500
500
|
|
|
501
|
-
// struct rtentry on x86_64: rt_dst @8,
|
|
502
|
-
// 16-byte sockaddr), rt_flags u16 @56,
|
|
501
|
+
// struct rtentry on 64-bit Linux, x86_64 and aarch64 alike: rt_dst @8,
|
|
502
|
+
// rt_gateway @24, rt_genmask @40 (each a 16-byte sockaddr), rt_flags u16 @56,
|
|
503
|
+
// rt_metric i16 @80, rt_dev char* @88.
|
|
503
504
|
const rtentry = ({ destination, prefix, gateway, device, metric }) => {
|
|
504
505
|
const route = new Uint8Array(120);
|
|
505
506
|
const routeView = view(route);
|
package/initramfs/bin/ps
CHANGED
|
@@ -16,6 +16,9 @@ const ttyName = (number) => {
|
|
|
16
16
|
const minor = (device & 0xff) | ((device >>> 12) & 0xfff00);
|
|
17
17
|
if (major >= 136 && major <= 143) return `pts/${minor}`;
|
|
18
18
|
if (major === 4 && minor < 64) return `tty${minor}`;
|
|
19
|
+
// Serial ports: the 8250 UARTs of a PC and the PL011 of QEMU's aarch64 virt.
|
|
20
|
+
if (major === 4) return `ttyS${minor - 64}`;
|
|
21
|
+
if (major === 204 && minor >= 64) return `ttyAMA${minor - 64}`;
|
|
19
22
|
if (major === 5 && minor === 0) return "tty";
|
|
20
23
|
return `${major}:${minor}`;
|
|
21
24
|
};
|
package/initramfs/bin/tar
CHANGED
|
@@ -41,6 +41,14 @@ for (let index = 0; index < argv.length; index++) {
|
|
|
41
41
|
}
|
|
42
42
|
if (argument === "--zstd") { options.compression = "zstd"; continue; }
|
|
43
43
|
if (argument === "--gzip") { options.compression = "gzip"; continue; }
|
|
44
|
+
// GNU tar compatibility used by js-udocker. Bun.Archive never restores
|
|
45
|
+
// ownership or permissions, and extraction replaces existing files, so
|
|
46
|
+
// these options already describe its normal extraction behaviour.
|
|
47
|
+
if (argument === "--no-same-owner" || argument === "--no-same-permissions" || argument === "--overwrite") continue;
|
|
48
|
+
// Bun.Archive does not extract device nodes. js-udocker separately removes
|
|
49
|
+
// whiteout files after extraction, so its excludes are safe no-ops here.
|
|
50
|
+
if (argument === "--exclude") { take(index++, argument); continue; }
|
|
51
|
+
if (argument.startsWith("--exclude=")) continue;
|
|
44
52
|
if (argument === "-C" || argument === "--directory") {
|
|
45
53
|
options.directory = take(index++, argument);
|
|
46
54
|
continue;
|
|
@@ -67,6 +75,7 @@ for (let index = 0; index < argv.length; index++) {
|
|
|
67
75
|
} else if (flag === "r") {
|
|
68
76
|
fail("-r append is not supported by Bun.Archive");
|
|
69
77
|
} else if (flag === "v") options.verbose = true;
|
|
78
|
+
else if (flag === "o") {} // GNU tar: --no-same-owner
|
|
70
79
|
else if (flag === "z") options.compression = "gzip";
|
|
71
80
|
else if (flag === "f") {
|
|
72
81
|
const attached = flags.slice(offset + 1);
|