buninu-linux 0.7.1 → 0.7.3

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.
@@ -115,6 +115,26 @@ function assetFilePath(key) {
115
115
  return assets ? assets.assetDiskPath(key) : path.join(__dirname, key);
116
116
  }
117
117
 
118
+ function windowsConptyDllPath() {
119
+ const dll = assetFilePath("conpty/conpty.dll");
120
+ if (fs.existsSync(dll)) return dll;
121
+ if (!assets) return null;
122
+ try {
123
+ const dllBytes = assets.readAssetBytesSync("conpty/conpty.dll");
124
+ const hostBytes = assets.readAssetBytesSync("conpty/OpenConsole.exe");
125
+ const version = crypto.createHash("sha256").update(dllBytes).digest("hex").slice(0, 16);
126
+ const dir = path.join(os.tmpdir(), `jsgotty-conpty-${version}`);
127
+ fs.mkdirSync(dir, { recursive: true });
128
+ for (const [name, bytes] of [["conpty.dll", dllBytes], ["OpenConsole.exe", hostBytes]]) {
129
+ const dest = path.join(dir, name);
130
+ if (!fs.existsSync(dest)) fs.writeFileSync(dest, bytes);
131
+ }
132
+ return path.join(dir, "conpty.dll");
133
+ } catch (error) {
134
+ throw new Error(`Could not prepare bundled ConPTY: ${error.message}`);
135
+ }
136
+ }
137
+
118
138
  // URL-relative path -> asset key, or null when it escapes `static/`.
119
139
  // Normalizing against "/" is what makes "../../etc/passwd" unreachable.
120
140
  function staticAssetKey(relativePath) {
@@ -1624,6 +1644,10 @@ function createPtyBackend(options) {
1624
1644
  if (globalThis.Bun?.semver?.satisfies?.(
1625
1645
  globalThis.Bun?.version, ">=1.3.14"
1626
1646
  )) {
1647
+ const conptyDll = windowsConptyDllPath();
1648
+ if (conptyDll) {
1649
+ return new (require("./windows-conpty.js"))(options, conptyDll);
1650
+ }
1627
1651
  return new BunPtyBackend(options);
1628
1652
  }
1629
1653
  return new NodePtyBackend(options);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.11",
3
+ "version": "1.1.14",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "jsgotty",
9
- "version": "1.1.11",
9
+ "version": "1.1.14",
10
10
  "dependencies": {
11
11
  "node-addon-api": "^7.1.1",
12
12
  "ws": "^8.18.3"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "Share your terminal as a web application: A JavaScript rewrite of GoTTY",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -8,9 +8,13 @@
8
8
  },
9
9
  "type": "commonjs",
10
10
  "assets": [
11
+ "conpty",
11
12
  "static",
12
13
  "README.md",
13
- "README.en.md"
14
+ "README.en.md",
15
+ "LICENSE",
16
+ "NOTICE",
17
+ "LICENSES"
14
18
  ],
15
19
  "dependencies": {
16
20
  "node-addon-api": "^7.1.1",
@@ -0,0 +1,135 @@
1
+ // Windows PTY adapter for the out-of-band Microsoft Console package.
2
+ // Keep the same interface as BunPtyBackend in gotty.js.
3
+ const { dlopen, ptr } = require("bun:ffi");
4
+ const path = require("path");
5
+
6
+ const kernel = dlopen("kernel32.dll", {
7
+ CreatePipe: { args: ["ptr", "ptr", "ptr", "u32"], returns: "i32" },
8
+ InitializeProcThreadAttributeList: { args: ["ptr", "u32", "u32", "ptr"], returns: "i32" },
9
+ UpdateProcThreadAttribute: { args: ["ptr", "u32", "usize", "ptr", "usize", "ptr", "ptr"], returns: "i32" },
10
+ DeleteProcThreadAttributeList: { args: ["ptr"], returns: "void" },
11
+ CreateProcessW: { args: ["ptr", "ptr", "ptr", "ptr", "i32", "u32", "ptr", "ptr", "ptr", "ptr"], returns: "i32" },
12
+ PeekNamedPipe: { args: ["ptr", "ptr", "u32", "ptr", "ptr", "ptr"], returns: "i32" },
13
+ ReadFile: { args: ["ptr", "ptr", "u32", "ptr", "ptr"], returns: "i32" },
14
+ WriteFile: { args: ["ptr", "ptr", "u32", "ptr", "ptr"], returns: "i32" },
15
+ GetExitCodeProcess: { args: ["ptr", "ptr"], returns: "i32" },
16
+ TerminateProcess: { args: ["ptr", "u32"], returns: "i32" },
17
+ CloseHandle: { args: ["ptr"], returns: "i32" },
18
+ GetLastError: { args: [], returns: "u32" },
19
+ }).symbols;
20
+
21
+ const conptySymbols = {
22
+ ConptyCreatePseudoConsole: { args: ["u32", "ptr", "ptr", "u32", "ptr"], returns: "i32" },
23
+ ConptyResizePseudoConsole: { args: ["ptr", "u32"], returns: "i32" },
24
+ ConptyClosePseudoConsole: { args: ["ptr"], returns: "void" },
25
+ };
26
+ const wide = (value) => Buffer.from(`${value}\0`, "utf16le");
27
+ const handle = (buffer) => Number(buffer.readBigUInt64LE(0));
28
+ const size = (cols, rows) => ((Math.max(1, rows) & 0xffff) << 16) | (Math.max(1, cols) & 0xffff);
29
+ const winError = (operation) => new Error(`${operation} failed: ${kernel.GetLastError()}`);
30
+ // Windows CRT argv escaping: double backslashes before quotes and at the end.
31
+ function quoteArg(value) {
32
+ let result = '"', slashes = 0;
33
+ for (const char of String(value)) {
34
+ if (char === "\\") { slashes++; continue; }
35
+ if (char === '"') result += "\\".repeat(slashes * 2 + 1) + '"';
36
+ else result += "\\".repeat(slashes) + char;
37
+ slashes = 0;
38
+ }
39
+ return result + "\\".repeat(slashes * 2) + '"';
40
+ }
41
+
42
+ class WindowsConptyBackend {
43
+ constructor(options, dllPath) {
44
+ this.options = options;
45
+ this.dataHandlers = [];
46
+ this.exitHandlers = [];
47
+ this.closed = false;
48
+ this.conpty = dlopen(path.resolve(dllPath), conptySymbols).symbols;
49
+ const inputRead = Buffer.alloc(8), inputWrite = Buffer.alloc(8);
50
+ const outputRead = Buffer.alloc(8), outputWrite = Buffer.alloc(8);
51
+ if (!kernel.CreatePipe(inputRead, inputWrite, 0, 0)) throw winError("CreatePipe input");
52
+ if (!kernel.CreatePipe(outputRead, outputWrite, 0, 0)) throw winError("CreatePipe output");
53
+ this.input = handle(inputWrite);
54
+ this.output = handle(outputRead);
55
+ const pseudo = Buffer.alloc(8);
56
+ const hr = this.conpty.ConptyCreatePseudoConsole(
57
+ size(options.width || 80, options.height || 24), handle(inputRead), handle(outputWrite), 0, pseudo);
58
+ kernel.CloseHandle(handle(inputRead));
59
+ kernel.CloseHandle(handle(outputWrite));
60
+ if (hr !== 0) throw new Error(`ConptyCreatePseudoConsole failed: ${hr}`);
61
+ this.pseudo = handle(pseudo);
62
+
63
+ const listSize = Buffer.alloc(8);
64
+ kernel.InitializeProcThreadAttributeList(0, 1, 0, listSize);
65
+ const list = Buffer.alloc(Number(listSize.readBigUInt64LE(0)));
66
+ if (!kernel.InitializeProcThreadAttributeList(list, 1, 0, listSize)) throw winError("InitializeProcThreadAttributeList");
67
+ if (!kernel.UpdateProcThreadAttribute(list, 0, 0x00020016, this.pseudo, 8, 0, 0)) throw winError("UpdateProcThreadAttribute");
68
+ const startup = Buffer.alloc(112);
69
+ startup.writeUInt32LE(112, 0);
70
+ startup.writeUInt32LE(0x100, 60); // STARTF_USESTDHANDLES
71
+ startup.writeBigUInt64LE(BigInt(ptr(list)), 104);
72
+ const processInfo = Buffer.alloc(24);
73
+ const env = { ...process.env, TERM: "xterm-256color", ...(options.headerEnv || {}) };
74
+ const envBlock = wide(Object.entries(env).filter(([key]) => !key.startsWith("="))
75
+ .map(([key, value]) => `${key}=${value}`).join("\0") + "\0");
76
+ const executable = Bun.which(options.command) || options.command;
77
+ const command = wide([executable, ...options.argv].map(quoteArg).join(" "));
78
+ const ok = kernel.CreateProcessW(wide(executable), command, 0, 0, 0,
79
+ 0x00080000 | 0x00000400, envBlock, wide(process.cwd()), startup, processInfo);
80
+ kernel.DeleteProcThreadAttributeList(list);
81
+ if (!ok) throw winError("CreateProcessW");
82
+ this.process = handle(processInfo);
83
+ this.pid = processInfo.readUInt32LE(16);
84
+ kernel.CloseHandle(Number(processInfo.readBigUInt64LE(8)));
85
+ this.pollTimer = setInterval(() => this.poll(), 5);
86
+ }
87
+
88
+ onData(handler) { this.dataHandlers.push(handler); }
89
+ onExit(handler) { this.exitHandlers.push(handler); }
90
+ write(buffer) { this.writeBinary(buffer); }
91
+ writeBinary(buffer) {
92
+ const data = Buffer.from(buffer);
93
+ const written = Buffer.alloc(4);
94
+ for (let offset = 0; offset < data.length;) {
95
+ const chunk = data.subarray(offset);
96
+ if (!kernel.WriteFile(this.input, chunk, chunk.length, written, 0)) throw winError("WriteFile");
97
+ const count = written.readUInt32LE(0);
98
+ if (!count) throw new Error("WriteFile wrote no data");
99
+ offset += count;
100
+ }
101
+ }
102
+ resize(cols, rows) {
103
+ const hr = this.conpty.ConptyResizePseudoConsole(this.pseudo, size(cols, rows));
104
+ if (hr !== 0) throw new Error(`ConptyResizePseudoConsole failed: ${hr}`);
105
+ }
106
+ kill() { if (!this.closed) kernel.TerminateProcess(this.process, 1); }
107
+ close() { this.kill(); }
108
+
109
+ poll() {
110
+ if (this.closed) return;
111
+ const available = Buffer.alloc(4), readCount = Buffer.alloc(4);
112
+ if (kernel.PeekNamedPipe(this.output, 0, 0, 0, available, 0)) {
113
+ for (let bytes = available.readUInt32LE(0); bytes > 0; bytes = available.readUInt32LE(0)) {
114
+ const data = Buffer.alloc(Math.min(bytes, 65536));
115
+ if (!kernel.ReadFile(this.output, data, data.length, readCount, 0)) break;
116
+ const count = readCount.readUInt32LE(0);
117
+ if (!count) break;
118
+ for (const handler of this.dataHandlers) handler(data.subarray(0, count));
119
+ if (!kernel.PeekNamedPipe(this.output, 0, 0, 0, available, 0)) break;
120
+ }
121
+ }
122
+ const exitCode = Buffer.alloc(4);
123
+ if (kernel.GetExitCodeProcess(this.process, exitCode) && exitCode.readUInt32LE(0) !== 259) {
124
+ this.closed = true;
125
+ clearInterval(this.pollTimer);
126
+ this.conpty.ConptyClosePseudoConsole(this.pseudo);
127
+ kernel.CloseHandle(this.input);
128
+ kernel.CloseHandle(this.output);
129
+ kernel.CloseHandle(this.process);
130
+ for (const handler of this.exitHandlers) handler();
131
+ }
132
+ }
133
+ }
134
+
135
+ module.exports = WindowsConptyBackend;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu",
3
- "version": "0.4.15",
3
+ "version": "0.4.18",
4
4
  "description": "Buninu is a portable, self-bootstrapping Unix-like userspace built on Bun that runs across operating systems",
5
5
  "type": "module",
6
6
  "main": "./bin/init.js",
@@ -10,7 +10,7 @@
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/jjtseng93/buninu.git"
12
12
  },
13
- "homepage": "https://github.com/jjtseng93/buninu#readme",
13
+ "homepage": "https://buninu.org",
14
14
  "bugs": {
15
15
  "url": "https://github.com/jjtseng93/buninu/issues"
16
16
  },
@@ -6,9 +6,9 @@ xterm-compatible terminal with anti-aliased text, CJK, colour emoji, seamless
6
6
  box drawing and kitty graphics protocol images — no X11, Wayland or GPU.
7
7
 
8
8
  ```sh
9
- bunterm # run /bin/sh on the console you are on
10
9
  bunterm 2 # start a detached bunterm on /dev/tty2
11
- bunterm /dev/tty1 # the full virtual-console spelling also works
10
+ bunterm /dev/tty2 # the full virtual-console spelling also works
11
+ bunterm # take over the console you are on (on tty1 that hides the bun-repl)
12
12
  bunterm -s 20 # short for --font-size 20
13
13
  bunterm -e bun /buninu/apps/jsmdcui/src/index.js --demo
14
14
  bunterm --no-mouse # keyboard only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu-linux",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "A Linux distribution running Bun as PID 1 with the Buninu userspace.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -27,6 +27,29 @@ cp -a --reflink=auto "$native_dir/." "$staging/"
27
27
  cp README.md "$staging/usr/share/doc/buninu-linux/README.md"
28
28
  cp -a LICENSE NOTICE.md LICENSES "$staging/usr/share/licenses/"
29
29
 
30
+ # os-release(5): the canonical file lives in /usr/lib and /etc/os-release links
31
+ # to it. build-uki.sh embeds the same file as the UKI's .osrel section.
32
+ version="$(sed -n 's/^ *"version": *"\([^"]*\)".*/\1/p' package.json | head -n 1)"
33
+ test -n "$version" || { echo "error: no version in package.json" >&2; exit 1; }
34
+ printf '%s\n' \
35
+ 'NAME="Buninu Linux"' \
36
+ 'ID=buninu-linux' \
37
+ "VERSION=\"$version\"" \
38
+ "VERSION_ID=$version" \
39
+ "PRETTY_NAME=\"Buninu Linux $version\"" \
40
+ 'HOME_URL="https://buninu.org"' \
41
+ 'BUG_REPORT_URL="https://github.com/jjtseng93/buninu-linux/issues"' \
42
+ 'LOGO=buninu-linux' \
43
+ 'DEFAULT_HOSTNAME=buninu' > build/os-release
44
+ mkdir -p "$staging/usr/lib" "$staging/etc"
45
+ cp build/os-release "$staging/usr/lib/os-release"
46
+ ln -sfn ../usr/lib/os-release "$staging/etc/os-release"
47
+ # LOGO names an icon-theme icon; point the standard lookup paths at the Buninu
48
+ # icon that /buninu already ships instead of packing a second copy.
49
+ mkdir -p "$staging/usr/share/icons/hicolor/512x512/apps" "$staging/usr/share/pixmaps"
50
+ ln -sfn ../../../../../../buninu/icon.png "$staging/usr/share/icons/hicolor/512x512/apps/buninu-linux.png"
51
+ ln -sfn ../../../buninu/icon.png "$staging/usr/share/pixmaps/buninu-linux.png"
52
+
30
53
  # fakeroot lets cpio record the character devices without real root. Nothing
31
54
  # has mounted devtmpfs when the kernel execs Bun as PID 1, so every device
32
55
  # Bun's startup needs has to be in the archive itself: