@@ -0,0 +1,101 @@
|
||||
import { storeJson } from '../fileModels/storeJson'
|
||||
import { i18n } from '../i18n'
|
||||
import { sdk } from '../sdk'
|
||||
import {
|
||||
CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||
CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||
CERTRELAY_DEFAULT_SELF_URL,
|
||||
} from '../utils'
|
||||
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
const inputSpec = InputSpec.of({
|
||||
certrelaySelfUrl: Value.text({
|
||||
name: i18n('Certrelay Self URL'),
|
||||
description: i18n(
|
||||
'The publicly visible URL clients use to reach this certrelay (CERTRELAY_SELF_URL). Set this to match the external address StartOS exposes for the Certrelay interface (e.g. your .onion or clearnet domain).',
|
||||
),
|
||||
warning: null,
|
||||
footnote: null,
|
||||
default: CERTRELAY_DEFAULT_SELF_URL,
|
||||
required: true,
|
||||
masked: false,
|
||||
placeholder: 'https://certrelay.example.com',
|
||||
minLength: 1,
|
||||
maxLength: null,
|
||||
}),
|
||||
certrelayBootstrap: Value.toggle({
|
||||
name: i18n('Certrelay Bootstrap'),
|
||||
description: i18n(
|
||||
'Whether this relay bootstraps from peer relays on startup (CERTRELAY_BOOTSTRAP).',
|
||||
),
|
||||
warning: null,
|
||||
footnote: null,
|
||||
default: CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||
}),
|
||||
certrelayHealthcheckHandle: Value.text({
|
||||
name: i18n('Certrelay Healthcheck Handle'),
|
||||
description: i18n(
|
||||
'A handle the bundled fabric CLI resolves against this relay to verify end-to-end health (CERTRELAY_HEALTHCHECK_HANDLE).',
|
||||
),
|
||||
warning: null,
|
||||
footnote: null,
|
||||
default: CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||
required: true,
|
||||
masked: false,
|
||||
placeholder: 'account-digital-useful.genesis@key',
|
||||
minLength: 1,
|
||||
maxLength: null,
|
||||
}),
|
||||
})
|
||||
|
||||
export const configureCertrelay = sdk.Action.withInput(
|
||||
// id
|
||||
'configure-certrelay',
|
||||
|
||||
// metadata
|
||||
async ({ effects }) => ({
|
||||
name: i18n('Configure Certrelay'),
|
||||
description: i18n(
|
||||
'Set the certrelay self URL, bootstrap toggle, and healthcheck handle. Saving restarts the service so certrelay picks up the new values.',
|
||||
),
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
|
||||
// input
|
||||
inputSpec,
|
||||
|
||||
// prefill — load current values from store
|
||||
async ({ effects }) => {
|
||||
const store = await storeJson.read().once()
|
||||
return {
|
||||
certrelaySelfUrl: store?.certrelaySelfUrl ?? CERTRELAY_DEFAULT_SELF_URL,
|
||||
certrelayBootstrap:
|
||||
store?.certrelayBootstrap ?? CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||
certrelayHealthcheckHandle:
|
||||
store?.certrelayHealthcheckHandle ??
|
||||
CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||
}
|
||||
},
|
||||
|
||||
// run
|
||||
async ({ effects, input }) => {
|
||||
await storeJson.merge(effects, {
|
||||
certrelaySelfUrl: input.certrelaySelfUrl,
|
||||
certrelayBootstrap: input.certrelayBootstrap,
|
||||
certrelayHealthcheckHandle: input.certrelayHealthcheckHandle,
|
||||
})
|
||||
|
||||
return {
|
||||
version: '1',
|
||||
title: i18n('Success'),
|
||||
message: i18n(
|
||||
'Certrelay configuration saved. The service is restarting to apply the new settings.',
|
||||
),
|
||||
result: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
import { storeJson } from '../fileModels/storeJson'
|
||||
import { i18n } from '../i18n'
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
export const disableSubspaces = sdk.Action.withoutInput(
|
||||
// id
|
||||
'disable-subspaces',
|
||||
|
||||
// metadata
|
||||
async ({ effects }) => {
|
||||
const current = await storeJson.read((s) => s.enableSubspaces).once()
|
||||
return {
|
||||
name: i18n('Disable Subspaces'),
|
||||
description: i18n(
|
||||
'Stop the embedded Subspaces service. The compiled binaries and data at /data/subspaces are preserved and will be reused if Subspaces is re-enabled. Service restarts automatically.',
|
||||
),
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: current === true ? 'enabled' : 'hidden',
|
||||
}
|
||||
},
|
||||
|
||||
// run
|
||||
async ({ effects }) => {
|
||||
await storeJson.merge(effects, { enableSubspaces: false })
|
||||
|
||||
return {
|
||||
version: '1',
|
||||
title: i18n('Success'),
|
||||
message: i18n(
|
||||
'Subspaces disabled. The service is restarting; the Subspaces Web UI is no longer listening. Run "Enable Subspaces" to turn it back on.',
|
||||
),
|
||||
result: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
import { storeJson } from '../fileModels/storeJson'
|
||||
import { i18n } from '../i18n'
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
export const enableSubspaces = sdk.Action.withoutInput(
|
||||
// id
|
||||
'enable-subspaces',
|
||||
|
||||
// metadata
|
||||
async ({ effects }) => {
|
||||
const current = await storeJson.read((s) => s.enableSubspaces).once()
|
||||
return {
|
||||
name: i18n('Enable Subspaces'),
|
||||
description: i18n(
|
||||
'Turn on the embedded Subspaces service (off-chain Bitcoin handles). Starts three prebuilt daemons — subs (Web UI), subs-prover, and registry-server — each on its own interface. Service restarts automatically. subs loads the existing `default` spaces wallet; create it first via the Space-CLI Web UI (`spaces createwallet`) if you have not already.',
|
||||
),
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: current === true ? 'hidden' : 'enabled',
|
||||
}
|
||||
},
|
||||
|
||||
// run
|
||||
async ({ effects }) => {
|
||||
await storeJson.merge(effects, { enableSubspaces: true })
|
||||
|
||||
return {
|
||||
version: '1',
|
||||
title: i18n('Success'),
|
||||
message: i18n(
|
||||
'Subspaces enabled. The service is restarting; the Subspaces Web UI (7777), Prover (8888), and Registry (8081) interfaces come up once their daemons are healthy.',
|
||||
),
|
||||
result: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -1,6 +1,9 @@
|
||||
import { sdk } from '../sdk'
|
||||
import { configureCertrelay } from './configureCertrelay'
|
||||
import { disableExplorer } from './disableExplorer'
|
||||
import { disableSubspaces } from './disableSubspaces'
|
||||
import { enableExplorer } from './enableExplorer'
|
||||
import { enableSubspaces } from './enableSubspaces'
|
||||
import { exportWallet } from './exportWallet'
|
||||
import { importWallet } from './importWallet'
|
||||
import { resetDbState } from './resetDbState'
|
||||
@@ -8,6 +11,7 @@ import { resetExplorerState } from './resetExplorerState'
|
||||
import { resetIndexerState } from './resetIndexerState'
|
||||
import { resetPassword } from './resetPassword'
|
||||
import { resetSpacedState } from './resetSpacedState'
|
||||
import { resetSubspacesState } from './resetSubspacesState'
|
||||
import { setBitcoinRpc } from './setBitcoinRpc'
|
||||
import { showCredentials } from './showCredentials'
|
||||
import { showDbCredentials } from './showDbCredentials'
|
||||
@@ -29,3 +33,7 @@ export const actions = sdk.Actions.of()
|
||||
.addAction(resetDbState)
|
||||
.addAction(resetIndexerState)
|
||||
.addAction(resetExplorerState)
|
||||
.addAction(enableSubspaces)
|
||||
.addAction(disableSubspaces)
|
||||
.addAction(resetSubspacesState)
|
||||
.addAction(configureCertrelay)
|
||||
|
||||
@@ -24,7 +24,9 @@ export const resetExplorerState = sdk.Action.withoutInput(
|
||||
async ({ effects }) => {
|
||||
const res = await sdk.SubContainer.withTemp(
|
||||
effects,
|
||||
{ imageId: 'explorer-ui' },
|
||||
// TODO(prebuilt-explorer): switch back to the explorer image once it
|
||||
// exists. Any image with `rm` works for the wipe; `spaces` is always present.
|
||||
{ imageId: 'spaces' },
|
||||
sdk.Mounts.of().mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: null,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { i18n } from '../i18n'
|
||||
import { sdk } from '../sdk'
|
||||
import { dataDir, SUBSPACES_DIR } from '../utils'
|
||||
|
||||
export const resetSubspacesState = sdk.Action.withoutInput(
|
||||
// id
|
||||
'reset-subspaces-state',
|
||||
|
||||
// metadata
|
||||
async ({ effects }) => ({
|
||||
name: i18n('Reset Subspaces State'),
|
||||
description: i18n(
|
||||
'Wipe /data/subspaces so the next start re-creates empty Subspaces data, prover, and registry directories.',
|
||||
),
|
||||
warning: i18n(
|
||||
'This deletes all local Subspaces state — any handles/proofs stored on disk will be lost. The binaries ship in the image, so nothing needs to be re-downloaded. The spaces wallet on spaced is preserved.',
|
||||
),
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
|
||||
// run
|
||||
async ({ effects }) => {
|
||||
const res = await sdk.SubContainer.withTemp(
|
||||
effects,
|
||||
{ imageId: 'subspaces' },
|
||||
sdk.Mounts.of().mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: null,
|
||||
mountpoint: dataDir,
|
||||
readonly: false,
|
||||
}),
|
||||
'spaces-reset-subspaces',
|
||||
(subc) => subc.exec(['rm', '-rf', SUBSPACES_DIR], { user: 'root' }),
|
||||
)
|
||||
|
||||
if (res.exitCode !== 0) {
|
||||
return {
|
||||
version: '1',
|
||||
title: i18n('Failure'),
|
||||
message: i18n('Could not wipe Subspaces state: ${error}', {
|
||||
error: (res.stderr ?? '').toString() || `exit ${res.exitCode}`,
|
||||
}),
|
||||
result: null,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
version: '1',
|
||||
title: i18n('Success'),
|
||||
message: i18n(
|
||||
'Subspaces cache and data have been wiped. Start (or restart) the service to re-fetch and rebuild from GitHub.',
|
||||
),
|
||||
result: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user