Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
42 changes: 42 additions & 0 deletions .github/smoke-boot.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// CI boot smoke: the built panel must BOOT and serve /api/health on an EMPTY
// layout on every OS — no Crafty, no JDK, no servers, no token. Watchers are
// expected to log errors on the way up; the bar is "starts, serves, and
// degrades honestly", which is exactly what a first-run install looks like.
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { spawn } from 'node:child_process';

const PORT = 25571;
const root = mkdtempSync(join(tmpdir(), 'spawnpoint-smoke-'));
mkdirSync(join(root, 'Spawnpoint', 'data'), { recursive: true });
writeFileSync(join(root, 'Spawnpoint', 'data', 'settings.json'), JSON.stringify({ port: PORT }));

let exited = false;
const child = spawn(process.execPath, ['server/dist/index.js'], {
env: { ...process.env, SPAWNPOINT_ROOT: root },
stdio: 'inherit',
});
child.on('exit', (code) => {
exited = true;
console.error(`panel exited before the health check passed (code ${code})`);
process.exit(1);
});

const deadline = Date.now() + 90_000;
while (Date.now() < deadline && !exited) {
try {
const res = await fetch(`http://127.0.0.1:${PORT}/api/health`);
if (res.ok) {
console.log('panel boots and serves /api/health on', process.platform);
child.removeAllListeners('exit');
child.kill();
process.exit(0);
}
} catch { /* not up yet */ }
await new Promise((r) => setTimeout(r, 1000));
}
console.error('panel did not serve /api/health within 90s');
child.removeAllListeners('exit');
child.kill();
process.exit(1);
27 changes: 20 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Cross-platform build proof: the panel must COMPILE and BOOT on every OS a
# self-hoster might own. The Linux leg is the full experience; mac/windows
# legs prove the panel + preflight degrade gracefully (the client launch gate
# needs Linux + xvfb and says so honestly at runtime).
# Cross-platform proof: the panel must COMPILE and BOOT on every OS a
# self-hoster might own. All three legs now run the same bar — build, then
# boot the real server against an empty layout and serve /api/health (the
# shape of a first-run install). The client/join gates pick their display
# mode at runtime: xvfb on headless Linux, a small visible window on
# desktop OSes, an honest skip where no display exists.
name: ci
on:
push:
Expand All @@ -25,7 +27,18 @@ jobs:
- run: npm ci
- run: npm run build -w server
- run: npm run build -w web
# smoke: the built server must at least load its entrypoint without a
# crash on this OS (no Crafty/Java present in CI — a clean import and
# config bootstrap is the bar here)
# smoke 1: the built server must load its entrypoint cleanly
- run: node -e "import('./server/dist/config.js').then(m => { if (!m.PATHS) throw new Error('config failed'); console.log('config loads on', process.platform); })"
# smoke 2: boot the real panel on an empty layout and hit /api/health
- run: node .github/smoke-boot.mjs

# the repo is public — every push gets a full-history secret scan
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 changes: 15 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,18 @@ remember). Without them, servers are LAN/VPN-only and everything else works.

## Cross-platform honesty

Linux is the full experience. On macOS/Windows the panel and the server-side
dry-boot run fine; the headless-client and join gates require Linux +
`xvfb-run` and **skip with an honest "not verified" verdict** rather than
pretending to pass.
The gates need a display, and there are three answers to that:

- **Headless Linux + `xvfb-run`** — the client boots invisibly in a virtual
framebuffer.
- **macOS / Windows / a Linux desktop** — *visible-window mode*: the real
verification client opens as a small window on the desktop for a few
minutes, announced in the panel log so nobody closes it mid-verdict. Same
client, same fidelity, purely cosmetic cost.
- **A display-less box that isn't Linux** — the client and join gates **skip
with an honest "not verified" verdict** rather than pretending to pass.
The static scan and server dry-boot run everywhere regardless.

A stubbed-GL "headless anywhere" mode was considered and rejected: stubbed
rendering can mask or invent render-init crashes, which makes the test lie —
the same reason the boot gates refuse `-Xverify:none`.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ restarts, and a storage cleaner.

- Node.js 22+
- [Crafty Controller](https://craftycontrol.com/) managing your servers
- Linux is the full experience; on macOS/Windows the panel and server-side
preflight run fine, while the client/join gates need Linux + `xvfb` and
will skip with an honest verdict elsewhere.
- Any OS. On headless Linux the client/join gates run invisibly under `xvfb`;
on macOS/Windows (or a Linux desktop) the verification client opens as a
small window for a few minutes instead — same real client, same verdict.
A box with no display at all skips those two gates with an honest
"not verified" verdict; the server-side dry-boot always runs.

## Quick start

Expand Down
35 changes: 5 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"start": "node dist/index.js"
},
"dependencies": {
"@fastify/cookie": "^11.0.0",
"@fastify/cookie": "^11.1.2",
"@fastify/static": "^8.0.0",
"adm-zip": "^0.5.16",
"fastify": "^5.0.0",
Expand Down
Loading
Loading