@rizom/ops 0.2.0-alpha.141 → 0.2.0-alpha.143

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.
@@ -38,6 +38,11 @@ export interface ResolvedAtprotoConfig {
38
38
  export interface ResolvedPlaybooksConfig {
39
39
  onboarding?: boolean | undefined;
40
40
  }
41
+ export interface ResolvedSiteOverride {
42
+ package: string;
43
+ version: string;
44
+ theme?: string | undefined;
45
+ }
41
46
  export interface ResolvedUserIdentity {
42
47
  handle: string;
43
48
  cohort: string;
@@ -45,7 +50,10 @@ export interface ResolvedUserIdentity {
45
50
  model: "rover";
46
51
  preset: PilotPreset;
47
52
  domain: string;
53
+ cloudflareZoneId?: string | undefined;
48
54
  contentRepo: string;
55
+ addOverride?: string[];
56
+ siteOverride?: ResolvedSiteOverride;
49
57
  discordEnabled: boolean;
50
58
  discordAnchorUserId?: string;
51
59
  effectiveAiApiKey: string;
@@ -8,6 +8,7 @@ export interface ParsedArgs {
8
8
  pushTo?: string | undefined;
9
9
  cohort?: string | undefined;
10
10
  anchorId?: string | undefined;
11
+ handle?: string | undefined;
11
12
  };
12
13
  }
13
14
  export declare function parseArgs(argv: string[]): ParsedArgs;
package/dist/schema.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { z } from "@brains/utils/zod";
2
- export declare const presetSchema: z.ZodEnum<["core", "default", "pro"]>;
2
+ export declare const presetSchema: z.ZodEnum<{
3
+ core: "core";
4
+ default: "default";
5
+ pro: "pro";
6
+ }>;
3
7
  export declare const exactVersionSchema: z.ZodString;
4
8
  export declare const handleSchema: z.ZodString;
5
9
  export declare const secretNameSchema: z.ZodString;
@@ -11,209 +15,76 @@ export declare const pilotSchema: z.ZodObject<{
11
15
  githubOrg: z.ZodString;
12
16
  contentRepoPrefix: z.ZodString;
13
17
  domainSuffix: z.ZodString;
14
- preset: z.ZodEnum<["core", "default", "pro"]>;
18
+ preset: typeof presetSchema;
15
19
  aiApiKey: z.ZodString;
16
20
  gitSyncToken: z.ZodString;
17
21
  contentRepoAdminToken: z.ZodString;
18
22
  agePublicKey: z.ZodString;
19
- }, "strict", z.ZodTypeAny, {
20
- agePublicKey: string;
21
- schemaVersion: 1;
22
- brainVersion: string;
23
- model: "rover";
24
- githubOrg: string;
25
- contentRepoPrefix: string;
26
- domainSuffix: string;
27
- preset: "default" | "core" | "pro";
28
- aiApiKey: string;
29
- gitSyncToken: string;
30
- contentRepoAdminToken: string;
31
- }, {
32
- agePublicKey: string;
33
- schemaVersion: 1;
34
- brainVersion: string;
35
- model: "rover";
36
- githubOrg: string;
37
- contentRepoPrefix: string;
38
- domainSuffix: string;
39
- preset: "default" | "core" | "pro";
40
- aiApiKey: string;
41
- gitSyncToken: string;
42
- contentRepoAdminToken: string;
23
+ }>;
24
+ declare const anchorProfileSocialLinkSchema: z.ZodObject<{
25
+ platform: z.ZodEnum<{
26
+ github: "github";
27
+ instagram: "instagram";
28
+ linkedin: "linkedin";
29
+ email: "email";
30
+ website: "website";
31
+ }>;
32
+ url: z.ZodString;
33
+ label: z.ZodOptional<z.ZodString>;
34
+ }>;
35
+ declare const anchorProfileSchema: z.ZodObject<{
36
+ name: z.ZodOptional<z.ZodString>;
37
+ description: z.ZodOptional<z.ZodString>;
38
+ website: z.ZodOptional<z.ZodString>;
39
+ email: z.ZodOptional<z.ZodString>;
40
+ story: z.ZodOptional<z.ZodString>;
41
+ socialLinks: z.ZodOptional<z.ZodArray<typeof anchorProfileSocialLinkSchema>>;
42
+ }>;
43
+ declare const setupDeliverySchema: z.ZodObject<{
44
+ delivery: z.ZodLiteral<"email">;
45
+ email: z.ZodString;
46
+ }>;
47
+ declare const atprotoSchema: z.ZodObject<{
48
+ identifier: z.ZodString;
49
+ }>;
50
+ declare const siteOverrideSchema: z.ZodObject<{
51
+ package: z.ZodString;
52
+ version: typeof exactVersionSchema;
53
+ theme: z.ZodOptional<z.ZodString>;
54
+ }>;
55
+ declare const playbooksSchema: z.ZodObject<{
56
+ onboarding: z.ZodOptional<z.ZodBoolean>;
43
57
  }>;
44
58
  export declare const userSchema: z.ZodObject<{
45
59
  handle: z.ZodString;
46
60
  discord: z.ZodObject<{
47
61
  enabled: z.ZodBoolean;
48
62
  anchorUserId: z.ZodOptional<z.ZodString>;
49
- }, "strict", z.ZodTypeAny, {
50
- enabled: boolean;
51
- anchorUserId?: string | undefined;
52
- }, {
53
- enabled: boolean;
54
- anchorUserId?: string | undefined;
55
63
  }>;
56
64
  aiApiKeyOverride: z.ZodOptional<z.ZodString>;
57
65
  gitSyncTokenOverride: z.ZodOptional<z.ZodString>;
58
- setup: z.ZodOptional<z.ZodObject<{
59
- delivery: z.ZodLiteral<"email">;
60
- email: z.ZodString;
61
- }, "strict", z.ZodTypeAny, {
62
- email: string;
63
- delivery: "email";
64
- }, {
65
- email: string;
66
- delivery: "email";
67
- }>>;
68
- atproto: z.ZodOptional<z.ZodObject<{
69
- identifier: z.ZodString;
70
- }, "strict", z.ZodTypeAny, {
71
- identifier: string;
72
- }, {
73
- identifier: string;
74
- }>>;
75
- playbooks: z.ZodOptional<z.ZodObject<{
76
- onboarding: z.ZodOptional<z.ZodBoolean>;
77
- }, "strict", z.ZodTypeAny, {
78
- onboarding?: boolean | undefined;
79
- }, {
80
- onboarding?: boolean | undefined;
81
- }>>;
82
- anchorProfile: z.ZodOptional<z.ZodObject<{
83
- name: z.ZodOptional<z.ZodString>;
84
- description: z.ZodOptional<z.ZodString>;
85
- website: z.ZodOptional<z.ZodString>;
86
- email: z.ZodOptional<z.ZodString>;
87
- story: z.ZodOptional<z.ZodString>;
88
- socialLinks: z.ZodOptional<z.ZodArray<z.ZodObject<{
89
- platform: z.ZodEnum<["github", "instagram", "linkedin", "email", "website"]>;
90
- url: z.ZodString;
91
- label: z.ZodOptional<z.ZodString>;
92
- }, "strict", z.ZodTypeAny, {
93
- platform: "github" | "instagram" | "linkedin" | "email" | "website";
94
- url: string;
95
- label?: string | undefined;
96
- }, {
97
- platform: "github" | "instagram" | "linkedin" | "email" | "website";
98
- url: string;
99
- label?: string | undefined;
100
- }>, "many">>;
101
- }, "strict", z.ZodTypeAny, {
102
- name?: string | undefined;
103
- email?: string | undefined;
104
- website?: string | undefined;
105
- description?: string | undefined;
106
- story?: string | undefined;
107
- socialLinks?: {
108
- platform: "github" | "instagram" | "linkedin" | "email" | "website";
109
- url: string;
110
- label?: string | undefined;
111
- }[] | undefined;
112
- }, {
113
- name?: string | undefined;
114
- email?: string | undefined;
115
- website?: string | undefined;
116
- description?: string | undefined;
117
- story?: string | undefined;
118
- socialLinks?: {
119
- platform: "github" | "instagram" | "linkedin" | "email" | "website";
120
- url: string;
121
- label?: string | undefined;
122
- }[] | undefined;
123
- }>>;
124
- }, "strict", z.ZodTypeAny, {
125
- handle: string;
126
- discord: {
127
- enabled: boolean;
128
- anchorUserId?: string | undefined;
129
- };
130
- aiApiKeyOverride?: string | undefined;
131
- gitSyncTokenOverride?: string | undefined;
132
- setup?: {
133
- email: string;
134
- delivery: "email";
135
- } | undefined;
136
- atproto?: {
137
- identifier: string;
138
- } | undefined;
139
- playbooks?: {
140
- onboarding?: boolean | undefined;
141
- } | undefined;
142
- anchorProfile?: {
143
- name?: string | undefined;
144
- email?: string | undefined;
145
- website?: string | undefined;
146
- description?: string | undefined;
147
- story?: string | undefined;
148
- socialLinks?: {
149
- platform: "github" | "instagram" | "linkedin" | "email" | "website";
150
- url: string;
151
- label?: string | undefined;
152
- }[] | undefined;
153
- } | undefined;
154
- }, {
155
- handle: string;
156
- discord: {
157
- enabled: boolean;
158
- anchorUserId?: string | undefined;
159
- };
160
- aiApiKeyOverride?: string | undefined;
161
- gitSyncTokenOverride?: string | undefined;
162
- setup?: {
163
- email: string;
164
- delivery: "email";
165
- } | undefined;
166
- atproto?: {
167
- identifier: string;
168
- } | undefined;
169
- playbooks?: {
170
- onboarding?: boolean | undefined;
171
- } | undefined;
172
- anchorProfile?: {
173
- name?: string | undefined;
174
- email?: string | undefined;
175
- website?: string | undefined;
176
- description?: string | undefined;
177
- story?: string | undefined;
178
- socialLinks?: {
179
- platform: "github" | "instagram" | "linkedin" | "email" | "website";
180
- url: string;
181
- label?: string | undefined;
182
- }[] | undefined;
183
- } | undefined;
66
+ domainOverride: z.ZodOptional<z.ZodString>;
67
+ cloudflareZoneId: z.ZodOptional<z.ZodString>;
68
+ contentRepoOverride: z.ZodOptional<z.ZodString>;
69
+ addOverride: z.ZodOptional<z.ZodArray<z.ZodString>>;
70
+ siteOverride: z.ZodOptional<typeof siteOverrideSchema>;
71
+ setup: z.ZodOptional<typeof setupDeliverySchema>;
72
+ atproto: z.ZodOptional<typeof atprotoSchema>;
73
+ playbooks: z.ZodOptional<typeof playbooksSchema>;
74
+ anchorProfile: z.ZodOptional<typeof anchorProfileSchema>;
184
75
  }>;
185
- export declare const cohortSchema: z.ZodEffects<z.ZodObject<{
186
- members: z.ZodArray<z.ZodString, "many">;
76
+ export declare const cohortSchema: z.ZodObject<{
77
+ members: z.ZodArray<z.ZodString>;
187
78
  brainVersionOverride: z.ZodOptional<z.ZodString>;
188
- presetOverride: z.ZodOptional<z.ZodEnum<["core", "default", "pro"]>>;
79
+ presetOverride: z.ZodOptional<typeof presetSchema>;
189
80
  aiApiKeyOverride: z.ZodOptional<z.ZodString>;
190
81
  gitSyncTokenOverride: z.ZodOptional<z.ZodString>;
191
- }, "strict", z.ZodTypeAny, {
192
- members: string[];
193
- aiApiKeyOverride?: string | undefined;
194
- gitSyncTokenOverride?: string | undefined;
195
- brainVersionOverride?: string | undefined;
196
- presetOverride?: "default" | "core" | "pro" | undefined;
197
- }, {
198
- members: string[];
199
- aiApiKeyOverride?: string | undefined;
200
- gitSyncTokenOverride?: string | undefined;
201
- brainVersionOverride?: string | undefined;
202
- presetOverride?: "default" | "core" | "pro" | undefined;
203
- }>, {
204
- members: string[];
205
- aiApiKeyOverride?: string | undefined;
206
- gitSyncTokenOverride?: string | undefined;
207
- brainVersionOverride?: string | undefined;
208
- presetOverride?: "default" | "core" | "pro" | undefined;
209
- }, {
210
- members: string[];
211
- aiApiKeyOverride?: string | undefined;
212
- gitSyncTokenOverride?: string | undefined;
213
- brainVersionOverride?: string | undefined;
214
- presetOverride?: "default" | "core" | "pro" | undefined;
215
82
  }>;
216
- export type PilotConfig = z.infer<typeof pilotSchema>;
217
- export type UserConfig = z.infer<typeof userSchema>;
218
- export type CohortConfig = z.infer<typeof cohortSchema>;
219
- export type PilotPreset = z.infer<typeof presetSchema>;
83
+ export type PilotConfig = z.output<typeof pilotSchema>;
84
+ export type PilotConfigInput = z.input<typeof pilotSchema>;
85
+ export type UserConfig = z.output<typeof userSchema>;
86
+ export type UserConfigInput = z.input<typeof userSchema>;
87
+ export type CohortConfig = z.output<typeof cohortSchema>;
88
+ export type CohortConfigInput = z.input<typeof cohortSchema>;
89
+ export type PilotPreset = z.output<typeof presetSchema>;
90
+ export {};
@@ -4,18 +4,8 @@ declare const encryptedUserSecretsSchema: z.ZodObject<{
4
4
  discordBotToken: z.ZodOptional<z.ZodString>;
5
5
  aiApiKey: z.ZodOptional<z.ZodString>;
6
6
  atprotoAppPassword: z.ZodOptional<z.ZodString>;
7
- }, "strict", z.ZodTypeAny, {
8
- aiApiKey?: string | undefined;
9
- gitSyncToken?: string | undefined;
10
- discordBotToken?: string | undefined;
11
- atprotoAppPassword?: string | undefined;
12
- }, {
13
- aiApiKey?: string | undefined;
14
- gitSyncToken?: string | undefined;
15
- discordBotToken?: string | undefined;
16
- atprotoAppPassword?: string | undefined;
17
7
  }>;
18
- export type EncryptedUserSecrets = z.infer<typeof encryptedUserSecretsSchema>;
8
+ export type EncryptedUserSecrets = z.output<typeof encryptedUserSecretsSchema>;
19
9
  export interface SecretsEncryptOptions {
20
10
  env?: NodeJS.ProcessEnv | undefined;
21
11
  logger?: ((message: string) => void) | undefined;
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
1
  {
2
2
  "name": "@rizom/ops",
3
+ "version": "0.2.0-alpha.143",
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": "Apache-2.0",
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
+ "age-encryption": "^0.3.0"
21
+ },
22
+ "devDependencies": {
23
+ "@brains/deploy-support": "workspace:*",
24
+ "@brains/eslint-config": "workspace:*",
25
+ "@brains/typescript-config": "workspace:*",
26
+ "@brains/utils": "workspace:*",
27
+ "@types/bun": "^1.3.14",
28
+ "typescript": "^6.0.3"
29
+ },
30
+ "engines": {
31
+ "bun": ">=1.3.3"
6
32
  },
7
- "version": "0.2.0-alpha.141",
8
- "type": "module",
9
33
  "exports": {
10
34
  ".": {
11
35
  "types": "./dist/index.d.ts",
12
36
  "import": "./dist/index.js"
13
37
  },
14
38
  "./deploy": {
15
- "bun": "./dist/deploy.js",
16
39
  "types": "./dist/deploy.d.ts",
17
- "import": "./dist/deploy.js"
40
+ "import": "./dist/deploy.js",
41
+ "bun": "./dist/deploy.js"
18
42
  }
19
43
  },
20
- "main": "./dist/index.js",
21
- "types": "./dist/index.d.ts",
22
- "bin": {
23
- "brains-ops": "./dist/brains-ops.js"
24
- },
25
44
  "files": [
26
45
  "dist",
27
46
  "templates"
28
47
  ],
29
- "scripts": {
30
- "build": "bun scripts/build.ts",
31
- "prepublishOnly": "bun scripts/build.ts",
32
- "typecheck": "tsc --noEmit",
33
- "lint": "eslint . --ext .ts,.tsx --max-warnings 0",
34
- "lint:fix": "eslint . --ext .ts,.tsx --max-warnings 0 --fix",
35
- "test": "bun run build && bun test --timeout 20000",
36
- "test:smoke": "bun run build && RUN_SMOKE_TESTS=1 bun test --timeout 60000"
37
- },
38
- "dependencies": {
39
- "age-encryption": "^0.3.0"
40
- },
41
- "devDependencies": {
42
- "@brains/deploy-support": "workspace:*",
43
- "@brains/eslint-config": "workspace:*",
44
- "@brains/typescript-config": "workspace:*",
45
- "@brains/utils": "workspace:*",
46
- "@types/bun": "latest",
47
- "typescript": "^5.3.3"
48
+ "homepage": "https://github.com/rizom-ai/brains/tree/main/packages/brains-ops#readme",
49
+ "main": "./dist/index.js",
50
+ "publishConfig": {
51
+ "access": "public"
48
52
  },
49
53
  "repository": {
50
54
  "type": "git",
51
55
  "url": "https://github.com/rizom-ai/brains.git",
52
56
  "directory": "packages/brains-ops"
53
57
  },
54
- "license": "Apache-2.0",
55
- "author": "Yeehaa <yeehaa@rizom.ai> (https://rizom.ai)",
56
- "homepage": "https://github.com/rizom-ai/brains/tree/main/packages/brains-ops#readme",
57
- "bugs": "https://github.com/rizom-ai/brains/issues",
58
- "engines": {
59
- "bun": ">=1.3.3"
58
+ "scripts": {
59
+ "build": "bun scripts/build.ts",
60
+ "lint": "eslint . --max-warnings 0",
61
+ "lint:fix": "eslint . --max-warnings 0 --fix",
62
+ "prepublishOnly": "bun scripts/build.ts",
63
+ "test": "bun run build && bun test --timeout 20000",
64
+ "test:smoke": "bun run build && RUN_SMOKE_TESTS=1 bun test --timeout 60000",
65
+ "typecheck": "tsc --noEmit"
60
66
  },
61
- "keywords": [
62
- "brains",
63
- "ops",
64
- "cli",
65
- "deploy",
66
- "fleet",
67
- "operator"
68
- ]
67
+ "type": "module",
68
+ "types": "./dist/index.d.ts"
69
69
  }
@@ -11,6 +11,10 @@ on:
11
11
  branches: ["main"]
12
12
  paths:
13
13
  - pilot.yaml
14
+ - cohorts/*.yaml
15
+ - users/*.yaml
16
+ - package.json
17
+ - bun.lock
14
18
  - deploy/**
15
19
  - .github/workflows/build.yml
16
20
 
@@ -26,17 +30,16 @@ jobs:
26
30
  with:
27
31
  ref: ${{ github.sha }}
28
32
 
29
- - name: Extract image metadata inputs
33
+ - uses: oven-sh/setup-bun@v2
34
+
35
+ - name: Install operator tooling
36
+ run: bun install
37
+
38
+ - name: Resolve image metadata inputs
39
+ env:
40
+ BRAIN_VERSION_INPUT: ${{ inputs.brain_version || '' }}
30
41
  run: |
31
- BRAIN_VERSION="${{ inputs.brain_version || '' }}"
32
- if [ -z "$BRAIN_VERSION" ]; then
33
- BRAIN_VERSION="$(grep '^brainVersion:' pilot.yaml | sed 's/^brainVersion:[[:space:]]*//' | tr -d '"' | tr -d "'")"
34
- fi
35
- if [ -z "$BRAIN_VERSION" ]; then
36
- echo "Missing brainVersion in pilot.yaml" >&2
37
- exit 1
38
- fi
39
- echo "BRAIN_VERSION=$BRAIN_VERSION" >> "$GITHUB_ENV"
42
+ bun deploy/scripts/resolve-build-config.ts
40
43
  echo "IMAGE_REPOSITORY=ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}" >> "$GITHUB_ENV"
41
44
 
42
45
  - name: Set up Docker Buildx
@@ -48,7 +51,7 @@ jobs:
48
51
  with:
49
52
  images: ${{ env.IMAGE_REPOSITORY }}
50
53
  tags: |
51
- type=raw,value=brain-${{ env.BRAIN_VERSION }}
54
+ type=raw,value=${{ env.IMAGE_TAG }}
52
55
 
53
56
  - name: Log in to GHCR
54
57
  uses: docker/login-action@v4
@@ -66,6 +69,7 @@ jobs:
66
69
  push: true
67
70
  build-args: |
68
71
  BRAIN_VERSION=${{ env.BRAIN_VERSION }}
72
+ SITE_PACKAGES=${{ env.SITE_PACKAGES }}
69
73
  tags: ${{ steps.meta.outputs.tags }}
70
74
  labels: |
71
75
  ${{ steps.meta.outputs.labels }}
@@ -10,6 +10,7 @@ on:
10
10
  push:
11
11
  branches: ["main"]
12
12
  paths:
13
+ - users/*.yaml
13
14
  - users/*/.env
14
15
  - users/*/brain.yaml
15
16
  - users/*/content/**
@@ -112,12 +113,16 @@ jobs:
112
113
  KAMAL_SSH_PRIVATE_KEY: ${{ secrets.KAMAL_SSH_PRIVATE_KEY }}
113
114
  KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
114
115
  CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
115
- CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
116
- CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
117
- PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
116
+ CONFIG_CF_ZONE_ID: ${{ steps.user_config.outputs.cloudflare_zone_id }}
117
+ SHARED_CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
118
+ SHARED_CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
119
+ SHARED_PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
118
120
  run: |
119
121
  export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
120
122
  export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
123
+ export CF_ZONE_ID="${CONFIG_CF_ZONE_ID:-${CF_ZONE_ID:-$SHARED_CF_ZONE_ID}}"
124
+ export CERTIFICATE_PEM="${CERTIFICATE_PEM:-$SHARED_CERTIFICATE_PEM}"
125
+ export PRIVATE_KEY_PEM="${PRIVATE_KEY_PEM:-$SHARED_PRIVATE_KEY_PEM}"
121
126
  bun deploy/scripts/validate-secrets.ts
122
127
 
123
128
  - name: Seed content repo
@@ -137,7 +142,7 @@ jobs:
137
142
 
138
143
  - name: Wait for shared image tag
139
144
  run: |
140
- IMAGE_TAG="${{ steps.user_config.outputs.image_repository }}:brain-${{ steps.user_config.outputs.brain_version }}"
145
+ IMAGE_TAG="${{ steps.user_config.outputs.image_repository }}:${{ steps.user_config.outputs.image_tag }}"
141
146
  for attempt in $(seq 1 24); do
142
147
  if docker manifest inspect "$IMAGE_TAG" >/dev/null 2>&1; then
143
148
  exit 0
@@ -178,11 +183,13 @@ jobs:
178
183
  SETUP_EMAIL_API_KEY: ${{ secrets.SETUP_EMAIL_API_KEY }}
179
184
  SETUP_EMAIL_FROM: ${{ secrets.SETUP_EMAIL_FROM }}
180
185
  KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
181
- CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
182
- PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
186
+ SHARED_CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
187
+ SHARED_PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
183
188
  run: |
184
189
  export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
185
190
  export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
191
+ export CERTIFICATE_PEM="${CERTIFICATE_PEM:-$SHARED_CERTIFICATE_PEM}"
192
+ export PRIVATE_KEY_PEM="${PRIVATE_KEY_PEM:-$SHARED_PRIVATE_KEY_PEM}"
186
193
  bun deploy/scripts/write-kamal-secrets.ts
187
194
 
188
195
  - name: Provision server
@@ -198,12 +205,18 @@ jobs:
198
205
  - name: Update Cloudflare DNS
199
206
  env:
200
207
  CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
201
- CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
208
+ CONFIG_CF_ZONE_ID: ${{ steps.user_config.outputs.cloudflare_zone_id }}
209
+ SHARED_CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
202
210
  BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
211
+ WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
203
212
  PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
204
213
  SERVER_IP: ${{ steps.provision.outputs.server_ip }}
205
214
  run: |
215
+ export CF_ZONE_ID="${CONFIG_CF_ZONE_ID:-${CF_ZONE_ID:-$SHARED_CF_ZONE_ID}}"
206
216
  bun deploy/scripts/update-dns.ts
217
+ if [ -n "$WWW_DOMAIN" ]; then
218
+ BRAIN_DOMAIN="$WWW_DOMAIN" bun deploy/scripts/update-dns.ts
219
+ fi
207
220
  BRAIN_DOMAIN="$PREVIEW_DOMAIN" bun deploy/scripts/update-dns.ts
208
221
 
209
222
  - name: Validate SSH key
@@ -227,10 +240,11 @@ jobs:
227
240
  - name: Deploy
228
241
  env:
229
242
  SERVER_IP: ${{ steps.provision.outputs.server_ip }}
230
- VERSION: brain-${{ steps.user_config.outputs.brain_version }}
243
+ VERSION: ${{ steps.user_config.outputs.image_tag }}
231
244
  IMAGE_REPOSITORY: ${{ steps.user_config.outputs.image_repository }}
232
245
  REGISTRY_USERNAME: ${{ steps.user_config.outputs.registry_username }}
233
246
  BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
247
+ WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
234
248
  PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
235
249
  BRAIN_YAML_PATH: ${{ steps.user_config.outputs.brain_yaml_path }}
236
250
  run: kamal setup --skip-push -c deploy/kamal/deploy.yml
@@ -239,9 +253,13 @@ jobs:
239
253
  env:
240
254
  SERVER_IP: ${{ steps.provision.outputs.server_ip }}
241
255
  BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
256
+ WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
242
257
  PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
243
258
  run: |
244
259
  curl -I -k --max-time 20 --resolve "$BRAIN_DOMAIN:443:$SERVER_IP" "https://$BRAIN_DOMAIN/health"
260
+ if [ -n "$WWW_DOMAIN" ]; then
261
+ curl -I -k --max-time 20 --resolve "$WWW_DOMAIN:443:$SERVER_IP" "https://$WWW_DOMAIN/"
262
+ fi
245
263
  curl -I -k --max-time 20 --resolve "$PREVIEW_DOMAIN:443:$SERVER_IP" "https://$PREVIEW_DOMAIN/"
246
264
 
247
265
  - name: Upload generated config
@@ -22,6 +22,14 @@ writeGitHubEnv("AI_API_KEY", secrets["aiApiKey"] ?? "");
22
22
  writeGitHubEnv("GIT_SYNC_TOKEN", secrets["gitSyncToken"] ?? "");
23
23
  writeGitHubEnv("DISCORD_BOT_TOKEN", secrets["discordBotToken"] ?? "");
24
24
  writeGitHubEnv("ATPROTO_APP_PASSWORD", secrets["atprotoAppPassword"] ?? "");
25
+ writeGitHubEnv(
26
+ "CERTIFICATE_PEM",
27
+ decodeEscapedSecret(secrets["certificatePem"]),
28
+ );
29
+ writeGitHubEnv(
30
+ "PRIVATE_KEY_PEM",
31
+ decodeEscapedSecret(secrets["privateKeyPem"]),
32
+ );
25
33
 
26
34
  writeGitHubOutput(
27
35
  "shared_ai_api_key_secret_name",
@@ -66,6 +74,10 @@ function parseFlatYaml(contents: string): Record<string, string> {
66
74
  return result;
67
75
  }
68
76
 
77
+ function decodeEscapedSecret(value: string | undefined): string {
78
+ return value?.replace(/\\n/g, "\n") ?? "";
79
+ }
80
+
69
81
  function requireFlatValue(
70
82
  values: Record<string, string>,
71
83
  key: string,
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env bun
2
+ import { createHash } from "node:crypto";
3
+ import { loadPilotRegistry } from "@rizom/ops";
4
+ import { writeGitHubEnv, writeGitHubOutput } from "./helpers";
5
+
6
+ const requestedBrainVersion = process.env["BRAIN_VERSION_INPUT"]?.trim();
7
+ const registry = await loadPilotRegistry(process.cwd());
8
+ const brainVersion =
9
+ requestedBrainVersion && requestedBrainVersion.length > 0
10
+ ? requestedBrainVersion
11
+ : registry.pilot.brainVersion;
12
+
13
+ const sitePackages = [
14
+ ...new Set(
15
+ registry.users
16
+ .filter((user) => user.brainVersion === brainVersion)
17
+ .flatMap((user) =>
18
+ user.siteOverride
19
+ ? [`${user.siteOverride.package}@${user.siteOverride.version}`]
20
+ : [],
21
+ ),
22
+ ),
23
+ ].sort();
24
+ const sitePackagesValue = sitePackages.join(" ");
25
+ const imageTag = buildImageTag(brainVersion, sitePackages);
26
+
27
+ writeGitHubEnv("BRAIN_VERSION", brainVersion);
28
+ writeGitHubEnv("SITE_PACKAGES", sitePackagesValue);
29
+ writeGitHubEnv("IMAGE_TAG", imageTag);
30
+ writeGitHubOutput("brain_version", brainVersion);
31
+ writeGitHubOutput("site_packages", sitePackagesValue);
32
+ writeGitHubOutput("image_tag", imageTag);
33
+
34
+ console.log(`BRAIN_VERSION=${brainVersion}`);
35
+ console.log(`IMAGE_TAG=${imageTag}`);
36
+ console.log(
37
+ sitePackages.length > 0
38
+ ? `SITE_PACKAGES=${sitePackagesValue}`
39
+ : "SITE_PACKAGES=(none)",
40
+ );
41
+
42
+ function buildImageTag(brainVersion: string, sitePackages: string[]): string {
43
+ if (sitePackages.length === 0) {
44
+ return `brain-${brainVersion}`;
45
+ }
46
+
47
+ const siteHash = createHash("sha256")
48
+ .update(sitePackages.join("\n"))
49
+ .digest("hex")
50
+ .slice(0, 12);
51
+
52
+ return `brain-${brainVersion}-sites-${siteHash}`;
53
+ }