diff --git a/README.md b/README.md index 96794de..36b75d3 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ through a browser-based terminal. | Field | Value | | --- | --- | -| Spaces image | `docker.io/horologger/spaces:v0.2.1a` | -| Subspaces image | `docker.io/horologger/subs:v0.1.2b` | -| Certrelay image | `docker.io/horologger/certrelay:v0.2.7b` | -| Nacho image | `docker.io/horologger/nacho:v1.0.0` | +| Spaces image | `docker.io/horologger/spaces:v0.3.0` | +| Subspaces image | `docker.io/horologger/subs:v0.1.2c` | +| Certrelay image | `docker.io/horologger/certrelay:v0.2.8` | +| Nacho image | `docker.io/horologger/nacho:v1.0.0b` | | Architectures | `linux/amd64`, `linux/arm64` | | Entrypoint | StartOS-managed (image entrypoints are **not** used directly) | @@ -54,7 +54,7 @@ image's `docker_entrypoint.sh`; daemons are defined in `startos/main.ts`. | --- | --- | --- | | `/data` | `main` | Spaces data directory (`SPACED_DATA_DIR`), wallets, indexes, and `store.json` | | `/data/mainnet/.cookie` | `main` | Spaced RPC cookie (auto-generated by `spaced` at startup) | -| `/data/store.json` | `main` | StartOS-managed credentials (Space-CLI Web UI password, bitcoind RPC user/password, spaced RPC user/password) and feature config (Subspaces toggles, Certrelay + Nacho settings). | +| `/data/store.json` | `main` | StartOS-managed credentials (Space-CLI Web UI password, bitcoind RPC user/password, spaced RPC user/password, registry API keys) and feature config (Subspaces toggles, Certrelay + Nacho settings). | | `/data/subspaces/data` | `main` | `subs` runtime data (`SUBS_DATA_DIR`) — handles, proofs, config.db. Persists across restarts and enable/disable toggles. | | `/data/subspaces/prover` | `main` | Working directory for the `subs-prover` daemon. | | `/data/subspaces/registry` | `main` | Working directory for the `registry-server` daemon. | @@ -87,6 +87,9 @@ On the first install, StartOS: The nacho ignore-names list lives in `/data/nacho/ignore_names.txt` (not in the store); the `nacho-setup` oneshot creates that file with the default (`fold,swifty`) on first daemon start if it doesn't exist. + Registry API keys (`registryApiKey` / `subsdApiKey`) are **not** seeded at + install — run **Create Registry API Keys** before (or when prompted after) + enabling Subspaces; `registry-server` will not start without them. 6. Launches `spaced` as a managed daemon (no `screen`, no shell auto-start) and the `gotty` web terminal once the bashrc oneshot completes. 7. Runs `certrelay-setup` (creates `/data/certrelay/data`, copies `fabric` and @@ -110,7 +113,6 @@ mainnet-only. | Bitcoin RPC username / password (registered on bitcoind) | Wallet creation, bidding, and registration -- all driven via `space-cli` inside the terminal | | Spaced RPC username / password (`SPACED_RPC_USER`/`SPACED_RPC_PASSWORD`) | `space-cli` flags and subcommands | | Subspaces auth toggle + credentials (`SUBS_BASIC_AUTH_USER`/`SUBS_BASIC_AUTH_PASSWORD`, off by default) | | -| `SUBS_PUBLISH_REQUIRE_FINALIZED` (off by default; via **Configure Subspaces**) | | | Subspaces Prover auth toggle + credentials (`SUBS_PROVER_BASIC_AUTH_USER`/`SUBS_PROVER_BASIC_AUTH_PASSWORD`, off by default) | | | Chain selection (locked to `mainnet`) | | | Spaced data directory and RPC bind | | @@ -145,7 +147,6 @@ mainnet-only. | `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-subspaces` | Configure Subspaces | Enabled | Any | Toggle `SUBS_PUBLISH_REQUIRE_FINALIZED` (default **off**). When on, subs requires finalized commitments before certificate publish. Stored in `store.subsPublishRequireFinalized`; injected into the `subs` daemon env. Saving restarts the service. | | `enable-subs-auth` | Enable Subspaces Auth | Enabled (hidden when already on) | Any | Sets `store.subsAuthEnabled = true`. If no credentials exist yet, generates `{ username: 'spaces', password: random32 }` and stores them in `store.subsAuth`. Restart re-launches `subs` with `SUBS_BASIC_AUTH_USER`/`SUBS_BASIC_AUTH_PASSWORD` env vars set. | | `disable-subs-auth` | Disable Subspaces Auth | Enabled (hidden when already off) | Any | Sets `store.subsAuthEnabled = false`. Stored credentials are preserved so a later re-enable does not regenerate them. Restart re-launches `subs` without the auth env vars. | | `show-subs-credentials` | Show Subspaces Auth Credentials | Enabled | Any | Surfaces the current `SUBS_BASIC_AUTH_USER`/`SUBS_BASIC_AUTH_PASSWORD` (from `store.subsAuth`) plus a loopback connection URL. Reports whether auth is currently enforced. | @@ -154,6 +155,8 @@ mainnet-only. | `disable-subs-prover-auth` | Disable Subspaces Prover Auth | Enabled (hidden when already off) | Any | Sets `store.subsProverAuthEnabled = false`. Stored credentials are preserved. Restart re-launches `subs-prover` without the auth env vars. | | `show-subs-prover-credentials` | Show Subspaces Prover Auth Credentials | Enabled | Any | Surfaces the current `SUBS_PROVER_BASIC_AUTH_USER`/`SUBS_PROVER_BASIC_AUTH_PASSWORD` (from `store.subsProverAuth`) plus a loopback connection URL. Reports whether auth is currently enforced. | | `set-subs-prover-credentials` | Set Subspaces Prover Auth Credentials | Enabled | Any | Set or rotate `store.subsProverAuth`. Same UX as `set-subs-credentials` (blank password → auto-generated). Restarts the service only if prover auth is currently enabled. | +| `create-registry-api-keys` | Create Registry API Keys | Enabled | Any | Generates (or rotates) `store.registryApiKey` + `store.subsdApiKey` as 64-char hex (`openssl rand -hex 32`). Both are required and must differ. Injected as `REGISTRY_API_KEY` / `SUBSD_API_KEY` into `registry-server`. Put `SUBSD_API_KEY` in subs Settings → Registry Server → Auth Token. Restarts so the registry picks up the new keys. When Subspaces is enabled without valid keys, a critical task points here. | +| `show-registry-api-keys` | Show Registry API Keys | Enabled | Any | Surfaces the stored `REGISTRY_API_KEY` and `SUBSD_API_KEY`. Returns blanks until Create Registry API Keys has been run. | | `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. | | `configure-nacho` | Configure Nacho | Enabled | Any | Writes the Ignore Names list to `/data/nacho/ignore_names.txt` (read at runtime by nacho — survives daemon restarts independent of the store) and sets `EXPO_PUBLIC_WORKSHOP_PDF_LINK_TEXT` in `store.json`. Prefill reads the file (with the package default as fallback) plus the current store value. Saving restarts the service. | | `upload-support-pdf` | Upload Support PDF | Enabled | Any | Required `.pdf` file upload that overwrites `/data/support.pdf` on the main volume (read with `fs/promises`, piped via stdin into a temp `spaces` subcontainer's `cat > /data/support.pdf`). Split out from Configure Nacho because StartOS's form serializes an unselected `Value.file` as `{}` rather than `null`, which `Value.file(..., required: false).nullable()` rejects; a `required: true` dedicated action avoids the empty-form path entirely. | @@ -198,18 +201,22 @@ the same idempotent init logic runs and reuses the existing credentials in 6. **The web terminal is independent of spaced.** Gotty stays reachable even when `spaced` is crash-looping, so you can always shell in to diagnose. 7. **Subspaces is opt-in.** Fresh installs do not run Subspaces. Run **Enable - Subspaces** to start it. It ships as the prebuilt `horologger/subs:v0.1.2b` + Subspaces** to start it. It ships as the prebuilt `horologger/subs:v0.1.2c` image (no compile step), so enabling is fast — the daemons start as soon as the image is on the box. Enabling runs three daemons, each with its own StartOS interface: **subs** (Web UI + Subs API, 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. + prover's interface is always registered even when its daemon is off. Before + (or when prompted after) enabling, run **Create Registry API Keys** — + `registry-server` requires distinct `REGISTRY_API_KEY` and `SUBSD_API_KEY` + env vars at boot. Paste `SUBSD_API_KEY` into subs Settings → Registry + Server → Auth Token, then click Test. Use `REGISTRY_API_KEY` as the Bearer + token for intake/`POST /register` clients. 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. 8. **Certrelay is always on.** It ships as the prebuilt - `horologger/certrelay:v0.2.7b` image (static musl binaries) and runs on + `horologger/certrelay:v0.2.8` image (static musl binaries) and runs on 7778 from service start. It talks to the local spaced over loopback using the `store.spacedAuth` credentials. The `fabric` and `monitor` binaries are copied to `/data/bin/` for manual use from the Space-CLI Web UI; neither is auto-started @@ -250,10 +257,10 @@ spaces balance package_id: spaces upstream_version: subspacesplus images: - spaces: docker.io/horologger/spaces:v0.2.1a - subspaces: docker.io/horologger/subs:v0.1.2b - certrelay: docker.io/horologger/certrelay:v0.2.7b - nacho: docker.io/horologger/nacho:v1.0.0 + spaces: docker.io/horologger/spaces:v0.3.0 + subspaces: docker.io/horologger/subs:v0.1.2c + certrelay: docker.io/horologger/certrelay:v0.2.8 + nacho: docker.io/horologger/nacho:v1.0.0b architectures: [x86_64, aarch64] volumes: main: /data @@ -286,18 +293,26 @@ spaced_env_vars: - APP_USER - APP_PASSWORD spaces: - image: horologger/spaces:v0.2.1a # spaced, space-cli, gotty (terminal image) + image: horologger/spaces:v0.3.0 # spaced, space-cli, gotty (terminal image) 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.2b # prebuilt; no compile step + image: horologger/subs:v0.1.2c # prebuilt; no compile step data_dir: /data/subspaces/data wallet: default # loaded by subs, NOT created (create via space-cli first) daemons: # three separate daemons, each its own interface subs: { bin: /usr/local/bin/subs, port: 7777 } subs-prover: { bin: /usr/local/bin/subs-prover, port: 8888, gpu: none } registry-server: { bin: /usr/local/bin/registry-server, port: 8081 } # remapped from upstream 8080 + registry_api_keys: # mandatory for registry-server boot; must differ + store_fields: [registryApiKey, subsdApiKey] + env_vars: [REGISTRY_API_KEY, SUBSD_API_KEY] # always injected into registry-server + generate: openssl-rand-hex-32 # 64 lowercase hex via create-registry-api-keys + subs_ui: Settings → Registry Server → Auth Token = SUBSD_API_KEY + actions: + - create-registry-api-keys + - show-registry-api-keys auth: # optional HTTP basic auth in front of subs (Web UI + Subs API on 7777) default_enabled: false store_enabled_field: subsAuthEnabled @@ -306,11 +321,6 @@ subspaces: env_vars: # injected only when subsAuthEnabled=true AND subsAuth!=null - SUBS_BASIC_AUTH_USER - SUBS_BASIC_AUTH_PASSWORD - publish_require_finalized: - default: false - store_field: subsPublishRequireFinalized - env_var: SUBS_PUBLISH_REQUIRE_FINALIZED # always 'true' or 'false' on subs daemon - action: configure-subspaces actions: - enable-subs-auth - disable-subs-auth @@ -331,7 +341,7 @@ subspaces: - set-subs-prover-credentials certrelay: always_on: true - image: horologger/certrelay:v0.2.7b # prebuilt; static musl binaries + image: horologger/certrelay:v0.2.8 # prebuilt; static musl binaries server_bin: /usr/local/bin/certrelay fabric_bin: /usr/local/bin/fabric # copied to /data/bin/fabric for the CLI monitor_bin: /usr/local/bin/monitor # copied to /data/bin/monitor (CLI only; not started) @@ -350,7 +360,7 @@ certrelay: CERTRELAY_HEALTHCHECK_HANDLE: account-digital-useful.genesis@key # via action nacho: always_on: true - image: horologger/nacho:v1.0.0 # Expo dev server + image: horologger/nacho:v1.0.0b # Expo dev server port: 8082 data_dir: /data/nacho ignore_names_file: /data/nacho/ignore_names.txt # read at runtime; default 'fold,swifty' seeded by nacho-setup @@ -374,7 +384,6 @@ actions: - enable-subspaces - disable-subspaces - set-subs-prover - - configure-subspaces - enable-subs-auth - disable-subs-auth - show-subs-credentials @@ -383,6 +392,8 @@ actions: - disable-subs-prover-auth - show-subs-prover-credentials - set-subs-prover-credentials + - create-registry-api-keys + - show-registry-api-keys - reset-subspaces-state - configure-certrelay - configure-nacho diff --git a/instructions.md b/instructions.md index 47b0b27..f718d80 100644 --- a/instructions.md +++ b/instructions.md @@ -57,14 +57,32 @@ spaces walletbalance default `/data/mainnet/wallets/default` to `.bakNNN` suffixes so no prior state is lost, then calls `space-cli importwallet` and `space-cli loadwallet`. Requires the service to be running. +- **Create Registry API Keys** — generates (or rotates) `REGISTRY_API_KEY` and + `SUBSD_API_KEY` for the Subspaces registry. Required before the registry + daemon will start. +- **Show Registry API Keys** — displays the stored registry keys. + +## Subspaces registry API keys + +The embedded `registry-server` will not start without two distinct bearer +secrets. Before enabling Subspaces (or when StartOS prompts you after): + +1. Run **Create Registry API Keys** — generates `REGISTRY_API_KEY` and + `SUBSD_API_KEY` (same shape as `openssl rand -hex 32`). +2. In the Subspaces Web UI, open **Settings → Registry Server**, set **Auth + Token** to `SUBSD_API_KEY`, and click **Test**. +3. Give `REGISTRY_API_KEY` to any intake client that calls `POST /register`. + +Use **Show Registry API Keys** to retrieve the stored values later, or re-run +**Create Registry API Keys** to rotate them (then update the Auth Token). ## Limitations - **Mainnet only.** No testnet, signet, or regtest. - **Container images** (see also the package manifest): the `spaced` runtime uses - `horologger/spaces:v0.2.1a`; Subspaces, Certrelay, and Nacho use - `horologger/subs:v0.1.2b`, `horologger/certrelay:v0.2.7b`, and - `horologger/nacho:v1.0.0`. + `horologger/spaces:v0.3.0`; Subspaces, Certrelay, and Nacho use + `horologger/subs:v0.1.2c`, `horologger/certrelay:v0.2.8`, and + `horologger/nacho:v1.0.0b`. - The image's `docker_entrypoint.sh` is not used. `spaced` is managed by StartOS, not started inside a `screen` session by login. - The only externally bound port is `8080` (gotty). Spaced RPC stays on diff --git a/startos/actions/configureSubspaces.ts b/startos/actions/configureSubspaces.ts deleted file mode 100644 index 4b0eaf3..0000000 --- a/startos/actions/configureSubspaces.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { storeJson } from '../fileModels/storeJson' -import { i18n } from '../i18n' -import { sdk } from '../sdk' - -const { InputSpec, Value } = sdk - -const inputSpec = InputSpec.of({ - subsPublishRequireFinalized: Value.toggle({ - name: i18n('Require Finalized Publish'), - description: i18n( - 'When on, subs blocks certificate publish until commitments are finalized (150 confirmations). Maps to SUBS_PUBLISH_REQUIRE_FINALIZED in the subs daemon environment. Off by default.', - ), - warning: null, - footnote: null, - default: false, - }), -}) - -export const configureSubspaces = sdk.Action.withInput( - // id - 'configure-subspaces', - - // metadata - async ({ effects }) => ({ - name: i18n('Configure Subspaces'), - description: i18n( - 'Set user-tunable subs options. Saving restarts the service so the subs daemon picks up the new environment.', - ), - warning: null, - allowedStatuses: 'any', - group: null, - visibility: 'enabled', - }), - - // input - inputSpec, - - // prefill — current value (unset/null => false) - async ({ effects }) => { - const enabled = await storeJson - .read((s) => s.subsPublishRequireFinalized) - .once() - return { subsPublishRequireFinalized: enabled === true } - }, - - // run - async ({ effects, input }) => { - await storeJson.merge(effects, { - subsPublishRequireFinalized: input.subsPublishRequireFinalized, - }) - - return { - version: '1', - title: i18n('Success'), - message: input.subsPublishRequireFinalized - ? i18n( - 'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is enabled; the service is restarting.', - ) - : i18n( - 'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is disabled; the service is restarting.', - ), - result: null, - } - }, -) diff --git a/startos/actions/createRegistryApiKeys.ts b/startos/actions/createRegistryApiKeys.ts new file mode 100644 index 0000000..13e3057 --- /dev/null +++ b/startos/actions/createRegistryApiKeys.ts @@ -0,0 +1,62 @@ +import { storeJson } from '../fileModels/storeJson' +import { i18n } from '../i18n' +import { sdk } from '../sdk' +import { generateRegistryApiKeys } from '../utils' + +export const createRegistryApiKeys = sdk.Action.withoutInput( + // id + 'create-registry-api-keys', + + // metadata + async ({ effects }) => ({ + name: i18n('Create Registry API Keys'), + description: i18n( + 'Generate (or rotate) REGISTRY_API_KEY and SUBSD_API_KEY for the embedded registry-server. Both are required and must differ. Put SUBSD_API_KEY in subs Settings → Registry Server → Auth Token. REGISTRY_API_KEY is for intake clients calling POST /register. Saving restarts the service so registry-server picks up the new keys.', + ), + warning: null, + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + // run + async ({ effects }) => { + const { registryApiKey, subsdApiKey } = generateRegistryApiKeys() + await storeJson.merge(effects, { registryApiKey, subsdApiKey }) + + return { + version: '1', + title: i18n('Success'), + message: i18n( + 'Registry API keys saved. Paste SUBSD_API_KEY into subs Settings → Registry Server → Auth Token, then click Test. Use REGISTRY_API_KEY as the Bearer token for POST /register clients. The service is restarting so registry-server picks up the new keys.', + ), + result: { + type: 'group', + value: [ + { + type: 'single', + name: i18n('REGISTRY_API_KEY'), + description: i18n( + 'Bearer secret for intake/POST /register clients.', + ), + value: registryApiKey, + masked: true, + copyable: true, + qr: false, + }, + { + type: 'single', + name: i18n('SUBSD_API_KEY'), + description: i18n( + 'Paste into subs Settings → Registry Server → Auth Token (subs↔registry channel).', + ), + value: subsdApiKey, + masked: true, + copyable: true, + qr: false, + }, + ], + }, + } + }, +) diff --git a/startos/actions/index.ts b/startos/actions/index.ts index b36577b..6c00896 100644 --- a/startos/actions/index.ts +++ b/startos/actions/index.ts @@ -1,7 +1,7 @@ import { sdk } from '../sdk' import { configureCertrelay } from './configureCertrelay' import { configureNacho } from './configureNacho' -import { configureSubspaces } from './configureSubspaces' +import { createRegistryApiKeys } from './createRegistryApiKeys' import { disableSubsAuth } from './disableSubsAuth' import { disableSubsProverAuth } from './disableSubsProverAuth' import { disableSubspaces } from './disableSubspaces' @@ -19,6 +19,7 @@ import { setSubsProver } from './setSubsProver' import { setSubsProverCredentials } from './setSubsProverCredentials' import { showCredentials } from './showCredentials' import { showPassword } from './showPassword' +import { showRegistryApiKeys } from './showRegistryApiKeys' import { showSpacedCredentials } from './showSpacedCredentials' import { showSubsCredentials } from './showSubsCredentials' import { showSubsProverCredentials } from './showSubsProverCredentials' @@ -49,5 +50,6 @@ export const actions = sdk.Actions.of() .addAction(resetSubspacesState) .addAction(configureCertrelay) .addAction(configureNacho) - .addAction(configureSubspaces) + .addAction(createRegistryApiKeys) + .addAction(showRegistryApiKeys) .addAction(uploadSupportPdf) diff --git a/startos/actions/showRegistryApiKeys.ts b/startos/actions/showRegistryApiKeys.ts new file mode 100644 index 0000000..8148042 --- /dev/null +++ b/startos/actions/showRegistryApiKeys.ts @@ -0,0 +1,74 @@ +import { storeJson } from '../fileModels/storeJson' +import { i18n } from '../i18n' +import { sdk } from '../sdk' + +export const showRegistryApiKeys = sdk.Action.withoutInput( + // id + 'show-registry-api-keys', + + // metadata + async ({ effects }) => ({ + name: i18n('Show Registry API Keys'), + description: i18n( + 'Display the stored REGISTRY_API_KEY and SUBSD_API_KEY used by registry-server. Returns blanks if Create Registry API Keys has not been run yet.', + ), + warning: null, + allowedStatuses: 'any', + group: null, + visibility: 'enabled', + }), + + // run + async ({ effects }) => { + const [registryApiKey, subsdApiKey] = await Promise.all([ + storeJson.read((s) => s.registryApiKey).once(), + storeJson.read((s) => s.subsdApiKey).once(), + ]) + + const ready = + !!registryApiKey && + !!subsdApiKey && + registryApiKey.length > 0 && + subsdApiKey.length > 0 && + registryApiKey !== subsdApiKey + + return { + version: '1', + title: i18n('Show Registry API Keys'), + message: ready + ? i18n( + 'Paste SUBSD_API_KEY into subs Settings → Registry Server → Auth Token. Use REGISTRY_API_KEY as the Bearer token for POST /register clients.', + ) + : i18n( + 'No valid registry API keys are stored yet. Run Create Registry API Keys first (both keys must be non-empty and different).', + ), + result: { + type: 'group', + value: [ + { + type: 'single', + name: i18n('REGISTRY_API_KEY'), + description: i18n( + 'Bearer secret for intake/POST /register clients.', + ), + value: registryApiKey ?? '', + masked: true, + copyable: true, + qr: false, + }, + { + type: 'single', + name: i18n('SUBSD_API_KEY'), + description: i18n( + 'Paste into subs Settings → Registry Server → Auth Token (subs↔registry channel).', + ), + value: subsdApiKey ?? '', + masked: true, + copyable: true, + qr: false, + }, + ], + }, + } + }, +) diff --git a/startos/fileModels/storeJson.ts b/startos/fileModels/storeJson.ts index 75ffa19..1dda0f3 100644 --- a/startos/fileModels/storeJson.ts +++ b/startos/fileModels/storeJson.ts @@ -39,7 +39,12 @@ const shape = z.object({ certrelayBootstrap: z.boolean().nullable().catch(null), certrelayHealthcheckHandle: z.string().nullable().catch(null), nachoWorkshopPdfLinkText: z.string().nullable().catch(null), - subsPublishRequireFinalized: z.boolean().nullable().catch(null), + // Bearer secrets for the embedded registry-server (examples/registry-server). + // REGISTRY_API_KEY guards POST /register; SUBSD_API_KEY is the Auth Token + // for the subs↔registry channel (Settings → Registry Server → Auth Token). + // Both are required and must differ or registry-server refuses to start. + registryApiKey: z.string().nullable().catch(null), + subsdApiKey: z.string().nullable().catch(null), }) export const storeJson = FileHelper.json( diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts index c844e26..d326451 100644 --- a/startos/i18n/dictionaries/default.ts +++ b/startos/i18n/dictionaries/default.ts @@ -289,16 +289,25 @@ const dict = { 205, 'Subspaces Prover Auth credentials saved. Auth is currently DISABLED — enable it with "Enable Subspaces Prover Auth" to enforce these credentials.': 206, - 'Configure Subspaces': 207, - 'Set user-tunable subs options. Saving restarts the service so the subs daemon picks up the new environment.': - 208, - 'Require Finalized Publish': 209, - 'When on, subs blocks certificate publish until commitments are finalized (150 confirmations). Maps to SUBS_PUBLISH_REQUIRE_FINALIZED in the subs daemon environment. Off by default.': - 210, - 'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is enabled; the service is restarting.': - 211, - 'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is disabled; the service is restarting.': - 212, + 'Create Registry API Keys': 213, + 'Generate (or rotate) REGISTRY_API_KEY and SUBSD_API_KEY for the embedded registry-server. Both are required and must differ. Put SUBSD_API_KEY in subs Settings → Registry Server → Auth Token. REGISTRY_API_KEY is for intake clients calling POST /register. Saving restarts the service so registry-server picks up the new keys.': + 214, + 'Registry API keys saved. Paste SUBSD_API_KEY into subs Settings → Registry Server → Auth Token, then click Test. Use REGISTRY_API_KEY as the Bearer token for POST /register clients. The service is restarting so registry-server picks up the new keys.': + 215, + REGISTRY_API_KEY: 216, + SUBSD_API_KEY: 217, + 'Bearer secret for intake/POST /register clients.': 218, + 'Paste into subs Settings → Registry Server → Auth Token (subs↔registry channel).': + 219, + 'Show Registry API Keys': 220, + 'Display the stored REGISTRY_API_KEY and SUBSD_API_KEY used by registry-server. Returns blanks if Create Registry API Keys has not been run yet.': + 221, + 'Paste SUBSD_API_KEY into subs Settings → Registry Server → Auth Token. Use REGISTRY_API_KEY as the Bearer token for POST /register clients.': + 222, + 'No valid registry API keys are stored yet. Run Create Registry API Keys first (both keys must be non-empty and different).': + 223, + 'Subspaces registry-server requires REGISTRY_API_KEY and SUBSD_API_KEY before it can start': + 224, } as const /** diff --git a/startos/init/index.ts b/startos/init/index.ts index eac23d6..34569db 100644 --- a/startos/init/index.ts +++ b/startos/init/index.ts @@ -5,6 +5,7 @@ import { setInterfaces } from '../interfaces' import { sdk } from '../sdk' import { versionGraph } from '../versions' import { taskBtcAuth } from './taskBtcAuth' +import { taskRegistryApiKeys } from './taskRegistryApiKeys' import { taskSeedCertrelay } from './taskSeedCertrelay' import { taskSeedEnableSubspaces } from './taskSeedEnableSubspaces' import { taskSeedNacho } from './taskSeedNacho' @@ -20,6 +21,7 @@ export const init = sdk.setupInit( taskBtcAuth, taskSeedSpacedAuth, taskSeedEnableSubspaces, + taskRegistryApiKeys, taskSeedCertrelay, taskSeedNacho, taskSetPassword, diff --git a/startos/init/taskRegistryApiKeys.ts b/startos/init/taskRegistryApiKeys.ts new file mode 100644 index 0000000..2a8879d --- /dev/null +++ b/startos/init/taskRegistryApiKeys.ts @@ -0,0 +1,28 @@ +import { createRegistryApiKeys } from '../actions/createRegistryApiKeys' +import { storeJson } from '../fileModels/storeJson' +import { i18n } from '../i18n' +import { sdk } from '../sdk' + +// registry-server refuses to start without both keys (and they must differ). +// Only prompt when Subspaces is enabled so installs that leave it off are not +// blocked; enabling Subspaces without keys surfaces this critical task. +export const taskRegistryApiKeys = sdk.setupOnInit(async (effects) => { + const store = await storeJson.read().once() + if (store?.enableSubspaces !== true) return + + const registryApiKey = store?.registryApiKey + const subsdApiKey = store?.subsdApiKey + const ready = + !!registryApiKey && + !!subsdApiKey && + registryApiKey.length > 0 && + subsdApiKey.length > 0 && + registryApiKey !== subsdApiKey + if (ready) return + + await sdk.action.createOwnTask(effects, createRegistryApiKeys, 'critical', { + reason: i18n( + 'Subspaces registry-server requires REGISTRY_API_KEY and SUBSD_API_KEY before it can start', + ), + }) +}) diff --git a/startos/main.ts b/startos/main.ts index 7461aa6..e4d3339 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -149,8 +149,6 @@ export const main = sdk.setupMain(async ({ effects }) => { SUBS_SPACED_RPC_PASSWORD: spacedAuth.password, SUBS_PROVER_ENDPOINT: `http://127.0.0.1:${SUBSPACES_PROVER_PORT}`, SUBS_REGISTRY_ENDPOINT: `http://127.0.0.1:${SUBSPACES_REGISTRY_PORT}`, - SUBS_PUBLISH_REQUIRE_FINALIZED: - store.subsPublishRequireFinalized === true ? 'true' : 'false', HOME: SUBSPACES_DATA_DIR, RUST_LOG: 'subs=info,subs_prover=info,registry_server=info', ...subsAuthEnv, @@ -181,6 +179,9 @@ export const main = sdk.setupMain(async ({ effects }) => { REGISTRY_SERVER_PORT: String(SUBSPACES_REGISTRY_PORT), HOME: SUBSPACES_REGISTRY_DIR, RUST_LOG: 'registry_server=info', + // examples/registry-server requires both at boot; they must differ. + REGISTRY_API_KEY: store.registryApiKey ?? '', + SUBSD_API_KEY: store.subsdApiKey ?? '', } const certrelaySub = sdk.SubContainer.of( @@ -371,6 +372,22 @@ export const main = sdk.setupMain(async ({ effects }) => { // 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 => { + const registryApiKey = store.registryApiKey + const subsdApiKey = store.subsdApiKey + if ( + !registryApiKey || + !subsdApiKey || + registryApiKey.length === 0 || + subsdApiKey.length === 0 || + registryApiKey === subsdApiKey + ) { + // taskRegistryApiKeys surfaces Create Registry API Keys when Subspaces is + // on without valid keys; refuse to start the registry half-configured. + throw new Error( + 'Registry API keys are not configured. Run Create Registry API Keys.', + ) + } + let c = chain .addOneshot('subspaces-dirs', { subcontainer: subspacesSub, diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 85ea3a6..6618541 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -14,19 +14,19 @@ export const manifest = setupManifest({ volumes: ['main'], images: { spaces: { - source: { dockerTag: 'horologger/spaces:v0.2.1a' }, + source: { dockerTag: 'horologger/spaces:v0.3.0' }, arch: ['x86_64', 'aarch64'], }, subspaces: { - source: { dockerTag: 'horologger/subs:v0.1.2b' }, + source: { dockerTag: 'horologger/subs:v0.1.2c' }, arch: ['x86_64', 'aarch64'], }, certrelay: { - source: { dockerTag: 'horologger/certrelay:v0.2.7b' }, + source: { dockerTag: 'horologger/certrelay:v0.2.8' }, arch: ['x86_64', 'aarch64'], }, nacho: { - source: { dockerTag: 'horologger/nacho:v1.0.0' }, + source: { dockerTag: 'horologger/nacho:v1.0.0b' }, arch: ['x86_64', 'aarch64'], }, }, diff --git a/startos/utils.ts b/startos/utils.ts index feb1cb0..68eb64f 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -74,6 +74,27 @@ export function randomPassword() { }) } +// Matches upstream docs: `openssl rand -hex 32` → 64 lowercase hex chars. +export function randomApiKey() { + return utils.getDefaultString({ + charset: 'a-f,0-9', + len: 64, + }) +} + +// registry-server refuses to start unless both keys are non-empty and different. +export function generateRegistryApiKeys(): { + registryApiKey: string + subsdApiKey: string +} { + const registryApiKey = randomApiKey() + let subsdApiKey = randomApiKey() + while (subsdApiKey === registryApiKey) { + subsdApiKey = randomApiKey() + } + return { registryApiKey, subsdApiKey } +} + // Banner box rendering for the gotty terminal MOTD. Canonical width is 100 // monospaced cells: 1 cell each for the left/right side borders, plus 1-cell // gutters on each side of text, leaving 96 cells of usable text. All public diff --git a/startos/versions/current.ts b/startos/versions/current.ts index bc4c1ff..b450c4c 100644 --- a/startos/versions/current.ts +++ b/startos/versions/current.ts @@ -1,9 +1,14 @@ import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' export const current = VersionInfo.of({ - version: '0.2.1:5', + version: '0.3.0:0', releaseNotes: { - en_US: `Bump Subspaces to \`horologger/subs:v0.1.2b\` and Certrelay to \`horologger/certrelay:v0.2.7b\` (spaces \`v0.2.1a\` and nacho \`v1.0.0\` unchanged). + en_US: `Update to upstream Spaces **0.3.0** (\`horologger/spaces:v0.3.0\`). Also bundles \`subs:v0.1.2c\`, \`certrelay:v0.2.8\`, and \`nacho:v1.0.0b\`. + +**Wrapper** + +- Add **Create Registry API Keys** / **Show Registry API Keys** for the embedded \`registry-server\` (\`REGISTRY_API_KEY\` + \`SUBSD_API_KEY\`; required and must differ) +- Remove **Configure Subspaces** and \`SUBS_PUBLISH_REQUIRE_FINALIZED\` Requires StartOS 0.4.0-beta.10 (\`start-sdk\` 2.0.9).`, },