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
168 lines
5.7 KiB
JavaScript
168 lines
5.7 KiB
JavaScript
export const DEFAULT_APPLIANCE_ROOT = '/opt/alga-appliance';
|
|
export const DEFAULT_SETUP_PORT = 8080;
|
|
export const DEFAULT_K3S_KUBECONFIG = '/etc/rancher/k3s/k3s.yaml';
|
|
|
|
export const HOST_BOOTSTRAP_PHASES = Object.freeze([
|
|
{
|
|
id: 'substrate',
|
|
label: 'k3s substrate',
|
|
responsibilities: Object.freeze([
|
|
'install-or-start-k3s',
|
|
'wait-for-kubernetes-api'
|
|
])
|
|
},
|
|
{
|
|
id: 'assets',
|
|
label: 'baked control-plane assets',
|
|
responsibilities: Object.freeze([
|
|
'import-baked-image-archives',
|
|
'apply-local-storage-manifest',
|
|
'apply-control-plane-manifests'
|
|
])
|
|
},
|
|
{
|
|
id: 'handoff',
|
|
label: 'setup handoff',
|
|
responsibilities: Object.freeze([
|
|
'report-setup-url',
|
|
'report-fallback-command'
|
|
])
|
|
}
|
|
]);
|
|
|
|
export const CONTROL_PLANE_RESPONSIBILITIES = Object.freeze([
|
|
'serve-setup-ui',
|
|
'serve-status-api',
|
|
'validate-setup-inputs',
|
|
'persist-release-selection',
|
|
'configure-flux-source',
|
|
'configure-application-runtime-values',
|
|
'create-initial-tenant-admin-secret',
|
|
'trigger-application-bootstrap',
|
|
'report-application-status-and-blockers'
|
|
]);
|
|
|
|
export const HOST_BOOTSTRAP_FORBIDDEN_RESPONSIBILITIES = Object.freeze([
|
|
...CONTROL_PLANE_RESPONSIBILITIES,
|
|
'run-primary-host-setup-service',
|
|
'depend-on-github-before-setup-ui',
|
|
'depend-on-dns-before-setup-ui',
|
|
'depend-on-registry-pulls-before-setup-ui',
|
|
'depend-on-flux-before-setup-ui'
|
|
]);
|
|
|
|
function trimTrailingSlash(value) {
|
|
return value.replace(/\/+$/, '') || '/';
|
|
}
|
|
|
|
export function appliancePaths(options = {}) {
|
|
const root = trimTrailingSlash(options.applianceRoot || DEFAULT_APPLIANCE_ROOT);
|
|
return {
|
|
root,
|
|
localPathStorageManifest: `${root}/manifests/local-path-storage.yaml`,
|
|
controlPlaneManifestDir: `${root}/control-plane/manifests`,
|
|
controlPlaneImageDir: `${root}/control-plane/images`,
|
|
setupTokenFile: options.setupTokenFile || '/var/lib/alga-appliance/setup-token',
|
|
fallbackCommand: options.fallbackCommand || `${root}/bin/alga-control-plane-reapply`,
|
|
kubeconfig: options.kubeconfig || DEFAULT_K3S_KUBECONFIG
|
|
};
|
|
}
|
|
|
|
export function buildHostBootstrapPlan(options = {}) {
|
|
const paths = appliancePaths(options);
|
|
const port = Number(options.setupPort || DEFAULT_SETUP_PORT);
|
|
|
|
return {
|
|
boundaryVersion: 1,
|
|
setupPort: port,
|
|
paths,
|
|
phases: HOST_BOOTSTRAP_PHASES.map((phase) => ({ ...phase, responsibilities: [...phase.responsibilities] })),
|
|
commands: [
|
|
{
|
|
id: 'ensure-k3s',
|
|
phase: 'substrate',
|
|
action: 'install-or-start-k3s',
|
|
idempotent: true,
|
|
requiresNetwork: false,
|
|
description: 'Install k3s if absent, otherwise ensure the existing k3s service is running.'
|
|
},
|
|
{
|
|
id: 'wait-kubernetes-api',
|
|
phase: 'substrate',
|
|
action: 'wait-for-kubernetes-api',
|
|
idempotent: true,
|
|
requiresNetwork: false,
|
|
description: `Wait until kubectl can talk to ${paths.kubeconfig}.`
|
|
},
|
|
{
|
|
id: 'import-control-plane-images',
|
|
phase: 'assets',
|
|
action: 'import-baked-image-archives',
|
|
idempotent: true,
|
|
requiresNetwork: false,
|
|
path: paths.controlPlaneImageDir,
|
|
description: 'Import every baked control-plane image archive into k3s/containerd before applying workloads.'
|
|
},
|
|
{
|
|
id: 'apply-local-storage',
|
|
phase: 'assets',
|
|
action: 'apply-local-storage-manifest',
|
|
idempotent: true,
|
|
requiresNetwork: false,
|
|
path: paths.localPathStorageManifest,
|
|
description: 'Apply local-path storage from the installed appliance path.'
|
|
},
|
|
{
|
|
id: 'apply-control-plane',
|
|
phase: 'assets',
|
|
action: 'apply-control-plane-manifests',
|
|
idempotent: true,
|
|
requiresNetwork: false,
|
|
path: paths.controlPlaneManifestDir,
|
|
description: 'Apply the baked appliance control-plane namespace, RBAC, service, and workloads.'
|
|
},
|
|
{
|
|
id: 'report-setup-url',
|
|
phase: 'handoff',
|
|
action: 'report-setup-url',
|
|
idempotent: true,
|
|
requiresNetwork: false,
|
|
port,
|
|
tokenFile: paths.setupTokenFile,
|
|
description: 'Print and persist the tokenized setup URL for the Kubernetes-hosted control plane.'
|
|
},
|
|
{
|
|
id: 'report-fallback-command',
|
|
phase: 'handoff',
|
|
action: 'report-fallback-command',
|
|
idempotent: true,
|
|
requiresNetwork: false,
|
|
command: paths.fallbackCommand,
|
|
description: 'Print the local recovery command that reapplies the baked control-plane bundle.'
|
|
}
|
|
],
|
|
forbiddenHostResponsibilities: [...HOST_BOOTSTRAP_FORBIDDEN_RESPONSIBILITIES],
|
|
controlPlaneResponsibilities: [...CONTROL_PLANE_RESPONSIBILITIES]
|
|
};
|
|
}
|
|
|
|
export function assertHostBootstrapBoundary(plan = buildHostBootstrapPlan()) {
|
|
const commandActions = new Set((plan.commands || []).map((command) => command.action));
|
|
const forbidden = (plan.forbiddenHostResponsibilities || []).filter((responsibility) => commandActions.has(responsibility));
|
|
if (forbidden.length > 0) {
|
|
throw new Error(`Host bootstrap crosses the control-plane boundary: ${forbidden.join(', ')}`);
|
|
}
|
|
|
|
const networkedBeforeHandoff = (plan.commands || []).filter((command) => command.phase !== 'handoff' && command.requiresNetwork);
|
|
if (networkedBeforeHandoff.length > 0) {
|
|
throw new Error(`Host bootstrap requires network before setup UI: ${networkedBeforeHandoff.map((command) => command.id).join(', ')}`);
|
|
}
|
|
|
|
const nonIdempotent = (plan.commands || []).filter((command) => !command.idempotent);
|
|
if (nonIdempotent.length > 0) {
|
|
throw new Error(`Host bootstrap command is not idempotent: ${nonIdempotent.map((command) => command.id).join(', ')}`);
|
|
}
|
|
|
|
return true;
|
|
}
|