@celilo/cli 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CELILO_CORE_MODULES.md +1 -0
- package/CELILO_SUBSYSTEMS.md +1 -1
- package/README.md +1 -1
- package/drizzle/0032_module_jail_policies.sql +28 -0
- package/drizzle/0033_build_bus_hook_runs.sql +37 -0
- package/drizzle/meta/_journal.json +15 -1
- package/package.json +3 -3
- package/schemas/system_config.json +5 -0
- package/src/__integration__/container-services-cli.integration.test.ts +1 -1
- package/src/ansible/inventory.test.ts +2 -1
- package/src/api/sessions.test.ts +5 -4
- package/src/api-clients/proxmox.ts +10 -7
- package/src/cli/backup-rename.test.ts +5 -4
- package/src/cli/cli.test.ts +3 -2
- package/src/cli/commands/console-get-chain.test.ts +2 -1
- package/src/cli/commands/events.test.ts +2 -2
- package/src/cli/commands/firewall-interface-list.test.ts +4 -3
- package/src/cli/commands/machine-list.test.ts +57 -0
- package/src/cli/commands/machine-list.ts +35 -4
- package/src/cli/commands/module-config.test.ts +2 -1
- package/src/cli/commands/module-deploy.ts +8 -2
- package/src/cli/commands/module-generate.test.ts +53 -0
- package/src/cli/commands/module-generate.ts +31 -26
- package/src/cli/commands/module-health.ts +1 -0
- package/src/cli/commands/module-import-aspect.test.ts +2 -1
- package/src/cli/commands/module-import-registry.test.ts +3 -2
- package/src/cli/commands/module-jail.test.ts +242 -0
- package/src/cli/commands/module-jail.ts +227 -0
- package/src/cli/commands/module-list-jail.test.ts +136 -0
- package/src/cli/commands/module-list.ts +30 -3
- package/src/cli/commands/module-operations.test.ts +2 -1
- package/src/cli/commands/module-publish.test.ts +11 -18
- package/src/cli/commands/module-update.test.ts +28 -12
- package/src/cli/commands/module-update.ts +4 -1
- package/src/cli/commands/module-upgrade.test.ts +130 -1
- package/src/cli/commands/module-upgrade.ts +77 -3
- package/src/cli/commands/module-verify.test.ts +3 -4
- package/src/cli/commands/module-verify.ts +0 -1
- package/src/cli/commands/notify-config.test.ts +5 -3
- package/src/cli/commands/publish/execute.ts +4 -1
- package/src/cli/commands/publish/index.ts +11 -1
- package/src/cli/commands/publish/module-registry.test.ts +24 -1
- package/src/cli/commands/publish/module-registry.ts +23 -4
- package/src/cli/commands/publish/plan.ts +1 -1
- package/src/cli/commands/publish/types.ts +7 -0
- package/src/cli/commands/registry-owner.test.ts +3 -2
- package/src/cli/commands/registry-token.test.ts +3 -2
- package/src/cli/commands/system-apply-config-equivalence.test.ts +5 -7
- package/src/cli/commands/system-audit.ts +20 -6
- package/src/cli/commands/system-config.test.ts +148 -0
- package/src/cli/commands/system-config.ts +26 -1
- package/src/cli/commands/system-doctor-remediation-gate.test.ts +295 -0
- package/src/cli/commands/system-doctor.test.ts +143 -6
- package/src/cli/commands/system-doctor.ts +142 -25
- package/src/cli/commands/system-init-deprecation.test.ts +7 -4
- package/src/cli/commands/system-migrate.test.ts +2 -1
- package/src/cli/commands/system-update.ts +5 -0
- package/src/cli/completion.ts +8 -2
- package/src/cli/flag-surface-gate.test.ts +279 -0
- package/src/cli/index.ts +70 -3
- package/src/cli/parser.test.ts +37 -1
- package/src/cli/restore-command.test.ts +5 -4
- package/src/cli/restore-migration-failure.test.ts +4 -3
- package/src/cli/tui/audit-state.ts +2 -0
- package/src/config/paths.test.ts +22 -22
- package/src/db/client.test.ts +46 -1
- package/src/db/client.ts +26 -0
- package/src/db/migrate.test.ts +2 -1
- package/src/db/migrate.ts +16 -16
- package/src/db/schema.ts +63 -0
- package/src/hooks/capability-loader-firewall.test.ts +9 -1
- package/src/hooks/capability-loader.test.ts +78 -0
- package/src/hooks/capability-loader.ts +45 -2
- package/src/hooks/executor.test.ts +161 -1
- package/src/hooks/executor.ts +87 -21
- package/src/hooks/hook-jail-unreachability.test.ts +17 -18
- package/src/hooks/hook-store.test.ts +6 -0
- package/src/hooks/hook-trespass.test.ts +26 -9
- package/src/hooks/jail.test.ts +152 -5
- package/src/hooks/jail.ts +93 -4
- package/src/manifest/contracts/v1.ts +13 -0
- package/src/module/packaging/audit.ts +9 -26
- package/src/module/packaging/build-paths.test.ts +127 -0
- package/src/module/packaging/build-paths.ts +175 -0
- package/src/module/packaging/build.test.ts +71 -1
- package/src/module/packaging/build.ts +60 -0
- package/src/module/packaging/extract.ts +1 -5
- package/src/policy/module-script-scan.ts +51 -107
- package/src/policy/no-hand-built-ssh.test.ts +5 -1
- package/src/policy/no-swallowed-refusal.test.ts +14 -15
- package/src/registry/client.test.ts +2 -2
- package/src/secrets/storage.test.ts +1 -1
- package/src/services/alerting/ack.test.ts +2 -1
- package/src/services/alerting/cadence-migration.test.ts +3 -2
- package/src/services/alerting/coverage-source.test.ts +2 -1
- package/src/services/alerting/deferral.test.ts +2 -1
- package/src/services/alerting/delivery-loop.test.ts +2 -1
- package/src/services/alerting/deploy-hooks.test.ts +2 -1
- package/src/services/alerting/inbound-poller.test.ts +2 -1
- package/src/services/alerting/inbound.test.ts +2 -1
- package/src/services/alerting/keys.test.ts +4 -0
- package/src/services/alerting/keys.ts +12 -2
- package/src/services/alerting/notification-responder.test.ts +2 -1
- package/src/services/alerting/run-monitor.test.ts +2 -1
- package/src/services/alerting/store.test.ts +2 -1
- package/src/services/alerting/sweep-runner.test.ts +2 -1
- package/src/services/alerting/tokens.test.ts +2 -1
- package/src/services/aspect-approvals.test.ts +2 -1
- package/src/services/aspect-reconcile.test.ts +4 -3
- package/src/services/aspect-runner.test.ts +2 -1
- package/src/services/audit/health.test.ts +97 -2
- package/src/services/audit/health.ts +64 -2
- package/src/services/audit/index.test.ts +22 -0
- package/src/services/audit/index.ts +8 -2
- package/src/services/audit/jail-exemptions.test.ts +42 -0
- package/src/services/audit/jail-exemptions.ts +44 -0
- package/src/services/audit/module-integrity.test.ts +23 -22
- package/src/services/audit/module-integrity.ts +7 -16
- package/src/services/audit/types.ts +2 -1
- package/src/services/backup-age-agreement.test.ts +2 -1
- package/src/services/backup-create.ts +9 -0
- package/src/services/backup-envelope-roundtrip.test.ts +3 -2
- package/src/services/backup-in-flight-refusal.test.ts +3 -2
- package/src/services/build-bus/hook-dispatch-mgmt.test.ts +48 -0
- package/src/services/build-bus/hook-dispatcher.ts +49 -1
- package/src/services/bus-ensure-flow.test.ts +3 -2
- package/src/services/bus-interview-park.test.ts +4 -3
- package/src/services/bus-interview.test.ts +2 -2
- package/src/services/bus-secret-flow.test.ts +3 -2
- package/src/services/capability-table-rows.test.ts +2 -1
- package/src/services/celilo-events.test.ts +1 -1
- package/src/services/celilo-mgmt-hooks.test.ts +23 -5
- package/src/services/consumer-cleanup.test.ts +3 -2
- package/src/services/container-service.test.ts +3 -2
- package/src/services/control-plane-bootstrap.test.ts +2 -1
- package/src/services/cross-module-read.test.ts +4 -3
- package/src/services/deploy-preflight.ts +7 -0
- package/src/services/deploy-validation.test.ts +3 -2
- package/src/services/deployed-systems.ts +1 -1
- package/src/services/dns-internal-records.test.ts +3 -2
- package/src/services/dns-provider-backfill.test.ts +2 -1
- package/src/services/dns-registrations.test.ts +2 -1
- package/src/services/ensure-interview.test.ts +3 -2
- package/src/services/fleet-checks.test.ts +126 -6
- package/src/services/fleet-checks.ts +176 -3
- package/src/services/fleet-key.test.ts +3 -2
- package/src/services/health-runner.test.ts +87 -2
- package/src/services/health-runner.ts +40 -11
- package/src/services/infrastructure-selector.test.ts +3 -2
- package/src/services/infrastructure-variable-resolver.test.ts +2 -1
- package/src/services/jail-exemptions.test.ts +125 -0
- package/src/services/jail-exemptions.ts +81 -0
- package/src/services/machine-pool.test.ts +3 -2
- package/src/services/module-config.test.ts +2 -1
- package/src/services/module-deploy.dns-repoint.test.ts +2 -1
- package/src/services/module-deploy.ts +48 -24
- package/src/services/module-operations.test.ts +2 -1
- package/src/services/module-subscriptions.test.ts +4 -3
- package/src/services/network-discovery.test.ts +64 -1
- package/src/services/network-discovery.ts +30 -5
- package/src/services/port-forwards.test.ts +2 -1
- package/src/services/programmatic-responder.aspect.test.ts +2 -1
- package/src/services/proxmox-reconcile.test.ts +2 -1
- package/src/services/responder-probe.test.ts +1 -1
- package/src/services/restore-from-file.test.ts +7 -6
- package/src/services/restore-preflight.test.ts +3 -2
- package/src/services/secret-schema-loader.test.ts +2 -1
- package/src/services/ssh-key-manager.test.ts +4 -3
- package/src/services/static-content-converge.test.ts +5 -4
- package/src/services/static-content-converge.ts +33 -10
- package/src/services/system-state-stage.test.ts +4 -3
- package/src/services/trusted-sources.test.ts +3 -2
- package/src/services/update/orchestrator.test.ts +2 -0
- package/src/services/zone-detector.test.ts +2 -1
- package/src/test-utils/bus-responder.ts +6 -4
- package/src/test-utils/db-path.ts +25 -0
- package/src/test-utils/integration.ts +7 -0
- package/src/test-utils/module-fixtures.ts +5 -6
- package/src/module/packaging/generated-plane.test.ts +0 -79
- package/src/module/packaging/generated-plane.ts +0 -134
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
compareVerbatimRoleAssets,
|
|
7
|
-
describeVerbatimDifferences,
|
|
8
|
-
readVerbatimRoleAssets,
|
|
9
|
-
} from './generated-plane';
|
|
10
|
-
|
|
11
|
-
describe('compareVerbatimRoleAssets', () => {
|
|
12
|
-
const asset = 'ansible/roles/vpn/files/vpn-linux-x86_64';
|
|
13
|
-
|
|
14
|
-
test('identical trees produce no differences', () => {
|
|
15
|
-
const both = new Map([[asset, 'aaa']]);
|
|
16
|
-
expect(compareVerbatimRoleAssets(both, new Map(both))).toEqual([]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('the celilo#925 shape: generated holds the previous version bytes', () => {
|
|
20
|
-
const differences = compareVerbatimRoleAssets(
|
|
21
|
-
new Map([[asset, 'new-digest']]),
|
|
22
|
-
new Map([[asset, 'old-digest']]),
|
|
23
|
-
);
|
|
24
|
-
expect(differences).toEqual([
|
|
25
|
-
{
|
|
26
|
-
relPath: asset,
|
|
27
|
-
reason: 'stale',
|
|
28
|
-
installedDigest: 'new-digest',
|
|
29
|
-
generatedDigest: 'old-digest',
|
|
30
|
-
},
|
|
31
|
-
]);
|
|
32
|
-
expect(describeVerbatimDifferences(differences)[0]).toContain(asset);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('an asset generation never produced is missing, not stale', () => {
|
|
36
|
-
expect(compareVerbatimRoleAssets(new Map([[asset, 'aaa']]), new Map())).toEqual([
|
|
37
|
-
{ relPath: asset, reason: 'missing', installedDigest: 'aaa', generatedDigest: null },
|
|
38
|
-
]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('output for a role the module no longer has is not a difference', () => {
|
|
42
|
-
// Nothing includes it, so refusing a deploy over it would refuse a correct
|
|
43
|
-
// module for a file Ansible never reads.
|
|
44
|
-
expect(
|
|
45
|
-
compareVerbatimRoleAssets(new Map(), new Map([['ansible/roles/gone/files/x', 'aaa']])),
|
|
46
|
-
).toEqual([]);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('readVerbatimRoleAssets', () => {
|
|
51
|
-
test('digests role files/ assets and nothing else', async () => {
|
|
52
|
-
const root = mkdtempSync(join(tmpdir(), 'celilo-plane-'));
|
|
53
|
-
try {
|
|
54
|
-
mkdirSync(join(root, 'ansible', 'roles', 'vpn', 'files', 'nested'), { recursive: true });
|
|
55
|
-
mkdirSync(join(root, 'ansible', 'roles', 'vpn', 'templates'), { recursive: true });
|
|
56
|
-
writeFileSync(join(root, 'ansible', 'roles', 'vpn', 'files', 'bin'), 'NEW');
|
|
57
|
-
writeFileSync(join(root, 'ansible', 'roles', 'vpn', 'files', 'nested', 'blob'), 'B');
|
|
58
|
-
// Templated, and expected to differ once generated. Must not be compared.
|
|
59
|
-
writeFileSync(join(root, 'ansible', 'roles', 'vpn', 'templates', 'x.j2'), '{{ v }}');
|
|
60
|
-
|
|
61
|
-
const assets = await readVerbatimRoleAssets(root);
|
|
62
|
-
expect([...assets.keys()].sort()).toEqual([
|
|
63
|
-
'ansible/roles/vpn/files/bin',
|
|
64
|
-
'ansible/roles/vpn/files/nested/blob',
|
|
65
|
-
]);
|
|
66
|
-
} finally {
|
|
67
|
-
rmSync(root, { recursive: true, force: true });
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('a module with no ansible/ at all yields nothing rather than throwing', async () => {
|
|
72
|
-
const root = mkdtempSync(join(tmpdir(), 'celilo-plane-'));
|
|
73
|
-
try {
|
|
74
|
-
expect((await readVerbatimRoleAssets(root)).size).toBe(0);
|
|
75
|
-
} finally {
|
|
76
|
-
rmSync(root, { recursive: true, force: true });
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The second plane: is what we would deploy built from what we installed?
|
|
3
|
-
*
|
|
4
|
-
* A module exists in four places — the published package, the installed tree,
|
|
5
|
-
* the generated project, and the host — and celilo reported a version for the
|
|
6
|
-
* first as though it described the last. Between them sit three copies and two
|
|
7
|
-
* transformations, and nothing verified any of them against the one before it
|
|
8
|
-
* (openspec/changes/module-integrity-rigor, D3).
|
|
9
|
-
*
|
|
10
|
-
* Only VERBATIM assets have a meaningful expected digest. Ansible templates
|
|
11
|
-
* most of what it writes, and a `.j2` in `generated/` is supposed to differ
|
|
12
|
-
* from its source. `ansible/roles/<role>/files/` is the exception: it holds
|
|
13
|
-
* static assets — built binaries, certs, blobs — copied byte for byte because
|
|
14
|
-
* they need no variable resolution and may not survive utf-8 round-tripping.
|
|
15
|
-
*
|
|
16
|
-
* That is exactly where celilo#925 lived. `copyAnsibleRoleFilesDirs` skipped an
|
|
17
|
-
* existing destination on bun, so a module's built binary landed in
|
|
18
|
-
* `generated/` once, at first generate, and no later version replaced it.
|
|
19
|
-
* `cp` reported no error, Ansible copied that first binary forever and reported
|
|
20
|
-
* `ok`, and the module's version field advanced past code that was never
|
|
21
|
-
* shipped. This comparison is local, takes milliseconds, needs no SSH, and is
|
|
22
|
-
* the check that turns that silence into a refusal.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { existsSync } from 'node:fs';
|
|
26
|
-
import { readdir } from 'node:fs/promises';
|
|
27
|
-
import { join, relative } from 'node:path';
|
|
28
|
-
import { computeFileChecksum } from './checksum';
|
|
29
|
-
|
|
30
|
-
export interface VerbatimAssetDifference {
|
|
31
|
-
/** Path relative to the module root, identical in both trees. */
|
|
32
|
-
relPath: string;
|
|
33
|
-
/** `missing`: generation never produced it. `stale`: it holds other bytes. */
|
|
34
|
-
reason: 'missing' | 'stale';
|
|
35
|
-
installedDigest: string;
|
|
36
|
-
generatedDigest: string | null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Pure. Both sides are digest maps keyed by the same module-relative path.
|
|
41
|
-
*
|
|
42
|
-
* An asset present in `generated` but absent from `installed` is NOT reported.
|
|
43
|
-
* That is a role the module no longer has, whose old output nothing includes,
|
|
44
|
-
* and failing a deploy for it would refuse a correct module for a stale file
|
|
45
|
-
* Ansible never reads.
|
|
46
|
-
*/
|
|
47
|
-
export function compareVerbatimRoleAssets(
|
|
48
|
-
installed: ReadonlyMap<string, string>,
|
|
49
|
-
generated: ReadonlyMap<string, string>,
|
|
50
|
-
): VerbatimAssetDifference[] {
|
|
51
|
-
const differences: VerbatimAssetDifference[] = [];
|
|
52
|
-
for (const [relPath, installedDigest] of installed) {
|
|
53
|
-
const generatedDigest = generated.get(relPath) ?? null;
|
|
54
|
-
if (generatedDigest === null) {
|
|
55
|
-
differences.push({ relPath, reason: 'missing', installedDigest, generatedDigest: null });
|
|
56
|
-
} else if (generatedDigest !== installedDigest) {
|
|
57
|
-
differences.push({ relPath, reason: 'stale', installedDigest, generatedDigest });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return differences;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Digest every `ansible/roles/<role>/files/**` asset under `root`, keyed by its
|
|
65
|
-
* path relative to `root`. The generated project mirrors that layout, so the
|
|
66
|
-
* two maps this produces are directly comparable.
|
|
67
|
-
*/
|
|
68
|
-
export async function readVerbatimRoleAssets(root: string): Promise<Map<string, string>> {
|
|
69
|
-
const assets = new Map<string, string>();
|
|
70
|
-
const rolesDir = join(root, 'ansible', 'roles');
|
|
71
|
-
if (!existsSync(rolesDir)) return assets;
|
|
72
|
-
|
|
73
|
-
for (const role of await readdir(rolesDir, { withFileTypes: true })) {
|
|
74
|
-
if (!role.isDirectory()) continue;
|
|
75
|
-
const filesDir = join(rolesDir, role.name, 'files');
|
|
76
|
-
if (!existsSync(filesDir)) continue;
|
|
77
|
-
for (const filePath of await listFiles(filesDir)) {
|
|
78
|
-
assets.set(relative(root, filePath), await computeFileChecksum(filePath));
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return assets;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function listFiles(dir: string): Promise<string[]> {
|
|
85
|
-
const found: string[] = [];
|
|
86
|
-
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
87
|
-
const full = join(dir, entry.name);
|
|
88
|
-
if (entry.isDirectory()) {
|
|
89
|
-
found.push(...(await listFiles(full)));
|
|
90
|
-
} else if (entry.isFile()) {
|
|
91
|
-
found.push(full);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return found;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* One line per difference, naming the file. celilo#925 took days partly because
|
|
99
|
-
* nothing anywhere named the file that had gone stale.
|
|
100
|
-
*/
|
|
101
|
-
export function describeVerbatimDifferences(differences: VerbatimAssetDifference[]): string[] {
|
|
102
|
-
return differences.map((d) =>
|
|
103
|
-
d.reason === 'missing'
|
|
104
|
-
? `${d.relPath}: generation never produced it (installed ${d.installedDigest})`
|
|
105
|
-
: `${d.relPath}: generated holds ${d.generatedDigest}, installed is ${d.installedDigest}`,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* The deploy pre-flight (D6). Returns the refusal message, or `null` to proceed.
|
|
111
|
-
*
|
|
112
|
-
* Local, milliseconds, no SSH, and it runs before anything contacts a system.
|
|
113
|
-
* It deliberately does NOT verify after the deploy: Ansible's `changed` is
|
|
114
|
-
* truthful about what Ansible did, and the lie in celilo#925 was upstream of
|
|
115
|
-
* Ansible.
|
|
116
|
-
*/
|
|
117
|
-
export async function refuseIfGeneratedIsStale(
|
|
118
|
-
moduleId: string,
|
|
119
|
-
modulePath: string,
|
|
120
|
-
generatedPath: string,
|
|
121
|
-
): Promise<string | null> {
|
|
122
|
-
const differences = compareVerbatimRoleAssets(
|
|
123
|
-
await readVerbatimRoleAssets(modulePath),
|
|
124
|
-
await readVerbatimRoleAssets(generatedPath),
|
|
125
|
-
);
|
|
126
|
-
if (differences.length === 0) return null;
|
|
127
|
-
return [
|
|
128
|
-
`Refusing to deploy ${moduleId}: the generated project does not match the installed module.`,
|
|
129
|
-
` ${differences.length} verbatim asset(s) differ:`,
|
|
130
|
-
...describeVerbatimDifferences(differences).map((line) => ` ${line}`),
|
|
131
|
-
'',
|
|
132
|
-
`Deploying would ship these bytes and report success. Run 'celilo module generate ${moduleId}' and try again.`,
|
|
133
|
-
].join('\n');
|
|
134
|
-
}
|