Build Service / BuildPackage (push) Has been cancelled
Remove the embedded explorer/indexer stack, switch subspaces to prebuilt images, add certrelay and nacho as always-on services, expose Spaces and Subs APIs, and wire optional HTTP basic auth for subs and subs-prover with the correct SUBS_BASIC_AUTH_PASSWORD env var. Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
6.3 KiB
TypeScript
172 lines
6.3 KiB
TypeScript
import { utils } from '@start9labs/start-sdk'
|
|
|
|
export const uiPort = 8080
|
|
|
|
export const spacedRpcPort = 7225
|
|
|
|
export const dataDir = '/data'
|
|
|
|
export const APP_USER = 'admin'
|
|
|
|
export const BITCOIN_RPC_USER = 'spaces'
|
|
|
|
export const BITCOIND_PACKAGE_ID = 'bitcoind'
|
|
export const BITCOIND_RPC_HOSTNAME = 'bitcoind.startos'
|
|
export const BITCOIND_RPC_PORT = 8332
|
|
|
|
export const SPACED_CHAIN = 'mainnet'
|
|
|
|
// Subspaces ships as the prebuilt horologger/subs image; binaries live at
|
|
// these paths inside it. No build-from-source step.
|
|
export const SUBSPACES_SUBS_BIN = '/usr/local/bin/subs'
|
|
export const SUBSPACES_PROVER_BIN = '/usr/local/bin/subs-prover'
|
|
export const SUBSPACES_REGISTRY_BIN = '/usr/local/bin/registry-server'
|
|
|
|
export const SUBSPACES_DIR = '/data/subspaces'
|
|
export const SUBSPACES_DATA_DIR = '/data/subspaces/data'
|
|
export const SUBSPACES_PROVER_DIR = '/data/subspaces/prover'
|
|
export const SUBSPACES_REGISTRY_DIR = '/data/subspaces/registry'
|
|
|
|
export const SUBSPACES_UI_PORT = 7777
|
|
export const SUBSPACES_PROVER_PORT = 8888
|
|
// 8081 (not the upstream default 8080) to avoid colliding with the gotty
|
|
// Space-CLI Web UI on 8080.
|
|
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.
|
|
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_FABRIC_DEST = '/data/bin/fabric'
|
|
export const CERTRELAY_DIR = '/data/certrelay'
|
|
export const CERTRELAY_DATA_DIR = '/data/certrelay/data'
|
|
export const CERTRELAY_PORT = 7778
|
|
export const CERTRELAY_CHAIN = 'mainnet'
|
|
export const CERTRELAY_BIND = '0.0.0.0'
|
|
export const CERTRELAY_REMOTE_IP_HEADER = 'x-forwarded-for'
|
|
export const CERTRELAY_ANCHOR_REFRESH = '300'
|
|
export const CERTRELAY_DEFAULT_SELF_URL = 'https://certrelay.spacesops.com'
|
|
export const CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE =
|
|
'account-digital-useful.genesis@key'
|
|
export const CERTRELAY_DEFAULT_BOOTSTRAP = false
|
|
|
|
// Nacho (Expo dev server, horologger/nacho image). Always on; UI interface on
|
|
// 8082 (the image's exposed port, matches EXPO_DEV_PORT).
|
|
export const NACHO_PORT = 8082
|
|
export const NACHO_DIR = '/data/nacho'
|
|
// Last-resort fallback if StartOS hasn't yet populated the subs-api interface's
|
|
// address info at the moment main runs. In normal operation EXPO_PUBLIC_API_BASE_URL
|
|
// is derived dynamically from the subs-api StartOS interface — see main.ts.
|
|
export const NACHO_FALLBACK_API_BASE_URL = `http://127.0.0.1:${SUBSPACES_UI_PORT}`
|
|
export const NACHO_DEFAULT_IGNORE_NAMES = 'fold,swifty'
|
|
export const NACHO_DEFAULT_WORKSHOP_PDF_LINK_TEXT = ''
|
|
|
|
export function randomPassword() {
|
|
// bitcoind's generate-rpc-dependent action validates the password against
|
|
// /^[A-Za-z0-9_-]+$/, so the charset must stay in that set.
|
|
return utils.getDefaultString({
|
|
charset: 'a-z,A-Z,1-9',
|
|
len: 32,
|
|
})
|
|
}
|
|
|
|
// 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
|
|
// helpers here guarantee every line they emit is exactly BANNER_WIDTH cells.
|
|
//
|
|
// Use renderBanner() rather than hand-drawing rows so wrapping + padding stay
|
|
// correct when content changes. See bashrc in main.ts.
|
|
export const BANNER_WIDTH = 100
|
|
const BANNER_INNER = BANNER_WIDTH - 2 // 98 cells between the two side borders
|
|
const BANNER_TEXT = BANNER_INNER - 2 // 96 cells of usable text (1-cell gutters)
|
|
|
|
// Cell width assuming all code points are width-1 (true for ASCII, em-dash,
|
|
// and the box-drawing chars used here). Does NOT handle CJK or emoji — if you
|
|
// add any, switch to a wcwidth-aware count.
|
|
function cellWidth(s: string): number {
|
|
return Array.from(s).length
|
|
}
|
|
|
|
function padRight(s: string, width: number): string {
|
|
const w = cellWidth(s)
|
|
return w >= width ? s : s + ' '.repeat(width - w)
|
|
}
|
|
|
|
// Greedy word-wrap on whitespace runs. Lines that already fit pass through
|
|
// verbatim (preserving leading indent and any internal whitespace). Wrapping
|
|
// only kicks in when a line is wider than `width`; long single tokens get
|
|
// hard-broken so a long URL never overflows the box.
|
|
function wrapText(text: string, width: number): string[] {
|
|
if (cellWidth(text) <= width) return [text]
|
|
|
|
const out: string[] = []
|
|
const tokens = text.match(/\s+|\S+/g) ?? ['']
|
|
let line = ''
|
|
for (const tok of tokens) {
|
|
const isWs = /^\s/.test(tok)
|
|
const candidate = line + tok
|
|
if (cellWidth(candidate) <= width) {
|
|
line = candidate
|
|
continue
|
|
}
|
|
// Doesn't fit — flush current line and decide what to do with this token.
|
|
if (line) out.push(line)
|
|
if (isWs) {
|
|
// Discard the whitespace run at the wrap point; continuation starts flush.
|
|
line = ''
|
|
continue
|
|
}
|
|
// Non-whitespace token longer than width: hard-break.
|
|
let rest = tok
|
|
while (cellWidth(rest) > width) {
|
|
const arr = Array.from(rest)
|
|
out.push(arr.slice(0, width).join(''))
|
|
rest = arr.slice(width).join('')
|
|
}
|
|
line = rest
|
|
}
|
|
if (line) out.push(line)
|
|
if (out.length === 0) out.push('')
|
|
return out
|
|
}
|
|
|
|
function bannerTop(title?: string): string {
|
|
if (!title) return '┌' + '─'.repeat(BANNER_INNER) + '┐'
|
|
const head = '┌─ ' + title + ' '
|
|
const tail = BANNER_WIDTH - cellWidth(head) - 1
|
|
return head + '─'.repeat(Math.max(0, tail)) + '┐'
|
|
}
|
|
|
|
function bannerBottom(): string {
|
|
return '└' + '─'.repeat(BANNER_INNER) + '┘'
|
|
}
|
|
|
|
function bannerBlank(): string {
|
|
return '│' + ' '.repeat(BANNER_INNER) + '│'
|
|
}
|
|
|
|
function bannerRow(text: string): string[] {
|
|
return wrapText(text, BANNER_TEXT).map(
|
|
(line) => '│ ' + padRight(line, BANNER_TEXT) + ' │',
|
|
)
|
|
}
|
|
|
|
// Render a complete banner: top border, one or more content rows (text wrapped
|
|
// to fit; '' renders as a blank inner row), and bottom border. Every returned
|
|
// line is exactly BANNER_WIDTH cells wide.
|
|
export function renderBanner(
|
|
title: string | undefined,
|
|
lines: string[],
|
|
): string[] {
|
|
const out: string[] = [bannerTop(title)]
|
|
for (const l of lines) {
|
|
if (l === '') out.push(bannerBlank())
|
|
else out.push(...bannerRow(l))
|
|
}
|
|
out.push(bannerBottom())
|
|
return out
|
|
}
|