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>
This commit is contained in:
2026-08-20 19:35:15 -04:00
co-authored by Cursor
parent 89f4475e99
commit 2b0224a678
14 changed files with 305 additions and 116 deletions
-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 { configureCertrelay } from './configureCertrelay'
import { configureNacho } from './configureNacho'
import { configureSubspaces } from './configureSubspaces'
import { createRegistryApiKeys } from './createRegistryApiKeys'
import { disableSubsAuth } from './disableSubsAuth'
import { disableSubsProverAuth } from './disableSubsProverAuth'
import { disableSubspaces } from './disableSubspaces'
@@ -19,6 +19,7 @@ import { setSubsProver } from './setSubsProver'
import { setSubsProverCredentials } from './setSubsProverCredentials'
import { showCredentials } from './showCredentials'
import { showPassword } from './showPassword'
import { showRegistryApiKeys } from './showRegistryApiKeys'
import { showSpacedCredentials } from './showSpacedCredentials'
import { showSubsCredentials } from './showSubsCredentials'
import { showSubsProverCredentials } from './showSubsProverCredentials'
@@ -49,5 +50,6 @@ export const actions = sdk.Actions.of()
.addAction(resetSubspacesState)
.addAction(configureCertrelay)
.addAction(configureNacho)
.addAction(configureSubspaces)
.addAction(createRegistryApiKeys)
.addAction(showRegistryApiKeys)
.addAction(uploadSupportPdf)
+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),
certrelayHealthcheckHandle: z.string().nullable().catch(null),
nachoWorkshopPdfLinkText: z.string().nullable().catch(null),
subsPublishRequireFinalized: z.boolean().nullable().catch(null),
// Bearer secrets for the embedded registry-server (examples/registry-server).
// REGISTRY_API_KEY guards POST /register; SUBSD_API_KEY is the Auth Token
// for the subs↔registry channel (Settings → Registry Server → Auth Token).
// Both are required and must differ or registry-server refuses to start.
registryApiKey: z.string().nullable().catch(null),
subsdApiKey: z.string().nullable().catch(null),
})
export const storeJson = FileHelper.json(
+19 -10
View File
@@ -289,16 +289,25 @@ const dict = {
205,
'Subspaces Prover Auth credentials saved. Auth is currently DISABLED — enable it with "Enable Subspaces Prover Auth" to enforce these credentials.':
206,
'Configure Subspaces': 207,
'Set user-tunable subs options. Saving restarts the service so the subs daemon picks up the new environment.':
208,
'Require Finalized Publish': 209,
'When on, subs blocks certificate publish until commitments are finalized (150 confirmations). Maps to SUBS_PUBLISH_REQUIRE_FINALIZED in the subs daemon environment. Off by default.':
210,
'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is enabled; the service is restarting.':
211,
'Subspaces configuration saved. SUBS_PUBLISH_REQUIRE_FINALIZED is disabled; the service is restarting.':
212,
'Create Registry API Keys': 213,
'Generate (or rotate) REGISTRY_API_KEY and SUBSD_API_KEY for the embedded registry-server. Both are required and must differ. Put SUBSD_API_KEY in subs Settings → Registry Server → Auth Token. REGISTRY_API_KEY is for intake clients calling POST /register. Saving restarts the service so registry-server picks up the new keys.':
214,
'Registry API keys saved. Paste SUBSD_API_KEY into subs Settings → Registry Server → Auth Token, then click Test. Use REGISTRY_API_KEY as the Bearer token for POST /register clients. The service is restarting so registry-server picks up the new keys.':
215,
REGISTRY_API_KEY: 216,
SUBSD_API_KEY: 217,
'Bearer secret for intake/POST /register clients.': 218,
'Paste into subs Settings → Registry Server → Auth Token (subs↔registry channel).':
219,
'Show Registry API Keys': 220,
'Display the stored REGISTRY_API_KEY and SUBSD_API_KEY used by registry-server. Returns blanks if Create Registry API Keys has not been run yet.':
221,
'Paste SUBSD_API_KEY into subs Settings → Registry Server → Auth Token. Use REGISTRY_API_KEY as the Bearer token for POST /register clients.':
222,
'No valid registry API keys are stored yet. Run Create Registry API Keys first (both keys must be non-empty and different).':
223,
'Subspaces registry-server requires REGISTRY_API_KEY and SUBSD_API_KEY before it can start':
224,
} as const
/**
+2
View File
@@ -5,6 +5,7 @@ import { setInterfaces } from '../interfaces'
import { sdk } from '../sdk'
import { versionGraph } from '../versions'
import { taskBtcAuth } from './taskBtcAuth'
import { taskRegistryApiKeys } from './taskRegistryApiKeys'
import { taskSeedCertrelay } from './taskSeedCertrelay'
import { taskSeedEnableSubspaces } from './taskSeedEnableSubspaces'
import { taskSeedNacho } from './taskSeedNacho'
@@ -20,6 +21,7 @@ export const init = sdk.setupInit(
taskBtcAuth,
taskSeedSpacedAuth,
taskSeedEnableSubspaces,
taskRegistryApiKeys,
taskSeedCertrelay,
taskSeedNacho,
taskSetPassword,
+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_PROVER_ENDPOINT: `http://127.0.0.1:${SUBSPACES_PROVER_PORT}`,
SUBS_REGISTRY_ENDPOINT: `http://127.0.0.1:${SUBSPACES_REGISTRY_PORT}`,
SUBS_PUBLISH_REQUIRE_FINALIZED:
store.subsPublishRequireFinalized === true ? 'true' : 'false',
HOME: SUBSPACES_DATA_DIR,
RUST_LOG: 'subs=info,subs_prover=info,registry_server=info',
...subsAuthEnv,
@@ -181,6 +179,9 @@ export const main = sdk.setupMain(async ({ effects }) => {
REGISTRY_SERVER_PORT: String(SUBSPACES_REGISTRY_PORT),
HOME: SUBSPACES_REGISTRY_DIR,
RUST_LOG: 'registry_server=info',
// examples/registry-server requires both at boot; they must differ.
REGISTRY_API_KEY: store.registryApiKey ?? '',
SUBSD_API_KEY: store.subsdApiKey ?? '',
}
const certrelaySub = sdk.SubContainer.of(
@@ -371,6 +372,22 @@ export const main = sdk.setupMain(async ({ effects }) => {
// subs daemon) to any existing chain. `as any` because the chain's TS type
// depends on prior IDs and we can't easily express the union here.
const withSubspaces = (chain: any): any => {
const registryApiKey = store.registryApiKey
const subsdApiKey = store.subsdApiKey
if (
!registryApiKey ||
!subsdApiKey ||
registryApiKey.length === 0 ||
subsdApiKey.length === 0 ||
registryApiKey === subsdApiKey
) {
// taskRegistryApiKeys surfaces Create Registry API Keys when Subspaces is
// on without valid keys; refuse to start the registry half-configured.
throw new Error(
'Registry API keys are not configured. Run Create Registry API Keys.',
)
}
let c = chain
.addOneshot('subspaces-dirs', {
subcontainer: subspacesSub,
+4 -4
View File
@@ -14,19 +14,19 @@ export const manifest = setupManifest({
volumes: ['main'],
images: {
spaces: {
source: { dockerTag: 'horologger/spaces:v0.2.1a' },
source: { dockerTag: 'horologger/spaces:v0.3.0' },
arch: ['x86_64', 'aarch64'],
},
subspaces: {
source: { dockerTag: 'horologger/subs:v0.1.2b' },
source: { dockerTag: 'horologger/subs:v0.1.2c' },
arch: ['x86_64', 'aarch64'],
},
certrelay: {
source: { dockerTag: 'horologger/certrelay:v0.2.7b' },
source: { dockerTag: 'horologger/certrelay:v0.2.8' },
arch: ['x86_64', 'aarch64'],
},
nacho: {
source: { dockerTag: 'horologger/nacho:v1.0.0' },
source: { dockerTag: 'horologger/nacho:v1.0.0b' },
arch: ['x86_64', 'aarch64'],
},
},
+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
// 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
+7 -2
View File
@@ -1,9 +1,14 @@
import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
export const current = VersionInfo.of({
version: '0.2.1:5',
version: '0.3.0:0',
releaseNotes: {
en_US: `Bump Subspaces to \`horologger/subs:v0.1.2b\` and Certrelay to \`horologger/certrelay:v0.2.7b\` (spaces \`v0.2.1a\` and nacho \`v1.0.0\` unchanged).
en_US: `Update to upstream Spaces **0.3.0** (\`horologger/spaces:v0.3.0\`). Also bundles \`subs:v0.1.2c\`, \`certrelay:v0.2.8\`, and \`nacho:v1.0.0b\`.
**Wrapper**
- Add **Create Registry API Keys** / **Show Registry API Keys** for the embedded \`registry-server\` (\`REGISTRY_API_KEY\` + \`SUBSD_API_KEY\`; required and must differ)
- Remove **Configure Subspaces** and \`SUBS_PUBLISH_REQUIRE_FINALIZED\`
Requires StartOS 0.4.0-beta.10 (\`start-sdk\` 2.0.9).`,
},