working
Some checks are pending
Build Service / BuildPackage (push) Waiting to run

This commit is contained in:
2026-05-14 05:39:56 -04:00
parent 5b7cd13dc0
commit 172e3f63ac
33 changed files with 1633 additions and 0 deletions

187
startos/main.ts Normal file
View File

@@ -0,0 +1,187 @@
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'],
})
})