20 KiB
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 |
| Indexer-Go image | docker.io/golang:1.23-alpine |
| Explorer-UI image | docker.io/node:20-alpine |
| 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. The Indexer-Go image is the upstream
golang:1.23-alpine, used both to compile the indexer's sync binary +
goose migrator (one-time, at install) and to run the compiled binaries on
each start.
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 |
Spaces-protocol explorer-indexer source (spacesprotocol/explorer-indexer), fetched from GitHub on first start. Contains cmd/sync/, pkg/, sql/schema/, go.mod, etc. |
/data/explorer-indexer/bin/sync |
main |
Compiled indexer sync binary (go build ./cmd/sync). |
/data/explorer-indexer/bin/goose |
main |
Compiled goose migrator (go install github.com/pressly/goose/v3/cmd/goose). Used by indexer-migrate to apply sql/schema/*.sql. |
/data/explorer-indexer/.installed-sha |
main |
Marker file recording the pinned commit + build-ID currently extracted. If the build-ID in the package code differs from the marker, the next start wipes and re-fetches. |
/data/explorer-ui |
main |
SvelteKit explorer source (randomlogin/explorer), fetched on first explorer-enable. Contains src/, static/, server.js, package.json, etc. |
/data/explorer-ui/build |
main |
SvelteKit node-adapter build output. node build (i.e. node /data/explorer-ui/build) runs the production server. |
/data/explorer-ui/node_modules |
main |
npm dependencies (~300-500 MB). Re-fetched on Reset Explorer UI State. |
/data/explorer-ui/.installed-sha |
main |
Marker file for the pinned explorer commit + build-ID. |
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. - Launches
spacedas a managed daemon (noscreen, no shell auto-start) and thegottyweb terminal once the bashrc oneshot completes.
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 with the full
daemon graph:
- Postgres-chown oneshot creates
/data/postgreswith correct ownership. - PostgreSQL daemon starts on loopback
127.0.0.1:5432. indexer-fetchdownloads the pinnedspacesprotocol/explorer-indexertarball into/data/explorer-indexerand applies two compatibility patches for our older spaced binary (see Limitations).indexer-buildrunsCGO_ENABLED=0 go build -o /data/explorer-indexer/bin/sync ./cmd/syncandGOBIN=/data/explorer-indexer/bin go install github.com/pressly/goose/v3/cmd/goose@v3.24.3.indexer-cleanup-legacychecks for and removes leftover TS-indexer schema if present (from earlier package versions).indexer-migraterunsgoose ... upto applysql/schema/*.sql.indexerdaemon (/data/explorer-indexer/bin/sync) starts polling bitcoind + spaced and writes blocks, transactions, spaces, rollouts, etc. to PostgreSQL.explorer-fetchdownloads the pinnedrandomlogin/explorertarball into/data/explorer-ui(skipped if marker already matches).explorer-installrunsnpm install+PUBLIC_BTC_NETWORK=mainnet npm run buildto produce/data/explorer-ui/build/. Skipped ifbuild/index.jsalready exists. First-time install can take 1-3 minutes for the npm download.explorer-uidaemon (node /data/explorer-ui/build) starts on port 3000. The SvelteKit app reads exclusively from PostgreSQL — it does not talk to spaced or bitcoind directly. The UI link will appear in the StartOS dashboard alongside the gotty terminal.
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. No built-in auth — relies on StartOS interface exposure controls. Only useful while the embedded explorer is enabled (otherwise nothing is listening). |
| Spaced RPC | 7225 | HTTP JSON-RPC | Loopback only | Internal use, static-cred-authenticated via SPACED_RPC_USER / SPACED_RPC_PASSWORD from store.json.spacedAuth |
| 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. |
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 127.0.0.1:7225 |
indexer (daemon ready) |
Indexer Process | 60 s | Daemon liveness only — health is reported by indexer-sync below |
explorer-ui (daemon ready) |
Explorer Web UI | 60 s | TCP listen on 0.0.0.0:3000. Only present when embedded explorer is enabled. |
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-sync (standalone) |
Indexer Sync | 60 s | psql the blocks table for MAX(height) WHERE orphan = FALSE AND height >= 0; cross-check against spaced's current tip via space-cli getserverinfo. success when within 5 blocks of the tip; otherwise loading with explicit lag. |
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). - Only port 8080 (gotty) is exposed externally. Spaced RPC (7225) and PostgreSQL (5432) are loopback-only, and any other ports present in the image (e.g. 22253, 3000, 5173, 8081) are not bound.
- 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. Fresh installs run in spaces-only
mode (spaced + gotty). Run Enable Embedded Explorer to start
PostgreSQL + the Go indexer; Disable Embedded Explorer stops them. The
postgresandindexer-goimages are bundled in the .s9pk regardless so toggling is instant, no re-download. Indexed 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 embedded indexer (and any future explorer UI). Connect to it from inside the gotty terminal withpsql $DB_URL(only meaningful while the explorer is enabled). - Enabling the explorer requires internet on its first start. The pinned
commit of
spacesprotocol/explorer-indexeris downloaded from GitHub into/data/explorer-indexer, andgo build+go install github.com/pressly/goose/v3/cmd/goosefetch Go modules. After that the indexer runs offline; the only network it needs is bitcoind (already a StartOS dependency) and the local spaced RPC. - Explorer first-enable latency is ~2-5 minutes. The Go indexer needs to
download modules and compile from scratch the first time you turn it on.
Subsequent restarts (and toggle off→on) skip
indexer-fetch+indexer-buildbecause the binaries already exist on the volume. Use the Reset Indexer State action to force a rebuild. - Subspaces pointers data is not indexed. Our spaced binary predates the
pointers feature. The indexer source is patched during
indexer-fetchto treatptrs_rootas optional and to skip thegetptrblockmetaRPC call. All other spaces protocol data — blocks, transactions, spaces, rollouts, root anchors with empty pointers root — indexes normally. A future rebuild ofhorologger/spaceswith a newer spaced will let us drop these patches.
What Is Unchanged from Upstream
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 walletcreate default
spaces walletbalance default
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
indexer-go: docker.io/golang:1.23-alpine
explorer-ui: docker.io/node:20-alpine
architectures: [x86_64, aarch64]
volumes:
main: /data
ports:
ui: 8080
explorer_ui: 3000 # external, only useful while explorer enabled
spaced_rpc: 7225 # loopback only
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
bundled_when_disabled: true # images stay in .s9pk; data on /data preserved
indexer:
source_repo: spacesprotocol/explorer-indexer
pinned_commit: 00ae1e548734d93f1a8bb9f48d2290f459e12b35
install_dir: /data/explorer-indexer
bin_dir: /data/explorer-indexer/bin
schema_dir: /data/explorer-indexer/sql/schema
language: Go
build_image: golang:1.23-alpine
migrations_tool: goose
goose_version: v3.24.3
explorer_ui:
source_repo: randomlogin/explorer
pinned_commit: c827da1754c3cba5c5507d2c29f21b8fa231344d
install_dir: /data/explorer-ui
build_dir: /data/explorer-ui/build
language: TypeScript (SvelteKit)
build_image: node:20-alpine
build_command: PUBLIC_BTC_NETWORK=mainnet npm run build
daemon_command: node /data/explorer-ui/build
port: 3000
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