Skip to content

Commit e514e82

Browse files
committed
work in progress working on SEA distributable binary of project runner
1 parent 145306b commit e514e82

File tree

9 files changed

+165
-16
lines changed

9 files changed

+165
-16
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,5 @@ g
183183
*.pkg
184184
*.zip
185185

186-
src/packages/lite/build
186+
src/packages/lite/build
187+
src/packages/project-runner/build

src/packages/lite/sea/build-sea.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ case "$OS" in
5252
;;
5353
esac
5454

55-
rm cocalc.js cocalc-lite.tar.xz
55+
rm cocalc.js cocalc-lite.tar.xz sea-prep.blob
5656

5757
mkdir -p ../build/sea
5858
mv $TARGET ../build/sea

src/packages/lite/sea/cocalc-template.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ const os = require("node:os");
99
const path = require("node:path");
1010
const { spawnSync } = require("node:child_process");
1111

12-
// Choose where to extract (version this if you’ll update assets)
12+
// Choose where to extract
1313
const destDir = path.join(
1414
process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"),
15-
"cocalc-lite",
15+
"cocalc",
16+
"lite",
1617
VERSION,
1718
);
1819

@@ -35,12 +36,12 @@ if (!fs.existsSync(stamp)) {
3536
);
3637

3738
if (child.error) {
38-
console.error("Failed to run tar:", r.error);
39+
console.error("Failed to run tar:", child.error);
3940
process.exit(1);
4041
}
4142
if (child.status !== 0) {
42-
console.error(`tar exited with code ${r.status}`);
43-
process.exit(r.status);
43+
console.error(`tar exited with code ${child.status}`);
44+
process.exit(child.status);
4445
}
4546

4647
console.log("Assets ready at:", destDir);
@@ -62,9 +63,7 @@ process.argv = [process.execPath, script, ...process.argv.slice(2)];
6263

6364
// make sure PATH (and any other env) includes your extracted tools
6465
process.env.PATH =
65-
path.join(destDir, "cocalc-lite/lite/bin") +
66-
path.delimiter +
67-
process.env.PATH;
66+
path.join(destDir, "lite/bin") + path.delimiter + process.env.PATH;
6867

6968
process.env.AUTH_TOKEN = "random";
7069

src/packages/project-runner/run/env.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { join } from "node:path";
33
import base_path from "@cocalc/backend/base-path";
44

55
export function dataPath(HOME: string): string {
6-
return join(HOME, ".cache", "cocalc");
6+
return join(HOME, ".cache", "cocalc", "project");
77
}
88

99
// see also packages/project/secret-token.ts
@@ -91,6 +91,7 @@ export function getEnvironment({
9191
COCALC_USERNAME: USER,
9292
USER,
9393
COCALC_EXTRA_ENV: extra_env,
94+
// probably want to be more careful with PATH
9495
PATH: `${HOME}/bin:${HOME}/.local/bin:${process.env.PATH}`,
9596
CONAT_SERVER: conatServer,
9697
COCALC_SECRET_TOKEN: secretTokenPath(HOME),

src/packages/project-runner/run/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { mkdir } from "fs/promises";
3434
import { exists } from "@cocalc/backend/misc/async-utils-node";
3535
import { spawn } from "node:child_process";
3636
import { type Configuration } from "./types";
37-
export { type Configuration};
37+
export { type Configuration };
3838
import { limits } from "./limits";
3939
import {
4040
client as createFileClient,
@@ -116,7 +116,7 @@ async function start({
116116
if (!isValidUUID(project_id)) {
117117
throw Error("start: project_id must be valid");
118118
}
119-
logger.debug("start", { project_id, config });
119+
logger.debug("start", { project_id, config: { ...config, secret: "xxx" } });
120120
if (children[project_id] != null && children[project_id].exitCode == null) {
121121
logger.debug("start -- already running");
122122
return;
@@ -138,6 +138,7 @@ async function start({
138138
env: config?.env,
139139
HOME: home,
140140
});
141+
env.PATH = dirname(process.argv[0]) + ":" + (env.PATH ?? "");
141142
await setupDataPath(home);
142143
if (config?.secret) {
143144
await writeSecretToken(home, config.secret);
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env bash
2+
set -Eeuo pipefail
3+
4+
# --- config ---
5+
NAME="cocalc-project-runner"
6+
7+
export VERSION="$npm_package_version"
8+
FUSE="NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2" # must match your sea-config.json
9+
MACHINE="$(uname -m)"
10+
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
11+
12+
TARGET="./$NAME-$VERSION-$MACHINE-$OS"
13+
14+
NODE_BIN="$(command -v node)"
15+
16+
echo "Building CoCalc Project Runner SEA for $OS"
17+
18+
# 1) Stage the node runtime we’ll inject into
19+
cp "$NODE_BIN" "$TARGET"
20+
chmod u+w "$TARGET"
21+
22+
cp ../build/tarball/$NAME-$VERSION-$MACHINE-$OS.tar.xz $NAME.tar.xz
23+
envsubst < cocalc-template.js > cocalc.js
24+
25+
# 2) Bundle app into a SEA blob
26+
# This writes ./sea-prep.blob using your sea-config.json
27+
node --experimental-sea-config sea-config.json
28+
29+
# 3) Platform-specific injection and signing
30+
case "$OS" in
31+
darwin)
32+
# Remove existing signature before mutation (ok if it fails on already-unsigned copy)
33+
codesign --remove-signature "$TARGET" || true
34+
35+
# Inject the SEA blob into the Mach-O binary, specifying the segment name for macOS
36+
npx -y postject "$TARGET" NODE_SEA_BLOB ./sea-prep.blob \
37+
--sentinel-fuse "$FUSE" \
38+
--macho-segment-name NODE_SEA
39+
40+
# Re-sign ad-hoc so macOS will run it
41+
codesign --force --sign - "$TARGET"
42+
;;
43+
44+
linux)
45+
# Inject into the ELF binary (no Mach-O segment flag on Linux)
46+
npx -y postject "$TARGET" NODE_SEA_BLOB ./sea-prep.blob \
47+
--sentinel-fuse "$FUSE"
48+
;;
49+
50+
*)
51+
echo "Unsupported OS: $OS" >&2
52+
exit 2
53+
;;
54+
esac
55+
56+
rm cocalc.js $NAME.tar.xz sea-prep.blob
57+
58+
mkdir -p ../build/sea
59+
mv $TARGET ../build/sea
60+
61+
62+
echo "Done. Built $TARGET"

src/packages/project-runner/sea/build-tarball.sh

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,8 @@ rm -rf better-sqlite3@*/node_modules/better-sqlite3/deps/sqlite3
9090

9191
cd "$SRC"/..
9292
rm -rf *.md .github .git docs
93-
mv src/packages/* .
94-
rm -rf src
9593
# remove rustic -- only needed for file server
96-
rm -f backend/node_modules/.bin/rustic
94+
rm -f src/packages/backend/node_modules/.bin/rustic
9795

9896
cd $TMP
9997
mkdir -p $BIN/../build/tarball
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// cocalc.js
2+
const VERSION = "${VERSION}";
3+
4+
const { getRawAsset } = require("node:sea");
5+
const fs = require("node:fs");
6+
const os = require("node:os");
7+
const path = require("node:path");
8+
const { spawnSync } = require("node:child_process");
9+
10+
const destDir = path.join(
11+
process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"),
12+
"cocalc",
13+
"project-runner",
14+
VERSION,
15+
);
16+
17+
const stamp = path.join(destDir, ".ok");
18+
if (!fs.existsSync(stamp)) {
19+
console.log("Unpacking...");
20+
// Read the SEA asset into a Buffer
21+
const ab = getRawAsset("cocalc-project-runner.tar.xz"); // ArrayBuffer (no copy)
22+
const buf = Buffer.from(new Uint8Array(ab)); // turn into Node Buffer
23+
24+
fs.mkdirSync(destDir, { recursive: true });
25+
26+
const child = spawnSync(
27+
"tar",
28+
["-Jxf", "-", "-C", destDir, "--strip-components=1"],
29+
{
30+
input: buf,
31+
stdio: ["pipe", "inherit", "inherit"],
32+
},
33+
);
34+
35+
if (child.error) {
36+
console.error("Failed to run tar:", child.error);
37+
process.exit(1);
38+
}
39+
if (child.status !== 0) {
40+
console.error(`tar exited with code ${child.status}`);
41+
process.exit(child.status);
42+
}
43+
44+
console.log("Assets ready at:", destDir);
45+
fs.writeFileSync(stamp, "");
46+
}
47+
48+
const Module = require("node:module");
49+
50+
const looksLikeScript = process.argv[2] && !process.argv[2].startsWith("-");
51+
52+
if (looksLikeScript) {
53+
process.argv = [process.execPath, ...process.argv.slice(2)];
54+
} else {
55+
console.log("Starting CoCalc Project Runner");
56+
57+
const script = path.join(destDir, "src/packages/project-runner/bin/start.js");
58+
59+
if (!fs.existsSync(script)) {
60+
console.error("missing start.js at", script);
61+
process.exit(1);
62+
}
63+
64+
// set up argv and cwd as if launched directly
65+
process.chdir(path.dirname(script));
66+
process.argv = [process.execPath, script, ...process.argv.slice(2)];
67+
68+
// make sure PATH (and any other env) includes your extracted tools
69+
process.env.PATH =
70+
path.join(destDir, "src/packages/project-runner/bin/") +
71+
path.delimiter +
72+
process.env.PATH;
73+
74+
process.env.AUTH_TOKEN = "random";
75+
}
76+
77+
// run like “node start.js”
78+
Module.runMain(); // loads process.argv[1] as the main script
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"main": "cocalc.js",
3+
"output": "sea-prep.blob",
4+
"disableExperimentalSEAWarning": true,
5+
"useSnapshot": false,
6+
"assets": {
7+
"cocalc-project-runner.tar.xz": "cocalc-project-runner.tar.xz"
8+
}
9+
}

0 commit comments

Comments
 (0)