e06ab59652
apple / swift (push) Successful in 1m18s
apple / screenshots (push) Successful in 4m42s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 49s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
ci / bench (push) Successful in 5m14s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 56s
arch / build-publish (push) Successful in 15m11s
android / android (push) Successful in 15m50s
deb / build-publish (push) Successful in 16m9s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m12s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m8s
ci / rust (push) Successful in 24m55s
docker / deploy-docs (push) Successful in 10s
The optional supervision layer (RFC §8): one service runs everything in <config_dir>/scripts/ plus installed punktfunk-plugin-* packages (<config_dir>/plugins/node_modules/), as Effect fibers. - Plugins (a definePlugin default export, either main shape) are SUPERVISED: a failure restarts them with capped exponential backoff (jittered, 1s→60s); a clean return completes them. The Effect shape runs under the PunktfunkHost layer; the async-fn shape gets a facade client whose close is scope-guaranteed. - Bare scripts are one-shot: importing them is the run, no restart (export a plugin to be supervised). - Shutdown is STRUCTURAL: SIGINT/SIGTERM interrupt the whole fiber tree, so Effect plugins' scoped finalizers run and clients close before exit — the systemctl-stop story, and the reason the Effect plugin shape exists at all. - The sshd rule applies to unit files (world-writable → refused loudly); cache-busted imports make restarts real; --list for inventory. 6 new bun tests (17 total green): discovery + refusal, both plugin shapes against a mock host, crash→restart with backoff, one-shot semantics, and finalizer-on-interrupt. Live-verified against a real host: a supervised watcher plugin received library.changed through the pinned tunnel, and SIGTERM shut the tree down structurally (exit 0). Deferred to the packaging follow-up (release.yml is in flight in a parallel session): the vendored-Bun deb/rpm/iss packages and the host-log-ring tee (needs a host ingest endpoint); console page rides the other console surfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
#!/usr/bin/env bun
|
|
// `punktfunk-scripting` — run the operator's scripts and punktfunk-plugin-* packages under
|
|
// supervision (see ./runner.ts). SIGINT/SIGTERM interrupt the whole tree structurally, so
|
|
// every plugin's finalizers run before exit (the systemd-stop story).
|
|
//
|
|
// bun src/runner-cli.ts [--scripts DIR] [--plugins DIR] [--list]
|
|
import { Effect, Fiber } from "effect";
|
|
import { discoverUnits, runner } from "./runner.js";
|
|
|
|
const arg = (flag: string): string | undefined => {
|
|
const i = process.argv.indexOf(flag);
|
|
return i >= 0 ? process.argv[i + 1] : undefined;
|
|
};
|
|
|
|
const options = {
|
|
scriptsDir: arg("--scripts"),
|
|
pluginsDir: arg("--plugins"),
|
|
};
|
|
|
|
if (process.argv.includes("--list")) {
|
|
for (const u of discoverUnits(options)) console.log(`${u.name}\t${u.file}`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const fiber = Effect.runFork(runner(options));
|
|
let stopping = false;
|
|
const shutdown = (signal: string) => {
|
|
if (stopping) return process.exit(1); // second signal = get out now
|
|
stopping = true;
|
|
console.log(`${new Date().toISOString()} [runner] ${signal} — interrupting units…`);
|
|
void Effect.runPromise(Fiber.interrupt(fiber)).finally(() => process.exit(0));
|
|
};
|
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
|
|
await Effect.runPromise(Fiber.await(fiber));
|