singularity-forge/scripts/run-vega-source-server.mjs
Mikael Hugo c26de39afa
Some checks are pending
sf self-deploy / build, test, and publish server image (push) Waiting to run
sf self-deploy / deploy test and probe (push) Blocked by required conditions
sf self-deploy / promote prod (push) Blocked by required conditions
feat: add source-mounted sf server self-deploy
2026-05-17 22:00:01 +02:00

175 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
/**
* run-vega-source-server.mjs — start the source-mounted SF server container on
* vega without requiring Docker Compose.
*
* Purpose: keep the local production server containerized while using the live
* checkout at /home/mhugo/code/singularity-forge as the source of truth.
*
* Consumer: `npm run docker:vega:up` on vega.
*/
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { createServer } from "node:net";
import { homedir } from "node:os";
import { basename, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
const image = process.env.SF_VEGA_IMAGE || "sf-source-server:vega";
const bind = process.env.SF_VEGA_BIND || "127.0.0.1";
const workspace = resolve(process.env.SF_WORKSPACE_DIR || root);
const name = process.env.SF_VEGA_CONTAINER || defaultContainerName(workspace);
const uid = process.env.PUID || String(process.getuid?.() ?? 1000);
const gid = process.env.PGID || String(process.getgid?.() ?? 1000);
const command = process.argv[2] ?? "up";
if (command === "--help" || command === "-h" || command === "help") {
process.stdout.write(`Usage:
npm run docker:vega:up
SF_WORKSPACE_DIR=/path/to/repo npm run docker:vega:up
node scripts/run-vega-source-server.mjs print
node scripts/run-vega-source-server.mjs logs
node scripts/run-vega-source-server.mjs down
Environment:
SF_WORKSPACE_DIR repo mounted at /workspace, defaults to this checkout
SF_VEGA_BIND host bind address, defaults to 127.0.0.1
SF_VEGA_PORT fixed host port; unset auto-picks 4000-4099
SF_VEGA_CONTAINER explicit container name
`);
process.exit(0);
}
if (command === "print") {
const port = await resolvePort();
process.stdout.write(
JSON.stringify(
{ name, image, bind, port, workspace, sfSource: root },
null,
2,
) + "\n",
);
process.exit(0);
}
if (command === "logs") {
run("docker", ["logs", "-f", "--tail=200", name]);
process.exit(0);
}
if (command === "down") {
run("docker", ["rm", "-f", name]);
process.exit(0);
}
if (command !== "up") {
process.stderr.write(`Unknown command: ${command}\n`);
process.exit(2);
}
const port = await resolvePort();
const allowedOrigins =
process.env.SF_WEB_ALLOWED_ORIGINS ||
`http://127.0.0.1:${port},http://localhost:${port}`;
run("docker", [
"build",
"-f",
"docker/Dockerfile.source-server",
"-t",
image,
".",
]);
spawnSync("docker", ["rm", "-f", name], { stdio: "ignore" });
run("docker", [
"run",
"-d",
"--name",
name,
"--restart",
"unless-stopped",
"--user",
`${uid}:${gid}`,
"-p",
`${bind}:${port}:4000`,
"-e",
"HOME=/home/node",
"-e",
"NODE_ENV=development",
"-e",
"SF_SOURCE_ROOT=/opt/sf",
"-e",
"SF_RUNTIME_SOURCE_ROOT=/opt/sf",
"-e",
"SF_RELEASE_MANIFEST=/opt/sf/dist/sf-release-manifest.json",
"-e",
"SF_WEB_PROJECT_CWD=/workspace",
"-e",
"HOSTNAME=0.0.0.0",
"-e",
"PORT=4000",
"-e",
"SF_WEB_HOST=0.0.0.0",
"-e",
"SF_WEB_PORT=4000",
"-e",
`SF_WEB_ALLOWED_ORIGINS=${allowedOrigins}`,
"-e",
"SF_DEV_SERVER_WATCH=1",
"-v",
`${root}:/opt/sf`,
"-v",
`${workspace}:/workspace`,
"-v",
`${homedir()}/.sf:/home/node/.sf`,
"-v",
`${homedir()}/.gitconfig:/home/node/.gitconfig:ro`,
image,
"node",
"/opt/sf/dist/web/standalone/server.js",
]);
process.stdout.write(`${name} listening on ${bind}:${port}\n`);
process.stdout.write(`SF source: ${root}\n`);
process.stdout.write(`Workspace: ${workspace}\n`);
async function resolvePort() {
if (process.env.SF_VEGA_PORT) return process.env.SF_VEGA_PORT;
for (let candidate = 4000; candidate <= 4099; candidate++) {
if (await isPortAvailable(candidate)) return String(candidate);
}
process.stderr.write("No free SF vega port found in 4000-4099\n");
process.exit(1);
}
function isPortAvailable(port) {
return new Promise((resolveAvailable) => {
const server = createServer();
server.once("error", () => resolveAvailable(false));
server.once("listening", () => {
server.close(() => resolveAvailable(true));
});
server.listen(port, bind);
});
}
function defaultContainerName(path) {
const slug = basename(path)
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
const hash = createHash("sha1").update(path).digest("hex").slice(0, 8);
return `sf-server-${slug || "workspace"}-${hash}`;
}
function run(command, args) {
const result = spawnSync(command, args, {
cwd: root,
stdio: "inherit",
env: process.env,
});
if (result.status !== 0) process.exit(result.status ?? 1);
}