Compare commits

...
2 Commits
Author SHA1 Message Date
spacesopsandCursor 2b0224a678 0.3.0:0
Build Service / BuildPackage (push) Canceled after 0s
Update to upstream Spaces 0.3.0 and refresh bundled images (subs v0.1.2c,
certrelay v0.2.8, nacho v1.0.0b). Add Create/Show Registry API Keys for
registry-server, remove Configure Subspaces and SUBS_PUBLISH_REQUIRE_FINALIZED,
and align README/instructions with the new tags.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 19:35:15 -04:00
spacesopsandCursor 89f4475e99 0.2.1:5
Bump Subspaces to subs:v0.1.2b and Certrelay to certrelay:v0.2.7b;
align README and instructions with the new tags.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 11:43:26 -04:00
14 changed files with 305 additions and 116 deletions
+38 -27
View File
@@ -37,10 +37,10 @@ through a browser-based terminal.
| Field | Value | | Field | Value |
| --- | --- | | --- | --- |
| Spaces image | `docker.io/horologger/spaces:v0.2.1a` | | Spaces image | `docker.io/horologger/spaces:v0.3.0` |
| Subspaces image | `docker.io/horologger/subs:v0.1.2` | | Subspaces image | `docker.io/horologger/subs:v0.1.2c` |
| Certrelay image | `docker.io/horologger/certrelay:v0.2.5` | | Certrelay image | `docker.io/horologger/certrelay:v0.2.8` |
| Nacho image | `docker.io/horologger/nacho:v1.0.0` | | Nacho image | `docker.io/horologger/nacho:v1.0.0b` |
| Architectures | `linux/amd64`, `linux/arm64` | | Architectures | `linux/amd64`, `linux/arm64` |
| Entrypoint | StartOS-managed (image entrypoints are **not** used directly) | | 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` | `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/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/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/prover` | `main` | Working directory for the `subs-prover` daemon. |
| `/data/subspaces/registry` | `main` | Working directory for the `registry-server` 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 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 in the store); the `nacho-setup` oneshot creates that file with the
default (`fold,swifty`) on first daemon start if it doesn't exist. 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) 6. Launches `spaced` as a managed daemon (no `screen`, no shell auto-start)
and the `gotty` web terminal once the bashrc oneshot completes. and the `gotty` web terminal once the bashrc oneshot completes.
7. Runs `certrelay-setup` (creates `/data/certrelay/data`, copies `fabric` and 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 | | 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 | | 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) | | | 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) | | | Subspaces Prover auth toggle + credentials (`SUBS_PROVER_BASIC_AUTH_USER`/`SUBS_PROVER_BASIC_AUTH_PASSWORD`, off by default) | |
| Chain selection (locked to `mainnet`) | | | Chain selection (locked to `mainnet`) | |
| Spaced data directory and RPC bind | | | 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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `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-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. | | `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. | | `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 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. 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 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.2` 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 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 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** StartOS interface: **subs** (Web UI + Subs API, 7777), **registry-server**
(8081), and — only if the separate **Enable / Disable Subspaces Prover** (8081), and — only if the separate **Enable / Disable Subspaces Prover**
toggle is on (default **off**) — **subs-prover** (8888, no GPU). The toggle is on (default **off**) — **subs-prover** (8888, no GPU). The
prover's interface is always registered even when its daemon is off. subs prover's interface is always registered even when its daemon is off. Before
loads the existing `default` spaces wallet at startup and does **not** (or when prompted after) enabling, run **Create Registry API Keys** —
create it — create it first from the Space-CLI Web UI with `spaces `registry-server` requires distinct `REGISTRY_API_KEY` and `SUBSD_API_KEY`
createwallet` if you haven't. 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 8. **Certrelay is always on.** It ships as the prebuilt
`horologger/certrelay:v0.2.5` 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 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 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 `/data/bin/` for manual use from the Space-CLI Web UI; neither is auto-started
@@ -250,10 +257,10 @@ spaces balance
package_id: spaces package_id: spaces
upstream_version: subspacesplus upstream_version: subspacesplus
images: images:
spaces: docker.io/horologger/spaces:v0.2.1a spaces: docker.io/horologger/spaces:v0.3.0
subspaces: docker.io/horologger/subs:v0.1.2 subspaces: docker.io/horologger/subs:v0.1.2c
certrelay: docker.io/horologger/certrelay:v0.2.5 certrelay: docker.io/horologger/certrelay:v0.2.8
nacho: docker.io/horologger/nacho:v1.0.0 nacho: docker.io/horologger/nacho:v1.0.0b
architectures: [x86_64, aarch64] architectures: [x86_64, aarch64]
volumes: volumes:
main: /data main: /data
@@ -286,18 +293,26 @@ spaced_env_vars:
- APP_USER - APP_USER
- APP_PASSWORD - APP_PASSWORD
spaces: 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: subspaces:
default_enabled: false # opt-in via Enable Subspaces action default_enabled: false # opt-in via Enable Subspaces action
store_field: enableSubspaces store_field: enableSubspaces
subs_prover_default_enabled: false # separate toggle: set-subs-prover (store.enableSubsProver) subs_prover_default_enabled: false # separate toggle: set-subs-prover (store.enableSubsProver)
image: horologger/subs:v0.1.2 # prebuilt; no compile step image: horologger/subs:v0.1.2c # prebuilt; no compile step
data_dir: /data/subspaces/data data_dir: /data/subspaces/data
wallet: default # loaded by subs, NOT created (create via space-cli first) wallet: default # loaded by subs, NOT created (create via space-cli first)
daemons: # three separate daemons, each its own interface daemons: # three separate daemons, each its own interface
subs: { bin: /usr/local/bin/subs, port: 7777 } subs: { bin: /usr/local/bin/subs, port: 7777 }
subs-prover: { bin: /usr/local/bin/subs-prover, port: 8888, gpu: none } 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-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) auth: # optional HTTP basic auth in front of subs (Web UI + Subs API on 7777)
default_enabled: false default_enabled: false
store_enabled_field: subsAuthEnabled store_enabled_field: subsAuthEnabled
@@ -306,11 +321,6 @@ subspaces:
env_vars: # injected only when subsAuthEnabled=true AND subsAuth!=null env_vars: # injected only when subsAuthEnabled=true AND subsAuth!=null
- SUBS_BASIC_AUTH_USER - SUBS_BASIC_AUTH_USER
- SUBS_BASIC_AUTH_PASSWORD - 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: actions:
- enable-subs-auth - enable-subs-auth
- disable-subs-auth - disable-subs-auth
@@ -331,7 +341,7 @@ subspaces:
- set-subs-prover-credentials - set-subs-prover-credentials
certrelay: certrelay:
always_on: true always_on: true
image: horologger/certrelay:v0.2.5 # prebuilt; static musl binaries image: horologger/certrelay:v0.2.8 # prebuilt; static musl binaries
server_bin: /usr/local/bin/certrelay server_bin: /usr/local/bin/certrelay
fabric_bin: /usr/local/bin/fabric # copied to /data/bin/fabric for the CLI 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) 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 CERTRELAY_HEALTHCHECK_HANDLE: account-digital-useful.genesis@key # via action
nacho: nacho:
always_on: true always_on: true
image: horologger/nacho:v1.0.0 # Expo dev server image: horologger/nacho:v1.0.0b # Expo dev server
port: 8082 port: 8082
data_dir: /data/nacho data_dir: /data/nacho
ignore_names_file: /data/nacho/ignore_names.txt # read at runtime; default 'fold,swifty' seeded by nacho-setup 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 - enable-subspaces
- disable-subspaces - disable-subspaces
- set-subs-prover - set-subs-prover
- configure-subspaces
- enable-subs-auth - enable-subs-auth
- disable-subs-auth - disable-subs-auth
- show-subs-credentials - show-subs-credentials
@@ -383,6 +392,8 @@ actions:
- disable-subs-prover-auth - disable-subs-prover-auth
- show-subs-prover-credentials - show-subs-prover-credentials
- set-subs-prover-credentials - set-subs-prover-credentials
- create-registry-api-keys
- show-registry-api-keys
- reset-subspaces-state - reset-subspaces-state
- configure-certrelay - configure-certrelay
- configure-nacho - configure-nacho
+21 -3
View File
@@ -57,14 +57,32 @@ spaces walletbalance default
`/data/mainnet/wallets/default` to `.bakNNN` suffixes so no prior state is `/data/mainnet/wallets/default` to `.bakNNN` suffixes so no prior state is
lost, then calls `space-cli importwallet` and `space-cli loadwallet`. lost, then calls `space-cli importwallet` and `space-cli loadwallet`.
Requires the service to be running. 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 ## Limitations
- **Mainnet only.** No testnet, signet, or regtest. - **Mainnet only.** No testnet, signet, or regtest.
- **Container images** (see also the package manifest): the `spaced` runtime uses - **Container images** (see also the package manifest): the `spaced` runtime uses
`horologger/spaces:v0.2.1a`; Subspaces, Certrelay, and Nacho use `horologger/spaces:v0.3.0`; Subspaces, Certrelay, and Nacho use
`horologger/subs:v0.1.2`, `horologger/certrelay:v0.2.5`, and `horologger/subs:v0.1.2c`, `horologger/certrelay:v0.2.8`, and
`horologger/nacho:v1.0.0`. `horologger/nacho:v1.0.0b`.
- The image's `docker_entrypoint.sh` is not used. `spaced` is managed by - The image's `docker_entrypoint.sh` is not used. `spaced` is managed by
StartOS, not started inside a `screen` session by login. StartOS, not started inside a `screen` session by login.
- The only externally bound port is `8080` (gotty). Spaced RPC stays on - The only externally bound port is `8080` (gotty). Spaced RPC stays on
-65
View File
@@ -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,
}
},
)
+62
View File
@@ -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,
},
],
},
}
},
)
+4 -2
View File
@@ -1,7 +1,7 @@
import { sdk } from '../sdk' import { sdk } from '../sdk'
import { configureCertrelay } from './configureCertrelay' import { configureCertrelay } from './configureCertrelay'
import { configureNacho } from './configureNacho' import { configureNacho } from './configureNacho'
import { configureSubspaces } from './configureSubspaces' import { createRegistryApiKeys } from './createRegistryApiKeys'
import { disableSubsAuth } from './disableSubsAuth' import { disableSubsAuth } from './disableSubsAuth'
import { disableSubsProverAuth } from './disableSubsProverAuth' import { disableSubsProverAuth } from './disableSubsProverAuth'
import { disableSubspaces } from './disableSubspaces' import { disableSubspaces } from './disableSubspaces'
@@ -19,6 +19,7 @@ import { setSubsProver } from './setSubsProver'
import { setSubsProverCredentials } from './setSubsProverCredentials' import { setSubsProverCredentials } from './setSubsProverCredentials'
import { showCredentials } from './showCredentials' import { showCredentials } from './showCredentials'
import { showPassword } from './showPassword' import { showPassword } from './showPassword'
import { showRegistryApiKeys } from './showRegistryApiKeys'
import { showSpacedCredentials } from './showSpacedCredentials' import { showSpacedCredentials } from './showSpacedCredentials'
import { showSubsCredentials } from './showSubsCredentials' import { showSubsCredentials } from './showSubsCredentials'
import { showSubsProverCredentials } from './showSubsProverCredentials' import { showSubsProverCredentials } from './showSubsProverCredentials'
@@ -49,5 +50,6 @@ export const actions = sdk.Actions.of()
.addAction(resetSubspacesState) .addAction(resetSubspacesState)
.addAction(configureCertrelay) .addAction(configureCertrelay)
.addAction(configureNacho) .addAction(configureNacho)
.addAction(configureSubspaces) .addAction(createRegistryApiKeys)
.addAction(showRegistryApiKeys)
.addAction(uploadSupportPdf) .addAction(uploadSupportPdf)
+74
View File
@@ -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,
},
],
},
}
},
)
+6 -1
View File
@@ -39,7 +39,12 @@ const shape = z.object({
certrelayBootstrap: z.boolean().nullable().catch(null), certrelayBootstrap: z.boolean().nullable().catch(null),
certrelayHealthcheckHandle: z.string().nullable().catch(null), certrelayHealthcheckHandle: z.string().nullable().catch(null),
nachoWorkshopPdfLinkText: 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( export const storeJson = FileHelper.json(
+19 -10
View File
@@ -289,16 +289,25 @@ const dict = {
205, 205,
'Subspaces Prover Auth credentials saved. Auth is currently DISABLED — enable it with "Enable Subspaces Prover Auth" to enforce these credentials.': 'Subspaces Prover Auth credentials saved. Auth is currently DISABLED — enable it with "Enable Subspaces Prover Auth" to enforce these credentials.':
206, 206,
'Configure Subspaces': 207, 'Create Registry API Keys': 213,
'Set user-tunable subs options. Saving restarts the service so the subs daemon picks up the new environment.': '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.':
208, 214,
'Require Finalized Publish': 209, '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.':
'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.': 215,
210, REGISTRY_API_KEY: 216,
'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is enabled; the service is restarting.': SUBSD_API_KEY: 217,
211, 'Bearer secret for intake/POST /register clients.': 218,
'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is disabled; the service is restarting.': 'Paste into subs Settings → Registry Server → Auth Token (subs↔registry channel).':
212, 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 } as const
/** /**
+2
View File
@@ -5,6 +5,7 @@ import { setInterfaces } from '../interfaces'
import { sdk } from '../sdk' import { sdk } from '../sdk'
import { versionGraph } from '../versions' import { versionGraph } from '../versions'
import { taskBtcAuth } from './taskBtcAuth' import { taskBtcAuth } from './taskBtcAuth'
import { taskRegistryApiKeys } from './taskRegistryApiKeys'
import { taskSeedCertrelay } from './taskSeedCertrelay' import { taskSeedCertrelay } from './taskSeedCertrelay'
import { taskSeedEnableSubspaces } from './taskSeedEnableSubspaces' import { taskSeedEnableSubspaces } from './taskSeedEnableSubspaces'
import { taskSeedNacho } from './taskSeedNacho' import { taskSeedNacho } from './taskSeedNacho'
@@ -20,6 +21,7 @@ export const init = sdk.setupInit(
taskBtcAuth, taskBtcAuth,
taskSeedSpacedAuth, taskSeedSpacedAuth,
taskSeedEnableSubspaces, taskSeedEnableSubspaces,
taskRegistryApiKeys,
taskSeedCertrelay, taskSeedCertrelay,
taskSeedNacho, taskSeedNacho,
taskSetPassword, taskSetPassword,
+28
View File
@@ -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',
),
})
})
+19 -2
View File
@@ -149,8 +149,6 @@ export const main = sdk.setupMain(async ({ effects }) => {
SUBS_SPACED_RPC_PASSWORD: spacedAuth.password, SUBS_SPACED_RPC_PASSWORD: spacedAuth.password,
SUBS_PROVER_ENDPOINT: `http://127.0.0.1:${SUBSPACES_PROVER_PORT}`, SUBS_PROVER_ENDPOINT: `http://127.0.0.1:${SUBSPACES_PROVER_PORT}`,
SUBS_REGISTRY_ENDPOINT: `http://127.0.0.1:${SUBSPACES_REGISTRY_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, HOME: SUBSPACES_DATA_DIR,
RUST_LOG: 'subs=info,subs_prover=info,registry_server=info', RUST_LOG: 'subs=info,subs_prover=info,registry_server=info',
...subsAuthEnv, ...subsAuthEnv,
@@ -181,6 +179,9 @@ export const main = sdk.setupMain(async ({ effects }) => {
REGISTRY_SERVER_PORT: String(SUBSPACES_REGISTRY_PORT), REGISTRY_SERVER_PORT: String(SUBSPACES_REGISTRY_PORT),
HOME: SUBSPACES_REGISTRY_DIR, HOME: SUBSPACES_REGISTRY_DIR,
RUST_LOG: 'registry_server=info', 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( 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 // 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. // depends on prior IDs and we can't easily express the union here.
const withSubspaces = (chain: any): any => { 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 let c = chain
.addOneshot('subspaces-dirs', { .addOneshot('subspaces-dirs', {
subcontainer: subspacesSub, subcontainer: subspacesSub,
+4 -4
View File
@@ -14,19 +14,19 @@ export const manifest = setupManifest({
volumes: ['main'], volumes: ['main'],
images: { images: {
spaces: { spaces: {
source: { dockerTag: 'horologger/spaces:v0.2.1a' }, source: { dockerTag: 'horologger/spaces:v0.3.0' },
arch: ['x86_64', 'aarch64'], arch: ['x86_64', 'aarch64'],
}, },
subspaces: { subspaces: {
source: { dockerTag: 'horologger/subs:v0.1.2' }, source: { dockerTag: 'horologger/subs:v0.1.2c' },
arch: ['x86_64', 'aarch64'], arch: ['x86_64', 'aarch64'],
}, },
certrelay: { certrelay: {
source: { dockerTag: 'horologger/certrelay:v0.2.5' }, source: { dockerTag: 'horologger/certrelay:v0.2.8' },
arch: ['x86_64', 'aarch64'], arch: ['x86_64', 'aarch64'],
}, },
nacho: { nacho: {
source: { dockerTag: 'horologger/nacho:v1.0.0' }, source: { dockerTag: 'horologger/nacho:v1.0.0b' },
arch: ['x86_64', 'aarch64'], arch: ['x86_64', 'aarch64'],
}, },
}, },
+21
View File
@@ -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 // 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 // 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 // gutters on each side of text, leaving 96 cells of usable text. All public
+7 -2
View File
@@ -1,9 +1,14 @@
import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
export const current = VersionInfo.of({ export const current = VersionInfo.of({
version: '0.2.1:4', version: '0.3.0:0',
releaseNotes: { releaseNotes: {
en_US: `Repack so the archive bundles \`horologger/spaces:v0.2.1a\` as declared in the manifest (other images unchanged: \`subs:v0.1.2\`, \`certrelay:v0.2.5\`, \`nacho:v1.0.0\`). 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).`, Requires StartOS 0.4.0-beta.10 (\`start-sdk\` 2.0.9).`,
}, },