import { i18n } from './i18n' import { sdk } from './sdk' import { uiPort, dataDir } from './utils' import { storeJson } from './fileModels/store.json' export const main = sdk.setupMain(async ({ effects }) => { console.info(i18n('Starting Station Master!')) // Reactive, field-scoped: rotating the join secret (getJoinSecret.ts) rewrites store.json, // which re-runs setupMain and restarts the daemon with the new value. const joinSecret = await storeJson.read((s) => s.joinSecret).const(effects) return sdk.Daemons.of(effects).addDaemon('server', { subcontainer: sdk.SubContainer.of( effects, { imageId: 'main' }, sdk.Mounts.of().mountVolume({ volumeId: 'data', subpath: null, mountpoint: dataDir, readonly: false, }), 'station-master-sub', ), exec: { command: ['node', 'src/server/index.ts'], env: { // PORT/BIND_ADDRESS/DIST_DIR are left at src/server/index.ts's own defaults (8081, // 0.0.0.0, ./dist relative to the Dockerfile's WORKDIR) — nothing here needs to differ // from them, so only what actually must be supplied is set explicitly. DATA_DIR: dataDir, // generateJoinSecret.ts seeds this before main.ts ever runs, so it is never actually // empty — the fallback only avoids threading `string | undefined` through `env`, which // wants `Record`. JOIN_SECRET: joinSecret ?? '', }, }, // Health check, run on each polling interval. `checkPortListening` reports ready once the // daemon binds `uiPort`; the 'ui' interface (interfaces.ts) exposes the same port. ready: { display: i18n('Multiplayer Server'), fn: () => sdk.healthCheck.checkPortListening(effects, uiPort, { successMessage: i18n('The multiplayer server is ready'), errorMessage: i18n('The multiplayer server is not ready'), }), }, requires: [], }) })