@rizom/ops 0.2.0-alpha.45 → 0.2.0-alpha.451
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 +82 -1
- package/dist/age-key-bootstrap.d.ts +1 -1
- package/dist/brains-ops.js +736 -335
- package/dist/capability-bundle-migration.d.ts +22 -0
- package/dist/cert-bootstrap.d.ts +4 -2
- package/dist/content-repo-ref.d.ts +10 -0
- package/dist/content-repo.d.ts +1 -1
- package/dist/deploy.js +110 -166
- package/dist/directory-sync-stress-system.d.ts +75 -0
- package/dist/directory-sync-stress.d.ts +117 -0
- package/dist/entries/deploy.d.ts +4 -2
- package/dist/health-watchdog-smoke.d.ts +54 -0
- package/dist/image-inventory.d.ts +4 -0
- package/dist/image-types.d.ts +7 -0
- package/dist/images.d.ts +82 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +736 -323
- package/dist/legacy-pilot-migration.d.ts +11 -0
- package/dist/legacy-projection-job-recovery.d.ts +29 -0
- package/dist/load-registry.d.ts +61 -6
- package/dist/observed-status.d.ts +1 -1
- package/dist/parse-args.d.ts +2 -12
- package/dist/preview-domain.d.ts +9 -0
- package/dist/reconcile-dry-run.d.ts +9 -0
- package/dist/run-command.d.ts +20 -4
- package/dist/schema.d.ts +126 -163
- package/dist/secrets-encrypt.d.ts +7 -13
- package/dist/secrets-push.d.ts +1 -1
- package/dist/ssh-key-bootstrap.d.ts +1 -26
- package/dist/stage-legacy-crossover.d.ts +25 -0
- package/dist/stress-command.d.ts +14 -0
- package/dist/stress-git-checkout.d.ts +21 -0
- package/dist/stress-health-monitor.d.ts +46 -0
- package/dist/upgrade.d.ts +10 -0
- package/dist/user-offboard.d.ts +61 -0
- package/dist/verify-user.d.ts +22 -0
- package/package.json +50 -42
- package/templates/rover-pilot/.env.schema +20 -5
- package/templates/rover-pilot/.github/actions/varlock-env/action.yml +47 -0
- package/templates/rover-pilot/.github/workflows/build.yml +70 -17
- package/templates/rover-pilot/.github/workflows/deploy.yml +74 -57
- package/templates/rover-pilot/.github/workflows/directory-sync-stress.yml +119 -0
- package/templates/rover-pilot/.github/workflows/health-watchdog-smoke.yml +95 -0
- package/templates/rover-pilot/.github/workflows/offboard.yml +107 -0
- package/templates/rover-pilot/.github/workflows/reconcile.yml +10 -4
- package/templates/rover-pilot/.github/workflows/upgrade.yml +104 -0
- package/templates/rover-pilot/README.md +13 -5
- package/templates/rover-pilot/deploy/scripts/create-predeploy-backup.ts +936 -0
- package/templates/rover-pilot/deploy/scripts/decrypt-user-secrets.ts +80 -24
- package/templates/rover-pilot/deploy/scripts/helpers.ts +2 -0
- package/templates/rover-pilot/deploy/scripts/install-health-watchdog.ts +144 -0
- package/templates/rover-pilot/deploy/scripts/provision-server.ts +51 -21
- package/templates/rover-pilot/deploy/scripts/resolve-deploy-handles.ts +4 -0
- package/templates/rover-pilot/deploy/scripts/resolve-missing-images.ts +13 -0
- package/templates/rover-pilot/deploy/scripts/resolve-user-config.ts +40 -9
- package/templates/rover-pilot/deploy/scripts/sync-content-repo.ts +51 -47
- package/templates/rover-pilot/deploy/scripts/update-dns.ts +72 -16
- package/templates/rover-pilot/deploy/scripts/validate-secrets.ts +12 -1
- package/templates/rover-pilot/deploy/scripts/verify-runtime-image.ts +18 -0
- package/templates/rover-pilot/docs/canonical-crossover-record.md +107 -0
- package/templates/rover-pilot/docs/onboarding-checklist.md +23 -14
- package/templates/rover-pilot/docs/operator-playbook.md +357 -29
- package/templates/rover-pilot/docs/user-onboarding.md +48 -463
- package/templates/rover-pilot/package.json +1 -0
- package/templates/rover-pilot/pilot.yaml +6 -4
- package/dist/origin-ca.d.ts +0 -1
- package/dist/push-secrets.d.ts +0 -9
- package/dist/push-target.d.ts +0 -2
- package/dist/run-subprocess.d.ts +0 -6
- package/templates/rover-pilot/.kamal/hooks/pre-deploy +0 -9
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type StressCommandResult, type StressCommandRunner } from "./stress-command";
|
|
2
|
+
/**
|
|
3
|
+
* Git operations against one content-repo checkout, executed through a
|
|
4
|
+
* stress command runner with token-bearing credentials.
|
|
5
|
+
*/
|
|
6
|
+
export declare class GitCheckout {
|
|
7
|
+
#private;
|
|
8
|
+
readonly dir: string;
|
|
9
|
+
constructor(runner: StressCommandRunner, dir: string, token: string);
|
|
10
|
+
/** Clone `repository`'s main branch into `dir` and pin it to origin/main. */
|
|
11
|
+
static clone(runner: StressCommandRunner, repository: string, dir: string, token: string): Promise<GitCheckout>;
|
|
12
|
+
run(args: readonly string[], required?: boolean): Promise<StressCommandResult>;
|
|
13
|
+
output(args: readonly string[]): Promise<string>;
|
|
14
|
+
sync(hardReset?: boolean): Promise<void>;
|
|
15
|
+
/** Stage everything and commit as the ops identity. */
|
|
16
|
+
commitAll(message: string): Promise<void>;
|
|
17
|
+
pushMainWithRebase(): Promise<void>;
|
|
18
|
+
/** Delete every remote stress backup branch; returns the deleted names. */
|
|
19
|
+
pruneStressBackupBranches(): Promise<string[]>;
|
|
20
|
+
private environment;
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FetchLike } from "@brains/utils/fetch-like";
|
|
2
|
+
import { type StressHealthSample, type StressRuntimeSample } from "./directory-sync-stress";
|
|
3
|
+
export interface HealthPayload {
|
|
4
|
+
version: string;
|
|
5
|
+
entities: number;
|
|
6
|
+
entityCounts: {
|
|
7
|
+
entityType: string;
|
|
8
|
+
count: number;
|
|
9
|
+
}[];
|
|
10
|
+
}
|
|
11
|
+
export declare function noteCount(payload: HealthPayload): number;
|
|
12
|
+
export interface StressHealthMonitorOptions {
|
|
13
|
+
domain: string;
|
|
14
|
+
fetchImpl: FetchLike;
|
|
15
|
+
now: () => Date;
|
|
16
|
+
sleep: (milliseconds: number) => Promise<void>;
|
|
17
|
+
/** Take one runtime sample (e.g. docker stats over SSH), if available. */
|
|
18
|
+
sampleRuntime: () => Promise<StressRuntimeSample | undefined>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Samples deployment health and runtime resource usage during a stress run,
|
|
22
|
+
* and answers entity-baseline waits against the operational health endpoint.
|
|
23
|
+
*/
|
|
24
|
+
export declare class StressHealthMonitor {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(options: StressHealthMonitorOptions);
|
|
27
|
+
/** ISO timestamp of the most recent start() call. */
|
|
28
|
+
get startedAt(): string;
|
|
29
|
+
/** The first sampling failure since start(), if any. */
|
|
30
|
+
get error(): unknown;
|
|
31
|
+
get healthSamples(): readonly StressHealthSample[];
|
|
32
|
+
get runtimeSamples(): readonly StressRuntimeSample[];
|
|
33
|
+
/** Health samples between start() and markGateEnd() (or now). */
|
|
34
|
+
gateHealthSamples(): StressHealthSample[];
|
|
35
|
+
/** Exclude samples taken after this point from the gate window. */
|
|
36
|
+
markGateEnd(): void;
|
|
37
|
+
/** Probe optional health endpoints and add responsive ones to the loop. */
|
|
38
|
+
discoverEndpoints(): Promise<void>;
|
|
39
|
+
start(): void;
|
|
40
|
+
/** Stop sampling and wait for the loops; a loop error stays on `error`. */
|
|
41
|
+
stop(): Promise<void>;
|
|
42
|
+
waitForHealthSnapshot(timeoutMs: number): Promise<HealthPayload | undefined>;
|
|
43
|
+
waitForEntityBaseline(expectedNotes: number | undefined, expectedEntities: number | undefined, timeoutMs: number): Promise<HealthPayload | undefined>;
|
|
44
|
+
waitForStableEntityBaseline(expectedNotes: number | undefined, expectedEntities: number | undefined, timeoutMs: number): Promise<HealthPayload | undefined>;
|
|
45
|
+
fetchHealthPayload(): Promise<HealthPayload | undefined>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type UpgradeExec = (command: string[], cwd: string) => Promise<void>;
|
|
2
|
+
export interface UpgradeOptions {
|
|
3
|
+
exec?: UpgradeExec | undefined;
|
|
4
|
+
version?: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface UpgradeResult {
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function upgradePilotRepo(rootDir: string, options?: UpgradeOptions): Promise<UpgradeResult>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { FetchLike } from "@brains/utils/fetch-like";
|
|
2
|
+
export interface OffboardPilotUsersOptions {
|
|
3
|
+
handles: string[];
|
|
4
|
+
confirmation?: string | undefined;
|
|
5
|
+
dryRun?: boolean | undefined;
|
|
6
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
7
|
+
fetchImpl?: FetchLike | undefined;
|
|
8
|
+
logger?: ((message: string) => void) | undefined;
|
|
9
|
+
sleep?: ((delayMs: number) => Promise<void>) | undefined;
|
|
10
|
+
driver?: PilotOffboardDriver | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface PilotOffboardTarget {
|
|
13
|
+
handle: string;
|
|
14
|
+
instanceName: string;
|
|
15
|
+
domain: string;
|
|
16
|
+
previewDomain: string;
|
|
17
|
+
wwwDomain?: string | undefined;
|
|
18
|
+
contentRepo: string;
|
|
19
|
+
cloudflareZoneId: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PilotOffboardServer {
|
|
22
|
+
id: number;
|
|
23
|
+
name: string;
|
|
24
|
+
ip?: string | undefined;
|
|
25
|
+
}
|
|
26
|
+
export interface PilotOffboardDnsRecord {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
type: "A" | "CNAME";
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
export interface PilotOffboardInspection {
|
|
33
|
+
server?: PilotOffboardServer | undefined;
|
|
34
|
+
dnsRecords: PilotOffboardDnsRecord[];
|
|
35
|
+
contentRepoArchived: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface PilotOffboardDriver {
|
|
38
|
+
inspect(target: PilotOffboardTarget): Promise<PilotOffboardInspection>;
|
|
39
|
+
archiveContentRepo(target: PilotOffboardTarget): Promise<void>;
|
|
40
|
+
deleteDnsRecord(target: PilotOffboardTarget, record: PilotOffboardDnsRecord): Promise<void>;
|
|
41
|
+
deleteServer(target: PilotOffboardTarget, server: PilotOffboardServer): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export interface OffboardPilotUserPlan {
|
|
44
|
+
target: PilotOffboardTarget;
|
|
45
|
+
inspection: PilotOffboardInspection;
|
|
46
|
+
}
|
|
47
|
+
export interface OffboardPilotUsersResult {
|
|
48
|
+
dryRun: boolean;
|
|
49
|
+
handles: string[];
|
|
50
|
+
confirmation: string;
|
|
51
|
+
plans: OffboardPilotUserPlan[];
|
|
52
|
+
}
|
|
53
|
+
export declare function offboardPilotUsers(rootDir: string, options: OffboardPilotUsersOptions): Promise<OffboardPilotUsersResult>;
|
|
54
|
+
interface DefaultDriverOptions {
|
|
55
|
+
env: NodeJS.ProcessEnv;
|
|
56
|
+
fetchImpl?: FetchLike | undefined;
|
|
57
|
+
contentRepoTokenName: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function createPilotOffboardDriver(options: DefaultDriverOptions): PilotOffboardDriver;
|
|
60
|
+
export declare function assertPilotUsersRemoved(rootDir: string, handles: string[]): Promise<void>;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FetchLike } from "@brains/utils/fetch-like";
|
|
2
|
+
import { type ResolvedUser } from "./load-registry";
|
|
3
|
+
export interface VerifyPilotUserOptions {
|
|
4
|
+
fetchImpl?: FetchLike;
|
|
5
|
+
logger?: (message: string) => void;
|
|
6
|
+
operationalRetryAttempts?: number;
|
|
7
|
+
operationalRetryDelayMs?: number;
|
|
8
|
+
sleep?: (delayMs: number) => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface FailedCheck {
|
|
11
|
+
name: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
export interface VerifyPilotUserResult {
|
|
15
|
+
handle: string;
|
|
16
|
+
bundles: ResolvedUser["bundles"];
|
|
17
|
+
domain: string;
|
|
18
|
+
contentRepo: string;
|
|
19
|
+
checks: string[];
|
|
20
|
+
failedChecks: FailedCheck[];
|
|
21
|
+
}
|
|
22
|
+
export declare function verifyPilotUser(rootDir: string, handle: string, options?: VerifyPilotUserOptions): Promise<VerifyPilotUserResult>;
|
package/package.json
CHANGED
|
@@ -1,67 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rizom/ops",
|
|
3
|
+
"version": "0.2.0-alpha.451",
|
|
3
4
|
"description": "Operator CLI for managing private brain fleet registry repos",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"brains",
|
|
7
|
+
"ops",
|
|
8
|
+
"cli",
|
|
9
|
+
"deploy",
|
|
10
|
+
"fleet",
|
|
11
|
+
"operator"
|
|
12
|
+
],
|
|
13
|
+
"license": "AGPL-3.0-only",
|
|
14
|
+
"author": "Yeehaa <yeehaa@rizom.ai> (https://rizom.ai)",
|
|
15
|
+
"bin": {
|
|
16
|
+
"brains-ops": "./dist/brains-ops.js"
|
|
17
|
+
},
|
|
18
|
+
"bugs": "https://github.com/rizom-ai/brains/issues",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@libsql/client": "^0.17.4",
|
|
21
|
+
"age-encryption": "^0.3.0",
|
|
22
|
+
"yaml": "^2.9.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@brains/deploy-support": "workspace:*",
|
|
26
|
+
"@brains/eslint-config": "workspace:*",
|
|
27
|
+
"@brains/job-queue": "workspace:*",
|
|
28
|
+
"@brains/profile": "workspace:*",
|
|
29
|
+
"@brains/site-composition": "workspace:*",
|
|
30
|
+
"@brains/test-utils": "workspace:*",
|
|
31
|
+
"@brains/typescript-config": "workspace:*",
|
|
32
|
+
"@brains/utils": "workspace:*",
|
|
33
|
+
"@types/bun": "^1.4.0",
|
|
34
|
+
"typescript": "^7.0.2"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"bun": ">=1.3.3"
|
|
6
38
|
},
|
|
7
|
-
"version": "0.2.0-alpha.45",
|
|
8
|
-
"type": "module",
|
|
9
39
|
"exports": {
|
|
10
40
|
".": {
|
|
11
41
|
"types": "./dist/index.d.ts",
|
|
12
42
|
"import": "./dist/index.js"
|
|
13
43
|
},
|
|
14
44
|
"./deploy": {
|
|
15
|
-
"bun": "./dist/deploy.js",
|
|
16
45
|
"types": "./dist/deploy.d.ts",
|
|
17
|
-
"import": "./dist/deploy.js"
|
|
46
|
+
"import": "./dist/deploy.js",
|
|
47
|
+
"bun": "./dist/deploy.js"
|
|
18
48
|
}
|
|
19
49
|
},
|
|
20
|
-
"main": "./dist/index.js",
|
|
21
|
-
"types": "./dist/index.d.ts",
|
|
22
|
-
"bin": {
|
|
23
|
-
"brains-ops": "./dist/brains-ops.js"
|
|
24
|
-
},
|
|
25
50
|
"files": [
|
|
26
51
|
"dist",
|
|
27
52
|
"templates"
|
|
28
53
|
],
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"lint": "eslint . --ext .ts",
|
|
34
|
-
"lint:fix": "eslint . --ext .ts --fix",
|
|
35
|
-
"test": "bun test --timeout 20000"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"age-encryption": "^0.3.0"
|
|
39
|
-
},
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@brains/eslint-config": "workspace:*",
|
|
42
|
-
"@brains/typescript-config": "workspace:*",
|
|
43
|
-
"@brains/utils": "workspace:*",
|
|
44
|
-
"@types/bun": "latest",
|
|
45
|
-
"typescript": "^5.3.3"
|
|
54
|
+
"homepage": "https://github.com/rizom-ai/brains/tree/main/packages/brains-ops#readme",
|
|
55
|
+
"main": "./dist/index.js",
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
46
58
|
},
|
|
47
59
|
"repository": {
|
|
48
60
|
"type": "git",
|
|
49
61
|
"url": "https://github.com/rizom-ai/brains.git",
|
|
50
62
|
"directory": "packages/brains-ops"
|
|
51
63
|
},
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "bun scripts/build.ts",
|
|
66
|
+
"lint": "eslint . --max-warnings 0",
|
|
67
|
+
"lint:fix": "eslint . --max-warnings 0 --fix",
|
|
68
|
+
"prepublishOnly": "bun scripts/build.ts",
|
|
69
|
+
"test": "bun run build && bun test --timeout 20000",
|
|
70
|
+
"test:smoke": "bun run build && RUN_SMOKE_TESTS=1 bun test --timeout 60000",
|
|
71
|
+
"typecheck": "tsc --noEmit"
|
|
58
72
|
},
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
"ops",
|
|
62
|
-
"cli",
|
|
63
|
-
"deploy",
|
|
64
|
-
"fleet",
|
|
65
|
-
"operator"
|
|
66
|
-
]
|
|
73
|
+
"type": "module",
|
|
74
|
+
"types": "./dist/index.d.ts"
|
|
67
75
|
}
|
|
@@ -20,16 +20,31 @@ GIT_SYNC_TOKEN=
|
|
|
20
20
|
# @required @sensitive
|
|
21
21
|
CONTENT_REPO_ADMIN_TOKEN=
|
|
22
22
|
|
|
23
|
-
# MCP interface
|
|
24
|
-
# Comes from the decrypted users/<handle>.secrets.yaml.age file.
|
|
25
|
-
# @required @sensitive
|
|
26
|
-
MCP_AUTH_TOKEN=
|
|
27
|
-
|
|
28
23
|
# Discord (optional, per-user)
|
|
29
24
|
# Comes from the decrypted users/<handle>.secrets.yaml.age file when enabled.
|
|
30
25
|
# @sensitive
|
|
31
26
|
DISCORD_BOT_TOKEN=
|
|
32
27
|
|
|
28
|
+
# Stored with the per-user Discord credentials and injected at deploy time.
|
|
29
|
+
# @sensitive
|
|
30
|
+
DISCORD_PUBLIC_KEY=
|
|
31
|
+
|
|
32
|
+
# @sensitive
|
|
33
|
+
DISCORD_APPLICATION_ID=
|
|
34
|
+
|
|
35
|
+
# AT Protocol publishing/discovery (optional, per-user)
|
|
36
|
+
# Comes from the decrypted users/<handle>.secrets.yaml.age file when configured.
|
|
37
|
+
# @sensitive
|
|
38
|
+
ATPROTO_APP_PASSWORD=
|
|
39
|
+
|
|
40
|
+
# Setup email delivery (optional, shared)
|
|
41
|
+
# Used when a user file declares setup.delivery: email.
|
|
42
|
+
# @sensitive
|
|
43
|
+
SETUP_EMAIL_API_KEY=
|
|
44
|
+
|
|
45
|
+
# @sensitive
|
|
46
|
+
SETUP_EMAIL_FROM=
|
|
47
|
+
|
|
33
48
|
# ---- deploy/provision vars ----
|
|
34
49
|
|
|
35
50
|
# @required @sensitive
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Load Bitwarden environment through Varlock
|
|
2
|
+
description: Load, mask, and export the pilot environment from Varlock
|
|
3
|
+
|
|
4
|
+
runs:
|
|
5
|
+
using: composite
|
|
6
|
+
steps:
|
|
7
|
+
- name: Load shared environment
|
|
8
|
+
shell: bash
|
|
9
|
+
run: |
|
|
10
|
+
for attempt in 1 2 3; do
|
|
11
|
+
if bunx varlock@1.1.0 load --path .env.schema --format json --compact > /tmp/varlock-env.json; then
|
|
12
|
+
break
|
|
13
|
+
fi
|
|
14
|
+
if [ "$attempt" = "3" ]; then
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
echo "Varlock load failed (attempt $attempt/3); retrying in 5s..."
|
|
18
|
+
sleep 5
|
|
19
|
+
done
|
|
20
|
+
node <<'NODE'
|
|
21
|
+
import { appendFileSync, readFileSync, rmSync } from "node:fs";
|
|
22
|
+
const env = JSON.parse(readFileSync("/tmp/varlock-env.json", "utf8"));
|
|
23
|
+
const output = process.env.GITHUB_ENV;
|
|
24
|
+
if (!output) throw new Error("Missing GITHUB_ENV");
|
|
25
|
+
const newline = "\n";
|
|
26
|
+
const chunks = [];
|
|
27
|
+
for (const [key, value] of Object.entries(env)) {
|
|
28
|
+
if (key === "BWS_ACCESS_TOKEN" || value === null || value === undefined) continue;
|
|
29
|
+
const text = String(value).replace(/\r\n/g, newline);
|
|
30
|
+
const escapedMask = text
|
|
31
|
+
.replace(/%/g, "%25")
|
|
32
|
+
.replace(/\r/g, "%0D")
|
|
33
|
+
.replace(/\n/g, "%0A");
|
|
34
|
+
if (escapedMask.length > 0) {
|
|
35
|
+
process.stdout.write(`::add-mask::${escapedMask}${newline}`);
|
|
36
|
+
}
|
|
37
|
+
if (text.includes(newline)) {
|
|
38
|
+
let delimiter = `VARLOCK_${key}_EOF`;
|
|
39
|
+
while (text.includes(delimiter)) delimiter += "_X";
|
|
40
|
+
chunks.push(`${key}<<${delimiter}${newline}${text}${newline}${delimiter}`);
|
|
41
|
+
} else {
|
|
42
|
+
chunks.push(`${key}=${text}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
appendFileSync(output, `${chunks.join(newline)}${newline}`);
|
|
46
|
+
rmSync("/tmp/varlock-env.json", { force: true });
|
|
47
|
+
NODE
|
|
@@ -2,10 +2,29 @@ name: Build
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
brain_version:
|
|
7
|
+
description: Build one Brain version with the full fleet package union
|
|
8
|
+
required: false
|
|
9
|
+
type: string
|
|
10
|
+
site_packages:
|
|
11
|
+
description: "Extra exact site/theme package pins for an explicit build. All builds include the full fleet package union; conflicting pins are rejected."
|
|
12
|
+
required: false
|
|
13
|
+
type: string
|
|
14
|
+
overwrite:
|
|
15
|
+
description: Rebuild an existing tag in place. Published tags are immutable; confirm only when replacing a tag whose containers were never deployed.
|
|
16
|
+
required: false
|
|
17
|
+
type: boolean
|
|
18
|
+
default: false
|
|
5
19
|
push:
|
|
6
20
|
branches: ["main"]
|
|
7
21
|
paths:
|
|
22
|
+
# The image set is a pure function of the declared fleet state: a push
|
|
23
|
+
# that changes it builds exactly what it declares, so the deploy (fired
|
|
24
|
+
# off the same push via Reconcile) finds its tag ready.
|
|
8
25
|
- pilot.yaml
|
|
26
|
+
- cohorts/**
|
|
27
|
+
- users/*.yaml
|
|
9
28
|
- deploy/**
|
|
10
29
|
- .github/workflows/build.yml
|
|
11
30
|
|
|
@@ -14,50 +33,84 @@ permissions:
|
|
|
14
33
|
packages: write
|
|
15
34
|
|
|
16
35
|
jobs:
|
|
17
|
-
|
|
36
|
+
resolve:
|
|
18
37
|
runs-on: ubuntu-latest
|
|
38
|
+
outputs:
|
|
39
|
+
images_json: ${{ steps.resolve.outputs.images_json }}
|
|
19
40
|
steps:
|
|
20
41
|
- uses: actions/checkout@v5
|
|
21
42
|
with:
|
|
22
43
|
ref: ${{ github.sha }}
|
|
23
44
|
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
- uses: oven-sh/setup-bun@v2
|
|
46
|
+
|
|
47
|
+
- name: Install operator tooling
|
|
48
|
+
run: bun install
|
|
49
|
+
|
|
50
|
+
- name: Log in to GHCR
|
|
51
|
+
uses: docker/login-action@v4
|
|
52
|
+
with:
|
|
53
|
+
registry: ghcr.io
|
|
54
|
+
username: ${{ github.actor }}
|
|
55
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
56
|
+
|
|
57
|
+
- name: Resolve images to build
|
|
58
|
+
id: resolve
|
|
59
|
+
env:
|
|
60
|
+
BRAIN_VERSION_INPUT: ${{ inputs.brain_version || '' }}
|
|
61
|
+
SITE_PACKAGES_INPUT: ${{ inputs.site_packages || '' }}
|
|
62
|
+
ALLOW_TAG_OVERWRITE: ${{ inputs.overwrite && 'true' || 'false' }}
|
|
63
|
+
run: bun deploy/scripts/resolve-missing-images.ts
|
|
64
|
+
|
|
65
|
+
no_builds:
|
|
66
|
+
needs: resolve
|
|
67
|
+
if: needs.resolve.outputs.images_json == '[]'
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
steps:
|
|
70
|
+
- name: Skip when every declared image exists
|
|
71
|
+
run: echo "All declared images already exist; nothing to build."
|
|
72
|
+
|
|
73
|
+
build:
|
|
74
|
+
needs: resolve
|
|
75
|
+
if: needs.resolve.outputs.images_json != '[]'
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
strategy:
|
|
78
|
+
fail-fast: false
|
|
79
|
+
matrix:
|
|
80
|
+
image: ${{ fromJson(needs.resolve.outputs.images_json) }}
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/checkout@v5
|
|
83
|
+
with:
|
|
84
|
+
ref: ${{ github.sha }}
|
|
33
85
|
|
|
34
86
|
- name: Set up Docker Buildx
|
|
35
|
-
uses: docker/setup-buildx-action@
|
|
87
|
+
uses: docker/setup-buildx-action@v4
|
|
36
88
|
|
|
37
89
|
- name: Extract image metadata
|
|
38
90
|
id: meta
|
|
39
|
-
uses: docker/metadata-action@
|
|
91
|
+
uses: docker/metadata-action@v6
|
|
40
92
|
with:
|
|
41
|
-
images:
|
|
93
|
+
images: ghcr.io/${{ github.repository }}
|
|
42
94
|
tags: |
|
|
43
|
-
type=raw,value
|
|
95
|
+
type=raw,value=${{ matrix.image.tag }}
|
|
44
96
|
|
|
45
97
|
- name: Log in to GHCR
|
|
46
|
-
uses: docker/login-action@
|
|
98
|
+
uses: docker/login-action@v4
|
|
47
99
|
with:
|
|
48
100
|
registry: ghcr.io
|
|
49
101
|
username: ${{ github.actor }}
|
|
50
102
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
51
103
|
|
|
52
104
|
- name: Build and push image
|
|
53
|
-
uses: docker/build-push-action@
|
|
105
|
+
uses: docker/build-push-action@v7
|
|
54
106
|
with:
|
|
55
107
|
context: .
|
|
56
108
|
file: deploy/Dockerfile
|
|
57
109
|
target: fleet
|
|
58
110
|
push: true
|
|
59
111
|
build-args: |
|
|
60
|
-
BRAIN_VERSION=${{
|
|
112
|
+
BRAIN_VERSION=${{ matrix.image.brain_version }}
|
|
113
|
+
SITE_PACKAGES=${{ matrix.image.site_packages }}
|
|
61
114
|
tags: ${{ steps.meta.outputs.tags }}
|
|
62
115
|
labels: |
|
|
63
116
|
${{ steps.meta.outputs.labels }}
|