Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

123 lines
4.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const tokenPath = process.env.ALGA_APPLIANCE_TOKEN_FILE || '/var/lib/alga-appliance/setup-token';
const port = Number(process.env.ALGA_APPLIANCE_PORT || 8080);
const issueFile = process.env.ALGA_APPLIANCE_ISSUE_FILE || '/etc/issue';
const motdFile = process.env.ALGA_APPLIANCE_MOTD_FILE || '/etc/motd';
const runBannerFile = process.env.ALGA_APPLIANCE_RUN_BANNER_FILE || '/run/alga-appliance-setup.txt';
const buildInfoFile = process.env.ALGA_APPLIANCE_BUILD_INFO_FILE || '/etc/alga-appliance/build-info.json';
const consoleTtys = (process.env.ALGA_APPLIANCE_CONSOLE_TTYS || '')
.split(',')
.map((value) => value.trim())
.filter(Boolean);
function readJson(file) {
if (!fs.existsSync(file)) return null;
try {
return JSON.parse(fs.readFileSync(file, 'utf8'));
} catch {
return null;
}
}
function buildTimestampLine() {
const buildInfo = readJson(buildInfoFile);
if (buildInfo && typeof buildInfo.buildTimestamp === 'string' && buildInfo.buildTimestamp) {
return `Build timestamp: ${buildInfo.buildTimestamp}`;
}
return 'Build timestamp: unavailable';
}
function detectIp() {
let nets = {};
try {
nets = os.networkInterfaces();
} catch {
return '127.0.0.1';
}
for (const entries of Object.values(nets)) {
for (const addr of entries || []) {
if (addr.family === 'IPv4' && !addr.internal) {
return addr.address;
}
}
}
return '127.0.0.1';
}
const ip = detectIp();
const token = fs.existsSync(tokenPath) ? fs.readFileSync(tokenPath, 'utf8').trim() : '<pending>';
const lines = [
'Alga Appliance setup handoff',
`Node IP: ${ip}`,
buildTimestampLine(),
'Bootstrap layers:',
' 1. k3s substrate starting/ready on this host',
' 2. baked Kubernetes control plane applying from /opt/alga-appliance/control-plane',
' 3. setup UI served by the Kubernetes-hosted control plane',
`Setup URL: http://${ip}:${port}/`,
`One-time setup token: ${token}`,
'Enter this token once at the setup page, then choose a management password.',
'Web setup on port 8080 is the primary path.',
'',
'Sign in to this host with the account you created during installation.',
'Forgot the management password? sudo alga-appliance-reset-admin',
'',
'If the console is cleared, press Enter or reopen the VM console to redisplay this banner.',
'Control-plane recovery: sudo /opt/alga-appliance/bin/alga-control-plane-reapply',
'Console setup fallback: sudo /usr/bin/env node /opt/alga-appliance/host-service/console-setup.mjs',
'For logs: sudo journalctl -u alga-appliance-bootstrap.service -u alga-appliance-console.service -u alga-host-agent.service -u k3s -f'
];
const banner = lines.join('\n');
const beginMarker = '### BEGIN ALGA APPLIANCE SETUP ###';
const endMarker = '### END ALGA APPLIANCE SETUP ###';
function writeManagedBlock(file, content) {
try {
fs.mkdirSync(path.dirname(file), { recursive: true });
const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
const block = `${beginMarker}\n${content}\n${endMarker}`;
const pattern = new RegExp(`${beginMarker}[\\s\\S]*?${endMarker}`);
const next = pattern.test(existing)
? existing.replace(pattern, block)
: `${existing.replace(/\s*$/, '')}\n\n${block}\n`;
fs.writeFileSync(file, next, { mode: 0o644 });
} catch (error) {
process.stderr.write(`warning: unable to write ${file}: ${error instanceof Error ? error.message : String(error)}\n`);
}
}
function writePlainFile(file, content) {
try {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${content}\n`, { mode: 0o644 });
} catch (error) {
process.stderr.write(`warning: unable to write ${file}: ${error instanceof Error ? error.message : String(error)}\n`);
}
}
function writeConsoleTtys(content) {
const payload = `\n${content}\n\n`;
for (const tty of consoleTtys) {
try {
if (fs.existsSync(tty)) {
fs.appendFileSync(tty, payload);
}
} catch (error) {
process.stderr.write(`warning: unable to write ${tty}: ${error instanceof Error ? error.message : String(error)}\n`);
}
}
}
writeManagedBlock(issueFile, banner);
writeManagedBlock(motdFile, banner);
writePlainFile(runBannerFile, banner);
writeConsoleTtys(banner);
process.stdout.write(`\n${banner}\n\n`);