@rizom/ops 0.2.0-alpha.45 → 0.2.0-alpha.450

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.
Files changed (70) hide show
  1. package/README.md +82 -1
  2. package/dist/age-key-bootstrap.d.ts +1 -1
  3. package/dist/brains-ops.js +736 -335
  4. package/dist/capability-bundle-migration.d.ts +22 -0
  5. package/dist/cert-bootstrap.d.ts +4 -2
  6. package/dist/content-repo-ref.d.ts +10 -0
  7. package/dist/content-repo.d.ts +1 -1
  8. package/dist/deploy.js +110 -166
  9. package/dist/directory-sync-stress-system.d.ts +75 -0
  10. package/dist/directory-sync-stress.d.ts +117 -0
  11. package/dist/entries/deploy.d.ts +4 -2
  12. package/dist/health-watchdog-smoke.d.ts +54 -0
  13. package/dist/image-inventory.d.ts +4 -0
  14. package/dist/image-types.d.ts +7 -0
  15. package/dist/images.d.ts +82 -0
  16. package/dist/index.d.ts +8 -0
  17. package/dist/index.js +736 -323
  18. package/dist/legacy-pilot-migration.d.ts +11 -0
  19. package/dist/legacy-projection-job-recovery.d.ts +29 -0
  20. package/dist/load-registry.d.ts +61 -6
  21. package/dist/observed-status.d.ts +1 -1
  22. package/dist/parse-args.d.ts +2 -12
  23. package/dist/preview-domain.d.ts +9 -0
  24. package/dist/reconcile-dry-run.d.ts +9 -0
  25. package/dist/run-command.d.ts +20 -4
  26. package/dist/schema.d.ts +126 -163
  27. package/dist/secrets-encrypt.d.ts +7 -13
  28. package/dist/secrets-push.d.ts +1 -1
  29. package/dist/ssh-key-bootstrap.d.ts +1 -26
  30. package/dist/stage-legacy-crossover.d.ts +25 -0
  31. package/dist/stress-command.d.ts +14 -0
  32. package/dist/stress-git-checkout.d.ts +21 -0
  33. package/dist/stress-health-monitor.d.ts +46 -0
  34. package/dist/upgrade.d.ts +10 -0
  35. package/dist/user-offboard.d.ts +61 -0
  36. package/dist/verify-user.d.ts +22 -0
  37. package/package.json +50 -42
  38. package/templates/rover-pilot/.env.schema +20 -5
  39. package/templates/rover-pilot/.github/actions/varlock-env/action.yml +47 -0
  40. package/templates/rover-pilot/.github/workflows/build.yml +70 -17
  41. package/templates/rover-pilot/.github/workflows/deploy.yml +74 -57
  42. package/templates/rover-pilot/.github/workflows/directory-sync-stress.yml +119 -0
  43. package/templates/rover-pilot/.github/workflows/health-watchdog-smoke.yml +95 -0
  44. package/templates/rover-pilot/.github/workflows/offboard.yml +107 -0
  45. package/templates/rover-pilot/.github/workflows/reconcile.yml +10 -4
  46. package/templates/rover-pilot/.github/workflows/upgrade.yml +104 -0
  47. package/templates/rover-pilot/README.md +13 -5
  48. package/templates/rover-pilot/deploy/scripts/create-predeploy-backup.ts +936 -0
  49. package/templates/rover-pilot/deploy/scripts/decrypt-user-secrets.ts +80 -24
  50. package/templates/rover-pilot/deploy/scripts/helpers.ts +2 -0
  51. package/templates/rover-pilot/deploy/scripts/install-health-watchdog.ts +144 -0
  52. package/templates/rover-pilot/deploy/scripts/provision-server.ts +51 -21
  53. package/templates/rover-pilot/deploy/scripts/resolve-deploy-handles.ts +4 -0
  54. package/templates/rover-pilot/deploy/scripts/resolve-missing-images.ts +13 -0
  55. package/templates/rover-pilot/deploy/scripts/resolve-user-config.ts +40 -9
  56. package/templates/rover-pilot/deploy/scripts/sync-content-repo.ts +51 -47
  57. package/templates/rover-pilot/deploy/scripts/update-dns.ts +72 -16
  58. package/templates/rover-pilot/deploy/scripts/validate-secrets.ts +12 -1
  59. package/templates/rover-pilot/deploy/scripts/verify-runtime-image.ts +18 -0
  60. package/templates/rover-pilot/docs/canonical-crossover-record.md +107 -0
  61. package/templates/rover-pilot/docs/onboarding-checklist.md +23 -14
  62. package/templates/rover-pilot/docs/operator-playbook.md +357 -29
  63. package/templates/rover-pilot/docs/user-onboarding.md +48 -463
  64. package/templates/rover-pilot/package.json +1 -0
  65. package/templates/rover-pilot/pilot.yaml +6 -4
  66. package/dist/origin-ca.d.ts +0 -1
  67. package/dist/push-secrets.d.ts +0 -9
  68. package/dist/push-target.d.ts +0 -2
  69. package/dist/run-subprocess.d.ts +0 -6
  70. 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.450",
3
4
  "description": "Operator CLI for managing private brain fleet registry repos",
4
- "publishConfig": {
5
- "access": "public"
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
- "scripts": {
30
- "build": "bun scripts/build.ts",
31
- "prepublishOnly": "bun scripts/build.ts",
32
- "typecheck": "tsc --noEmit",
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
- "license": "Apache-2.0",
53
- "author": "Yeehaa <yeehaa@rizom.ai> (https://rizom.ai)",
54
- "homepage": "https://github.com/rizom-ai/brains/tree/main/packages/brains-ops#readme",
55
- "bugs": "https://github.com/rizom-ai/brains/issues",
56
- "engines": {
57
- "bun": ">=1.3.3"
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
- "keywords": [
60
- "brains",
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
- build:
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
- - name: Extract image metadata inputs
25
- run: |
26
- BRAIN_VERSION="$(grep '^brainVersion:' pilot.yaml | sed 's/^brainVersion:[[:space:]]*//' | tr -d '"' | tr -d "'")"
27
- if [ -z "$BRAIN_VERSION" ]; then
28
- echo "Missing brainVersion in pilot.yaml" >&2
29
- exit 1
30
- fi
31
- echo "BRAIN_VERSION=$BRAIN_VERSION" >> "$GITHUB_ENV"
32
- echo "IMAGE_REPOSITORY=ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}" >> "$GITHUB_ENV"
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@v3
87
+ uses: docker/setup-buildx-action@v4
36
88
 
37
89
  - name: Extract image metadata
38
90
  id: meta
39
- uses: docker/metadata-action@v5
91
+ uses: docker/metadata-action@v6
40
92
  with:
41
- images: ${{ env.IMAGE_REPOSITORY }}
93
+ images: ghcr.io/${{ github.repository }}
42
94
  tags: |
43
- type=raw,value=brain-${{ env.BRAIN_VERSION }}
95
+ type=raw,value=${{ matrix.image.tag }}
44
96
 
45
97
  - name: Log in to GHCR
46
- uses: docker/login-action@v3
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@v6
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=${{ env.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 }}