From 70b8aab0f310ad50cee9c531e83acef1079e7c7f Mon Sep 17 00:00:00 2001 From: spacesops Date: Sun, 24 May 2026 19:10:21 -0400 Subject: [PATCH] v0.0.9.2 --- README.md | 15 ++-- startos/actions/index.ts | 2 + startos/actions/setSubsProver.ts | 61 ++++++++++++++++ startos/fileModels/storeJson.ts | 1 + startos/i18n/dictionaries/default.ts | 13 ++++ startos/init/taskSeedEnableSubspaces.ts | 18 +++-- startos/interfaces.ts | 22 +++++- startos/main.ts | 70 +++++++++++-------- startos/versions/index.ts | 4 +- startos/versions/{v0.0.9.1.ts => v0.0.9.2.ts} | 8 ++- 10 files changed, 169 insertions(+), 45 deletions(-) create mode 100644 startos/actions/setSubsProver.ts rename startos/versions/{v0.0.9.1.ts => v0.0.9.2.ts} (77%) diff --git a/README.md b/README.md index a309981..8bc30bc 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,8 @@ preserved. | --- | --- | --- | --- | --- | | Space-CLI Web UI (gotty terminal) | 8080 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | Basic auth: `admin:` | | Explorer Web UI | 3000 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | SvelteKit explorer. **TODO** — daemon stubbed pending the prebuilt explorer image; the interface is still registered but nothing listens yet. | -| Subspaces Web UI | 7777 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `subs` UI. No built-in auth. Only useful while Subspaces is enabled. | +| Subspaces Web UI | 7777 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `subs` daemon (subsd). No built-in auth. Only useful while Subspaces is enabled. | +| Subs API | 7777 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | The subsd REST API (same daemon/port as above), exposed as a distinct `api` interface separate from the prover and registry. Only useful while Subspaces is enabled. | | Subspaces Prover | 8888 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `subs-prover` RISC Zero prover server (no GPU). Its own StartOS interface. Only useful while Subspaces is enabled. | | Subspaces Registry | 8081 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `registry-server` for publishing/resolving handles (remapped from its upstream default 8080 to avoid colliding with the gotty terminal on 8080). Its own StartOS interface. Only useful while Subspaces is enabled. | | Certrelay | 7778 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `certrelay` server (always on). Serves cryptographic proofs binding handles to owner keys. **Setup:** run **Configure Certrelay** and set `CERTRELAY_SELF_URL` to the publicly visible URL StartOS exposes for this interface. | @@ -178,6 +179,7 @@ preserved. | `enable-subspaces` | Enable Subspaces | Enabled (hidden when already on) | Any | Sets `store.enableSubspaces = true` and triggers a service restart so the Subspaces chain (build → wallet → subs-prover → subs) takes effect. | | `disable-subspaces` | Disable Subspaces | Enabled (hidden when already off) | Any | Sets `store.enableSubspaces = false` and triggers a service restart so the subs and subs-prover daemons stop. On-disk data at `/data/subspaces` is preserved. | | `reset-subspaces-state` | Reset Subspaces State | Enabled | Any | Deletes `/data/subspaces` (runtime data only — binaries ship in the image). Use to wipe local handles/proofs. The spaces wallet on spaced is preserved. Warning-gated. | +| `set-subs-prover` | Enable / Disable Subspaces Prover | Enabled | Any | Toggle (`store.enableSubsProver`, default **off**) that starts/stops only the `subs-prover` daemon, independent of the overall Subspaces toggle. When off, the daemon doesn't start but its interface (8888) stays registered. Saving restarts the service. | | `configure-certrelay` | Configure Certrelay | Enabled | Any | Sets `CERTRELAY_SELF_URL` (text), `CERTRELAY_BOOTSTRAP` (toggle), and `CERTRELAY_HEALTHCHECK_HANDLE` (text) in `store.json`; prefilled with current values. Saving restarts the service so certrelay picks up the new config. | ## Backups and Restore @@ -193,7 +195,7 @@ the same idempotent init logic runs and reuses the existing credentials in | --- | --- | --- | --- | | `postgres` (daemon `ready`) | Database | 60 s | TCP listen on `127.0.0.1:5432` | | `spaced` (daemon `ready`) | Spaced RPC | 120 s | TCP listen on `127.0.0.1:7225` | -| `subs-prover` (daemon `ready`) | Subspaces Prover | 60 s | TCP listen on `0.0.0.0:8888`. Only present when Subspaces is enabled. | +| `subs-prover` (daemon `ready`) | Subspaces Prover | 60 s | TCP listen on `0.0.0.0:8888`. Only present when Subspaces **and** the `set-subs-prover` toggle are enabled (toggle defaults off). Polled every 30 s; nothing depends on it. | | `subs-registry` (daemon `ready`) | Subspaces Registry | 60 s | TCP listen on `0.0.0.0:8081`. Only present when Subspaces is enabled. | | `subs` (daemon `ready`) | Subspaces Web UI | 60 s | TCP listen on `0.0.0.0:7777`. Only present when Subspaces is enabled. | | `certrelay` (daemon `ready`) | Certrelay | 120 s | TCP listen on `0.0.0.0:7778`. Always on. Polled every 30 s. | @@ -240,8 +242,11 @@ the same idempotent init logic runs and reuses the existing credentials in Subspaces** to start it. It ships as the prebuilt `horologger/subs:v0.1.0` image (no compile step), so enabling is fast — the three daemons start as soon as the image is on the box. Enabling runs three daemons, each with its - own StartOS interface: **subs** (Web UI, 7777), **subs-prover** (8888, no - GPU), **registry-server** (8081). subs loads the existing `default` spaces + own StartOS interface: **subs** (Web UI, 7777), **registry-server** (8081), + and — only if the separate **Enable / Disable Subspaces Prover** toggle is + on (default **off**) — **subs-prover** (8888, no GPU). The prover's + interface is always registered even when its daemon is off. subs loads the + existing `default` spaces wallet at startup and does **not** create it — create it first from the Space-CLI Web UI with `spaces createwallet` if you haven't. Subspaces is independent of the Embedded Explorer toggle — you can run any combination @@ -348,6 +353,7 @@ explorer_ui: subspaces: default_enabled: false # opt-in via Enable Subspaces action store_field: enableSubspaces + subs_prover_default_enabled: false # separate toggle: set-subs-prover (store.enableSubsProver) image: horologger/subs:v0.1.0 # prebuilt; no compile step data_dir: /data/subspaces/data wallet: default # loaded by subs, NOT created (create via space-cli first) @@ -407,6 +413,7 @@ actions: - reset-explorer-state - enable-subspaces - disable-subspaces + - set-subs-prover - reset-subspaces-state - configure-certrelay ``` diff --git a/startos/actions/index.ts b/startos/actions/index.ts index ecdda3e..e05cb53 100644 --- a/startos/actions/index.ts +++ b/startos/actions/index.ts @@ -13,6 +13,7 @@ import { resetPassword } from './resetPassword' import { resetSpacedState } from './resetSpacedState' import { resetSubspacesState } from './resetSubspacesState' import { setBitcoinRpc } from './setBitcoinRpc' +import { setSubsProver } from './setSubsProver' import { showCredentials } from './showCredentials' import { showDbCredentials } from './showDbCredentials' import { showPassword } from './showPassword' @@ -35,5 +36,6 @@ export const actions = sdk.Actions.of() .addAction(resetExplorerState) .addAction(enableSubspaces) .addAction(disableSubspaces) + .addAction(setSubsProver) .addAction(resetSubspacesState) .addAction(configureCertrelay) diff --git a/startos/actions/setSubsProver.ts b/startos/actions/setSubsProver.ts new file mode 100644 index 0000000..2a6154b --- /dev/null +++ b/startos/actions/setSubsProver.ts @@ -0,0 +1,61 @@ +import { storeJson } from '../fileModels/storeJson' +import { i18n } from '../i18n' +import { sdk } from '../sdk' + +const { InputSpec, Value } = sdk + +const inputSpec = InputSpec.of({ + enabled: Value.toggle({ + name: i18n('Run Subspaces Prover'), + description: i18n( + 'When on, the subs-prover daemon starts (only while Subspaces itself is enabled). It runs lengthy timing tests on boot and can take a long time to become ready. Off by default. The Subspaces Prover interface (port 8888) stays registered either way.', + ), + warning: null, + footnote: null, + default: false, + }), +}) + +export const setSubsProver = sdk.Action.withInput( + // id + 'set-subs-prover', + + // metadata + async ({ effects }) => ({ + name: i18n('Enable / Disable Subspaces Prover'), + description: i18n( + 'Independently start or stop the subs-prover daemon, separate from the overall Subspaces toggle. Saving restarts the service.', + ), + warning: null, + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + // input + inputSpec, + + // prefill — current value + async ({ effects }) => { + const enabled = await storeJson.read((s) => s.enableSubsProver).once() + return { enabled: enabled === true } + }, + + // run + async ({ effects, input }) => { + await storeJson.merge(effects, { enableSubsProver: input.enabled }) + + return { + version: '1', + title: i18n('Success'), + message: input.enabled + ? i18n( + 'Subspaces Prover enabled. The service is restarting; the prover starts after the other Subspaces daemons and may take a while to become ready.', + ) + : i18n( + 'Subspaces Prover disabled. The service is restarting; the prover daemon will not start. Its interface (8888) remains registered.', + ), + result: null, + } + }, +) diff --git a/startos/fileModels/storeJson.ts b/startos/fileModels/storeJson.ts index b37ec54..e976d98 100644 --- a/startos/fileModels/storeJson.ts +++ b/startos/fileModels/storeJson.ts @@ -27,6 +27,7 @@ const shape = z.object({ .catch(null), enableExplorer: z.boolean().nullable().catch(null), enableSubspaces: z.boolean().nullable().catch(null), + enableSubsProver: z.boolean().nullable().catch(null), certrelaySelfUrl: z.string().nullable().catch(null), certrelayBootstrap: z.boolean().nullable().catch(null), certrelayHealthcheckHandle: z.string().nullable().catch(null), diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts index 39da3fd..3f8c4f6 100644 --- a/startos/i18n/dictionaries/default.ts +++ b/startos/i18n/dictionaries/default.ts @@ -128,6 +128,19 @@ const dict = { 133, 'Certrelay configuration saved. The service is restarting to apply the new settings.': 134, + 'Enable / Disable Subspaces Prover': 135, + 'Independently start or stop the subs-prover daemon, separate from the overall Subspaces toggle. Saving restarts the service.': + 136, + 'Run Subspaces Prover': 137, + 'When on, the subs-prover daemon starts (only while Subspaces itself is enabled). It runs lengthy timing tests on boot and can take a long time to become ready. Off by default. The Subspaces Prover interface (port 8888) stays registered either way.': + 138, + 'Subspaces Prover enabled. The service is restarting; the prover starts after the other Subspaces daemons and may take a while to become ready.': + 139, + 'Subspaces Prover disabled. The service is restarting; the prover daemon will not start. Its interface (8888) remains registered.': + 140, + 'Subs API': 141, + 'REST API of the subs daemon (subsd) for Subspaces operations. Distinct from the Subspaces Prover and Subspaces Registry. Only useful while Subspaces is enabled.': + 142, // interfaces.ts 'Space-CLI Web UI': 12, diff --git a/startos/init/taskSeedEnableSubspaces.ts b/startos/init/taskSeedEnableSubspaces.ts index 3690d80..625b998 100644 --- a/startos/init/taskSeedEnableSubspaces.ts +++ b/startos/init/taskSeedEnableSubspaces.ts @@ -2,12 +2,16 @@ import { storeJson } from '../fileModels/storeJson' import { sdk } from '../sdk' export const taskSeedEnableSubspaces = sdk.setupOnInit(async (effects) => { - const existing = await storeJson.read((s) => s.enableSubspaces).once() - if (existing !== null && existing !== undefined) return + const store = await storeJson.read().once() - await storeJson.merge( - effects, - { enableSubspaces: false }, - { allowWriteAfterConst: true }, - ) + const patch: { enableSubspaces?: boolean; enableSubsProver?: boolean } = {} + if (store?.enableSubspaces === null || store?.enableSubspaces === undefined) + patch.enableSubspaces = false + // subs-prover is independently gated and defaults OFF. + if (store?.enableSubsProver === null || store?.enableSubsProver === undefined) + patch.enableSubsProver = false + + if (Object.keys(patch).length > 0) { + await storeJson.merge(effects, patch, { allowWriteAfterConst: true }) + } }) diff --git a/startos/interfaces.ts b/startos/interfaces.ts index beba7d2..73cf0e1 100644 --- a/startos/interfaces.ts +++ b/startos/interfaces.ts @@ -68,7 +68,27 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => { query: {}, }) - const subspacesReceipt = await subspacesMultiOrigin.export([subspaces]) + // The subs daemon ("subsd — HTTP REST API server for subs operations") serves + // its REST API on the same port. Expose it as a distinct `api` interface, + // separate from the prover (8888) and registry (8081) interfaces. + const subsApi = sdk.createInterface(effects, { + name: i18n('Subs API'), + id: 'subs-api', + description: i18n( + 'REST API of the subs daemon (subsd) for Subspaces operations. Distinct from the Subspaces Prover and Subspaces Registry. Only useful while Subspaces is enabled.', + ), + type: 'api', + masked: false, + schemeOverride: null, + username: null, + path: '', + query: {}, + }) + + const subspacesReceipt = await subspacesMultiOrigin.export([ + subspaces, + subsApi, + ]) const proverMulti = sdk.MultiHost.of(effects, 'subspaces-prover-multi') const proverMultiOrigin = await proverMulti.bindPort(SUBSPACES_PROVER_PORT, { diff --git a/startos/main.ts b/startos/main.ts index 9e2a48b..54dac2c 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -56,6 +56,8 @@ export const main = sdk.setupMain(async ({ effects }) => { const { password: APP_PASSWORD, btcAuth, dbAuth, spacedAuth } = store const enableExplorer = store.enableExplorer === true const enableSubspaces = store.enableSubspaces === true + // subs-prover is independently gated and defaults OFF (null/undefined => false). + const enableSubsProver = store.enableSubsProver === true const spacedEnv = { SPACED_CHAIN, @@ -262,8 +264,8 @@ export const main = sdk.setupMain(async ({ effects }) => { // Appends the Subspaces chain (fetch → build → wallet ensure → subs-prover → // subs daemon) to any existing chain. `as any` because the chain's TS type // depends on prior IDs and we can't easily express the union here. - const withSubspaces = (chain: any): any => - chain + const withSubspaces = (chain: any): any => { + let c = chain .addOneshot('subspaces-dirs', { subcontainer: subspacesSub, exec: { @@ -278,32 +280,6 @@ export const main = sdk.setupMain(async ({ effects }) => { }, requires: [], }) - .addDaemon('subs-prover', { - subcontainer: subspacesSub, - exec: { - command: [ - 'sh', - '-c', - `cd ${SUBSPACES_PROVER_DIR} && exec ${SUBSPACES_PROVER_BIN} --server --server-port ${SUBSPACES_PROVER_PORT}`, - ], - env: proverEnv, - // Run as root: the horologger/subs image declares USER subs, but our - // /data volume + subdirs are root-owned, so the non-root user can't - // traverse/write them. Root sidesteps the ownership mismatch. - user: 'root', - }, - ready: { - display: i18n('Subspaces Prover'), - fn: () => - sdk.healthCheck.checkPortListening(effects, SUBSPACES_PROVER_PORT, { - successMessage: i18n('subs-prover is ready'), - errorMessage: i18n('subs-prover is not ready'), - }), - gracePeriod: 60_000, - trigger: sdk.trigger.cooldownTrigger(30_000), - }, - requires: ['subspaces-dirs'], - }) .addDaemon('subs-registry', { subcontainer: subspacesSub, exec: { @@ -349,6 +325,44 @@ export const main = sdk.setupMain(async ({ effects }) => { requires: ['subspaces-dirs', 'spaced', 'subs-registry'], }) + // subs-prover is opt-in and DISABLED BY DEFAULT (toggle via the "Enable + // Subspaces Prover" action). Its interface (8888) stays registered either + // way (see interfaces.ts), but the daemon only starts when enabled. It's + // added LAST and depends on every other subspaces daemon; it runs lengthy + // timing tests on boot and NOTHING depends on it, so its slow startup never + // blocks anything else. + if (enableSubsProver) { + c = c.addDaemon('subs-prover', { + subcontainer: subspacesSub, + exec: { + command: [ + 'sh', + '-c', + `cd ${SUBSPACES_PROVER_DIR} && exec ${SUBSPACES_PROVER_BIN} --server --server-port ${SUBSPACES_PROVER_PORT}`, + ], + env: proverEnv, + // Run as root: the horologger/subs image declares USER subs, but our + // /data volume + subdirs are root-owned, so the non-root user can't + // traverse/write them. Root sidesteps the ownership mismatch. + user: 'root', + }, + ready: { + display: i18n('Subspaces Prover'), + fn: () => + sdk.healthCheck.checkPortListening(effects, SUBSPACES_PROVER_PORT, { + successMessage: i18n('subs-prover is ready'), + errorMessage: i18n('subs-prover is not ready'), + }), + gracePeriod: 60_000, + trigger: sdk.trigger.cooldownTrigger(30_000), + }, + requires: ['subspaces-dirs', 'subs-registry', 'subs'], + }) + } + + return c + } + if (!enableExplorer) { // Spaces-only mode: spaced + gotty terminal. No PostgreSQL, no indexer. // User can flip the toggle via the Enable Embedded Explorer action; diff --git a/startos/versions/index.ts b/startos/versions/index.ts index d6d9e8b..07515d9 100644 --- a/startos/versions/index.ts +++ b/startos/versions/index.ts @@ -1,7 +1,7 @@ import { VersionGraph } from '@start9labs/start-sdk' -import { v_0_0_9_1 } from './v0.0.9.1' +import { v_0_0_9_2 } from './v0.0.9.2' export const versionGraph = VersionGraph.of({ - current: v_0_0_9_1, + current: v_0_0_9_2, other: [], }) diff --git a/startos/versions/v0.0.9.1.ts b/startos/versions/v0.0.9.2.ts similarity index 77% rename from startos/versions/v0.0.9.1.ts rename to startos/versions/v0.0.9.2.ts index 65a473e..763ab7c 100644 --- a/startos/versions/v0.0.9.1.ts +++ b/startos/versions/v0.0.9.2.ts @@ -1,9 +1,11 @@ import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' -export const v_0_0_9_1 = VersionInfo.of({ - version: '0.0.9:1', +export const v_0_0_9_2 = VersionInfo.of({ + version: '0.0.9:2', releaseNotes: { - en_US: `- Embedded Certrelay (prebuilt \`horologger/certrelay:v0.2.3\` image, static musl binaries). Always on — runs the \`certrelay\` server on port 7778 from service start, as its own StartOS interface. It serves cryptographic proofs binding Bitcoin-anchored handles to owner keys, talking to the local spaced over loopback using the store.spacedAuth credentials. The bundled \`fabric\` CLI is copied to /data/bin/fabric so it's runnable from the Space-CLI Web UI. New "Configure Certrelay" action sets CERTRELAY_SELF_URL, CERTRELAY_BOOTSTRAP, and CERTRELAY_HEALTHCHECK_HANDLE (stored in store.json; saving restarts the service). Set CERTRELAY_SELF_URL to the publicly visible URL StartOS exposes for the Certrelay interface. + en_US: `- Added a dedicated "Subs API" interface (type api) on the subs daemon's port (7777), distinct from the Subspaces Prover (8888) and Subspaces Registry (8081) interfaces. The subs daemon is "subsd — an HTTP REST API server"; this surfaces that REST API as its own dashboard entry. +- New "Enable / Disable Subspaces Prover" action independently gates just the subs-prover daemon (store.enableSubsProver), **disabled by default**. When off, subs-prover does not start (it runs lengthy boot-time timing tests), but its interface on 8888 stays registered. It is added last and nothing depends on it, so its slow startup never blocks subs, the registry, or certrelay. +- Embedded Certrelay (prebuilt \`horologger/certrelay:v0.2.3\` image, static musl binaries). Always on — runs the \`certrelay\` server on port 7778 from service start, as its own StartOS interface. It serves cryptographic proofs binding Bitcoin-anchored handles to owner keys, talking to the local spaced over loopback using the store.spacedAuth credentials. The bundled \`fabric\` CLI is copied to /data/bin/fabric so it's runnable from the Space-CLI Web UI. New "Configure Certrelay" action sets CERTRELAY_SELF_URL, CERTRELAY_BOOTSTRAP, and CERTRELAY_HEALTHCHECK_HANDLE (stored in store.json; saving restarts the service). Set CERTRELAY_SELF_URL to the publicly visible URL StartOS exposes for the Certrelay interface. - Removed the build-from-source chains for the Go indexer and the SvelteKit explorer, and dropped their builder images (\`golang:1.23-alpine\`, \`node:20-alpine\`). This shrinks the .s9pk and removes the multi-minute first-enable compile. The indexer + explorer-UI daemons are now stubbed with TODOs in startos/main.ts pending prebuilt images (indexer image not yet produced; explorer image to be provided). Enabling the embedded explorer currently starts PostgreSQL only. All explorer-indexer actions (show-db-credentials, reset-db-state, reset-indexer-state, reset-explorer-state) are retained. - Embedded Subspaces support (prebuilt \`horologger/subs:v0.1.0\` image). Opt-in via the new "Enable Subspaces" action; "Disable Subspaces" stops it. No compile step — enabling starts three prebuilt daemons, each on its own StartOS interface: **subs** (Web UI, 7777), **subs-prover** (RISC Zero prover, no GPU, 8888), and **registry-server** (handle registry, 8081). subs loads the existing \`default\` spaces wallet at startup (it does not create one). Runtime data persists at /data/subspaces/data across restarts and toggles. New "Reset Subspaces State" action wipes local data. Adds a fifth manifest image (\`horologger/subs:v0.1.0\`, ~84 MB) — switched from building \`spacesops/subs\` from source with \`rust:1-slim\` to shrink the .s9pk and eliminate the multi-minute first-enable compile. - Five distinct interfaces now appear in the dashboard: **Space-CLI Web UI** (gotty terminal, 8080), **Explorer Web UI** (3000, opt-in), **Subspaces Web UI** (7777, opt-in), **Subspaces Prover** (8888, opt-in), **Subspaces Registry** (8081, opt-in). Internal "Web UI" references renamed to **Space-CLI Web UI** for clarity.