Spaces on StartOS
Upstream docs: https://docs.spacesprotocol.org/
Everything not listed in this document should behave the same as upstream
spaced/space-cli. If a feature, setting, or behavior is not mentioned here, the upstream documentation is accurate and fully applicable.
Spaces is a permissionless protocol for sovereign Bitcoin-anchored identities.
This package runs the spaced daemon
against Bitcoin mainnet and exposes space-cli
through a browser-based terminal.
Table of Contents
- Image and Container Runtime
- Volume and Data Layout
- Installation and First-Run Flow
- Configuration Management
- Network Access and Interfaces
- Actions
- Backups and Restore
- Health Checks
- Limitations and Differences
- What Is Unchanged from Upstream
- Quick Reference for AI Consumers
Image and Container Runtime
| Field | Value |
|---|---|
| Spaces image | docker.io/horologger/spaces |
| PostgreSQL image | docker.io/postgres:16.3 |
| Subspaces image | docker.io/horologger/subs:v0.1.0 |
| Certrelay image | docker.io/horologger/certrelay:v0.2.3 |
| Indexer image | TODO — prebuilt explorer-indexer image, not yet produced |
| Explorer image | TODO — prebuilt explorer (SvelteKit) image |
| Architectures | linux/amd64, linux/arm64 |
| Entrypoint | StartOS-managed (image entrypoints are not used directly) |
The Spaces image bundles spaced, space-cli, bitcoin-cli, gotty, node,
npm, screen, and a small shell environment. StartOS ignores the Spaces
image's docker_entrypoint.sh; daemons are defined in startos/main.ts. The
PostgreSQL image is the upstream postgres:16.3, launched via its official
docker-entrypoint.sh.
Note (build-from-source removed): earlier builds compiled the Go indexer and the SvelteKit explorer from source inside
golang:1.23-alpineandnode:20-alpinebuilder images. Those images and their build chains were removed to shrink the.s9pkand eliminate the multi-minute first-enable compile. They will be replaced with prebuilt images (see the TODO rows above). Until then, enabling the embedded explorer starts PostgreSQL only — the indexer and explorer-UI daemons are stubbed out instartos/main.ts.
Volume and Data Layout
| Path | Volume | Purpose |
|---|---|---|
/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 (web-UI password, bitcoind RPC user/password, PostgreSQL user/password/database) |
/data/postgres |
main |
PostgreSQL data directory (PGDATA). Owned by uid 999 (postgres). The whole main volume is backed up, so the database is included. |
/data/explorer-indexer |
main |
TODO — reserved for the prebuilt indexer's runtime data. The build-from-source layout (source, bin/sync, bin/goose, marker) is gone; reset-indexer-state still wipes this path. |
/data/explorer-ui |
main |
TODO — reserved for the prebuilt explorer's runtime data. The build-from-source layout (source, build/, node_modules, marker) is gone; reset-explorer-state still wipes this path. |
/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. |
/data/certrelay/data |
main |
Certrelay data directory (CERTRELAY_DATA_DIR) — anchors, peer state, sqlite. |
/data/bin/fabric |
main |
The fabric CLI, copied from the certrelay image on each start so the gotty terminal (which has /data/bin on PATH) can resolve handles. |
Installation and First-Run Flow
On the first install, StartOS:
- Seeds
store.json.passwordwith a random 32-char alphanumeric admin password and creates a critical task that points the user at Show Space-CLI Web UI Credentials so the password can be copied before login. - Seeds
store.json.btcAuthwith a randomspaces:<random>RPC credential pair and creates a critical cross-service task on Bitcoin that runs bitcoind'sgenerate-rpc-dependentaction to register the credentials inbitcoin.conf. - Seeds
store.json.dbAuthwith a random PostgreSQL password (usernamepostgres, databasespacesprotocol_explorer). No task is created — the credentials are only used internally by the embedded daemon. - Runs the
postgres-chownoneshot to create/data/postgreswith the right ownership, then launches the PostgreSQL daemon on loopback 5432. - Seeds
store.json.spacedAuthwith a randomspaces:<random>credential pair. Spaced is configured to require these viaSPACED_RPC_USER/SPACED_RPC_PASSWORD(cookie auth is not used in this package), so the gotty terminal'sspacesalias and the indexer share a single auth path. - Seeds
store.json.enableExplorer = false. The embedded explorer (PostgreSQL + Go indexer) is opt-in. - Seeds
store.jsoncertrelay defaults:certrelaySelfUrl = https://certrelay.spacesops.com,certrelayBootstrap = false,certrelayHealthcheckHandle = account-digital-useful.genesis@key. - Launches
spacedas a managed daemon (noscreen, no shell auto-start) and thegottyweb terminal once the bashrc oneshot completes. - Runs
certrelay-setup(creates/data/certrelay/data, copies thefabricCLI to/data/bin/fabric) and launches the certrelay daemon on 7778. Certrelay is always on; itsCERTRELAY_SPACED_RPC_URLuses the local spaced credentials fromstore.spacedAuth. Configure its self URL etc. via the Configure Certrelay action.
The user is not prompted to choose a chain or RPC mode — this package is mainnet-only.
Enabling the embedded explorer
Run the Enable Embedded Explorer action. That flips
store.json.enableExplorer = true and the service auto-restarts:
- Postgres-chown oneshot creates
/data/postgreswith correct ownership. - PostgreSQL daemon starts on loopback
127.0.0.1:5432.
TODO (prebuilt images pending): the indexer and explorer-UI daemons are currently stubbed out in
startos/main.ts. The build-from-source chain (indexer-fetch→indexer-build→indexer-migrate→indexer, andexplorer-fetch→explorer-install→explorer-ui) was removed to shrink the.s9pkand cut startup time. Until prebuilt images are wired in, enabling the explorer starts PostgreSQL only; nothing populates the DB and the Explorer Web UI (port 3000) will not resolve. Theshow-db-credentials,reset-db-state,reset-indexer-state, andreset-explorer-stateactions remain available.
Run Disable Embedded Explorer to turn it back off; on-disk data at
/data/postgres, /data/explorer-indexer, and /data/explorer-ui is
preserved.
Configuration Management
| StartOS-Managed | Upstream-Managed |
|---|---|
Web-UI username / password (admin + generated password) |
spaced runtime tuning via SPACED_* env vars in the image |
| Bitcoin RPC username / password (registered on bitcoind) | Wallet creation, bidding, and registration -- all driven via space-cli inside the terminal |
| PostgreSQL username / password / database (loopback only) | space-cli flags and subcommands |
Chain selection (locked to mainnet) |
|
| Spaced data directory and RPC bind |
Network Access and Interfaces
| Interface | Port | Protocol | Exposure | Notes |
|---|---|---|---|---|
| Space-CLI Web UI (gotty terminal) | 8080 | HTTP | LAN / .local / Tor / clearnet (via StartOS) |
Basic auth: admin:<store.password> |
| 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 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. |
| Spaces API (spaced RPC) | 7225 | HTTP JSON-RPC | LAN / .local / Tor / clearnet (via StartOS) |
spaced JSON-RPC, now bound to 0.0.0.0 and exposed as the Spaces API interface. Static-cred-authenticated via SPACED_RPC_USER / SPACED_RPC_PASSWORD from store.json.spacedAuth. Internal clients still connect over loopback. |
| PostgreSQL | 5432 | Postgres wire protocol | Loopback only (listen_addresses=127.0.0.1) |
Username/password from store.json.dbAuth; full DB_URL exported into the web terminal |
Actions
| ID | Name | Visibility | Availability | Purpose |
|---|---|---|---|---|
reset-password |
Reset Space-CLI Web UI Password | Enabled | Any | Regenerates the Space-CLI Web UI password and restarts the terminal daemon |
show-credentials |
Show Space-CLI Web UI Credentials | Hidden | Any | Surfaces the current admin username + masked password (launched by the first-install task) |
show-password |
Show Space-CLI Web UI Password | Enabled | Any | Same as show-credentials but visible in the actions list, for routine re-display of the current admin credentials |
set-bitcoin-rpc |
Set up Bitcoin RPC | Enabled | Any | Re-invokes bitcoind's generate-rpc-dependent with the stored credentials. Safe to call repeatedly. |
sync-status |
Sync Status | Enabled | Only running | Runs space-cli getserverinfo inside the daemon container and returns the JSON output |
reset-spaced-state |
Reset Spaced State | Enabled | Any | Deletes /data/mainnet/ so spaced resyncs its index from spaces' anchor. Preserves store.json (passwords + RPC credentials). Use when spaced crash-loops on a stale or corrupt index. |
export-wallet |
Export Wallet | Enabled | Only running | Runs space-cli exportwallet /data/mainnet/wallets_backup/default.json and surfaces the resulting JSON as a masked/copyable result. The file is also persisted inside the volume at that path. |
import-wallet |
Import Wallet | Enabled | Only running | Accepts a pasted JSON payload (textarea), writes it to /data/mainnet/wallets_backup/default.json (rotating the existing file to .bakNNN), rotates /data/mainnet/wallets/default to .bakNNN, then runs space-cli importwallet + loadwallet. |
enable-explorer |
Enable Embedded Explorer | Enabled (hidden when already on) | Any | Sets store.enableExplorer = true and triggers a service restart so the full daemon graph (postgres + indexer + indexer-sync HC) takes effect. |
disable-explorer |
Disable Embedded Explorer | Enabled (hidden when already off) | Any | Sets store.enableExplorer = false and triggers a service restart so the indexer and postgres daemons stop. On-disk data at /data/postgres and /data/explorer-indexer is preserved. |
show-db-credentials |
Show Database Credentials | Enabled | Any | Surfaces the PostgreSQL username, password, database, and full DB_URL (all copyable; secrets masked). Credentials exist regardless of whether the explorer is currently enabled. |
reset-db-state |
Reset Database State | Enabled | Any | Deletes /data/postgres so the next start re-initializes an empty database. Warning-gated. store.json is preserved. |
reset-indexer-state |
Reset Indexer State | Enabled | Any | Deletes /data/explorer-indexer so the next start re-fetches the indexer source and rebuilds the sync + goose binaries. Useful when bumping the pinned commit. PostgreSQL data is preserved. Warning-gated. |
reset-explorer-state |
Reset Explorer UI State | Enabled | Any | Deletes /data/explorer-ui so the next start re-fetches the SvelteKit source and rebuilds the bundle. PostgreSQL data is preserved. Warning-gated. |
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
sdk.Backups.ofVolumes('main') -- the entire /data volume is backed up,
including spaced state, wallets, the block index, and store.json. On restore,
the same idempotent init logic runs and reuses the existing credentials in
store.json.
Health Checks
| ID | Display | Grace period | Behaviour |
|---|---|---|---|
postgres (daemon ready) |
Database | 60 s | TCP listen on 127.0.0.1:5432 |
spaced (daemon ready) |
Spaced RPC | 120 s | TCP listen on 0.0.0.0:7225 (exposed as the Spaces API interface) |
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. |
web-terminal (daemon ready) |
Web Interface | default | TCP listen on 0.0.0.0:8080 |
sync (standalone) |
Spaced Sync | 30 s | Exec space-cli --output-format json getserverinfo; reports success when ready=true && progress=100%, otherwise loading with progress percentage |
indexer / explorer-ui / indexer-sync |
— | — | TODO — removed with the build-from-source chain. Will return when the prebuilt indexer + explorer images are wired in. |
Limitations and Differences
- Mainnet only. Testnet, testnet4, signet, and regtest are not exposed.
- The Spaces image's
docker_entrypoint.shis not used. Its auto-start ofspacedinsidescreenwould conflict with the StartOS-managed daemon. - Space-CLI Web UI is a terminal, not a graphical app. All Spaces operations happen
via
space-cli(aliased asspacesinside the shell). - Externally exposed ports (each a StartOS interface): 8080 (gotty Space-CLI Web UI, always on), 7225 (Spaces API / spaced RPC, always on), 7778 (Certrelay, always on), and — only while their feature is enabled — 3000 (Explorer Web UI, currently stubbed), 7777 (Subspaces Web UI), 8888 (Subspaces Prover), 8081 (Subspaces Registry). Loopback-only: PostgreSQL (5432). The spaced RPC is authenticated (static creds) even though it's now externally exposed. The Subspaces registry is remapped from its upstream default 8080 to 8081 to avoid colliding with the gotty terminal.
- Bitcoin Core 31.x is the only supported dependency. Earlier majors are not allowed by the manifest version range.
- The web terminal is independent of spaced. Gotty stays reachable even
when
spacedis crash-looping, so you can always shell in to diagnose. - The embedded explorer is opt-in — and currently PostgreSQL-only. Fresh
installs run in spaces-only mode (spaced + gotty). Run Enable Embedded
Explorer to start PostgreSQL; Disable Embedded Explorer stops it. The
indexer and explorer-UI daemons are stubbed out pending prebuilt images
(the build-from-source chain was removed to shrink the
.s9pkand cut startup time). So enabling the explorer today brings up an empty database and nothing else. PostgreSQL data is preserved across toggles. - PostgreSQL is embedded, not a separate service. There is no
cross-service dependency on a postgres .s9pk; the database is local to this
package, lives at
/data/postgres, and is intended for the (pending) prebuilt indexer + explorer UI. Connect to it from inside the gotty terminal withpsql $DB_URL. - Subspaces pointers data note. When the prebuilt indexer is wired back
in, be aware our spaced binary predates the spaces pointers feature; the
prior build-from-source indexer was patched to treat
ptrs_rootas optional and to skip thegetptrblockmetaRPC. A future rebuild ofhorologger/spaceswith a newer spaced will let us drop those patches. - 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.0image (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), 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 existingdefaultspaces wallet at startup and does not create it — create it first from the Space-CLI Web UI withspaces createwalletif you haven't. Subspaces is independent of the Embedded Explorer toggle — you can run any combination of {explorer, subspaces}. - Certrelay is always on. It ships as the prebuilt
horologger/certrelay:v0.2.3image (static musl binaries) and runs on 7778 from service start. It talks to the local spaced over loopback using thestore.spacedAuthcredentials. The bundledfabricCLI is copied to/data/bin/fabricso it's runnable from the Space-CLI Web UI. You must setCERTRELAY_SELF_URL(via Configure Certrelay) to the publicly visible URL StartOS exposes for the Certrelay interface, or peers/clients won't be able to reach this relay at the advertised address.
space-clisubcommands and flags work exactly as documented upstream.spacedhonours allSPACED_*environment variables not otherwise set by StartOS.- Wallet files, the spaces database, and the block index are managed by
spaceditself; StartOS only provides the volume. - bitcoind connectivity follows the standard StartOS dependency-service model
(
bitcoind.startos:8332).
Using the Web Terminal
After install:
- Run the Show Space-CLI Web UI Credentials action (the install task surfaces it).
- Open the Space-CLI Web UI from the StartOS dashboard.
- Log in with
adminand the displayed password. - Use
spaces <subcommand>-- it expands tospace-cli --chain mainnet --rpc-user "$SPACED_RPC_USER" --rpc-password "$SPACED_RPC_PASSWORD" <subcommand>.
Examples:
spaces getserverinfo
spaces createwallet # creates the `default` wallet (--wallet defaults to "default")
spaces balance
Quick Reference for AI Consumers
package_id: spaces
upstream_version: subspacesplus
images:
spaces: docker.io/horologger/spaces:v0.0.9s
postgres: docker.io/postgres:16.3
subspaces: docker.io/horologger/subs:v0.1.0
certrelay: docker.io/horologger/certrelay:v0.2.3
# TODO: prebuilt indexer image (was golang:1.23-alpine builder, removed)
# TODO: prebuilt explorer image (was node:20-alpine builder, removed)
architectures: [x86_64, aarch64]
volumes:
main: /data
ports:
ui: 8080
explorer_ui: 3000 # interface registered; daemon stubbed (TODO prebuilt image)
subspaces_ui: 7777 # external, only useful while subspaces enabled
subspaces_prover: 8888 # external (its own interface), subspaces only
subspaces_registry: 8081 # external (its own interface), subspaces only; remapped from upstream 8080
certrelay: 7778 # external (its own interface), always on
spaced_rpc: 7225 # external (Spaces API interface); spaced binds 0.0.0.0, auth-gated
postgres: 5432 # loopback only
dependencies:
- bitcoind
spaced_env_vars:
- SPACED_CHAIN
- SPACED_DATA_DIR
- SPACED_RPC_BIND
- SPACED_RPC_PORT
- SPACED_RPC_URL
- SPACED_RPC_USER
- SPACED_RPC_PASSWORD
- SPACED_BLOCK_INDEX
- SPACED_BITCOIN_RPC_URL
- SPACED_BITCOIN_RPC_USER
- SPACED_BITCOIN_RPC_PASSWORD
- BTC_RPC_HOST
- BTC_RPC_PORT
- BTC_RPC_USER
- BTC_RPC_PASSWORD
- APP_USER
- APP_PASSWORD
- DB_URL
postgres_env_vars:
- POSTGRES_USER
- POSTGRES_PASSWORD
- POSTGRES_DB
- PGDATA
explorer:
default_enabled: false # opt-in via Enable Embedded Explorer action
store_field: enableExplorer
status: postgres_only # indexer + explorer-ui daemons stubbed; see TODOs
reset_actions: [reset-db-state, reset-indexer-state, reset-explorer-state]
indexer:
status: TODO # build-from-source (Go) removed; prebuilt image pending
prior_source_repo: spacesprotocol/explorer-indexer
data_dir: /data/explorer-indexer # reset-indexer-state still wipes this
explorer_ui:
status: TODO # build-from-source (SvelteKit) removed; prebuilt image pending
prior_source_repo: randomlogin/explorer
data_dir: /data/explorer-ui # reset-explorer-state still wipes this
port: 3000 # interface registered; daemon stubbed
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)
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
certrelay:
always_on: true
image: horologger/certrelay:v0.2.3 # prebuilt; static musl binaries
server_bin: /usr/local/bin/certrelay
fabric_bin: /usr/local/bin/fabric # copied to /data/bin/fabric for the CLI
data_dir: /data/certrelay/data
port: 7778
spaced_rpc_creds: from store.spacedAuth (local spaced)
configurable_via_action: configure-certrelay
env:
CERTRELAY_CHAIN: mainnet
CERTRELAY_BIND: 0.0.0.0
CERTRELAY_PORT: '7778'
CERTRELAY_REMOTE_IP_HEADER: x-forwarded-for
CERTRELAY_ANCHOR_REFRESH: '300'
CERTRELAY_SELF_URL: https://certrelay.spacesops.com # default; set to your public URL
CERTRELAY_BOOTSTRAP: 'false' # toggle via action
CERTRELAY_HEALTHCHECK_HANDLE: account-digital-useful.genesis@key # via action
managed_env_vars:
- POSTGRES_URI
- BITCOIN_NODE_URI
- BITCOIN_NODE_USER
- BITCOIN_NODE_PASSWORD
- SPACES_NODE_URI
- RPC_USER
- RPC_PASSWORD
- ACTIVATION_BLOCK_HEIGHT
- FAST_SYNC_BLOCK_HEIGHT
- UPDATE_DB_INTERVAL
- MEMPOOL_CHUNK_SIZE
defaults:
ACTIVATION_BLOCK_HEIGHT: '871222' # spaces mainnet activation
FAST_SYNC_BLOCK_HEIGHT: '864000'
UPDATE_DB_INTERVAL: '5'
MEMPOOL_CHUNK_SIZE: '200'
actions:
- reset-password
- show-credentials
- show-password
- set-bitcoin-rpc
- sync-status
- reset-spaced-state
- export-wallet
- import-wallet
- enable-explorer
- disable-explorer
- show-db-credentials
- reset-db-state
- reset-indexer-state
- reset-explorer-state
- enable-subspaces
- disable-subspaces
- set-subs-prover
- reset-subspaces-state
- configure-certrelay