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 }