Document the FreeBSD 15 mac_do rule shape and expose soft setup verification for module/rule state without enforcing live host changes. --- Build: pass | Tests: pass — 2373 passed (704 files)
734 lines
22 KiB
TypeScript
734 lines
22 KiB
TypeScript
/**
|
|
* Step: verify — End-to-end health check of the full installation.
|
|
* Replaces 09-verify.sh
|
|
*
|
|
* Uses Postgres pool directly, platform-aware service checks.
|
|
*/
|
|
import { SERVICE_NAME } from '../src/platform-identity.js';
|
|
import { execSync } from 'child_process';
|
|
import fs from 'fs';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
|
|
import pg from 'pg';
|
|
|
|
import {
|
|
AGENT_CONFIG_DIR,
|
|
CMS_WEBROOT,
|
|
CODE_HOSTING_MODE,
|
|
FEATURE_GITEA,
|
|
FEATURE_GIT,
|
|
FEATURE_LLAMA_CPP,
|
|
FEATURE_OLLAMA,
|
|
LOCAL_LLM_PROVIDER,
|
|
GIT_DEFAULT_REPO_NAME,
|
|
GIT_STORAGE_ROOT,
|
|
OPS_DB_URL,
|
|
PLATFORM_RUNTIME_HOME,
|
|
STORE_DIR,
|
|
TENANT_ID,
|
|
GIT_JAIL_NAME,
|
|
} from '../src/config.js';
|
|
import { logger } from '../src/logger.js';
|
|
import { getTenantSiteAvailability } from '../src/site-availability.js';
|
|
import { collectSplitBrainStatus } from '../src/split-brain-status.js';
|
|
import {
|
|
deriveStripeStatus,
|
|
getStripeKeyMode,
|
|
type StripeKeyMode,
|
|
type StripeStatus,
|
|
} from '../src/stripe-config.js';
|
|
import { loadTenantSitePublishStatus } from '../src/tenant-site-publish.js';
|
|
import {
|
|
loadTenantRegistry,
|
|
type PlatformRegistry,
|
|
type TenantSiteRecord,
|
|
} from '../src/tenant-registry.js';
|
|
import { commandExists, getPlatform } from './platform.js';
|
|
import { loadPackageList } from './packages.js';
|
|
import { emitStatus } from './status.js';
|
|
|
|
const WORKER_PACKAGES = loadPackageList('worker-jail.txt');
|
|
const GIT_PACKAGES = loadPackageList('git-jail.txt');
|
|
const OLLAMA_PACKAGES = loadPackageList('ollama-jail.txt');
|
|
const LLAMA_CPP_PACKAGES = loadPackageList('llama-cpp-jail.txt');
|
|
|
|
export function getVerifyServicePidCandidates(projectRoot: string): string[] {
|
|
return [
|
|
path.join('/var/run', `${SERVICE_NAME}.pid`),
|
|
path.join(projectRoot, `${SERVICE_NAME}.pid`),
|
|
];
|
|
}
|
|
|
|
export function getMountAllowlistHomes(currentHome: string): string[] {
|
|
return [
|
|
...new Set([
|
|
currentHome,
|
|
PLATFORM_RUNTIME_HOME,
|
|
path.join('/home', TENANT_ID),
|
|
]),
|
|
];
|
|
}
|
|
|
|
export interface MacDoVerification {
|
|
module: 'loaded' | 'not_loaded' | 'unknown' | 'not_applicable';
|
|
rules: 'empty' | 'configured' | 'unavailable' | 'not_applicable';
|
|
}
|
|
|
|
export function verifyMacDoState(
|
|
platform: string,
|
|
execImpl: typeof execSync = execSync,
|
|
): MacDoVerification {
|
|
if (platform !== 'freebsd') {
|
|
return { module: 'not_applicable', rules: 'not_applicable' };
|
|
}
|
|
|
|
let module: MacDoVerification['module'] = 'unknown';
|
|
try {
|
|
execImpl('/sbin/kldstat -m mac_do', { stdio: 'ignore' });
|
|
module = 'loaded';
|
|
} catch {
|
|
module = 'not_loaded';
|
|
}
|
|
|
|
let rules: MacDoVerification['rules'] = 'unavailable';
|
|
try {
|
|
const output = execImpl('/sbin/sysctl -n security.mac.do.rules', {
|
|
encoding: 'utf-8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
});
|
|
rules = output.trim().length === 0 ? 'empty' : 'configured';
|
|
} catch {
|
|
rules = 'unavailable';
|
|
}
|
|
|
|
return { module, rules };
|
|
}
|
|
|
|
export interface TenantSitePublishVerification {
|
|
state:
|
|
| 'not_declared'
|
|
| 'planned_only'
|
|
| 'partial'
|
|
| 'available'
|
|
| 'inconsistent';
|
|
declaredSites: number;
|
|
enabledSites: number;
|
|
availableSites: number;
|
|
plannedSites: number;
|
|
disabledSites: number;
|
|
manifestMissing: number;
|
|
staleManifest: number;
|
|
manifestMismatch: number;
|
|
}
|
|
|
|
function siteManifestMatches(
|
|
manifest: ReturnType<typeof loadTenantSitePublishStatus>,
|
|
tenantId: string,
|
|
site: TenantSiteRecord,
|
|
availability: ReturnType<typeof getTenantSiteAvailability>,
|
|
): boolean {
|
|
if (!manifest) return false;
|
|
return (
|
|
manifest.result === 'published' &&
|
|
manifest.tenantId === tenantId &&
|
|
manifest.siteId === site.id &&
|
|
manifest.siteFqdn === site.fqdn &&
|
|
manifest.targetDir === availability.outputDir &&
|
|
manifest.targetIndex === availability.outputIndex
|
|
);
|
|
}
|
|
|
|
export function verifyTenantSitePublishState(
|
|
registry: PlatformRegistry,
|
|
webroot: string,
|
|
existsSyncImpl: (targetPath: string) => boolean = fs.existsSync,
|
|
loadStatusImpl: (
|
|
root: string,
|
|
tenantId: string,
|
|
siteId: string,
|
|
) => ReturnType<typeof loadTenantSitePublishStatus> = loadTenantSitePublishStatus,
|
|
): TenantSitePublishVerification {
|
|
let declaredSites = 0;
|
|
let enabledSites = 0;
|
|
let availableSites = 0;
|
|
let plannedSites = 0;
|
|
let disabledSites = 0;
|
|
let manifestMissing = 0;
|
|
let staleManifest = 0;
|
|
let manifestMismatch = 0;
|
|
|
|
for (const tenant of Object.values(registry.tenants)) {
|
|
for (const site of tenant.sites) {
|
|
declaredSites += 1;
|
|
if (site.exposure === 'disabled') {
|
|
disabledSites += 1;
|
|
continue;
|
|
}
|
|
enabledSites += 1;
|
|
const availability = getTenantSiteAvailability(
|
|
webroot,
|
|
tenant.id,
|
|
site.id,
|
|
existsSyncImpl,
|
|
);
|
|
const manifest = loadStatusImpl(webroot, tenant.id, site.id);
|
|
const hasManifest = !!manifest;
|
|
const manifestOk = siteManifestMatches(
|
|
manifest,
|
|
tenant.id,
|
|
site,
|
|
availability,
|
|
);
|
|
|
|
if (availability.hasOutput) {
|
|
availableSites += 1;
|
|
if (!hasManifest) {
|
|
manifestMissing += 1;
|
|
} else if (!manifestOk) {
|
|
manifestMismatch += 1;
|
|
}
|
|
} else {
|
|
plannedSites += 1;
|
|
if (hasManifest) {
|
|
staleManifest += 1;
|
|
if (!manifestOk) {
|
|
manifestMismatch += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const inconsistent =
|
|
manifestMissing > 0 || staleManifest > 0 || manifestMismatch > 0;
|
|
const state: TenantSitePublishVerification['state'] =
|
|
enabledSites === 0
|
|
? 'not_declared'
|
|
: inconsistent
|
|
? 'inconsistent'
|
|
: availableSites === 0
|
|
? 'planned_only'
|
|
: plannedSites === 0
|
|
? 'available'
|
|
: 'partial';
|
|
|
|
return {
|
|
state,
|
|
declaredSites,
|
|
enabledSites,
|
|
availableSites,
|
|
plannedSites,
|
|
disabledSites,
|
|
manifestMissing,
|
|
staleManifest,
|
|
manifestMismatch,
|
|
};
|
|
}
|
|
|
|
export async function run(_args: string[]): Promise<void> {
|
|
const projectRoot = process.cwd();
|
|
const platform = getPlatform();
|
|
const homeDir = os.homedir();
|
|
|
|
logger.info('Starting verification');
|
|
|
|
// 1. Check service status
|
|
let service = 'not_found';
|
|
const pidCandidates = getVerifyServicePidCandidates(projectRoot);
|
|
for (const pidFile of pidCandidates) {
|
|
if (!fs.existsSync(pidFile)) continue;
|
|
try {
|
|
const pid = fs.readFileSync(pidFile, 'utf-8').trim();
|
|
if (pid) {
|
|
execSync(`kill -0 ${pid}`, { stdio: 'ignore' });
|
|
service = 'running';
|
|
}
|
|
} catch {
|
|
service = 'stopped';
|
|
}
|
|
break;
|
|
}
|
|
logger.info({ service }, 'Service status');
|
|
|
|
const macDo = verifyMacDoState(platform);
|
|
|
|
// 2. Check jail runtime tools
|
|
let jailRuntime = 'missing';
|
|
try {
|
|
execSync('command -v bastille', { stdio: 'ignore' });
|
|
execSync('command -v jls', { stdio: 'ignore' });
|
|
jailRuntime = 'available';
|
|
} catch {
|
|
// No jail toolchain
|
|
}
|
|
|
|
// 3. Check credentials
|
|
let credentials = 'missing';
|
|
const envFile = path.join(projectRoot, '.env');
|
|
if (fs.existsSync(envFile)) {
|
|
const envContent = fs.readFileSync(envFile, 'utf-8');
|
|
if (
|
|
/^(ANTHROPIC_API_KEY|OPENAI_API_KEY|AZURE_OPENAI_API_KEY|GEMINI_API_KEY|GROQ_API_KEY|CEREBRAS_API_KEY|XAI_API_KEY|OPENROUTER_API_KEY|AI_GATEWAY_API_KEY|ZAI_API_KEY|MISTRAL_API_KEY|MINIMAX_API_KEY|OPENCODE_API_KEY|KIMI_API_KEY|AWS_BEARER_TOKEN_BEDROCK|AWS_ACCESS_KEY_ID)=/m.test(
|
|
envContent,
|
|
)
|
|
) {
|
|
credentials = 'configured';
|
|
}
|
|
}
|
|
|
|
// 4. Check Telegram bot configuration
|
|
let telegramAuth = 'not_configured';
|
|
let displayLocale = 'unset';
|
|
let timeZone = 'unset';
|
|
let stripe: StripeStatus = 'disabled';
|
|
let stripeKeyMode: StripeKeyMode = 'missing';
|
|
let stripeRefunds = 'no';
|
|
const stripeRuntimePresent = fs.existsSync(
|
|
path.join(projectRoot, 'jail', 'agent-runner', 'src', 'stripe-tools.ts'),
|
|
);
|
|
let stripeRuntime = stripeRuntimePresent ? 'present' : 'missing';
|
|
let cmsEnabled = 'no';
|
|
let cmsJailName = 'cms';
|
|
let cmsJail = 'not_configured';
|
|
let cmsNginx = 'not_configured';
|
|
let cmsAstro = 'not_configured';
|
|
let cmsStrapi = 'not_configured';
|
|
let cmsScreenshots = 'not_configured';
|
|
let featureGit = FEATURE_GIT ? 'YES' : 'NO';
|
|
let featureGitea = FEATURE_GITEA ? 'YES' : 'NO';
|
|
let codeHostingMode = CODE_HOSTING_MODE;
|
|
let gitJailName = GIT_JAIL_NAME;
|
|
let gitJail = 'not_configured';
|
|
let gitPackages = 'not_configured';
|
|
let gitStorage = 'not_configured';
|
|
let gitRepo = 'not_configured';
|
|
let gitStorageRoot = GIT_STORAGE_ROOT;
|
|
let gitDefaultRepoName = GIT_DEFAULT_REPO_NAME;
|
|
let forgejoService = 'not_configured';
|
|
let localLlmProvider = LOCAL_LLM_PROVIDER;
|
|
let featureOllama = FEATURE_OLLAMA ? 'YES' : 'NO';
|
|
let featureLlamaCpp = FEATURE_LLAMA_CPP ? 'YES' : 'NO';
|
|
let ollamaJailName = 'ollama';
|
|
let llamaCppJailName = 'llamacpp';
|
|
let ollamaJail = 'not_configured';
|
|
let ollamaPackages = 'not_configured';
|
|
let llamaCppJail = 'not_configured';
|
|
let llamaCppPackages = 'not_configured';
|
|
let envContent = '';
|
|
if (fs.existsSync(envFile)) {
|
|
envContent = fs.readFileSync(envFile, 'utf-8');
|
|
if (/^TELEGRAM_BOT_TOKEN=.+/m.test(envContent)) {
|
|
telegramAuth = 'configured';
|
|
}
|
|
const stripeKey =
|
|
envContent.match(/^STRIPE_SECRET_KEY=(.+)$/m)?.[1]?.trim() || '';
|
|
stripeKeyMode = getStripeKeyMode(stripeKey);
|
|
stripe = deriveStripeStatus(stripeKeyMode, stripeRuntimePresent);
|
|
stripeRefunds =
|
|
envContent.match(/^STRIPE_ENABLE_REFUNDS=(.+)$/m)?.[1]?.trim() || 'no';
|
|
displayLocale =
|
|
envContent.match(/^DISPLAY_LOCALE=(.+)$/m)?.[1]?.trim() || 'unset';
|
|
timeZone = envContent.match(/^TZ=(.+)$/m)?.[1]?.trim() || 'unset';
|
|
cmsEnabled = envContent.match(/^CMS_ENABLE=(.+)$/m)?.[1]?.trim() || 'yes';
|
|
cmsJailName =
|
|
envContent.match(/^CMS_JAIL_NAME=(.+)$/m)?.[1]?.trim() || cmsJailName;
|
|
featureGit =
|
|
envContent.match(/^FEATURE_GIT=(.+)$/m)?.[1]?.trim() || featureGit;
|
|
featureGitea =
|
|
envContent.match(/^FEATURE_GITEA=(.+)$/m)?.[1]?.trim() || featureGitea;
|
|
codeHostingMode =
|
|
envContent.match(/^CODE_HOSTING_MODE=(.+)$/m)?.[1]?.trim() ||
|
|
codeHostingMode;
|
|
localLlmProvider =
|
|
envContent.match(/^LOCAL_LLM_PROVIDER=(.+)$/m)?.[1]?.trim() ||
|
|
localLlmProvider;
|
|
featureOllama =
|
|
envContent.match(/^FEATURE_OLLAMA=(.+)$/m)?.[1]?.trim() || featureOllama;
|
|
featureLlamaCpp =
|
|
envContent.match(/^FEATURE_LLAMA_CPP=(.+)$/m)?.[1]?.trim() ||
|
|
featureLlamaCpp;
|
|
ollamaJailName =
|
|
envContent.match(/^OLLAMA_JAIL_NAME=(.+)$/m)?.[1]?.trim() ||
|
|
ollamaJailName;
|
|
llamaCppJailName =
|
|
envContent.match(/^LLAMA_CPP_JAIL_NAME=(.+)$/m)?.[1]?.trim() ||
|
|
llamaCppJailName;
|
|
gitJailName =
|
|
envContent.match(/^GIT_JAIL_NAME=(.+)$/m)?.[1]?.trim() || gitJailName;
|
|
gitStorageRoot =
|
|
envContent.match(/^GIT_STORAGE_ROOT=(.+)$/m)?.[1]?.trim() ||
|
|
gitStorageRoot;
|
|
gitDefaultRepoName =
|
|
envContent.match(/^GIT_DEFAULT_REPO_NAME=(.+)$/m)?.[1]?.trim() ||
|
|
gitDefaultRepoName;
|
|
}
|
|
|
|
// 5. Check registered groups (via Postgres)
|
|
let registeredGroups = 0;
|
|
try {
|
|
const pool = new pg.Pool({ connectionString: OPS_DB_URL, max: 3 });
|
|
const { rows } = await pool.query(
|
|
'SELECT COUNT(*) as count FROM registered_groups',
|
|
);
|
|
registeredGroups = parseInt((rows[0] as { count: string }).count, 10);
|
|
await pool.end();
|
|
} catch {
|
|
// Table might not exist
|
|
}
|
|
const groupRegistration = registeredGroups > 0 ? 'configured' : 'pending';
|
|
|
|
// 6. Check mount allowlist
|
|
let mountAllowlist = 'missing';
|
|
const allowlistHomes = getMountAllowlistHomes(homeDir);
|
|
for (const baseDir of allowlistHomes) {
|
|
if (
|
|
fs.existsSync(
|
|
path.join(baseDir, '.config', AGENT_CONFIG_DIR, 'mount-allowlist.json'),
|
|
)
|
|
) {
|
|
mountAllowlist = 'configured';
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 7. Check host/jail package baseline and CMS runtime
|
|
const rsyncTool = commandExists('rsync') ? 'available' : 'missing';
|
|
let workerJailName = 'worker';
|
|
let workerJailPackages = 'unknown';
|
|
if (platform === 'freebsd') {
|
|
try {
|
|
const jails = execSync('jls -N name', {
|
|
encoding: 'utf-8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
});
|
|
const jailList = jails.split('\n').map((line) => line.trim());
|
|
if (/^(YES|yes|true|TRUE|1)$/u.test(cmsEnabled)) {
|
|
cmsJailName = 'cms';
|
|
}
|
|
|
|
if (jailList.some((line) => line === workerJailName)) {
|
|
try {
|
|
for (const pkg of WORKER_PACKAGES) {
|
|
if (pkg === 'rsync') {
|
|
execSync(`bastille cmd ${workerJailName} command -v rsync`, {
|
|
stdio: 'ignore',
|
|
});
|
|
continue;
|
|
}
|
|
execSync(`bastille cmd ${workerJailName} pkg info ${pkg}`, {
|
|
stdio: 'ignore',
|
|
});
|
|
}
|
|
workerJailPackages = 'available';
|
|
} catch {
|
|
workerJailPackages = 'missing';
|
|
}
|
|
} else {
|
|
workerJailPackages = 'not_running';
|
|
}
|
|
|
|
if (
|
|
/^(YES|yes|true|TRUE|1)$/u.test(featureGit) ||
|
|
codeHostingMode === 'git' ||
|
|
codeHostingMode === 'gitea'
|
|
) {
|
|
if (jailList.some((line) => line === gitJailName)) {
|
|
gitJail = 'running';
|
|
try {
|
|
for (const pkg of GIT_PACKAGES) {
|
|
if (pkg === 'rsync') {
|
|
execSync(`bastille cmd ${gitJailName} command -v rsync`, {
|
|
stdio: 'ignore',
|
|
});
|
|
continue;
|
|
}
|
|
execSync(`bastille cmd ${gitJailName} pkg info ${pkg}`, {
|
|
stdio: 'ignore',
|
|
});
|
|
}
|
|
gitPackages = 'available';
|
|
} catch {
|
|
gitPackages = 'missing';
|
|
}
|
|
|
|
try {
|
|
execSync(`bastille cmd ${gitJailName} test -d ${gitStorageRoot}`, {
|
|
stdio: 'ignore',
|
|
});
|
|
gitStorage = 'available';
|
|
} catch {
|
|
gitStorage = 'missing';
|
|
}
|
|
|
|
try {
|
|
execSync(
|
|
`bastille cmd ${gitJailName} git --git-dir ${path.posix.join(gitStorageRoot, gitDefaultRepoName)} rev-parse --is-bare-repository`,
|
|
{ stdio: 'ignore' },
|
|
);
|
|
gitRepo = 'available';
|
|
} catch {
|
|
gitRepo = 'missing';
|
|
}
|
|
} else {
|
|
gitJail = 'missing';
|
|
gitPackages = 'missing';
|
|
gitStorage = 'missing';
|
|
gitRepo = 'missing';
|
|
}
|
|
}
|
|
|
|
if (
|
|
/^(YES|yes|true|TRUE|1)$/u.test(featureOllama) ||
|
|
localLlmProvider === 'ollama'
|
|
) {
|
|
if (jailList.some((line) => line === ollamaJailName)) {
|
|
ollamaJail = 'running';
|
|
try {
|
|
for (const pkg of OLLAMA_PACKAGES) {
|
|
execSync(`bastille cmd ${ollamaJailName} pkg info ${pkg}`, {
|
|
stdio: 'ignore',
|
|
});
|
|
}
|
|
ollamaPackages = 'available';
|
|
} catch {
|
|
ollamaPackages = 'missing';
|
|
}
|
|
} else {
|
|
ollamaJail = 'missing';
|
|
ollamaPackages = 'missing';
|
|
}
|
|
}
|
|
|
|
if (
|
|
/^(YES|yes|true|TRUE|1)$/u.test(featureLlamaCpp) ||
|
|
localLlmProvider === 'llama_cpp'
|
|
) {
|
|
if (jailList.some((line) => line === llamaCppJailName)) {
|
|
llamaCppJail = 'running';
|
|
try {
|
|
for (const pkg of LLAMA_CPP_PACKAGES) {
|
|
execSync(`bastille cmd ${llamaCppJailName} pkg info ${pkg}`, {
|
|
stdio: 'ignore',
|
|
});
|
|
}
|
|
llamaCppPackages = 'available';
|
|
} catch {
|
|
llamaCppPackages = 'missing';
|
|
}
|
|
} else {
|
|
llamaCppJail = 'missing';
|
|
llamaCppPackages = 'missing';
|
|
}
|
|
}
|
|
|
|
// Forgejo web UI check (inside git jail)
|
|
if (
|
|
codeHostingMode === 'gitea' ||
|
|
/^(YES|yes|true|TRUE|1)$/u.test(featureGitea)
|
|
) {
|
|
if (gitJail === 'running') {
|
|
try {
|
|
execSync(`bastille cmd ${gitJailName} service forgejo onestatus`, {
|
|
stdio: 'ignore',
|
|
});
|
|
forgejoService = 'running';
|
|
} catch {
|
|
forgejoService = 'stopped';
|
|
}
|
|
} else {
|
|
forgejoService = 'jail_missing';
|
|
}
|
|
}
|
|
|
|
if (/^(YES|yes|true|TRUE|1)$/u.test(cmsEnabled)) {
|
|
if (jailList.some((line) => line === cmsJailName)) {
|
|
cmsJail = 'running';
|
|
try {
|
|
execSync(`bastille cmd ${cmsJailName} service nginx onestatus`, {
|
|
stdio: 'ignore',
|
|
});
|
|
cmsNginx = 'running';
|
|
} catch {
|
|
cmsNginx = 'missing';
|
|
}
|
|
|
|
try {
|
|
execSync(`bastille cmd ${cmsJailName} test -f ${CMS_WEBROOT}/index.html`, {
|
|
stdio: 'ignore',
|
|
});
|
|
cmsAstro = 'available';
|
|
} catch {
|
|
cmsAstro = 'missing';
|
|
}
|
|
|
|
try {
|
|
execSync(`bastille cmd ${cmsJailName} service strapi onestatus`, {
|
|
stdio: 'ignore',
|
|
});
|
|
execSync(
|
|
`bastille cmd ${cmsJailName} test -f /home/${SERVICE_NAME}/strapi/package.json`,
|
|
{ stdio: 'ignore' },
|
|
);
|
|
cmsStrapi = 'running';
|
|
} catch {
|
|
cmsStrapi = 'missing';
|
|
}
|
|
|
|
try {
|
|
execSync(
|
|
`bastille cmd ${cmsJailName} test -d ${CMS_WEBROOT}/screenshots`,
|
|
{ stdio: 'ignore' },
|
|
);
|
|
cmsScreenshots = 'available';
|
|
} catch {
|
|
cmsScreenshots = 'missing';
|
|
}
|
|
} else {
|
|
cmsJail = 'missing';
|
|
cmsNginx = 'missing';
|
|
cmsAstro = 'missing';
|
|
cmsStrapi = 'missing';
|
|
cmsScreenshots = 'missing';
|
|
}
|
|
}
|
|
} catch {
|
|
workerJailPackages = 'unknown';
|
|
if (/^(YES|yes|true|TRUE|1)$/u.test(cmsEnabled)) {
|
|
cmsJail = 'unknown';
|
|
cmsNginx = 'unknown';
|
|
cmsAstro = 'unknown';
|
|
cmsStrapi = 'unknown';
|
|
cmsScreenshots = 'unknown';
|
|
}
|
|
}
|
|
} else {
|
|
workerJailPackages = 'not_applicable';
|
|
cmsJail = 'not_applicable';
|
|
cmsNginx = 'not_applicable';
|
|
cmsAstro = 'not_applicable';
|
|
cmsStrapi = 'not_applicable';
|
|
cmsScreenshots = 'not_applicable';
|
|
}
|
|
|
|
const splitBrain = await collectSplitBrainStatus();
|
|
const skillsArtifactExpected =
|
|
splitBrain.skillsArtifactSql === 'present' &&
|
|
splitBrain.skillsArtifactMetadata === 'present';
|
|
const skillsArtifactHealthy =
|
|
!skillsArtifactExpected || splitBrain.skillsArtifact === 'ready';
|
|
const tenantSitePublish = verifyTenantSitePublishState(
|
|
loadTenantRegistry(),
|
|
CMS_WEBROOT,
|
|
);
|
|
|
|
// Determine overall status
|
|
const cmsStrapiOk = ['running', 'missing', 'not_configured'].includes(
|
|
cmsStrapi,
|
|
);
|
|
const cmsHealthy =
|
|
!/^(YES|yes|true|TRUE|1)$/u.test(cmsEnabled) ||
|
|
(cmsJail === 'running' &&
|
|
cmsNginx === 'running' &&
|
|
cmsAstro === 'available' &&
|
|
cmsStrapiOk);
|
|
const gitHealthy =
|
|
(!/^(YES|yes|true|TRUE|1)$/u.test(featureGit) &&
|
|
!['git', 'gitea'].includes(codeHostingMode)) ||
|
|
(gitJail === 'running' &&
|
|
gitPackages === 'available' &&
|
|
gitStorage === 'available' &&
|
|
gitRepo === 'available');
|
|
const status =
|
|
service === 'running' &&
|
|
credentials !== 'missing' &&
|
|
telegramAuth !== 'not_configured' &&
|
|
stripe !== 'invalid' &&
|
|
stripe !== 'misconfigured' &&
|
|
rsyncTool === 'available' &&
|
|
splitBrain.skillsDb === 'available' &&
|
|
skillsArtifactHealthy &&
|
|
splitBrain.skillsRuntimeLookup === 'present' &&
|
|
splitBrain.memoryDb === 'available' &&
|
|
tenantSitePublish.state !== 'inconsistent' &&
|
|
(workerJailPackages === 'available' ||
|
|
workerJailPackages === 'not_applicable') &&
|
|
gitHealthy &&
|
|
cmsHealthy
|
|
? 'success'
|
|
: 'failed';
|
|
|
|
logger.info({ status }, 'Verification complete');
|
|
|
|
emitStatus('VERIFY', {
|
|
SERVICE: service,
|
|
JAIL_RUNTIME: jailRuntime,
|
|
MAC_DO_MODULE: macDo.module,
|
|
MAC_DO_RULES: macDo.rules,
|
|
CREDENTIALS: credentials,
|
|
TELEGRAM_AUTH: telegramAuth,
|
|
STRIPE: stripe,
|
|
STRIPE_KEY_MODE: stripeKeyMode,
|
|
STRIPE_REFUNDS: stripeRefunds,
|
|
STRIPE_RUNTIME: stripeRuntime,
|
|
DISPLAY_LOCALE: displayLocale,
|
|
TZ: timeZone,
|
|
REGISTERED_GROUPS: registeredGroups,
|
|
GROUP_REGISTRATION: groupRegistration,
|
|
MOUNT_ALLOWLIST: mountAllowlist,
|
|
RSYNC: rsyncTool,
|
|
WORKER_JAIL_PACKAGES: workerJailPackages,
|
|
WORKER_JAIL_RSYNC: workerJailPackages,
|
|
FEATURE_GIT: featureGit,
|
|
CODE_HOSTING_MODE: codeHostingMode,
|
|
LOCAL_LLM_PROVIDER: localLlmProvider,
|
|
FEATURE_OLLAMA: featureOllama,
|
|
FEATURE_LLAMA_CPP: featureLlamaCpp,
|
|
OLLAMA_JAIL: ollamaJail,
|
|
OLLAMA_JAIL_NAME: ollamaJailName,
|
|
OLLAMA_PACKAGES: ollamaPackages,
|
|
LLAMA_CPP_JAIL: llamaCppJail,
|
|
LLAMA_CPP_JAIL_NAME: llamaCppJailName,
|
|
LLAMA_CPP_PACKAGES: llamaCppPackages,
|
|
GIT_JAIL: gitJail,
|
|
GIT_JAIL_NAME: gitJailName,
|
|
GIT_PACKAGES: gitPackages,
|
|
GIT_STORAGE: gitStorage,
|
|
GIT_REPO: gitRepo,
|
|
FORGEJO_SERVICE: forgejoService,
|
|
SKILLS_DB: splitBrain.skillsDb,
|
|
SKILLS_ARTIFACT_SQL: splitBrain.skillsArtifactSql,
|
|
SKILLS_ARTIFACT_METADATA: splitBrain.skillsArtifactMetadata,
|
|
SKILLS_ARTIFACT_EXPECTED: skillsArtifactExpected ? 'yes' : 'no',
|
|
SKILLS_ARTIFACT: splitBrain.skillsArtifact,
|
|
SKILLS_ARTIFACT_VERSION: splitBrain.skillsArtifactVersion,
|
|
SKILLS_ARTIFACT_DB_VERSION: splitBrain.skillsArtifactDbVersion,
|
|
SKILLS_RUNTIME_LOOKUP: splitBrain.skillsRuntimeLookup,
|
|
SKILLS_ARTIFACT_ROWS: splitBrain.skillsArtifactRows,
|
|
SKILLS_DOCUMENT_ROWS: splitBrain.skillsDocumentRows,
|
|
SKILLS_CHUNK_ROWS: splitBrain.skillsChunkRows,
|
|
MEMORY_DB: splitBrain.memoryDb,
|
|
MEMORY_ROWS: splitBrain.memoryRows,
|
|
MEMORY_CHUNK_ROWS: splitBrain.memoryChunkRows,
|
|
MEMORY_EMBEDDING_ROWS: splitBrain.memoryEmbeddingRows,
|
|
CMS_ENABLE: cmsEnabled,
|
|
CMS_JAIL: cmsJail,
|
|
CMS_NGINX: cmsNginx,
|
|
CMS_ASTRO: cmsAstro,
|
|
CMS_STRAPI: cmsStrapi,
|
|
TENANT_SITE_PUBLISH: tenantSitePublish.state,
|
|
TENANT_SITES_DECLARED: tenantSitePublish.declaredSites,
|
|
TENANT_SITES_ENABLED: tenantSitePublish.enabledSites,
|
|
TENANT_SITES_AVAILABLE: tenantSitePublish.availableSites,
|
|
TENANT_SITES_PLANNED: tenantSitePublish.plannedSites,
|
|
TENANT_SITES_DISABLED: tenantSitePublish.disabledSites,
|
|
TENANT_SITE_MANIFEST_MISSING: tenantSitePublish.manifestMissing,
|
|
TENANT_SITE_MANIFEST_STALE: tenantSitePublish.staleManifest,
|
|
TENANT_SITE_MANIFEST_MISMATCH: tenantSitePublish.manifestMismatch,
|
|
CMS_SCREENSHOTS: cmsScreenshots,
|
|
STATUS: status,
|
|
LOG: 'logs/setup.log',
|
|
});
|
|
|
|
if (status === 'failed') process.exit(1);
|
|
}
|