Release v0.1.1:3 with Configure Subspaces, monitor CLI, and spaces v0.0.9x.
Build Service / BuildPackage (push) Has been cancelled

Adds a user toggle for SUBS_PUBLISH_REQUIRE_FINALIZED, copies the certrelay monitor binary to /data/bin for terminal use, and bumps the spaced image to horologger/spaces:v0.0.9x.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-16 18:53:22 -04:00
co-authored by Cursor
parent ad64a47d42
commit 1fa9674d49
11 changed files with 127 additions and 51 deletions
+65
View File
@@ -0,0 +1,65 @@
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,
}
},
)
+2
View File
@@ -1,6 +1,7 @@
import { sdk } from '../sdk'
import { configureCertrelay } from './configureCertrelay'
import { configureNacho } from './configureNacho'
import { configureSubspaces } from './configureSubspaces'
import { disableSubsAuth } from './disableSubsAuth'
import { disableSubsProverAuth } from './disableSubsProverAuth'
import { disableSubspaces } from './disableSubspaces'
@@ -48,4 +49,5 @@ export const actions = sdk.Actions.of()
.addAction(resetSubspacesState)
.addAction(configureCertrelay)
.addAction(configureNacho)
.addAction(configureSubspaces)
.addAction(uploadSupportPdf)
+1
View File
@@ -39,6 +39,7 @@ 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),
})
export const storeJson = FileHelper.json(
+10
View File
@@ -289,6 +289,16 @@ 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,
} as const
/**
+11 -3
View File
@@ -16,6 +16,8 @@ import {
CERTRELAY_DIR,
CERTRELAY_FABRIC_BIN,
CERTRELAY_FABRIC_DEST,
CERTRELAY_MONITOR_BIN,
CERTRELAY_MONITOR_DEST,
CERTRELAY_PORT,
CERTRELAY_REMOTE_IP_HEADER,
dataDir,
@@ -147,6 +149,8 @@ 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,
@@ -256,7 +260,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
'spaced is managed by StartOS — do not run it manually.',
'Use the `spaces` alias to call space-cli, e.g.',
' spaces getserverinfo',
'`fabric` resolves handles against the local certrelay.',
'`fabric` resolves handles; `monitor` is on PATH (copy-only, not auto-started).',
'Docs: https://docs.spacesprotocol.org/',
]),
'',
@@ -265,8 +269,9 @@ export const main = sdk.setupMain(async ({ effects }) => {
// Appends the always-on certrelay chain (setup oneshot + certrelay daemon) to
// any existing chain. certrelay serves cryptographic proofs binding handles to
// owner keys anchored to Bitcoin. The `fabric` CLI is copied onto /data/bin so
// the gotty terminal (which has /data/bin on PATH) can resolve handles.
// owner keys anchored to Bitcoin. The certrelay-setup oneshot copies `fabric`
// and `monitor` onto /data/bin for the gotty terminal (PATH includes /data/bin).
// Only the certrelay server is started as a daemon — monitor is a CLI file only.
const withCertrelay = (chain: any): any =>
chain
.addOneshot('certrelay-setup', {
@@ -280,6 +285,9 @@ export const main = sdk.setupMain(async ({ effects }) => {
echo "certrelay-setup: installing fabric CLI to ${CERTRELAY_FABRIC_DEST}..."; \
cp -f ${CERTRELAY_FABRIC_BIN} ${CERTRELAY_FABRIC_DEST}; \
chmod +x ${CERTRELAY_FABRIC_DEST}; \
echo "certrelay-setup: installing monitor CLI to ${CERTRELAY_MONITOR_DEST}..."; \
cp -f ${CERTRELAY_MONITOR_BIN} ${CERTRELAY_MONITOR_DEST}; \
chmod +x ${CERTRELAY_MONITOR_DEST}; \
echo "certrelay-setup: done."`,
],
user: 'root',
+1 -1
View File
@@ -14,7 +14,7 @@ export const manifest = setupManifest({
volumes: ['main'],
images: {
spaces: {
source: { dockerTag: 'horologger/spaces:v0.0.9s' },
source: { dockerTag: 'horologger/spaces:v0.0.9x' },
arch: ['x86_64', 'aarch64'],
},
subspaces: {
+5 -3
View File
@@ -34,12 +34,14 @@ export const SUBSPACES_PROVER_PORT = 8888
export const SUBSPACES_REGISTRY_PORT = 8081
export const SUBSPACES_WALLET = 'default'
// Certrelay ships as the prebuilt horologger/certrelay image; both binaries
// are static musl, so `fabric` runs anywhere once copied onto the volume.
// Certrelay ships as the prebuilt horologger/certrelay image; CLI binaries
// are static musl, so they run anywhere once copied onto the volume.
export const CERTRELAY_BIN = '/usr/local/bin/certrelay'
export const CERTRELAY_FABRIC_BIN = '/usr/local/bin/fabric'
// Copied here so the gotty terminal (which has /data/bin on PATH) can run it.
export const CERTRELAY_MONITOR_BIN = '/usr/local/bin/monitor'
// Copied here so the gotty terminal (which has /data/bin on PATH) can run them.
export const CERTRELAY_FABRIC_DEST = '/data/bin/fabric'
export const CERTRELAY_MONITOR_DEST = '/data/bin/monitor'
export const CERTRELAY_DIR = '/data/certrelay'
export const CERTRELAY_DATA_DIR = '/data/certrelay/data'
export const CERTRELAY_PORT = 7778
+2 -2
View File
@@ -1,7 +1,7 @@
import { VersionGraph } from '@start9labs/start-sdk'
import { v_0_1_1_2 } from './v0.1.1.2'
import { v_0_1_1_3 } from './v0.1.1.3'
export const versionGraph = VersionGraph.of({
current: v_0_1_1_2,
current: v_0_1_1_3,
other: [],
})
-37
View File
@@ -1,37 +0,0 @@
import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
export const v_0_1_1_2 = VersionInfo.of({
version: '0.1.1:2',
releaseNotes: {
en_US: `- **Optional HTTP basic auth in front of the Subspaces Prover (port 8888).** Same pattern as the subs auth: off by default, gated by \`store.subsProverAuthEnabled\` + \`store.subsProverAuth\`, wired via \`SUBS_PROVER_BASIC_AUTH_USER\` / \`SUBS_PROVER_BASIC_AUTH_PASSWORD\` env vars on the \`subs-prover\` daemon. Four new actions: **Enable Subspaces Prover Auth**, **Disable Subspaces Prover Auth**, **Show Subspaces Prover Auth Credentials**, **Set Subspaces Prover Auth Credentials**. Independent of subs auth — each daemon has its own toggle and credential store.
- **Optional HTTP basic auth in front of the Subspaces Web UI and Subs API.** Both share port 7777 on the \`subs\` daemon, so a single toggle gates both. Off by default. Wired via \`SUBS_BASIC_AUTH_USER\` / \`SUBS_BASIC_AUTH_PASSWORD\` env vars injected into subs's environment only when the toggle is on AND credentials are stored. Four new actions manage the lifecycle: **Enable Subspaces Auth** (auto-generates a \`spaces\` / random32 credential pair on first enable; preserves them on subsequent enables), **Disable Subspaces Auth** (preserves credentials across the toggle), **Show Subspaces Auth Credentials** (surfaces user/password + a loopback URL, reports current enforcement state), **Set Subspaces Auth Credentials** (rotate; blank password auto-generates a random one; also enables auth). Stored as \`store.subsAuth\` and \`store.subsAuthEnabled\`. NOTE: the in-package nacho client will break against an authed subs until separately wired — that integration is deferred.
- **Nacho ignore list is now file-backed at \`/data/nacho/ignore_names.txt\`** instead of \`EXPO_PUBLIC_IGNORE_NAMES\`. Expo inlines \`EXPO_PUBLIC_*\` into the bundle at build time and connected clients cache that bundle aggressively, so env-var changes only landed after a hard reload. Moving the list to a runtime-read file means nacho picks up changes without a bundle rebuild. The \`nacho-setup\` oneshot creates the file with the default (\`fold,swifty\`) on first start; the **Configure Nacho** action writes the file directly and prefills the form by reading it back (with the default as fallback). The \`nachoIgnoreNames\` field has been removed from \`store.json\`.
- **Milestone release: this version ships with Nacho bundled in.** The \`horologger/nacho:v1.0.0\` Expo dev server runs alongside spaced as an always-on UI on port 8082, wired to the Subs API and configurable via the "Configure Nacho" and "Upload Support PDF" actions.
- **Removed the embedded explorer feature and its indexer entirely.** Deleted the Explorer Web UI interface (port 3000), the postgres image, the PostgreSQL daemon + chown oneshot, and all stubs for the prebuilt indexer / explorer-UI images. Dropped the actions: enable-explorer, disable-explorer, show-db-credentials, reset-db-state, reset-indexer-state, reset-explorer-state. Removed the \`dbAuth\` and \`enableExplorer\` fields from store.json + their seed tasks (taskSeedDb, taskSeedEnableExplorer). main.ts collapsed to a single always-on chain (no more enableExplorer branching). Drop the related EXPLORER_*, INDEXER_*, POSTGRES_* constants from utils.ts.
- Added the nacho service (prebuilt \`horologger/nacho:v1.0.0\`, Expo dev server). Always on; runs the image's entrypoint and exposes a UI interface on port 8082. Env: \`EXPO_PUBLIC_API_BASE_URL\` is derived dynamically from the Subs API StartOS interface (\`sdk.serviceInterface.getOwn('subs-api').const()\`) — preferring the .local URL — so it tracks whatever host:port StartOS exposes for the subs daemon; \`EXPO_PUBLIC_IGNORE_NAMES\` (defaults to "fold,swifty") and \`EXPO_PUBLIC_WORKSHOP_PDF_LINK_TEXT\` (defaults to empty) are user-configurable via the new "Configure Nacho" action; the workshop PDF is uploaded via a separate "Upload Support PDF" action that overwrites /data/support.pdf on the main volume (split from Configure Nacho because the StartOS form serializes an unselected file input as {} rather than null, which Value.file's nullable parser rejects — a required-true dedicated action sidesteps the problem). Service auto-restarts when either the subs-api address info or the ignore-names value changes.
- Exposed the spaced JSON-RPC API as a new "Spaces API" StartOS interface (port 7225) and changed SPACED_RPC_BIND from 127.0.0.1 to 0.0.0.0 so external processes can reach it. It remains authenticated with the spaced RPC credentials (store.spacedAuth); internal clients still connect over loopback.
- New "Show Spaces API Credentials" action surfaces SPACED_RPC_USER / SPACED_RPC_PASSWORD (and a loopback connection URL) so you can authenticate external clients against the Spaces API.
- Added a dedicated "Subs API" interface (type api) on the subs daemon's port (7777), distinct from the Subspaces Prover (8888) and Subspaces Registry (8081) interfaces. The subs daemon is "subsd — an HTTP REST API server"; this surfaces that REST API as its own dashboard entry.
- New "Enable / Disable Subspaces Prover" action independently gates just the subs-prover daemon (store.enableSubsProver), **disabled by default**. When off, subs-prover does not start (it runs lengthy boot-time timing tests), but its interface on 8888 stays registered. It is added last and nothing depends on it, so its slow startup never blocks subs, the registry, or certrelay.
- Embedded Certrelay (prebuilt \`horologger/certrelay:v0.2.3\` image, static musl binaries). Always on — runs the \`certrelay\` server on port 7778 from service start, as its own StartOS interface. It serves cryptographic proofs binding Bitcoin-anchored handles to owner keys, talking to the local spaced over loopback using the store.spacedAuth credentials. The bundled \`fabric\` CLI is copied to /data/bin/fabric so it's runnable from the Space-CLI Web UI. New "Configure Certrelay" action sets CERTRELAY_SELF_URL, CERTRELAY_BOOTSTRAP, and CERTRELAY_HEALTHCHECK_HANDLE (stored in store.json; saving restarts the service). Set CERTRELAY_SELF_URL to the publicly visible URL StartOS exposes for the Certrelay interface.
- Removed the build-from-source chains for the Go indexer and the SvelteKit explorer, and dropped their builder images (\`golang:1.23-alpine\`, \`node:20-alpine\`). This shrinks the .s9pk and removes the multi-minute first-enable compile. The indexer + explorer-UI daemons are now stubbed with TODOs in startos/main.ts pending prebuilt images (indexer image not yet produced; explorer image to be provided). Enabling the embedded explorer currently starts PostgreSQL only. All explorer-indexer actions (show-db-credentials, reset-db-state, reset-indexer-state, reset-explorer-state) are retained.
- Embedded Subspaces support (prebuilt \`horologger/subs:\` image). Opt-in via the new "Enable Subspaces" action; "Disable Subspaces" stops it. No compile step — enabling starts three prebuilt daemons, each on its own StartOS interface: **subs** (Web UI, 7777), **subs-prover** (RISC Zero prover, no GPU, 8888), and **registry-server** (handle registry, 8081). subs loads the existing \`default\` spaces wallet at startup (it does not create one). Runtime data persists at /data/subspaces/data across restarts and toggles. New "Reset Subspaces State" action wipes local data. Adds a fifth manifest image (\`horologger/subs:\`, ~84 MB) — switched from building \`spacesops/subs\` from source with \`rust:1-slim\` to shrink the .s9pk and eliminate the multi-minute first-enable compile.
- Five distinct interfaces now appear in the dashboard: **Space-CLI Web UI** (gotty terminal, 8080), **Explorer Web UI** (3000, opt-in), **Subspaces Web UI** (7777, opt-in), **Subspaces Prover** (8888, opt-in), **Subspaces Registry** (8081, opt-in). Internal "Web UI" references renamed to **Space-CLI Web UI** for clarity.
- Embedded explorer (PostgreSQL + Go indexer + SvelteKit web UI) is now **opt-in**. Fresh installs run in spaces-only mode (spaced + gotty terminal). New "Enable Embedded Explorer" action turns the whole bundle on; "Disable Embedded Explorer" turns it back off. Both auto-restart the service so the new daemon graph takes effect. Indexed data on disk is preserved across toggles.
- Embedded SvelteKit explorer web UI (randomlogin/explorer @ c827da175). Fetched into /data/explorer-ui on first enable, \`npm install\` + \`npm run build\` produce a SvelteKit node-adapter bundle, and \`node build\` runs as a managed daemon on port 3000. Exposed as a StartOS interface so it appears alongside the gotty terminal in the dashboard. Reads exclusively from the embedded PostgreSQL; does not talk to spaced or bitcoind directly. New "Reset Explorer UI State" action wipes the cache for a clean rebuild. Adds a fourth manifest image \`node:20-alpine\`.
- Embedded spaces-protocol explorer indexer (Go, spacesprotocol/explorer-indexer @ 00ae1e548). When enabled, on first start its source tarball is fetched into /data/explorer-indexer, the \`sync\` binary and \`goose\` migrator are built into /data/explorer-indexer/bin, the goose-managed SQL schema is applied to the embedded PostgreSQL, and the sync binary runs as a managed daemon that polls bitcoind + spaced and writes to the database.
- Two patches applied to the indexer source during \`indexer-fetch\` to handle our older spaced binary: \`pkg/node/types.go\` makes \`ptrs_root\` optional in RootAnchor, and \`pkg/store/store.go\` skips the \`getptrblockmeta\` RPC call. Subspaces pointer data won't be indexed; everything else (blocks, transactions, spaces, rollouts, root anchors) does.
- Spaced is now configured with static RPC credentials (SPACED_RPC_USER / SPACED_RPC_PASSWORD seeded into store.json.spacedAuth) instead of cookie auth, so the indexer and the gotty terminal share a single auth path. space-cli inside the terminal aliases to \`--rpc-user "$SPACED_RPC_USER" --rpc-password "$SPACED_RPC_PASSWORD"\`.
- New "Indexer Sync" standalone health check queries the blocks table for the highest non-orphan block and cross-references against spaced's tip; success when within 5 blocks, otherwise loading with explicit lag.
- New "Reset Indexer State" action wipes /data/explorer-indexer so the next start re-fetches the pinned commit and rebuilds the binaries.
- Embedded PostgreSQL 16.3 daemon on loopback 127.0.0.1:5432 (data at /data/postgres). "Show Database Credentials" + "Reset Database State" actions. POSTGRES_URI exported into the gotty terminal.
- Indexer container is a third manifest image (\`golang:1.23-alpine\`) used both to build the binaries and to run \`sync\`.
- "Reset Spaced State" action so a corrupt /data/mainnet/ index can be wiped from the UI.
- Web terminal no longer requires spaced to be healthy, so gotty stays reachable when spaced crash-loops.
- Initial build bundles spaced + space-cli from horologger/spaces:v0.0.9s, managed spaced daemon (mainnet only), gotty browser terminal with admin basic auth, and the Bitcoin Core 31.x dependency.`,
},
migrations: {
up: async ({ effects }) => {},
down: IMPOSSIBLE,
},
})
+13
View File
@@ -0,0 +1,13 @@
import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
export const v_0_1_1_3 = VersionInfo.of({
version: '0.1.1:3',
releaseNotes: {
en_US: `- **Configure Subspaces** action toggles \`SUBS_PUBLISH_REQUIRE_FINALIZED\` on the \`subs\` daemon (default off). When enabled, subs blocks certificate publish until commitments are finalized (150 confirmations). Stored as \`store.subsPublishRequireFinalized\`; always injected into subs's environment as \`true\` or \`false\`.
- **monitor CLI** from the certrelay image is now copied to \`/data/bin/monitor\` alongside \`fabric\` on each start (file only — not run as a daemon). Both are on PATH in the Space-CLI Web UI terminal.`,
},
migrations: {
up: async ({ effects }) => {},
down: IMPOSSIBLE,
},
})