188 lines
5.6 KiB
TypeScript
188 lines
5.6 KiB
TypeScript
import { storeJson } from './fileModels/storeJson'
|
|
import { i18n } from './i18n'
|
|
import { sdk } from './sdk'
|
|
import {
|
|
APP_USER,
|
|
BITCOIND_RPC_HOSTNAME,
|
|
BITCOIND_RPC_PORT,
|
|
dataDir,
|
|
SPACED_CHAIN,
|
|
spacedRpcPort,
|
|
uiPort,
|
|
} from './utils'
|
|
|
|
export const main = sdk.setupMain(async ({ effects }) => {
|
|
console.info(i18n('Starting Spaces!'))
|
|
|
|
const store = await storeJson.read().const(effects)
|
|
if (!store?.password || !store?.btcAuth) {
|
|
// taskSetPassword + taskBtcAuth both seed these in init; if they aren't
|
|
// populated yet, init hasn't finished — let StartOS restart us.
|
|
throw new Error('Spaces store.json is not yet populated.')
|
|
}
|
|
|
|
const { password: APP_PASSWORD, btcAuth } = store
|
|
|
|
const spacedEnv = {
|
|
SPACED_CHAIN,
|
|
SPACED_DATA_DIR: dataDir,
|
|
SPACED_RPC_BIND: '127.0.0.1',
|
|
SPACED_RPC_PORT: String(spacedRpcPort),
|
|
SPACED_RPC_URL: `http://127.0.0.1:${spacedRpcPort}`,
|
|
SPACED_BLOCK_INDEX: 'true',
|
|
SPACED_BITCOIN_RPC_URL: `http://${BITCOIND_RPC_HOSTNAME}:${BITCOIND_RPC_PORT}`,
|
|
SPACED_BITCOIN_RPC_USER: btcAuth.username,
|
|
SPACED_BITCOIN_RPC_PASSWORD: btcAuth.password,
|
|
// legacy aliases for `bitcoin-cli` / shell helpers that read these names
|
|
BTC_RPC_HOST: BITCOIND_RPC_HOSTNAME,
|
|
BTC_RPC_PORT: String(BITCOIND_RPC_PORT),
|
|
BTC_RPC_USER: btcAuth.username,
|
|
BTC_RPC_PASSWORD: btcAuth.password,
|
|
APP_USER,
|
|
APP_PASSWORD,
|
|
}
|
|
|
|
const mounts = sdk.Mounts.of().mountVolume({
|
|
volumeId: 'main',
|
|
subpath: null,
|
|
mountpoint: dataDir,
|
|
readonly: false,
|
|
})
|
|
|
|
const spacedSub = await sdk.SubContainer.of(
|
|
effects,
|
|
{ imageId: 'spaces' },
|
|
mounts,
|
|
'spaced-sub',
|
|
)
|
|
|
|
const termSub = await sdk.SubContainer.of(
|
|
effects,
|
|
{ imageId: 'spaces' },
|
|
mounts,
|
|
'terminal-sub',
|
|
)
|
|
|
|
const bashrc = [
|
|
'export PATH=/root/.cargo/bin:/data/bin:/usr/local/bin:/usr/bin:/bin',
|
|
"export PS1='spaces:\\w$ '",
|
|
`alias spaces='space-cli --chain ${SPACED_CHAIN} --rpc-cookie ${dataDir}/${SPACED_CHAIN}/.cookie '`,
|
|
'cat <<EOF',
|
|
'',
|
|
'┌─ Spaces ─────────────────────────────────────────────────┐',
|
|
'│ spaced is managed by StartOS — do not run it manually. │',
|
|
'│ Use the `spaces` alias to call space-cli, e.g. │',
|
|
'│ spaces getserverinfo │',
|
|
'│ Docs: https://docs.spacesprotocol.org/ │',
|
|
'└──────────────────────────────────────────────────────────┘',
|
|
'',
|
|
'EOF',
|
|
].join('\n')
|
|
|
|
return sdk.Daemons.of(effects)
|
|
.addOneshot('bashrc', {
|
|
subcontainer: termSub,
|
|
exec: {
|
|
command: ['bash', '-c', `cat > /root/.bashrc <<'SPACES_BASHRC_EOF'
|
|
${bashrc}
|
|
SPACES_BASHRC_EOF`],
|
|
user: 'root',
|
|
},
|
|
requires: [],
|
|
})
|
|
.addDaemon('spaced', {
|
|
subcontainer: spacedSub,
|
|
exec: {
|
|
command: ['/root/.cargo/bin/spaced'],
|
|
env: spacedEnv,
|
|
},
|
|
ready: {
|
|
display: i18n('Spaced RPC'),
|
|
fn: () =>
|
|
sdk.healthCheck.checkPortListening(effects, spacedRpcPort, {
|
|
successMessage: i18n('spaced RPC is ready'),
|
|
errorMessage: i18n('spaced RPC is not ready'),
|
|
}),
|
|
gracePeriod: 120_000,
|
|
},
|
|
requires: [],
|
|
})
|
|
.addDaemon('web-terminal', {
|
|
subcontainer: termSub,
|
|
exec: {
|
|
command: [
|
|
'gotty',
|
|
'--port',
|
|
String(uiPort),
|
|
'-c',
|
|
`${APP_USER}:${APP_PASSWORD}`,
|
|
'--permit-write',
|
|
'--reconnect',
|
|
'/bin/bash',
|
|
],
|
|
env: spacedEnv,
|
|
},
|
|
ready: {
|
|
display: i18n('Web Interface'),
|
|
fn: () =>
|
|
sdk.healthCheck.checkPortListening(effects, uiPort, {
|
|
successMessage: i18n('The web terminal is ready'),
|
|
errorMessage: i18n('The web terminal is not ready'),
|
|
}),
|
|
},
|
|
requires: ['bashrc'],
|
|
})
|
|
.addHealthCheck('sync', {
|
|
ready: {
|
|
display: i18n('Spaced Sync'),
|
|
fn: async () => {
|
|
const probe = await spacedSub.exec(
|
|
[
|
|
'bash',
|
|
'-c',
|
|
`/root/.cargo/bin/space-cli --chain ${SPACED_CHAIN} --rpc-cookie ${dataDir}/${SPACED_CHAIN}/.cookie getserverinfo`,
|
|
],
|
|
{},
|
|
)
|
|
|
|
if (probe.exitCode !== 0) {
|
|
return {
|
|
result: 'starting',
|
|
message: i18n(
|
|
'spaced is querying Bitcoin and indexing — this can take a while on first run.',
|
|
),
|
|
}
|
|
}
|
|
|
|
const stdout = (probe.stdout ?? '').toString()
|
|
let progress = 0
|
|
try {
|
|
const parsed = JSON.parse(stdout) as {
|
|
ready?: boolean
|
|
progress?: number
|
|
chain?: { blocks?: number; headers?: number }
|
|
}
|
|
if (parsed.ready === true) {
|
|
return {
|
|
result: 'success',
|
|
message: i18n('spaced is fully synced.'),
|
|
}
|
|
}
|
|
progress = Math.floor((parsed.progress ?? 0) * 100)
|
|
} catch {
|
|
// fall through to loading
|
|
}
|
|
|
|
return {
|
|
result: 'loading',
|
|
message: i18n('spaced is indexing. Progress: ${pct}%', {
|
|
pct: String(progress),
|
|
}),
|
|
}
|
|
},
|
|
gracePeriod: 300_000,
|
|
},
|
|
requires: ['spaced'],
|
|
})
|
|
})
|