Built from source via a git submodule pinned to a tag, not a published image — the Dockerfile, main.ts, and manifest should never need to change for an ordinary version bump, only the submodule pin (see UPDATING.md). One volume, one interface serving the browser client + lobby/intent API + SSE stream from a single origin, no dependencies. The server-wide join secret (D14) is seeded on install, exposed via the Get Join Secret action, and blocks start behind a critical task until retrieved — the same first-set/rotation pattern as actual-budget-startos's admin password. Verified on phoenix.local: installs, the critical task correctly blocks an ordinary start, force-starting confirms the daemon binds its port and serves the client, and store.json correctly holds the install-seeded join secret. Not verified: the Get Join Secret action's execution end-to-end — start-cli's `package action run` fails with a client-side deserialization error that reproduces identically against actual-budget's already-shipped equivalent action, so this looks like a start-cli issue rather than a defect here. icon.svg is still the scaffold's hello-world placeholder — no real Station Master icon exists yet to ship in its place.
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { utils } from '@start9labs/start-sdk'
|
|
import { i18n } from '../i18n'
|
|
import { sdk } from '../sdk'
|
|
import { storeJson } from '../fileModels/store.json'
|
|
|
|
/**
|
|
* Mints a fresh join secret on every run and returns it — first-set and rotation are the same
|
|
* action (recipe-admin-credentials.md). Writing a new value restarts the daemon with it: main.ts
|
|
* reads `joinSecret` reactively via `.const(effects)`, so a rotation here takes effect
|
|
* immediately rather than needing a manual restart.
|
|
*
|
|
* Rotating invalidates the old secret for anyone who hasn't joined yet, but never removes a
|
|
* player already seated in a game — D14's secret gates the lobby door only (join-secret ≠
|
|
* session token, per `lobby-and-sessions.md` §1).
|
|
*/
|
|
export const getJoinSecret = sdk.Action.withoutInput(
|
|
'get-join-secret',
|
|
|
|
async () => ({
|
|
name: i18n('Get Join Secret'),
|
|
description: i18n('Retrieve or rotate the secret players need to create or join a game'),
|
|
warning: null,
|
|
allowedStatuses: 'any',
|
|
group: null,
|
|
visibility: 'enabled',
|
|
}),
|
|
|
|
async ({ effects }) => {
|
|
const joinSecret = utils.getDefaultString({ charset: 'a-z,A-Z,0-9', len: 24 })
|
|
await storeJson.merge(effects, { joinSecret })
|
|
|
|
return {
|
|
version: '1',
|
|
title: 'Join Secret',
|
|
message:
|
|
'Share this with anyone you want to be able to create or join a game on this server — ' +
|
|
"it doesn't identify a person or a seat, just who's allowed at the lobby door. Running " +
|
|
'this action again generates a new one and restarts the server with it; anyone already ' +
|
|
'seated in a game keeps playing, but the old secret stops working for new games and joins.',
|
|
result: {
|
|
type: 'single',
|
|
name: 'Join Secret',
|
|
description: null,
|
|
value: joinSecret,
|
|
masked: true,
|
|
copyable: true,
|
|
qr: false,
|
|
},
|
|
}
|
|
},
|
|
)
|