buninu-linux 0.9.56 → 0.10.56
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/LICENSES/MIT-ZilchOS-bootstrap-from-tcc.txt +21 -0
- package/NOTICE.md +34 -0
- package/README.md +3 -0
- package/initramfs/usr/lib/bootstrap/1.prepare-rootfs.js +106 -0
- package/initramfs/usr/lib/bootstrap/10.build-m4.js +40 -0
- package/initramfs/usr/lib/bootstrap/11.build-flex.js +50 -0
- package/initramfs/usr/lib/bootstrap/12.build-gcc.js +261 -0
- package/initramfs/usr/lib/bootstrap/2.enter-rootfs.js +49 -0
- package/initramfs/usr/lib/bootstrap/3.gen-musl-dev.js +141 -0
- package/initramfs/usr/lib/bootstrap/4.build-tcc.js +61 -0
- package/initramfs/usr/lib/bootstrap/5.build-make.js +58 -0
- package/initramfs/usr/lib/bootstrap/6.build-binutils.js +77 -0
- package/initramfs/usr/lib/bootstrap/7.build-gmp.js +68 -0
- package/initramfs/usr/lib/bootstrap/8.build-mpfr.js +59 -0
- package/initramfs/usr/lib/bootstrap/9.build-mpc.js +55 -0
- package/initramfs/usr/lib/bootstrap/LICENSE +21 -0
- package/initramfs/usr/lib/bootstrap/LICENSES/MIT-ZilchOS-bootstrap-from-tcc.txt +21 -0
- package/initramfs/usr/lib/bootstrap/NOTICE.md +65 -0
- package/initramfs/usr/lib/bootstrap/README.md +204 -0
- package/initramfs/usr/lib/bootstrap/downloads.md +49 -0
- package/initramfs/usr/lib/bootstrap/lib/build.js +155 -0
- package/initramfs/usr/lib/bootstrap/lib/common.js +134 -0
- package/initramfs/usr/lib/bootstrap/lib/compile-object.c +14 -0
- package/initramfs/usr/lib/bootstrap/lib/musl-headers.js +59 -0
- package/initramfs/usr/lib/bootstrap/lib/seed.js +148 -0
- package/initramfs/usr/lib/bootstrap/lib/startup-check.c +14 -0
- package/initramfs/usr/lib/bootstrap/lib/tcc-build.js +53 -0
- package/initramfs/usr/lib/bootstrap/sources/musl-1.2.6.tar.gz +0 -0
- package/initramfs/usr/lib/bootstrap/sources/tinycc-05f0faf-upstream.tar.gz +0 -0
- package/initramfs/usr/lib/bootstrap/sources/tinycc-local-bootstrap-files.tar.gz +0 -0
- package/package.json +1 -1
- package/test/bootstrap.test.js +65 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync, cpSync, mkdirSync, readFileSync, readdirSync,
|
|
3
|
+
renameSync, symlinkSync,
|
|
4
|
+
} from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
bootstrap, bun, download, elfInterpreter, run, sha256,
|
|
8
|
+
validateElf, write,
|
|
9
|
+
} from "./common.js";
|
|
10
|
+
|
|
11
|
+
const tools = [
|
|
12
|
+
"bin/tar", "bin/unzip", "usr/bin/env",
|
|
13
|
+
"lib/crc32.js", "lib/document.js", "lib/tar-strip.js", "lib/tar-exclude.js",
|
|
14
|
+
"usr/share/doc/buninu-linux/tar.md",
|
|
15
|
+
];
|
|
16
|
+
const shell = "buninu/apps/bunmsh/";
|
|
17
|
+
|
|
18
|
+
function addTree(files, directory, prefix) {
|
|
19
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
20
|
+
const path = join(directory, entry.name), target = prefix + entry.name;
|
|
21
|
+
if (entry.isDirectory()) addTree(files, path, target + "/");
|
|
22
|
+
else files.set(target, readFileSync(path)); // Resolve library/source symlinks.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function localSeed(arch) {
|
|
27
|
+
const files = new Map();
|
|
28
|
+
for (const path of tools) files.set(path, readFileSync("/" + path));
|
|
29
|
+
addTree(files, "/" + shell, shell);
|
|
30
|
+
for (const name of ["libgcc_s.so.1", "libstdc++.so.6"]) {
|
|
31
|
+
let bytes;
|
|
32
|
+
try { bytes = readFileSync("/lib/" + name); }
|
|
33
|
+
catch (error) { if (error.code !== "ENOENT") throw error; bytes = readFileSync("/usr/lib/" + name); }
|
|
34
|
+
files.set("usr/lib/" + name, bytes);
|
|
35
|
+
}
|
|
36
|
+
files.set(`lib/ld-musl-${arch}.so.1`, readFileSync(`/lib/ld-musl-${arch}.so.1`));
|
|
37
|
+
files.set("bin/bun", readFileSync("/bin/bun"));
|
|
38
|
+
files.set("etc/resolv.conf", readFileSync("/etc/resolv.conf"));
|
|
39
|
+
return files;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function releaseSeed(arch, work) {
|
|
43
|
+
const tarball = await download("buninu-linux@0.9.56");
|
|
44
|
+
// Bun's archive reader works in-process; no host tar is required.
|
|
45
|
+
const entries = await new Bun.Archive(readFileSync(tarball)).files();
|
|
46
|
+
const get = async name => {
|
|
47
|
+
const file = entries.get("package/" + name);
|
|
48
|
+
if (!file) throw new Error(`Pinned buninu-linux tarball is missing ${name}`);
|
|
49
|
+
return Buffer.from(await file.arrayBuffer());
|
|
50
|
+
};
|
|
51
|
+
const files = new Map();
|
|
52
|
+
for (const path of tools) files.set(path, await get("initramfs/" + path));
|
|
53
|
+
for (const [name, file] of entries) {
|
|
54
|
+
const prefix = "package/initramfs/";
|
|
55
|
+
if (name.startsWith(prefix + shell)) files.set(name.slice(prefix.length), Buffer.from(await file.arrayBuffer()));
|
|
56
|
+
}
|
|
57
|
+
for (const name of ["libgcc_s.so.1", "libstdc++.so.6"]) {
|
|
58
|
+
files.set("usr/lib/" + name, await get(`native/${arch}/lib/${name}`));
|
|
59
|
+
}
|
|
60
|
+
const loader = `ld-musl-${arch}.so.1`;
|
|
61
|
+
files.set("lib/" + loader, await get(`native/${arch}/lib/${loader}`));
|
|
62
|
+
files.set("etc/resolv.conf", await get("initramfs/etc/resolv.conf"));
|
|
63
|
+
|
|
64
|
+
console.log("② Extracting pinned musl-bun");
|
|
65
|
+
const build = arch === "aarch64" ? "bun-linux-aarch64-musl" : "bun-linux-x64-musl-baseline";
|
|
66
|
+
const zip = await download(`${build}@1.4.2`);
|
|
67
|
+
// unzip is a CLI with top-level process.exit; invoke it separately.
|
|
68
|
+
for (const path of ["bin/unzip", "lib/crc32.js"]) write(join(work, path), files.get(path));
|
|
69
|
+
await run([bun(), join(work, "bin/unzip"), zip, `${build}/bun`, "-d", work]);
|
|
70
|
+
files.set("bin/bun", readFileSync(join(work, build, "bun")));
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function validateSeed(files, arch) {
|
|
75
|
+
const loader = `lib/ld-musl-${arch}.so.1`;
|
|
76
|
+
for (const name of ["bin/bun", loader, "usr/lib/libgcc_s.so.1", "usr/lib/libstdc++.so.6"]) {
|
|
77
|
+
validateElf(name, files.get(name), arch);
|
|
78
|
+
}
|
|
79
|
+
if (elfInterpreter(files.get("bin/bun")) !== "/" + loader) throw new Error("Bun must use the target musl loader");
|
|
80
|
+
for (const name of ["index.js", "package.json", "src/main.js"]) {
|
|
81
|
+
if (!files.has(shell + name)) throw new Error(`Missing bunmsh ${name}`);
|
|
82
|
+
}
|
|
83
|
+
for (const name of files.keys()) {
|
|
84
|
+
if (name.includes("\\") || name.includes("\0") || name.split("/").some(p => ["", ".", ".."].includes(p))) {
|
|
85
|
+
throw new Error(`Unsafe seed path: ${name}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function writeSeed(root, arch, files, origin) {
|
|
91
|
+
mkdirSync(root);
|
|
92
|
+
for (const name of ["bin", "lib", "etc", "usr/lib", "usr/bin", "tmp", "root", "dev", "proc", "sys", "run"]) {
|
|
93
|
+
mkdirSync(join(root, name), { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
for (const [name, bytes] of files) {
|
|
96
|
+
const executable = name.startsWith("bin/") || name.startsWith("usr/bin/")
|
|
97
|
+
|| name === shell + "index.js" || /\.so\.[16]$/.test(name);
|
|
98
|
+
write(join(root, name), bytes, executable ? 0o755 : 0o644);
|
|
99
|
+
}
|
|
100
|
+
chmodSync(join(root, "tmp"), 0o1777);
|
|
101
|
+
write(join(root, "bin/sh"), `#!/bin/bun
|
|
102
|
+
// Guest entry shell; build stages use their own shell and explicit PATH.
|
|
103
|
+
const paths = [
|
|
104
|
+
"/bootstrap/result/bin",
|
|
105
|
+
"/bootstrap/toolchain/bin",
|
|
106
|
+
...(process.env.PATH || "/bin:/usr/bin").split(":"),
|
|
107
|
+
];
|
|
108
|
+
process.env.PATH = [...new Set(paths)].join(":");
|
|
109
|
+
await import("/buninu/apps/bunmsh/src/main.js");
|
|
110
|
+
`, 0o755);
|
|
111
|
+
symlinkSync(`../../lib/ld-musl-${arch}.so.1`, join(root, "usr/lib/libc.so"));
|
|
112
|
+
symlinkSync("libgcc_s.so.1", join(root, "usr/lib/libgcc_s.so"));
|
|
113
|
+
symlinkSync("libc.so", join(root, "usr/lib/libm.so"));
|
|
114
|
+
write(join(root, "etc/buninu-bootstrap.json"), JSON.stringify({ format: 1, arch, origin }, null, 2) + "\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sources = [
|
|
118
|
+
["musl-1.2.6.tar.gz", "d585fd3b613c66151fc3249e8ed44f77020cb5e6c1e635a616d3f9f82460512a"],
|
|
119
|
+
["tinycc-05f0faf-upstream.tar.gz", "300d4fd6fc4a7294403e17bfd29201954410ac2baa94f189526f6bef83524c19"],
|
|
120
|
+
["tinycc-local-bootstrap-files.tar.gz", "59216479774f3c61b06420ca9b03ad4b8dcbdbb242d2d236d2daace135e35d59"],
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
export function sourceArchives() {
|
|
124
|
+
return sources.map(([name, hash]) => {
|
|
125
|
+
const bytes = readFileSync(join(bootstrap, "sources", name));
|
|
126
|
+
if (sha256(bytes) !== hash) throw new Error(`Bundled source hash mismatch: ${name}`);
|
|
127
|
+
return new Bun.Archive(bytes);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function installSources(root, archives) {
|
|
132
|
+
const src = join(root, "bootstrap/src");
|
|
133
|
+
mkdirSync(src, { recursive: true });
|
|
134
|
+
await archives[0].extract(src);
|
|
135
|
+
renameSync(join(src, "musl-1.2.6"), join(src, "musl"));
|
|
136
|
+
await archives[1].extract(src);
|
|
137
|
+
renameSync(join(src, "tinycc-05f0faf"), join(src, "tinycc"));
|
|
138
|
+
await archives[2].extract(root);
|
|
139
|
+
// The overlay provides the cc wrapper. It becomes usable at stage 4.
|
|
140
|
+
chmodSync(join(root, "bin/cc"), 0o755);
|
|
141
|
+
// Copy the recipes/helpers, but do not duplicate the extracted tarballs.
|
|
142
|
+
const target = join(root, "bootstrap");
|
|
143
|
+
mkdirSync(target, { recursive: true });
|
|
144
|
+
const stages = readdirSync(bootstrap).filter(name => /^(?:[1-9]|1[0-2])\..*\.js$/.test(name));
|
|
145
|
+
for (const name of [...stages, "lib", "README.md", "downloads.md", "NOTICE.md", "LICENSE", "LICENSES"]) {
|
|
146
|
+
cpSync(join(bootstrap, name), join(target, name), { recursive: true, errorOnExist: true, force: false });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#include <stdio.h>
|
|
2
|
+
#include <stdlib.h>
|
|
3
|
+
#include <string.h>
|
|
4
|
+
static int initialized;
|
|
5
|
+
__attribute__((constructor)) static void init(void) { initialized = 37; }
|
|
6
|
+
__attribute__((destructor)) static void fini(void) { puts("destructor OK"); }
|
|
7
|
+
int main(int argc, char **argv) {
|
|
8
|
+
if (initialized != 37) return 1;
|
|
9
|
+
if (argc != 3 || strcmp(argv[1], "hello") || strcmp(argv[2], "two words")) return 2;
|
|
10
|
+
const char *value = getenv("CRT_TEST");
|
|
11
|
+
if (!value || strcmp(value,"seed-free")) return 3;
|
|
12
|
+
puts("argc/argv, environment, constructor OK");
|
|
13
|
+
return 42;
|
|
14
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Standalone TinyCC build used by stage 4 inside the target musl rootfs.
|
|
2
|
+
import { chmodSync, cpSync, existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { bun, run } from "./common.js";
|
|
5
|
+
|
|
6
|
+
export async function buildTcc() {
|
|
7
|
+
const root = "/bootstrap/src/tinycc";
|
|
8
|
+
if (!["x64", "arm64"].includes(process.arch)) throw new Error("Unsupported architecture");
|
|
9
|
+
const work = "/bootstrap/build/tinycc";
|
|
10
|
+
const runtime = "/bootstrap/toolchain/lib/tcc";
|
|
11
|
+
mkdirSync(work, { recursive: true });
|
|
12
|
+
mkdirSync(runtime, { recursive: true });
|
|
13
|
+
mkdirSync("/bootstrap/toolchain/bin", { recursive: true });
|
|
14
|
+
cpSync(join(root, "include"), join(runtime, "include"), { recursive: true });
|
|
15
|
+
const stage1 = "/bootstrap/toolchain/bin/tcc";
|
|
16
|
+
const stage2 = join(work, "tcc-stage2");
|
|
17
|
+
const flags = ["-B/bootstrap/toolchain/lib/tcc", "-nostdinc", "-I/usr/include", "-I/bootstrap/toolchain/lib/tcc/include", "-L/usr/lib", "-L/lib"];
|
|
18
|
+
|
|
19
|
+
console.log("1. Build standalone TinyCC using Bun.cc");
|
|
20
|
+
await run([bun(), join(root, "bun-bootstrap.js"), join(root, "bun-tcc-cli.c"), stage1, "0"]);
|
|
21
|
+
await run([stage1, "-v"]);
|
|
22
|
+
|
|
23
|
+
if (process.arch === "x64") {
|
|
24
|
+
console.log("2. Build x64 helpers for programs compiled by cc");
|
|
25
|
+
const objects = [];
|
|
26
|
+
for (const name of ["libtcc1", "va_list"]) {
|
|
27
|
+
const object = join(work, `${name}.o`);
|
|
28
|
+
await run([stage1, ...flags, "-c", join(root, "lib", `${name}.c`), "-o", object]);
|
|
29
|
+
objects.push(object);
|
|
30
|
+
}
|
|
31
|
+
await run([stage1, "-ar", "rcs", join(runtime, "libbootstrap-tcc-x64.a"), ...objects]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("3. Rebuild TinyCC with stage1");
|
|
35
|
+
await run([stage1, ...flags, join(root, "bun-tcc-cli.c"), "-lgcc_s", "-o", stage2]);
|
|
36
|
+
const a = await Bun.file(stage1).bytes();
|
|
37
|
+
const b = await Bun.file(stage2).bytes();
|
|
38
|
+
if (!Buffer.from(a).equals(Buffer.from(b))) throw new Error("stage1/stage2 differ");
|
|
39
|
+
console.log("stage1/stage2 byte-identical: true");
|
|
40
|
+
|
|
41
|
+
if (!existsSync("/bin/cc")) throw new Error("Extract the overlay's bin/cc first");
|
|
42
|
+
chmodSync("/bin/cc", 0o755);
|
|
43
|
+
console.log("4. Compile and run the libc smoke test through cc");
|
|
44
|
+
await run(["/bin/cc", join(root, "bun-hello-libc.c"), "-o", join(work, "hello-cc")]);
|
|
45
|
+
await run([join(work, "hello-cc")]);
|
|
46
|
+
if (process.arch === "x64") {
|
|
47
|
+
console.log("5. Check x64 conversions and variadic calls");
|
|
48
|
+
await run(["/bin/cc", join(root, "bun-runtime-check.c"), "-o", join(work, "runtime-check")]);
|
|
49
|
+
await run([join(work, "runtime-check")]);
|
|
50
|
+
}
|
|
51
|
+
console.log("Ready: /bin/cc (TinyCC stage1)");
|
|
52
|
+
}
|
|
53
|
+
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Run with Bun inside bunproot or the Buninu Linux VM.
|
|
2
|
+
import { afterAll, expect, test } from "bun:test";
|
|
3
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
assertAbsent, bootstrap, download, hasBuninuId, normalizeArch, options,
|
|
8
|
+
} from "../initramfs/usr/lib/bootstrap/lib/common.js";
|
|
9
|
+
|
|
10
|
+
const work = mkdtempSync(join(tmpdir(), "bootstrap-test-"));
|
|
11
|
+
afterAll(() => rmSync(work, { recursive: true, force: true }));
|
|
12
|
+
|
|
13
|
+
test("architecture aliases and optional destination", () => {
|
|
14
|
+
for (const alias of ["a", "arm64", "aarch64"]) {
|
|
15
|
+
expect(options([alias])).toEqual({ arch: "aarch64", rootfs: resolve("roota") });
|
|
16
|
+
}
|
|
17
|
+
for (const alias of ["x", "x64", "x86_64", "x86-64"]) {
|
|
18
|
+
expect(options([alias])).toEqual({ arch: "x86_64", rootfs: resolve("rootx") });
|
|
19
|
+
}
|
|
20
|
+
const arch = normalizeArch(process.arch);
|
|
21
|
+
expect(options([])).toEqual({ arch, rootfs: resolve(arch === "aarch64" ? "roota" : "rootx") });
|
|
22
|
+
expect(options(["./a path's root"])).toEqual({ arch, rootfs: resolve("./a path's root") });
|
|
23
|
+
expect(options(["x", "./custom"])).toEqual({ arch: "x86_64", rootfs: resolve("custom") });
|
|
24
|
+
expect(() => options(["--arch", "x"])).toThrow();
|
|
25
|
+
expect(() => options(["x", "one", "two"])).toThrow();
|
|
26
|
+
expect(() => normalizeArch("riscv64")).toThrow();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("local mode requires the exact ID, never NAME or a substring", () => {
|
|
30
|
+
for (const value of ["buninu-linux", '"buninu-linux"', "'buninu-linux'"]) {
|
|
31
|
+
expect(hasBuninuId(`NAME="Buninu Linux"\nID=${value}\n`)).toBe(true);
|
|
32
|
+
}
|
|
33
|
+
for (const text of [
|
|
34
|
+
'NAME="Buninu Linux"\nID=alpine\n',
|
|
35
|
+
'ID_LIKE=buninu-linux\n', '# ID=buninu-linux\n',
|
|
36
|
+
'ID=buninu-linux-other\n', 'ID=buninu-linux\nID=alpine\n',
|
|
37
|
+
'ID="$(echo buninu-linux)"\n',
|
|
38
|
+
]) expect(hasBuninuId(text)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("existing destinations, including dangling links, are refused", () => {
|
|
42
|
+
const file = join(work, "file"), link = join(work, "link");
|
|
43
|
+
writeFileSync(file, "keep me");
|
|
44
|
+
symlinkSync("missing", link);
|
|
45
|
+
expect(() => assertAbsent(file)).toThrow("Refusing existing destination");
|
|
46
|
+
expect(() => assertAbsent(link)).toThrow("Refusing existing destination");
|
|
47
|
+
expect(() => assertAbsent(join(work, "new"))).not.toThrow();
|
|
48
|
+
expect(readFileSync(file, "utf8")).toBe("keep me");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("a corrupt cached tarball is rejected before extraction or refetch", async () => {
|
|
52
|
+
const old = process.env.XDG_CACHE_HOME;
|
|
53
|
+
process.env.XDG_CACHE_HOME = work;
|
|
54
|
+
try {
|
|
55
|
+
const [url, pin] = Bun.YAML.parse(readFileSync(join(bootstrap, "downloads.md"), "utf8"))["buninu-linux@0.9.56"];
|
|
56
|
+
const cache = join(work, "buninu-bootstrap");
|
|
57
|
+
mkdirSync(cache);
|
|
58
|
+
writeFileSync(join(cache, pin.slice(7) + "-" + new URL(url).pathname.split("/").at(-1)), "corrupt");
|
|
59
|
+
await expect(download("buninu-linux@0.9.56")).rejects.toThrow("Cached file hash mismatch");
|
|
60
|
+
await expect(download("unlisted@version")).rejects.toThrow("Missing or invalid download pin");
|
|
61
|
+
} finally {
|
|
62
|
+
if (old === undefined) delete process.env.XDG_CACHE_HOME;
|
|
63
|
+
else process.env.XDG_CACHE_HOME = old;
|
|
64
|
+
}
|
|
65
|
+
});
|