@matthesketh/fleet 1.8.1 → 1.11.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/README.md +186 -16
- package/dist/bin/fleet-agent.d.ts +2 -0
- package/dist/bin/fleet-agent.js +7 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +73 -31
- package/dist/commands/add.d.ts +2 -1
- package/dist/commands/add.js +66 -59
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/audit.js +144 -0
- package/dist/commands/backup.d.ts +1 -0
- package/dist/commands/backup.js +510 -0
- package/dist/commands/boot-start.d.ts +3 -1
- package/dist/commands/boot-start.js +39 -47
- package/dist/commands/completions.d.ts +6 -0
- package/dist/commands/completions.js +83 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.js +96 -0
- package/dist/commands/deploy.js +3 -2
- package/dist/commands/deps.js +5 -1
- package/dist/commands/doctor.d.ts +32 -0
- package/dist/commands/doctor.js +186 -0
- package/dist/commands/egress.d.ts +1 -1
- package/dist/commands/egress.js +13 -10
- package/dist/commands/freeze.d.ts +8 -4
- package/dist/commands/freeze.js +77 -59
- package/dist/commands/git.js +2 -2
- package/dist/commands/health.d.ts +2 -1
- package/dist/commands/health.js +38 -56
- package/dist/commands/init.d.ts +2 -1
- package/dist/commands/init.js +83 -73
- package/dist/commands/install-mcp.d.ts +3 -1
- package/dist/commands/install-mcp.js +53 -34
- package/dist/commands/list.d.ts +2 -1
- package/dist/commands/list.js +22 -19
- package/dist/commands/logs.js +1 -1
- package/dist/commands/notify.d.ts +1 -0
- package/dist/commands/notify.js +51 -0
- package/dist/commands/patch-systemd.d.ts +7 -1
- package/dist/commands/patch-systemd.js +71 -31
- package/dist/commands/remove.d.ts +3 -1
- package/dist/commands/remove.js +37 -26
- package/dist/commands/restart.d.ts +4 -1
- package/dist/commands/restart.js +17 -20
- package/dist/commands/rollback.d.ts +4 -1
- package/dist/commands/rollback.js +33 -42
- package/dist/commands/secrets.js +157 -9
- package/dist/commands/start.d.ts +4 -1
- package/dist/commands/start.js +17 -20
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.js +21 -26
- package/dist/commands/stop.d.ts +4 -1
- package/dist/commands/stop.js +17 -20
- package/dist/commands/testflight.d.ts +1 -0
- package/dist/commands/testflight.js +193 -0
- package/dist/commands/update.d.ts +16 -0
- package/dist/commands/update.js +95 -0
- package/dist/core/audit/cache.d.ts +4 -0
- package/dist/core/audit/cache.js +37 -0
- package/dist/core/audit/config.d.ts +5 -0
- package/dist/core/audit/config.js +35 -0
- package/dist/core/audit/greenlight.d.ts +11 -0
- package/dist/core/audit/greenlight.js +81 -0
- package/dist/core/audit/reporters/cli.d.ts +3 -0
- package/dist/core/audit/reporters/cli.js +68 -0
- package/dist/core/audit/suppress.d.ts +6 -0
- package/dist/core/audit/suppress.js +37 -0
- package/dist/core/audit/target.d.ts +5 -0
- package/dist/core/audit/target.js +26 -0
- package/dist/core/audit/types.d.ts +54 -0
- package/dist/core/audit/types.js +5 -0
- package/dist/core/backup/browser-api.d.ts +66 -0
- package/dist/core/backup/browser-api.js +197 -0
- package/dist/core/backup/browser-server.d.ts +11 -0
- package/dist/core/backup/browser-server.js +241 -0
- package/dist/core/backup/browser-ui.d.ts +5 -0
- package/dist/core/backup/browser-ui.js +268 -0
- package/dist/core/backup/cloudflare.d.ts +7 -0
- package/dist/core/backup/cloudflare.js +82 -0
- package/dist/core/backup/config.d.ts +9 -0
- package/dist/core/backup/config.js +80 -0
- package/dist/core/backup/detect.d.ts +11 -0
- package/dist/core/backup/detect.js +71 -0
- package/dist/core/backup/dump.d.ts +11 -0
- package/dist/core/backup/dump.js +82 -0
- package/dist/core/backup/index.d.ts +9 -0
- package/dist/core/backup/index.js +9 -0
- package/dist/core/backup/repo.d.ts +71 -0
- package/dist/core/backup/repo.js +256 -0
- package/dist/core/backup/schedule.d.ts +17 -0
- package/dist/core/backup/schedule.js +90 -0
- package/dist/core/backup/sensitive.d.ts +5 -0
- package/dist/core/backup/sensitive.js +37 -0
- package/dist/core/backup/status.d.ts +3 -0
- package/dist/core/backup/status.js +29 -0
- package/dist/core/backup/statuspage.d.ts +23 -0
- package/dist/core/backup/statuspage.js +145 -0
- package/dist/core/backup/system.d.ts +24 -0
- package/dist/core/backup/system.js +209 -0
- package/dist/core/backup/totp.d.ts +16 -0
- package/dist/core/backup/totp.js +116 -0
- package/dist/core/backup/types.d.ts +70 -0
- package/dist/core/backup/types.js +7 -0
- package/dist/core/backup/unlock.d.ts +19 -0
- package/dist/core/backup/unlock.js +69 -0
- package/dist/core/boot-refresh.d.ts +1 -1
- package/dist/core/boot-refresh.js +10 -9
- package/dist/core/deps/actors/pr-creator.d.ts +5 -3
- package/dist/core/deps/actors/pr-creator.js +71 -18
- package/dist/core/deps/collectors/fetch-with-timeout.d.ts +7 -0
- package/dist/core/deps/collectors/fetch-with-timeout.js +16 -0
- package/dist/core/deps/collectors/npm.js +3 -1
- package/dist/core/deps/collectors/vulnerability.d.ts +8 -0
- package/dist/core/deps/collectors/vulnerability.js +31 -2
- package/dist/core/deps/config.js +6 -0
- package/dist/core/deps/scanner.js +1 -1
- package/dist/core/deps/types.d.ts +8 -0
- package/dist/core/env.d.ts +3 -0
- package/dist/core/env.js +11 -0
- package/dist/core/exec.d.ts +1 -0
- package/dist/core/exec.js +4 -0
- package/dist/core/file-lock.d.ts +18 -0
- package/dist/core/file-lock.js +44 -0
- package/dist/core/git-onboard.js +10 -13
- package/dist/core/github.d.ts +3 -1
- package/dist/core/github.js +10 -7
- package/dist/core/logs-policy.d.ts +5 -0
- package/dist/core/logs-policy.js +20 -1
- package/dist/core/operator.d.ts +21 -0
- package/dist/core/operator.js +54 -0
- package/dist/core/registry.d.ts +18 -0
- package/dist/core/registry.js +26 -0
- package/dist/core/routines/schema.d.ts +11 -11
- package/dist/core/routines/schema.js +14 -3
- package/dist/core/routines/store.d.ts +8 -8
- package/dist/core/secrets-ops.d.ts +31 -6
- package/dist/core/secrets-ops.js +208 -102
- package/dist/core/secrets-providers.js +2 -2
- package/dist/core/secrets-rotation.d.ts +1 -1
- package/dist/core/secrets-rotation.js +58 -52
- package/dist/core/secrets-v2-cleanup.d.ts +19 -0
- package/dist/core/secrets-v2-cleanup.js +94 -0
- package/dist/core/secrets-v2-creds.d.ts +9 -0
- package/dist/core/secrets-v2-creds.js +44 -0
- package/dist/core/secrets-v2-install.d.ts +13 -0
- package/dist/core/secrets-v2-install.js +76 -0
- package/dist/core/secrets-v2-keypair.d.ts +10 -0
- package/dist/core/secrets-v2-keypair.js +31 -0
- package/dist/core/secrets-v2-migrate.d.ts +29 -0
- package/dist/core/secrets-v2-migrate.js +395 -0
- package/dist/core/secrets-v2-ops.d.ts +36 -0
- package/dist/core/secrets-v2-ops.js +184 -0
- package/dist/core/secrets-v2-protocol.d.ts +19 -0
- package/dist/core/secrets-v2-protocol.js +60 -0
- package/dist/core/secrets-v2-snapshot.d.ts +36 -0
- package/dist/core/secrets-v2-snapshot.js +115 -0
- package/dist/core/secrets-v2.d.ts +21 -0
- package/dist/core/secrets-v2.js +249 -0
- package/dist/core/secrets.d.ts +39 -4
- package/dist/core/secrets.js +91 -11
- package/dist/core/self-update.d.ts +32 -11
- package/dist/core/self-update.js +52 -14
- package/dist/core/testflight/asc.d.ts +12 -0
- package/dist/core/testflight/asc.js +101 -0
- package/dist/core/testflight/credentials.d.ts +3 -0
- package/dist/core/testflight/credentials.js +35 -0
- package/dist/core/testflight/eas.d.ts +4 -0
- package/dist/core/testflight/eas.js +38 -0
- package/dist/core/testflight/resolve.d.ts +6 -0
- package/dist/core/testflight/resolve.js +44 -0
- package/dist/core/testflight/types.d.ts +13 -0
- package/dist/core/testflight/types.js +3 -0
- package/dist/core/testflight/workflow.d.ts +17 -0
- package/dist/core/testflight/workflow.js +65 -0
- package/dist/core/validate.d.ts +1 -0
- package/dist/core/validate.js +8 -0
- package/dist/mcp/audit-tools.d.ts +2 -0
- package/dist/mcp/audit-tools.js +94 -0
- package/dist/mcp/git-tools.js +1 -1
- package/dist/mcp/registry-bridge.d.ts +10 -0
- package/dist/mcp/registry-bridge.js +65 -0
- package/dist/mcp/secrets-tools.js +2 -2
- package/dist/mcp/server.js +16 -82
- package/dist/mcp/testflight-tools.d.ts +2 -0
- package/dist/mcp/testflight-tools.js +52 -0
- package/dist/registry/context.d.ts +7 -0
- package/dist/registry/context.js +37 -0
- package/dist/registry/index.d.ts +5 -0
- package/dist/registry/index.js +44 -0
- package/dist/registry/parse-args.d.ts +13 -0
- package/dist/registry/parse-args.js +74 -0
- package/dist/registry/registry.d.ts +24 -0
- package/dist/registry/registry.js +26 -0
- package/dist/registry/render.d.ts +3 -0
- package/dist/registry/render.js +29 -0
- package/dist/registry/types.d.ts +50 -0
- package/dist/registry/types.js +1 -0
- package/dist/templates/agent-unit.d.ts +5 -0
- package/dist/templates/agent-unit.js +40 -0
- package/dist/templates/app-unit-edit.d.ts +2 -0
- package/dist/templates/app-unit-edit.js +46 -0
- package/dist/templates/compose-edit.d.ts +2 -0
- package/dist/templates/compose-edit.js +156 -0
- package/dist/templates/nginx.js +11 -0
- package/dist/templates/systemd.js +6 -0
- package/dist/tui/components/ArgForm.d.ts +7 -0
- package/dist/tui/components/ArgForm.js +64 -0
- package/dist/tui/components/ArgForm.test.d.ts +1 -0
- package/dist/tui/components/ArgForm.test.js +19 -0
- package/dist/tui/components/KeyHint.js +5 -0
- package/dist/tui/hooks/use-secrets.d.ts +8 -8
- package/dist/tui/hooks/use-secrets.js +7 -7
- package/dist/tui/router.d.ts +1 -0
- package/dist/tui/router.js +26 -9
- package/dist/tui/router.test.d.ts +1 -0
- package/dist/tui/router.test.js +13 -0
- package/dist/tui/routines/components/SignalsGrid.test.js +2 -2
- package/dist/tui/routines/tabs/ScaffoldTab.js +1 -1
- package/dist/tui/tests/redaction-rerender.test.d.ts +1 -0
- package/dist/tui/tests/redaction-rerender.test.js +53 -0
- package/dist/tui/tests/scroll-flicker-proof.test.d.ts +1 -0
- package/dist/tui/tests/scroll-flicker-proof.test.js +145 -0
- package/dist/tui/types.d.ts +1 -1
- package/dist/tui/views/CommandPalette.d.ts +5 -0
- package/dist/tui/views/CommandPalette.js +90 -0
- package/dist/tui/views/CommandPalette.test.d.ts +1 -0
- package/dist/tui/views/CommandPalette.test.js +117 -0
- package/dist/tui/views/Dashboard.js +9 -6
- package/dist/tui/views/HealthView.js +9 -4
- package/dist/tui/views/SecretEdit.js +15 -16
- package/dist/tui/views/SecretEdit.test.d.ts +1 -0
- package/dist/tui/views/SecretEdit.test.js +82 -0
- package/dist/tui/views/SecretsView.js +26 -16
- package/package.json +8 -5
package/dist/core/logs-policy.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* compose override file (.fleet/logging.override.yml) per app, plus journald
|
|
4
4
|
* vacuum policy. Conservative defaults applied when unset.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync } from 'node:fs';
|
|
7
7
|
import { join, dirname } from 'node:path';
|
|
8
8
|
import { execSafe } from './exec.js';
|
|
9
9
|
export const DEFAULT_POLICY = {
|
|
@@ -50,12 +50,31 @@ export function buildComposeOverride(app, policy) {
|
|
|
50
50
|
export function overridePath(app) {
|
|
51
51
|
return join(app.composePath, '.fleet', 'logging.override.yml');
|
|
52
52
|
}
|
|
53
|
+
/** ensure the app repo's .gitignore covers .fleet/ so the auto-generated
|
|
54
|
+
* override file doesn't get committed accidentally. no-op when there's no
|
|
55
|
+
* .gitignore (operator may be using a different vcs or none at all) and
|
|
56
|
+
* idempotent — never appends a duplicate entry. */
|
|
57
|
+
export function ensureFleetGitignored(composePath) {
|
|
58
|
+
const giPath = join(composePath, '.gitignore');
|
|
59
|
+
if (!existsSync(giPath))
|
|
60
|
+
return;
|
|
61
|
+
const current = readFileSync(giPath, 'utf-8');
|
|
62
|
+
// accept any of: .fleet, .fleet/, /.fleet, /.fleet/ — operators write all
|
|
63
|
+
// four forms.
|
|
64
|
+
if (/^\s*\/?\.fleet\/?\s*$/m.test(current))
|
|
65
|
+
return;
|
|
66
|
+
const append = current.endsWith('\n') ? '' : '\n';
|
|
67
|
+
writeFileSync(giPath, `${current}${append}\n# auto-added by fleet logs setup\n.fleet/\n`);
|
|
68
|
+
}
|
|
53
69
|
export function writeComposeOverride(app, policy) {
|
|
54
70
|
const path = overridePath(app);
|
|
55
71
|
const dir = dirname(path);
|
|
56
72
|
if (!existsSync(dir))
|
|
57
73
|
mkdirSync(dir, { recursive: true });
|
|
58
74
|
writeFileSync(path, buildComposeOverride(app, policy));
|
|
75
|
+
// the override lands inside the operator's app repo. add .fleet/ to the
|
|
76
|
+
// app's .gitignore (if there is one) so it doesn't get committed.
|
|
77
|
+
ensureFleetGitignored(app.composePath);
|
|
59
78
|
return path;
|
|
60
79
|
}
|
|
61
80
|
/** Best-effort docker-side log status. Uses `docker inspect` for driver + size. */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface OperatorConfig {
|
|
2
|
+
username: string;
|
|
3
|
+
homeDir: string;
|
|
4
|
+
domain: string;
|
|
5
|
+
githubOrg: string;
|
|
6
|
+
}
|
|
7
|
+
export type OperatorField = keyof OperatorConfig;
|
|
8
|
+
export declare const OPERATOR_FIELDS: readonly OperatorField[];
|
|
9
|
+
/** test-only: clears the memoised config. */
|
|
10
|
+
export declare function _resetOperatorCache(): void;
|
|
11
|
+
/** path the operator config is read from / written to. exported so the
|
|
12
|
+
* fleet config command can print where it lives. */
|
|
13
|
+
export declare function operatorPath(): string;
|
|
14
|
+
/** loads operator identity from data/operator.json (gitignored, instance-local).
|
|
15
|
+
* throws if the file is missing or incomplete — there is no safe default,
|
|
16
|
+
* and guessing another operator's identity is never correct. */
|
|
17
|
+
export declare function loadOperator(): OperatorConfig;
|
|
18
|
+
/** persist the operator config to disk and clear the memoised copy so the
|
|
19
|
+
* next loadOperator() picks up the new values. atomic write via .tmp +
|
|
20
|
+
* rename so a crash mid-write never leaves a partial file behind. */
|
|
21
|
+
export declare function saveOperator(cfg: OperatorConfig): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { FleetError } from './errors.js';
|
|
5
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
export const OPERATOR_FIELDS = ['username', 'homeDir', 'domain', 'githubOrg'];
|
|
7
|
+
const FIELDS = OPERATOR_FIELDS;
|
|
8
|
+
let cache = null;
|
|
9
|
+
/** test-only: clears the memoised config. */
|
|
10
|
+
export function _resetOperatorCache() { cache = null; }
|
|
11
|
+
/** path the operator config is read from / written to. exported so the
|
|
12
|
+
* fleet config command can print where it lives. */
|
|
13
|
+
export function operatorPath() {
|
|
14
|
+
return process.env.FLEET_OPERATOR_PATH ?? join(here, '..', '..', 'data', 'operator.json');
|
|
15
|
+
}
|
|
16
|
+
/** loads operator identity from data/operator.json (gitignored, instance-local).
|
|
17
|
+
* throws if the file is missing or incomplete — there is no safe default,
|
|
18
|
+
* and guessing another operator's identity is never correct. */
|
|
19
|
+
export function loadOperator() {
|
|
20
|
+
if (cache)
|
|
21
|
+
return cache;
|
|
22
|
+
const path = operatorPath();
|
|
23
|
+
if (!existsSync(path)) {
|
|
24
|
+
throw new FleetError(`operator config not found at ${path} — ` +
|
|
25
|
+
`copy data/operator.example.json to data/operator.json and fill it in`);
|
|
26
|
+
}
|
|
27
|
+
const raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
28
|
+
for (const field of FIELDS) {
|
|
29
|
+
if (!raw[field])
|
|
30
|
+
throw new FleetError(`operator config ${path} is missing field: ${field}`);
|
|
31
|
+
}
|
|
32
|
+
cache = raw;
|
|
33
|
+
return cache;
|
|
34
|
+
}
|
|
35
|
+
/** persist the operator config to disk and clear the memoised copy so the
|
|
36
|
+
* next loadOperator() picks up the new values. atomic write via .tmp +
|
|
37
|
+
* rename so a crash mid-write never leaves a partial file behind. */
|
|
38
|
+
export function saveOperator(cfg) {
|
|
39
|
+
for (const field of FIELDS) {
|
|
40
|
+
if (typeof cfg[field] !== 'string' || cfg[field].length === 0) {
|
|
41
|
+
throw new FleetError(`operator config: ${field} must be a non-empty string`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const path = operatorPath();
|
|
45
|
+
const dir = dirname(path);
|
|
46
|
+
if (!existsSync(dir))
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
const tmp = path + '.tmp';
|
|
49
|
+
writeFileSync(tmp, JSON.stringify(cfg, null, 2) + '\n', { mode: 0o600 });
|
|
50
|
+
// rename is atomic on the same filesystem; covers the crash-mid-write race
|
|
51
|
+
// a plain writeFileSync exposes.
|
|
52
|
+
renameSync(tmp, path);
|
|
53
|
+
cache = null;
|
|
54
|
+
}
|
package/dist/core/registry.d.ts
CHANGED
|
@@ -61,3 +61,21 @@ export declare function findApp(reg: Registry, name: string): AppEntry | undefin
|
|
|
61
61
|
export declare function addApp(reg: Registry, app: AppEntry): Registry;
|
|
62
62
|
export declare function removeApp(reg: Registry, name: string): Registry;
|
|
63
63
|
export declare function registryPath(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Run a read-modify-write transaction against the registry under an
|
|
66
|
+
* inter-process lock. The lock is held for the full load → mutate → save
|
|
67
|
+
* cycle, so concurrent CLI / cron / systemd / bot invocations don't lose
|
|
68
|
+
* each other's updates.
|
|
69
|
+
*
|
|
70
|
+
* The mutator may return a different Registry object (e.g. one returned by
|
|
71
|
+
* `addApp` / `removeApp`, which mutate in place but also return the registry
|
|
72
|
+
* for chaining) or simply mutate the input and return it. The returned value
|
|
73
|
+
* is what gets persisted.
|
|
74
|
+
*
|
|
75
|
+
* Returns void: callers needing the post-save state should re-load. Keeping
|
|
76
|
+
* this side-effecting matches how `load()` + `save()` are used today.
|
|
77
|
+
*
|
|
78
|
+
* Important: do not call this from inside another `withRegistry` block on the
|
|
79
|
+
* same process — proper-lockfile is not reentrant and will deadlock.
|
|
80
|
+
*/
|
|
81
|
+
export declare function withRegistry(fn: (reg: Registry) => Registry | Promise<Registry>): Promise<void>;
|
package/dist/core/registry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync, mkdirSync, copyFileSync, renameSync, openSync, writeSync, fsyncSync, closeSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { withFileLock } from './file-lock.js';
|
|
4
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
6
|
function resolveRegistryPath() {
|
|
6
7
|
return process.env.FLEET_REGISTRY_PATH
|
|
@@ -92,3 +93,28 @@ export function removeApp(reg, name) {
|
|
|
92
93
|
export function registryPath() {
|
|
93
94
|
return resolveRegistryPath();
|
|
94
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Run a read-modify-write transaction against the registry under an
|
|
98
|
+
* inter-process lock. The lock is held for the full load → mutate → save
|
|
99
|
+
* cycle, so concurrent CLI / cron / systemd / bot invocations don't lose
|
|
100
|
+
* each other's updates.
|
|
101
|
+
*
|
|
102
|
+
* The mutator may return a different Registry object (e.g. one returned by
|
|
103
|
+
* `addApp` / `removeApp`, which mutate in place but also return the registry
|
|
104
|
+
* for chaining) or simply mutate the input and return it. The returned value
|
|
105
|
+
* is what gets persisted.
|
|
106
|
+
*
|
|
107
|
+
* Returns void: callers needing the post-save state should re-load. Keeping
|
|
108
|
+
* this side-effecting matches how `load()` + `save()` are used today.
|
|
109
|
+
*
|
|
110
|
+
* Important: do not call this from inside another `withRegistry` block on the
|
|
111
|
+
* same process — proper-lockfile is not reentrant and will deadlock.
|
|
112
|
+
*/
|
|
113
|
+
export async function withRegistry(fn) {
|
|
114
|
+
const path = resolveRegistryPath();
|
|
115
|
+
await withFileLock(path, async () => {
|
|
116
|
+
const reg = load();
|
|
117
|
+
const next = await fn(reg);
|
|
118
|
+
save(next);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -204,15 +204,14 @@ export declare const RoutineSchema: z.ZodObject<{
|
|
|
204
204
|
createdAt: z.ZodOptional<z.ZodString>;
|
|
205
205
|
updatedAt: z.ZodOptional<z.ZodString>;
|
|
206
206
|
}, "strip", z.ZodTypeAny, {
|
|
207
|
-
enabled: boolean;
|
|
208
207
|
name: string;
|
|
208
|
+
enabled: boolean;
|
|
209
209
|
id: string;
|
|
210
210
|
notify: {
|
|
211
211
|
config: Record<string, unknown>;
|
|
212
212
|
kind: "email" | "stdout" | "webhook" | "slack";
|
|
213
213
|
on: "always" | "failure" | "success";
|
|
214
214
|
}[];
|
|
215
|
-
description: string;
|
|
216
215
|
schedule: {
|
|
217
216
|
kind: "manual";
|
|
218
217
|
} | {
|
|
@@ -221,6 +220,8 @@ export declare const RoutineSchema: z.ZodObject<{
|
|
|
221
220
|
randomizedDelaySec: number;
|
|
222
221
|
persistent: boolean;
|
|
223
222
|
};
|
|
223
|
+
tags: string[];
|
|
224
|
+
description: string;
|
|
224
225
|
targets: string[];
|
|
225
226
|
perTarget: boolean;
|
|
226
227
|
task: {
|
|
@@ -244,7 +245,6 @@ export declare const RoutineSchema: z.ZodObject<{
|
|
|
244
245
|
tool: string;
|
|
245
246
|
args: Record<string, unknown>;
|
|
246
247
|
};
|
|
247
|
-
tags: string[];
|
|
248
248
|
updatedAt?: string | undefined;
|
|
249
249
|
createdAt?: string | undefined;
|
|
250
250
|
}, {
|
|
@@ -286,10 +286,10 @@ export declare const RoutineSchema: z.ZodObject<{
|
|
|
286
286
|
config?: Record<string, unknown> | undefined;
|
|
287
287
|
on?: "always" | "failure" | "success" | undefined;
|
|
288
288
|
}[] | undefined;
|
|
289
|
+
tags?: string[] | undefined;
|
|
289
290
|
description?: string | undefined;
|
|
290
291
|
targets?: string[] | undefined;
|
|
291
292
|
perTarget?: boolean | undefined;
|
|
292
|
-
tags?: string[] | undefined;
|
|
293
293
|
createdAt?: string | undefined;
|
|
294
294
|
}>;
|
|
295
295
|
export type Routine = z.infer<typeof RoutineSchema>;
|
|
@@ -303,13 +303,13 @@ export declare const RunEventSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObjec
|
|
|
303
303
|
}, "strip", z.ZodTypeAny, {
|
|
304
304
|
at: string;
|
|
305
305
|
kind: "start";
|
|
306
|
-
routineId: string;
|
|
307
306
|
target: string | null;
|
|
307
|
+
routineId: string;
|
|
308
308
|
}, {
|
|
309
309
|
at: string;
|
|
310
310
|
kind: "start";
|
|
311
|
-
routineId: string;
|
|
312
311
|
target: string | null;
|
|
312
|
+
routineId: string;
|
|
313
313
|
}>, z.ZodObject<{
|
|
314
314
|
kind: z.ZodLiteral<"stdout">;
|
|
315
315
|
chunk: z.ZodString;
|
|
@@ -370,14 +370,14 @@ export declare const RunEventSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObjec
|
|
|
370
370
|
error: z.ZodOptional<z.ZodString>;
|
|
371
371
|
}, "strip", z.ZodTypeAny, {
|
|
372
372
|
at: string;
|
|
373
|
-
status: "
|
|
373
|
+
status: "aborted" | "timeout" | "ok" | "queued" | "running" | "failed";
|
|
374
374
|
kind: "end";
|
|
375
375
|
exitCode: number;
|
|
376
376
|
durationMs: number;
|
|
377
377
|
error?: string | undefined;
|
|
378
378
|
}, {
|
|
379
379
|
at: string;
|
|
380
|
-
status: "
|
|
380
|
+
status: "aborted" | "timeout" | "ok" | "queued" | "running" | "failed";
|
|
381
381
|
kind: "end";
|
|
382
382
|
exitCode: number;
|
|
383
383
|
durationMs: number;
|
|
@@ -397,16 +397,16 @@ export declare const SignalSchema: z.ZodObject<{
|
|
|
397
397
|
collectedAt: z.ZodString;
|
|
398
398
|
ttlMs: z.ZodNumber;
|
|
399
399
|
}, "strip", z.ZodTypeAny, {
|
|
400
|
-
detail: string;
|
|
401
|
-
kind: "git-clean" | "git-ahead" | "git-behind" | "open-prs" | "pr-age-max" | "deps-outdated" | "deps-vulns" | "build-ok" | "tests-ok" | "env-schema-ok" | "container-up" | "ci-status" | "cache-age";
|
|
402
400
|
value: string | number | boolean | null;
|
|
401
|
+
kind: "git-clean" | "git-ahead" | "git-behind" | "open-prs" | "pr-age-max" | "deps-outdated" | "deps-vulns" | "build-ok" | "tests-ok" | "env-schema-ok" | "container-up" | "ci-status" | "cache-age";
|
|
402
|
+
detail: string;
|
|
403
403
|
repo: string;
|
|
404
404
|
state: "warn" | "error" | "unknown" | "ok";
|
|
405
405
|
collectedAt: string;
|
|
406
406
|
ttlMs: number;
|
|
407
407
|
}, {
|
|
408
|
-
kind: "git-clean" | "git-ahead" | "git-behind" | "open-prs" | "pr-age-max" | "deps-outdated" | "deps-vulns" | "build-ok" | "tests-ok" | "env-schema-ok" | "container-up" | "ci-status" | "cache-age";
|
|
409
408
|
value: string | number | boolean | null;
|
|
409
|
+
kind: "git-clean" | "git-ahead" | "git-behind" | "open-prs" | "pr-age-max" | "deps-outdated" | "deps-vulns" | "build-ok" | "tests-ok" | "env-schema-ok" | "container-up" | "ci-status" | "cache-age";
|
|
410
410
|
repo: string;
|
|
411
411
|
state: "warn" | "error" | "unknown" | "ok";
|
|
412
412
|
collectedAt: string;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const ROUTINE_ID_REGEX = /^[a-z][a-z0-9-]{0,62}$/;
|
|
3
3
|
const NO_SHELL_META = /^[^`$;&|><\n\r\\"]*$/;
|
|
4
|
+
// Printable ASCII only — no newlines, no control chars. Routine names and
|
|
5
|
+
// descriptions are interpolated into systemd unit files; a newline would let
|
|
6
|
+
// a hostile name inject directives like [Service]/User=root/ExecStart=…
|
|
7
|
+
// that the systemd loader then runs as root after daemon-reload. Keep this
|
|
8
|
+
// strict — widen explicitly if a future feature truly needs more.
|
|
9
|
+
const ROUTINE_TEXT_REGEX = /^[\x20-\x7E]+$/;
|
|
10
|
+
// systemd's documented OnCalendar grammar: weekdays, dates, times — all
|
|
11
|
+
// printable ASCII without quotes, semicolons, control chars or newlines.
|
|
12
|
+
// Same injection concern as above. Widen if "~" (last weekday) etc. is
|
|
13
|
+
// actually needed.
|
|
14
|
+
const ON_CALENDAR_REGEX = /^[A-Za-z0-9*\-/:., \t]+$/;
|
|
4
15
|
const DEFAULT_WALLCLOCK_MS = 15 * 60 * 1000;
|
|
5
16
|
const DEFAULT_TOKEN_CAP = 100_000;
|
|
6
17
|
const DEFAULT_MAX_USD = 5;
|
|
@@ -38,15 +49,15 @@ export const RoutineScheduleSchema = z.union([
|
|
|
38
49
|
z.object({ kind: z.literal('manual') }),
|
|
39
50
|
z.object({
|
|
40
51
|
kind: z.literal('calendar'),
|
|
41
|
-
onCalendar: z.string().min(1).max(200),
|
|
52
|
+
onCalendar: z.string().min(1).max(200).regex(ON_CALENDAR_REGEX, 'systemd OnCalendar tokens only (letters, digits, *-/:., space, tab)'),
|
|
42
53
|
randomizedDelaySec: z.number().int().nonnegative().max(3600).default(0),
|
|
43
54
|
persistent: z.boolean().default(true),
|
|
44
55
|
}),
|
|
45
56
|
]);
|
|
46
57
|
export const RoutineSchema = z.object({
|
|
47
58
|
id: z.string().regex(ROUTINE_ID_REGEX, 'lowercase alphanumeric and dashes only'),
|
|
48
|
-
name: z.string().min(1).max(100),
|
|
49
|
-
description: z.string().max(2000).default(''),
|
|
59
|
+
name: z.string().min(1).max(100).regex(ROUTINE_TEXT_REGEX, 'printable ASCII only, no newlines or control chars'),
|
|
60
|
+
description: z.string().max(2000).regex(/^[\x20-\x7E]*$/, 'printable ASCII only, no newlines or control chars').default(''),
|
|
50
61
|
schedule: RoutineScheduleSchema,
|
|
51
62
|
enabled: z.boolean().default(true),
|
|
52
63
|
targets: z.array(z.string().min(1)).default([]),
|
|
@@ -109,15 +109,14 @@ declare const FileSchema: z.ZodObject<{
|
|
|
109
109
|
createdAt: z.ZodOptional<z.ZodString>;
|
|
110
110
|
updatedAt: z.ZodOptional<z.ZodString>;
|
|
111
111
|
}, "strip", z.ZodTypeAny, {
|
|
112
|
-
enabled: boolean;
|
|
113
112
|
name: string;
|
|
113
|
+
enabled: boolean;
|
|
114
114
|
id: string;
|
|
115
115
|
notify: {
|
|
116
116
|
config: Record<string, unknown>;
|
|
117
117
|
kind: "email" | "stdout" | "webhook" | "slack";
|
|
118
118
|
on: "always" | "failure" | "success";
|
|
119
119
|
}[];
|
|
120
|
-
description: string;
|
|
121
120
|
schedule: {
|
|
122
121
|
kind: "manual";
|
|
123
122
|
} | {
|
|
@@ -126,6 +125,8 @@ declare const FileSchema: z.ZodObject<{
|
|
|
126
125
|
randomizedDelaySec: number;
|
|
127
126
|
persistent: boolean;
|
|
128
127
|
};
|
|
128
|
+
tags: string[];
|
|
129
|
+
description: string;
|
|
129
130
|
targets: string[];
|
|
130
131
|
perTarget: boolean;
|
|
131
132
|
task: {
|
|
@@ -149,7 +150,6 @@ declare const FileSchema: z.ZodObject<{
|
|
|
149
150
|
tool: string;
|
|
150
151
|
args: Record<string, unknown>;
|
|
151
152
|
};
|
|
152
|
-
tags: string[];
|
|
153
153
|
updatedAt?: string | undefined;
|
|
154
154
|
createdAt?: string | undefined;
|
|
155
155
|
}, {
|
|
@@ -191,25 +191,24 @@ declare const FileSchema: z.ZodObject<{
|
|
|
191
191
|
config?: Record<string, unknown> | undefined;
|
|
192
192
|
on?: "always" | "failure" | "success" | undefined;
|
|
193
193
|
}[] | undefined;
|
|
194
|
+
tags?: string[] | undefined;
|
|
194
195
|
description?: string | undefined;
|
|
195
196
|
targets?: string[] | undefined;
|
|
196
197
|
perTarget?: boolean | undefined;
|
|
197
|
-
tags?: string[] | undefined;
|
|
198
198
|
createdAt?: string | undefined;
|
|
199
199
|
}>, "many">;
|
|
200
200
|
defaultsSeededAt: z.ZodOptional<z.ZodString>;
|
|
201
201
|
}, "strip", z.ZodTypeAny, {
|
|
202
202
|
version: 1;
|
|
203
203
|
routines: {
|
|
204
|
-
enabled: boolean;
|
|
205
204
|
name: string;
|
|
205
|
+
enabled: boolean;
|
|
206
206
|
id: string;
|
|
207
207
|
notify: {
|
|
208
208
|
config: Record<string, unknown>;
|
|
209
209
|
kind: "email" | "stdout" | "webhook" | "slack";
|
|
210
210
|
on: "always" | "failure" | "success";
|
|
211
211
|
}[];
|
|
212
|
-
description: string;
|
|
213
212
|
schedule: {
|
|
214
213
|
kind: "manual";
|
|
215
214
|
} | {
|
|
@@ -218,6 +217,8 @@ declare const FileSchema: z.ZodObject<{
|
|
|
218
217
|
randomizedDelaySec: number;
|
|
219
218
|
persistent: boolean;
|
|
220
219
|
};
|
|
220
|
+
tags: string[];
|
|
221
|
+
description: string;
|
|
221
222
|
targets: string[];
|
|
222
223
|
perTarget: boolean;
|
|
223
224
|
task: {
|
|
@@ -241,7 +242,6 @@ declare const FileSchema: z.ZodObject<{
|
|
|
241
242
|
tool: string;
|
|
242
243
|
args: Record<string, unknown>;
|
|
243
244
|
};
|
|
244
|
-
tags: string[];
|
|
245
245
|
updatedAt?: string | undefined;
|
|
246
246
|
createdAt?: string | undefined;
|
|
247
247
|
}[];
|
|
@@ -287,10 +287,10 @@ declare const FileSchema: z.ZodObject<{
|
|
|
287
287
|
config?: Record<string, unknown> | undefined;
|
|
288
288
|
on?: "always" | "failure" | "success" | undefined;
|
|
289
289
|
}[] | undefined;
|
|
290
|
+
tags?: string[] | undefined;
|
|
290
291
|
description?: string | undefined;
|
|
291
292
|
targets?: string[] | undefined;
|
|
292
293
|
perTarget?: boolean | undefined;
|
|
293
|
-
tags?: string[] | undefined;
|
|
294
294
|
createdAt?: string | undefined;
|
|
295
295
|
}[];
|
|
296
296
|
defaultsSeededAt?: string | undefined;
|
|
@@ -8,10 +8,15 @@ export declare function safeSealApp(app: string, content: string, sourceFile: st
|
|
|
8
8
|
export declare function safeSealDbSecrets(app: string, secretsMap: Record<string, string>, sourceDir: string): SealValidation;
|
|
9
9
|
export declare function setSecret(app: string, key: string, value: string, opts?: {
|
|
10
10
|
allowWeak?: boolean;
|
|
11
|
-
}): void
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
/** read a single secret value. deliberately does NOT hold the manifest
|
|
13
|
+
* lock — a concurrent setSecret/seal does an atomic file rename, so the
|
|
14
|
+
* worst-case race here is reading the immediate-pre-rotation vault blob.
|
|
15
|
+
* audit logging happens at the command layer so programmatic reads
|
|
16
|
+
* (background sync, validation) don't pollute the audit trail. */
|
|
12
17
|
export declare function getSecret(app: string, key: string): string | null;
|
|
13
|
-
export declare function importEnvFile(app: string, path: string): number
|
|
14
|
-
export declare function importDbSecrets(app: string, dir: string): number
|
|
18
|
+
export declare function importEnvFile(app: string, path: string): Promise<number>;
|
|
19
|
+
export declare function importDbSecrets(app: string, dir: string): Promise<number>;
|
|
15
20
|
export declare function exportApp(app: string): string;
|
|
16
21
|
export interface DriftResult {
|
|
17
22
|
app: string;
|
|
@@ -22,12 +27,32 @@ export interface DriftResult {
|
|
|
22
27
|
}
|
|
23
28
|
export declare function detectDrift(app?: string): DriftResult[];
|
|
24
29
|
export declare function unsealAll(): void;
|
|
25
|
-
export declare function sealFromRuntime(app?: string): string[]
|
|
26
|
-
|
|
30
|
+
export declare function sealFromRuntime(app?: string): Promise<string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Rotate the age private key and re-encrypt every app's vault file with it.
|
|
33
|
+
*
|
|
34
|
+
* RECOVERY PROCEDURE (manual, only if rollback itself fails):
|
|
35
|
+
* 1. The previous private key is preserved at `<KEY_PATH>.old` while a
|
|
36
|
+
* rotation is in flight. If you see that file lying around, a rotation
|
|
37
|
+
* crashed mid-way.
|
|
38
|
+
* 2. Each app's pre-rotate encrypted file is preserved as
|
|
39
|
+
* `vault/<app>.{env,secrets}.age.bak-rotate-<ts>` for the duration of
|
|
40
|
+
* the rotation. They are removed automatically on success OR after a
|
|
41
|
+
* successful rollback.
|
|
42
|
+
* 3. To restore by hand: copy `<KEY_PATH>.old` back to `<KEY_PATH>`
|
|
43
|
+
* (chmod 0600), then for each `*.bak-rotate-<ts>` copy it over the
|
|
44
|
+
* matching encrypted file. This puts the vault back into the
|
|
45
|
+
* pre-rotation state.
|
|
46
|
+
*
|
|
47
|
+
* On a partial-failure path inside this function, that recovery is performed
|
|
48
|
+
* automatically before re-throwing. The whole rotation runs under the
|
|
49
|
+
* manifest's inter-process lock.
|
|
50
|
+
*/
|
|
51
|
+
export declare function rotateKey(): Promise<{
|
|
27
52
|
oldPubkey: string;
|
|
28
53
|
newPubkey: string;
|
|
29
54
|
appsRotated: string[];
|
|
30
|
-
}
|
|
55
|
+
}>;
|
|
31
56
|
export declare function getStatus(): {
|
|
32
57
|
initialized: boolean;
|
|
33
58
|
sealed: boolean;
|