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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
103 lines
3.6 KiB
JavaScript
Executable File
103 lines
3.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
import fs from 'node:fs';
|
|
import http from 'node:http';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
const socketPath = process.env.ALGA_HOST_AGENT_SOCKET || '/run/alga-appliance/host-agent.sock';
|
|
const socketGid = Number(process.env.ALGA_HOST_AGENT_SOCKET_GID || 10001);
|
|
const commandTimeoutMs = Number(process.env.ALGA_HOST_AGENT_COMMAND_TIMEOUT_MS || 20000);
|
|
|
|
function redactText(value) {
|
|
return String(value)
|
|
.replace(/((?:token|password|secret|client[-_]?key(?:-data)?|authorization)\s*[:=]\s*)([^\s"']+)/ig, '$1[REDACTED]')
|
|
.replace(/("(?:[^"]*(?:token|password|secret|clientKey|client-key-data)[^"]*)"\s*:\s*")([^"]+)(")/ig, '$1[REDACTED]$3')
|
|
.replace(/(authorization\s*:\s*bearer\s+)([^\s"']+)/ig, '$1[REDACTED]')
|
|
.replace(/(kind:\s*Secret[\s\S]*?\ndata:\n)([\s\S]*?)(\n(?:---|apiVersion:|kind:|metadata:)|$)/ig, '$1 [REDACTED_SECRET_DATA]: [REDACTED]$3');
|
|
}
|
|
|
|
function runCapture(command) {
|
|
const result = spawnSync('sh', ['-c', command], {
|
|
encoding: 'utf8',
|
|
timeout: commandTimeoutMs,
|
|
env: process.env
|
|
});
|
|
const output = [`$ ${command}`, '', result.stdout || '', result.stderr || ''].filter(Boolean).join('\n').trim();
|
|
return {
|
|
ok: result.status === 0 && !result.error,
|
|
status: result.status === null ? (result.error ? 124 : 1) : result.status,
|
|
content: redactText(output || `$ ${command}\n(no output)`)
|
|
};
|
|
}
|
|
|
|
function supportBundlePayload() {
|
|
const captures = [
|
|
['host/appliance-journal.txt', 'journalctl -u alga-appliance-bootstrap.service -u alga-appliance.service -u alga-appliance-console.service -u k3s -n 1000 --no-pager'],
|
|
['host/k3s-service-status.txt', 'systemctl status k3s --no-pager'],
|
|
['host/appliance-bootstrap-status.txt', 'systemctl status alga-appliance-bootstrap.service --no-pager'],
|
|
['host/appliance-console-status.txt', 'systemctl status alga-appliance-console.service --no-pager'],
|
|
['host/disk-usage.txt', 'df -h'],
|
|
['host/ip-addresses.txt', 'ip addr'],
|
|
['host/routes.txt', 'ip route'],
|
|
['host/resolv-conf.txt', 'cat /etc/resolv.conf'],
|
|
['host/dns-lookup-ghcr.txt', 'getent hosts ghcr.io'],
|
|
['host/https-ghcr.txt', 'curl -I --max-time 10 https://ghcr.io/v2/']
|
|
];
|
|
|
|
return {
|
|
ok: true,
|
|
generatedAt: new Date().toISOString(),
|
|
agent: 'alga-host-agent',
|
|
files: captures.map(([filePath, command]) => Object.assign({
|
|
path: filePath
|
|
}, runCapture(command)))
|
|
};
|
|
}
|
|
|
|
function json(res, statusCode, body) {
|
|
res.writeHead(statusCode, { 'content-type': 'application/json; charset=utf-8' });
|
|
res.end(`${JSON.stringify(body)}\n`);
|
|
}
|
|
|
|
const server = http.createServer((req, res) => {
|
|
if (req.method === 'GET' && req.url === '/v1/health') {
|
|
json(res, 200, { ok: true, service: 'alga-host-agent' });
|
|
return;
|
|
}
|
|
|
|
if (req.method === 'POST' && req.url === '/v1/support-bundle') {
|
|
json(res, 200, supportBundlePayload());
|
|
return;
|
|
}
|
|
|
|
json(res, 404, { ok: false, error: 'not found' });
|
|
});
|
|
|
|
fs.mkdirSync(path.dirname(socketPath), { recursive: true, mode: 0o755 });
|
|
try {
|
|
fs.unlinkSync(socketPath);
|
|
} catch (error) {
|
|
if (error.code !== 'ENOENT') throw error;
|
|
}
|
|
|
|
server.listen(socketPath, () => {
|
|
fs.chmodSync(socketPath, 0o660);
|
|
try {
|
|
fs.chownSync(socketPath, 0, socketGid);
|
|
} catch {
|
|
// Best effort. Socket mode still limits access according to host ownership.
|
|
}
|
|
// eslint-disable-next-line no-console
|
|
console.log(`alga-host-agent listening on ${socketPath}`);
|
|
});
|
|
|
|
function shutdown() {
|
|
server.close(() => {
|
|
try { fs.unlinkSync(socketPath); } catch {}
|
|
process.exit(0);
|
|
});
|
|
}
|
|
|
|
process.on('SIGTERM', shutdown);
|
|
process.on('SIGINT', shutdown);
|