Initial StartOS package for Station Master (v0.5.1)
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.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
import { sdk } from '../sdk'
|
||||
import { getJoinSecret } from './getJoinSecret'
|
||||
|
||||
export const actions = sdk.Actions.of().addAction(getJoinSecret)
|
||||
Reference in New Issue
Block a user