@rizom/ops 0.2.0-alpha.4 → 0.2.0-alpha.400
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 +88 -4
- package/dist/age-key-bootstrap.d.ts +17 -0
- package/dist/brains-ops.js +824 -156
- package/dist/capability-bundle-migration.d.ts +22 -0
- package/dist/cert-bootstrap.d.ts +5 -3
- package/dist/content-repo-ref.d.ts +10 -0
- package/dist/content-repo.d.ts +13 -0
- package/dist/default-user-runner.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 +11 -0
- package/dist/index.js +836 -156
- 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 +78 -7
- package/dist/observed-status.d.ts +12 -0
- package/dist/onboard-user.d.ts +2 -2
- package/dist/origin-ca.d.ts +2 -1
- package/dist/parse-args.d.ts +2 -10
- package/dist/preview-domain.d.ts +9 -0
- package/dist/push-secrets.d.ts +2 -9
- package/dist/push-target.d.ts +1 -2
- package/dist/reconcile-all.d.ts +2 -2
- package/dist/reconcile-cohort.d.ts +2 -2
- package/dist/reconcile-dry-run.d.ts +9 -0
- package/dist/reconcile-lib.d.ts +4 -2
- package/dist/run-command.d.ts +21 -4
- package/dist/run-subprocess.d.ts +1 -5
- package/dist/schema.d.ts +132 -69
- package/dist/secrets-encrypt.d.ts +26 -0
- 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-add.d.ts +15 -0
- package/dist/user-offboard.d.ts +61 -0
- package/dist/user-runner.d.ts +5 -0
- package/dist/verify-user.d.ts +22 -0
- package/package.json +50 -40
- package/templates/rover-pilot/.env.schema +28 -2
- package/templates/rover-pilot/.github/actions/varlock-env/action.yml +47 -0
- package/templates/rover-pilot/.github/workflows/build.yml +71 -17
- package/templates/rover-pilot/.github/workflows/deploy.yml +121 -45
- 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 +23 -3
- package/templates/rover-pilot/.github/workflows/upgrade.yml +104 -0
- package/templates/rover-pilot/README.md +19 -8
- package/templates/rover-pilot/deploy/scripts/create-predeploy-backup.ts +894 -0
- package/templates/rover-pilot/deploy/scripts/decrypt-user-secrets.ts +139 -0
- 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 +52 -22
- package/templates/rover-pilot/deploy/scripts/resolve-deploy-handles.ts +19 -4
- package/templates/rover-pilot/deploy/scripts/resolve-missing-images.ts +13 -0
- package/templates/rover-pilot/deploy/scripts/resolve-user-config.ts +45 -14
- package/templates/rover-pilot/deploy/scripts/sync-content-repo.ts +183 -0
- 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 +39 -10
- package/templates/rover-pilot/docs/operator-playbook.md +390 -14
- package/templates/rover-pilot/docs/user-onboarding.md +90 -0
- package/templates/rover-pilot/package.json +4 -0
- package/templates/rover-pilot/pilot.yaml +9 -3
- package/templates/rover-pilot/users/alice.yaml +5 -1
- package/dist/user-secret-names.d.ts +0 -6
- package/templates/rover-pilot/.kamal/hooks/pre-deploy +0 -9
- package/templates/rover-pilot/deploy/Dockerfile +0 -15
- package/templates/rover-pilot/deploy/kamal/deploy.yml +0 -39
|
@@ -7,13 +7,24 @@ on:
|
|
|
7
7
|
description: User handle
|
|
8
8
|
required: true
|
|
9
9
|
type: string
|
|
10
|
+
release_stale_lock:
|
|
11
|
+
description: Release an operator-confirmed stale Kamal deploy lock before retrying
|
|
12
|
+
required: false
|
|
13
|
+
type: boolean
|
|
14
|
+
default: false
|
|
10
15
|
push:
|
|
11
16
|
branches: ["main"]
|
|
12
17
|
paths:
|
|
13
18
|
- users/*/.env
|
|
14
19
|
- users/*/brain.yaml
|
|
20
|
+
- users/*/content/**
|
|
21
|
+
- users/*.secrets.yaml.age
|
|
15
22
|
- deploy/**
|
|
16
23
|
- .github/workflows/deploy.yml
|
|
24
|
+
workflow_run:
|
|
25
|
+
workflows: [Reconcile]
|
|
26
|
+
types: [completed]
|
|
27
|
+
branches: [main]
|
|
17
28
|
|
|
18
29
|
permissions:
|
|
19
30
|
contents: write
|
|
@@ -21,13 +32,16 @@ permissions:
|
|
|
21
32
|
|
|
22
33
|
jobs:
|
|
23
34
|
resolve_handles:
|
|
35
|
+
if: >
|
|
36
|
+
github.event_name != 'workflow_run' ||
|
|
37
|
+
github.event.workflow_run.conclusion == 'success'
|
|
24
38
|
runs-on: ubuntu-latest
|
|
25
39
|
outputs:
|
|
26
40
|
handles_json: ${{ steps.resolve.outputs.handles_json }}
|
|
27
41
|
steps:
|
|
28
42
|
- uses: actions/checkout@v5
|
|
29
43
|
with:
|
|
30
|
-
ref: ${{ github.sha }}
|
|
44
|
+
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.sha }}
|
|
31
45
|
fetch-depth: 0
|
|
32
46
|
|
|
33
47
|
- uses: oven-sh/setup-bun@v2
|
|
@@ -39,7 +53,7 @@ jobs:
|
|
|
39
53
|
id: resolve
|
|
40
54
|
env:
|
|
41
55
|
HANDLE_INPUT: ${{ inputs.handle || '' }}
|
|
42
|
-
BEFORE_SHA: ${{ github.event.before || '' }}
|
|
56
|
+
BEFORE_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.event.before || '' }}
|
|
43
57
|
run: bun deploy/scripts/resolve-deploy-handles.ts
|
|
44
58
|
|
|
45
59
|
no_changes:
|
|
@@ -66,13 +80,22 @@ jobs:
|
|
|
66
80
|
steps:
|
|
67
81
|
- uses: actions/checkout@v5
|
|
68
82
|
with:
|
|
69
|
-
ref: ${{ github.sha }}
|
|
83
|
+
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.sha }}
|
|
70
84
|
|
|
71
85
|
- uses: oven-sh/setup-bun@v2
|
|
72
86
|
|
|
73
87
|
- name: Install operator tooling
|
|
74
88
|
run: bun install
|
|
75
89
|
|
|
90
|
+
- name: Load shared env via Varlock
|
|
91
|
+
uses: ./.github/actions/varlock-env
|
|
92
|
+
env:
|
|
93
|
+
BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}
|
|
94
|
+
|
|
95
|
+
- name: Decrypt user secrets
|
|
96
|
+
id: user_secrets
|
|
97
|
+
run: bun deploy/scripts/decrypt-user-secrets.ts "$HANDLE"
|
|
98
|
+
|
|
76
99
|
- name: Reconcile selected user config
|
|
77
100
|
run: bunx brains-ops onboard "$GITHUB_WORKSPACE" "$HANDLE"
|
|
78
101
|
|
|
@@ -81,23 +104,13 @@ jobs:
|
|
|
81
104
|
run: bun deploy/scripts/resolve-user-config.ts
|
|
82
105
|
|
|
83
106
|
- name: Validate selected secrets
|
|
84
|
-
env:
|
|
85
|
-
AI_API_KEY: ${{ secrets[steps.user_config.outputs.ai_api_key_secret_name] }}
|
|
86
|
-
GIT_SYNC_TOKEN: ${{ secrets[steps.user_config.outputs.git_sync_token_secret_name] }}
|
|
87
|
-
MCP_AUTH_TOKEN: ${{ secrets[steps.user_config.outputs.mcp_auth_token_secret_name] }}
|
|
88
|
-
DISCORD_BOT_TOKEN: ${{ steps.user_config.outputs.discord_bot_token_secret_name != '' && secrets[steps.user_config.outputs.discord_bot_token_secret_name] || '' }}
|
|
89
|
-
HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
|
|
90
|
-
HCLOUD_SSH_KEY_NAME: ${{ secrets.HCLOUD_SSH_KEY_NAME }}
|
|
91
|
-
HCLOUD_SERVER_TYPE: ${{ secrets.HCLOUD_SERVER_TYPE }}
|
|
92
|
-
HCLOUD_LOCATION: ${{ secrets.HCLOUD_LOCATION }}
|
|
93
|
-
KAMAL_SSH_PRIVATE_KEY: ${{ secrets.KAMAL_SSH_PRIVATE_KEY }}
|
|
94
|
-
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
|
|
95
|
-
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
|
96
|
-
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
|
97
|
-
CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
|
|
98
|
-
PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
|
|
99
107
|
run: bun deploy/scripts/validate-secrets.ts
|
|
100
108
|
|
|
109
|
+
- name: Seed content repo
|
|
110
|
+
env:
|
|
111
|
+
CONTENT_REPO: ${{ steps.user_config.outputs.content_repo }}
|
|
112
|
+
run: bun deploy/scripts/sync-content-repo.ts
|
|
113
|
+
|
|
101
114
|
- name: Log in to GHCR
|
|
102
115
|
uses: docker/login-action@v3
|
|
103
116
|
with:
|
|
@@ -105,27 +118,31 @@ jobs:
|
|
|
105
118
|
username: ${{ github.actor }}
|
|
106
119
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
107
120
|
|
|
108
|
-
- name: Wait for
|
|
121
|
+
- name: Wait for image tag
|
|
109
122
|
run: |
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
# The Build workflow fires off the same config push (it derives the
|
|
124
|
+
# declared image set from the registry), so this wait must cover a
|
|
125
|
+
# full image build, not just registry propagation.
|
|
126
|
+
IMAGE_TAG="${{ steps.user_config.outputs.image_repository }}:${{ steps.user_config.outputs.image_tag }}"
|
|
127
|
+
for attempt in $(seq 1 60); do
|
|
112
128
|
if docker manifest inspect "$IMAGE_TAG" >/dev/null 2>&1; then
|
|
113
129
|
exit 0
|
|
114
130
|
fi
|
|
115
|
-
echo "
|
|
116
|
-
sleep
|
|
131
|
+
echo "Image tag not ready yet ($attempt/60): $IMAGE_TAG"
|
|
132
|
+
sleep 20
|
|
117
133
|
done
|
|
118
134
|
echo "Timed out waiting for $IMAGE_TAG" >&2
|
|
119
135
|
exit 1
|
|
120
136
|
|
|
137
|
+
- name: Verify runtime image packages
|
|
138
|
+
run: bun deploy/scripts/verify-runtime-image.ts
|
|
139
|
+
|
|
121
140
|
- name: Install Kamal
|
|
122
141
|
run: |
|
|
123
142
|
gem install --user-install kamal
|
|
124
143
|
ruby -r rubygems -e 'puts Gem.user_dir + "/bin"' >> "$GITHUB_PATH"
|
|
125
144
|
|
|
126
145
|
- name: Write Kamal SSH key
|
|
127
|
-
env:
|
|
128
|
-
KAMAL_SSH_PRIVATE_KEY: ${{ secrets.KAMAL_SSH_PRIVATE_KEY }}
|
|
129
146
|
run: bun deploy/scripts/write-ssh-key.ts
|
|
130
147
|
|
|
131
148
|
- name: Configure SSH client
|
|
@@ -142,33 +159,27 @@ jobs:
|
|
|
142
159
|
chmod 600 ~/.ssh/config
|
|
143
160
|
|
|
144
161
|
- name: Write .kamal/secrets
|
|
145
|
-
env:
|
|
146
|
-
AI_API_KEY: ${{ secrets[steps.user_config.outputs.ai_api_key_secret_name] }}
|
|
147
|
-
GIT_SYNC_TOKEN: ${{ secrets[steps.user_config.outputs.git_sync_token_secret_name] }}
|
|
148
|
-
MCP_AUTH_TOKEN: ${{ secrets[steps.user_config.outputs.mcp_auth_token_secret_name] }}
|
|
149
|
-
DISCORD_BOT_TOKEN: ${{ steps.user_config.outputs.discord_bot_token_secret_name != '' && secrets[steps.user_config.outputs.discord_bot_token_secret_name] || '' }}
|
|
150
|
-
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
|
|
151
|
-
CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
|
|
152
|
-
PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
|
|
153
162
|
run: bun deploy/scripts/write-kamal-secrets.ts
|
|
154
163
|
|
|
155
164
|
- name: Provision server
|
|
156
165
|
id: provision
|
|
157
166
|
env:
|
|
158
|
-
HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
|
|
159
|
-
HCLOUD_SSH_KEY_NAME: ${{ secrets.HCLOUD_SSH_KEY_NAME }}
|
|
160
|
-
HCLOUD_SERVER_TYPE: ${{ secrets.HCLOUD_SERVER_TYPE }}
|
|
161
|
-
HCLOUD_LOCATION: ${{ secrets.HCLOUD_LOCATION }}
|
|
162
167
|
INSTANCE_NAME: ${{ steps.user_config.outputs.instance_name }}
|
|
163
168
|
run: bun deploy/scripts/provision-server.ts
|
|
164
169
|
|
|
165
170
|
- name: Update Cloudflare DNS
|
|
166
171
|
env:
|
|
167
|
-
|
|
168
|
-
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
|
172
|
+
CF_ZONE_ID: ${{ steps.user_config.outputs.cloudflare_zone_id }}
|
|
169
173
|
BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
|
|
174
|
+
WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
|
|
175
|
+
PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
|
|
170
176
|
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
171
|
-
run:
|
|
177
|
+
run: |
|
|
178
|
+
bun deploy/scripts/update-dns.ts
|
|
179
|
+
if [ -n "$WWW_DOMAIN" ]; then
|
|
180
|
+
BRAIN_DOMAIN="$WWW_DOMAIN" bun deploy/scripts/update-dns.ts
|
|
181
|
+
fi
|
|
182
|
+
BRAIN_DOMAIN="$PREVIEW_DOMAIN" bun deploy/scripts/update-dns.ts
|
|
172
183
|
|
|
173
184
|
- name: Validate SSH key
|
|
174
185
|
run: ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null
|
|
@@ -188,17 +199,61 @@ jobs:
|
|
|
188
199
|
echo "SSH never became ready for $SSH_USER@$SERVER_IP" >&2
|
|
189
200
|
exit 1
|
|
190
201
|
|
|
202
|
+
- name: Create verified predeploy backup
|
|
203
|
+
env:
|
|
204
|
+
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
205
|
+
TARGET_HANDLE: ${{ matrix.handle }}
|
|
206
|
+
TARGET_VERSION: ${{ steps.user_config.outputs.image_tag }}
|
|
207
|
+
SERVICE_NAME: rover
|
|
208
|
+
run: bun deploy/scripts/create-predeploy-backup.ts
|
|
209
|
+
|
|
210
|
+
- name: Release operator-confirmed stale deploy lock
|
|
211
|
+
if: ${{ inputs.release_stale_lock }}
|
|
212
|
+
env:
|
|
213
|
+
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
214
|
+
VERSION: ${{ steps.user_config.outputs.image_tag }}
|
|
215
|
+
IMAGE_REPOSITORY: ${{ steps.user_config.outputs.image_repository }}
|
|
216
|
+
REGISTRY_USERNAME: ${{ steps.user_config.outputs.registry_username }}
|
|
217
|
+
BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
|
|
218
|
+
WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
|
|
219
|
+
PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
|
|
220
|
+
BRAIN_YAML_PATH: ${{ steps.user_config.outputs.brain_yaml_path }}
|
|
221
|
+
run: kamal lock release -c deploy/kamal/deploy.yml
|
|
222
|
+
|
|
191
223
|
- name: Deploy
|
|
192
224
|
env:
|
|
193
225
|
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
194
|
-
VERSION:
|
|
226
|
+
VERSION: ${{ steps.user_config.outputs.image_tag }}
|
|
227
|
+
IMAGE_REPOSITORY: ${{ steps.user_config.outputs.image_repository }}
|
|
228
|
+
REGISTRY_USERNAME: ${{ steps.user_config.outputs.registry_username }}
|
|
229
|
+
BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
|
|
230
|
+
WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
|
|
231
|
+
PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
|
|
232
|
+
BRAIN_YAML_PATH: ${{ steps.user_config.outputs.brain_yaml_path }}
|
|
195
233
|
run: kamal setup --skip-push -c deploy/kamal/deploy.yml
|
|
196
234
|
|
|
235
|
+
- name: Install container health watchdog
|
|
236
|
+
env:
|
|
237
|
+
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
238
|
+
run: |
|
|
239
|
+
SSH_USER="$(ruby -e 'require "yaml"; config = YAML.load_file("deploy/kamal/deploy.yml") || {}; puts(config.dig("ssh", "user") || "root")')" \
|
|
240
|
+
bun deploy/scripts/install-health-watchdog.ts
|
|
241
|
+
|
|
197
242
|
- name: Verify origin TLS
|
|
198
243
|
env:
|
|
199
244
|
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
200
245
|
BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
|
|
201
|
-
|
|
246
|
+
WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
|
|
247
|
+
PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
|
|
248
|
+
run: |
|
|
249
|
+
curl -I -k --max-time 20 --resolve "$BRAIN_DOMAIN:443:$SERVER_IP" "https://$BRAIN_DOMAIN/health/live"
|
|
250
|
+
if [ -n "$WWW_DOMAIN" ]; then
|
|
251
|
+
curl -I -k --max-time 20 --resolve "$WWW_DOMAIN:443:$SERVER_IP" "https://$WWW_DOMAIN/"
|
|
252
|
+
fi
|
|
253
|
+
curl -I -k --max-time 20 --resolve "$PREVIEW_DOMAIN:443:$SERVER_IP" "https://$PREVIEW_DOMAIN/"
|
|
254
|
+
|
|
255
|
+
- name: Verify operational health
|
|
256
|
+
run: bunx brains-ops verify-user "$GITHUB_WORKSPACE" "$HANDLE"
|
|
202
257
|
|
|
203
258
|
- name: Upload generated config
|
|
204
259
|
uses: actions/upload-artifact@v4
|
|
@@ -207,6 +262,7 @@ jobs:
|
|
|
207
262
|
path: |
|
|
208
263
|
users/${{ matrix.handle }}/brain.yaml
|
|
209
264
|
users/${{ matrix.handle }}/.env
|
|
265
|
+
users/${{ matrix.handle }}/content
|
|
210
266
|
|
|
211
267
|
- name: Dump remote proxy diagnostics
|
|
212
268
|
if: failure()
|
|
@@ -233,7 +289,7 @@ jobs:
|
|
|
233
289
|
steps:
|
|
234
290
|
- uses: actions/checkout@v5
|
|
235
291
|
with:
|
|
236
|
-
ref: ${{ github.sha }}
|
|
292
|
+
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.sha }}
|
|
237
293
|
|
|
238
294
|
- uses: oven-sh/setup-bun@v2
|
|
239
295
|
|
|
@@ -252,11 +308,31 @@ jobs:
|
|
|
252
308
|
|
|
253
309
|
- name: Commit generated config
|
|
254
310
|
run: |
|
|
311
|
+
# A newly added user's generated directory is untracked, and
|
|
312
|
+
# `git diff` cannot see untracked files — without intent-to-add
|
|
313
|
+
# the new config is silently dropped here and every later deploy
|
|
314
|
+
# skips that user (their files never appear in a commit range).
|
|
315
|
+
git add --intent-to-add -- users views
|
|
316
|
+
|
|
255
317
|
if git diff --quiet -- users views; then
|
|
256
318
|
exit 0
|
|
257
319
|
fi
|
|
320
|
+
|
|
321
|
+
git fetch origin "${{ github.ref_name }}"
|
|
322
|
+
if git diff --quiet "origin/${{ github.ref_name }}" -- users views; then
|
|
323
|
+
exit 0
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
patch_file="$(mktemp)"
|
|
327
|
+
git diff --binary -- users views > "$patch_file"
|
|
328
|
+
git reset --hard "origin/${{ github.ref_name }}"
|
|
329
|
+
git apply --3way --index "$patch_file"
|
|
330
|
+
|
|
331
|
+
if git diff --cached --quiet -- users views; then
|
|
332
|
+
exit 0
|
|
333
|
+
fi
|
|
334
|
+
|
|
258
335
|
git config user.name "github-actions[bot]"
|
|
259
336
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
260
|
-
git add users views
|
|
261
337
|
git commit -m "chore(ops): reconcile generated config"
|
|
262
|
-
git push
|
|
338
|
+
git push origin HEAD:${{ github.ref_name }}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
name: Directory Sync Stress
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "23 4 * * 1"
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
handle:
|
|
9
|
+
description: Smoke handle only
|
|
10
|
+
required: true
|
|
11
|
+
type: string
|
|
12
|
+
default: smoke
|
|
13
|
+
profile:
|
|
14
|
+
description: Workload profile
|
|
15
|
+
required: true
|
|
16
|
+
type: choice
|
|
17
|
+
default: regression
|
|
18
|
+
options:
|
|
19
|
+
- regression
|
|
20
|
+
- load
|
|
21
|
+
- stress
|
|
22
|
+
confirm:
|
|
23
|
+
description: Type stress:<handle> to confirm reversible content writes
|
|
24
|
+
required: true
|
|
25
|
+
type: string
|
|
26
|
+
verify_only:
|
|
27
|
+
description: Verify the content credential path without remote writes or a workload
|
|
28
|
+
required: false
|
|
29
|
+
type: boolean
|
|
30
|
+
default: false
|
|
31
|
+
|
|
32
|
+
permissions:
|
|
33
|
+
contents: read
|
|
34
|
+
|
|
35
|
+
concurrency:
|
|
36
|
+
group: directory-sync-stress-${{ github.event_name == 'schedule' && 'smoke' || inputs.handle }}
|
|
37
|
+
cancel-in-progress: false
|
|
38
|
+
|
|
39
|
+
jobs:
|
|
40
|
+
stress:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
timeout-minutes: 75
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v5
|
|
45
|
+
|
|
46
|
+
- uses: oven-sh/setup-bun@v2
|
|
47
|
+
|
|
48
|
+
- name: Install operator tooling
|
|
49
|
+
run: bun install
|
|
50
|
+
|
|
51
|
+
- name: Load Bitwarden environment through Varlock
|
|
52
|
+
uses: ./.github/actions/varlock-env
|
|
53
|
+
env:
|
|
54
|
+
BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}
|
|
55
|
+
|
|
56
|
+
- name: Verify smoke directory-sync credential path
|
|
57
|
+
if: ${{ github.event_name != 'schedule' && inputs.verify_only }}
|
|
58
|
+
env:
|
|
59
|
+
HANDLE_INPUT: ${{ inputs.handle }}
|
|
60
|
+
CONFIRM_INPUT: ${{ inputs.confirm }}
|
|
61
|
+
run: |
|
|
62
|
+
bunx brains-ops stress:directory-sync:verify-access "$GITHUB_WORKSPACE" "$HANDLE_INPUT" \
|
|
63
|
+
--confirm "$CONFIRM_INPUT" \
|
|
64
|
+
--artifacts-dir "$RUNNER_TEMP/directory-sync-stress"
|
|
65
|
+
|
|
66
|
+
- name: Run smoke directory-sync stress profile
|
|
67
|
+
if: ${{ github.event_name == 'schedule' || !inputs.verify_only }}
|
|
68
|
+
env:
|
|
69
|
+
HANDLE_INPUT: ${{ github.event_name == 'schedule' && 'smoke' || inputs.handle }}
|
|
70
|
+
PROFILE_INPUT: ${{ github.event_name == 'schedule' && 'regression' || inputs.profile }}
|
|
71
|
+
CONFIRM_INPUT: ${{ github.event_name == 'schedule' && 'stress:smoke' || inputs.confirm }}
|
|
72
|
+
run: |
|
|
73
|
+
bunx brains-ops stress:directory-sync "$GITHUB_WORKSPACE" "$HANDLE_INPUT" \
|
|
74
|
+
--profile "$PROFILE_INPUT" \
|
|
75
|
+
--confirm "$CONFIRM_INPUT" \
|
|
76
|
+
--artifacts-dir "$RUNNER_TEMP/directory-sync-stress"
|
|
77
|
+
|
|
78
|
+
- name: Upload stress evidence
|
|
79
|
+
if: always()
|
|
80
|
+
uses: actions/upload-artifact@v4
|
|
81
|
+
with:
|
|
82
|
+
name: directory-sync-stress-${{ github.event_name == 'schedule' && 'smoke' || inputs.handle }}-${{ github.event_name == 'schedule' && 'regression' || inputs.profile }}-${{ github.run_id }}
|
|
83
|
+
path: ${{ runner.temp }}/directory-sync-stress
|
|
84
|
+
if-no-files-found: warn
|
|
85
|
+
|
|
86
|
+
cleanup:
|
|
87
|
+
needs: stress
|
|
88
|
+
if: ${{ always() && (github.event_name == 'schedule' || !inputs.verify_only) }}
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
timeout-minutes: 20
|
|
91
|
+
steps:
|
|
92
|
+
- uses: actions/checkout@v5
|
|
93
|
+
|
|
94
|
+
- uses: oven-sh/setup-bun@v2
|
|
95
|
+
|
|
96
|
+
- name: Install operator tooling
|
|
97
|
+
run: bun install
|
|
98
|
+
|
|
99
|
+
- name: Load Bitwarden environment through Varlock
|
|
100
|
+
uses: ./.github/actions/varlock-env
|
|
101
|
+
env:
|
|
102
|
+
BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}
|
|
103
|
+
|
|
104
|
+
- name: Remove residual stress probes
|
|
105
|
+
env:
|
|
106
|
+
HANDLE_INPUT: ${{ github.event_name == 'schedule' && 'smoke' || inputs.handle }}
|
|
107
|
+
CONFIRM_INPUT: ${{ github.event_name == 'schedule' && 'stress:smoke' || inputs.confirm }}
|
|
108
|
+
run: |
|
|
109
|
+
bunx brains-ops stress:directory-sync:cleanup "$GITHUB_WORKSPACE" "$HANDLE_INPUT" \
|
|
110
|
+
--confirm "$CONFIRM_INPUT" \
|
|
111
|
+
--artifacts-dir "$RUNNER_TEMP/directory-sync-stress-cleanup"
|
|
112
|
+
|
|
113
|
+
- name: Upload cleanup evidence
|
|
114
|
+
if: always()
|
|
115
|
+
uses: actions/upload-artifact@v4
|
|
116
|
+
with:
|
|
117
|
+
name: directory-sync-stress-cleanup-${{ github.event_name == 'schedule' && 'smoke' || inputs.handle }}-${{ github.run_id }}
|
|
118
|
+
path: ${{ runner.temp }}/directory-sync-stress-cleanup
|
|
119
|
+
if-no-files-found: warn
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
name: Health Watchdog Smoke
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
handle:
|
|
7
|
+
description: Smoke fleet handle only
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
default: smoke
|
|
11
|
+
confirm:
|
|
12
|
+
description: Type watchdog-smoke:<handle> to confirm temporary container restarts
|
|
13
|
+
required: true
|
|
14
|
+
type: string
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: deploy-${{ inputs.handle }}
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
env:
|
|
24
|
+
WATCHDOG_SMOKE_RUN_ID: gha-${{ github.run_id }}-${{ github.run_attempt }}
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
smoke:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
timeout-minutes: 20
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v5
|
|
32
|
+
|
|
33
|
+
- uses: oven-sh/setup-bun@v2
|
|
34
|
+
|
|
35
|
+
- name: Install operator tooling
|
|
36
|
+
run: bun install
|
|
37
|
+
|
|
38
|
+
- name: Load Bitwarden environment through Varlock
|
|
39
|
+
uses: ./.github/actions/varlock-env
|
|
40
|
+
env:
|
|
41
|
+
BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}
|
|
42
|
+
|
|
43
|
+
- name: Run fleet watchdog isolation smoke
|
|
44
|
+
env:
|
|
45
|
+
HANDLE_INPUT: ${{ inputs.handle }}
|
|
46
|
+
CONFIRM_INPUT: ${{ inputs.confirm }}
|
|
47
|
+
run: |
|
|
48
|
+
bunx brains-ops smoke:health-watchdog "$GITHUB_WORKSPACE" "$HANDLE_INPUT" \
|
|
49
|
+
--confirm "$CONFIRM_INPUT" \
|
|
50
|
+
--run-id "$WATCHDOG_SMOKE_RUN_ID" \
|
|
51
|
+
--artifacts-dir "$RUNNER_TEMP/health-watchdog-smoke"
|
|
52
|
+
|
|
53
|
+
- name: Upload watchdog smoke evidence
|
|
54
|
+
if: always()
|
|
55
|
+
uses: actions/upload-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
name: health-watchdog-smoke-${{ inputs.handle }}-${{ github.run_id }}-${{ github.run_attempt }}
|
|
58
|
+
path: ${{ runner.temp }}/health-watchdog-smoke
|
|
59
|
+
if-no-files-found: warn
|
|
60
|
+
|
|
61
|
+
cleanup:
|
|
62
|
+
needs: smoke
|
|
63
|
+
if: always()
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
timeout-minutes: 10
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v5
|
|
68
|
+
|
|
69
|
+
- uses: oven-sh/setup-bun@v2
|
|
70
|
+
|
|
71
|
+
- name: Install operator tooling
|
|
72
|
+
run: bun install
|
|
73
|
+
|
|
74
|
+
- name: Load Bitwarden environment through Varlock
|
|
75
|
+
uses: ./.github/actions/varlock-env
|
|
76
|
+
env:
|
|
77
|
+
BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}
|
|
78
|
+
|
|
79
|
+
- name: Remove residual watchdog smoke fixtures
|
|
80
|
+
env:
|
|
81
|
+
HANDLE_INPUT: ${{ inputs.handle }}
|
|
82
|
+
CONFIRM_INPUT: ${{ inputs.confirm }}
|
|
83
|
+
run: |
|
|
84
|
+
bunx brains-ops smoke:health-watchdog:cleanup "$GITHUB_WORKSPACE" "$HANDLE_INPUT" \
|
|
85
|
+
--confirm "$CONFIRM_INPUT" \
|
|
86
|
+
--run-id "$WATCHDOG_SMOKE_RUN_ID" \
|
|
87
|
+
--artifacts-dir "$RUNNER_TEMP/health-watchdog-smoke-cleanup"
|
|
88
|
+
|
|
89
|
+
- name: Upload watchdog smoke cleanup evidence
|
|
90
|
+
if: always()
|
|
91
|
+
uses: actions/upload-artifact@v4
|
|
92
|
+
with:
|
|
93
|
+
name: health-watchdog-smoke-cleanup-${{ inputs.handle }}-${{ github.run_id }}-${{ github.run_attempt }}
|
|
94
|
+
path: ${{ runner.temp }}/health-watchdog-smoke-cleanup
|
|
95
|
+
if-no-files-found: warn
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
name: Offboard
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
handles:
|
|
7
|
+
description: Comma-separated pilot handles
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
confirm:
|
|
11
|
+
description: Exact confirmation (sunset:<sorted-comma-separated-handles>)
|
|
12
|
+
required: true
|
|
13
|
+
type: string
|
|
14
|
+
apply:
|
|
15
|
+
description: Archive content repos and destroy DNS, servers, and desired state
|
|
16
|
+
required: true
|
|
17
|
+
type: boolean
|
|
18
|
+
default: false
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: write
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: offboard-pilot-users
|
|
25
|
+
cancel-in-progress: false
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
offboard:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v5
|
|
32
|
+
with:
|
|
33
|
+
ref: main
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
|
|
36
|
+
- uses: oven-sh/setup-bun@v2
|
|
37
|
+
|
|
38
|
+
- name: Install operator tooling
|
|
39
|
+
run: bun install
|
|
40
|
+
|
|
41
|
+
- name: Load provider credentials
|
|
42
|
+
uses: ./.github/actions/varlock-env
|
|
43
|
+
env:
|
|
44
|
+
BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}
|
|
45
|
+
|
|
46
|
+
- name: Resolve handles
|
|
47
|
+
id: handles
|
|
48
|
+
env:
|
|
49
|
+
OFFBOARD_HANDLES: ${{ inputs.handles }}
|
|
50
|
+
run: |
|
|
51
|
+
node <<'NODE'
|
|
52
|
+
const handles = [...new Set(process.env.OFFBOARD_HANDLES
|
|
53
|
+
.split(',')
|
|
54
|
+
.map((handle) => handle.trim())
|
|
55
|
+
.filter(Boolean))]
|
|
56
|
+
.sort((left, right) => left.localeCompare(right));
|
|
57
|
+
if (handles.length === 0 || handles.some((handle) => !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(handle))) {
|
|
58
|
+
throw new Error('Invalid offboarding handle list');
|
|
59
|
+
}
|
|
60
|
+
const output = process.env.GITHUB_OUTPUT;
|
|
61
|
+
require('node:fs').appendFileSync(output, `json=${JSON.stringify(handles)}\n`);
|
|
62
|
+
NODE
|
|
63
|
+
|
|
64
|
+
- name: Preview offboarding plan
|
|
65
|
+
env:
|
|
66
|
+
OFFBOARD_HANDLES_JSON: ${{ steps.handles.outputs.json }}
|
|
67
|
+
run: |
|
|
68
|
+
mapfile -t handles < <(node -e 'for (const handle of JSON.parse(process.env.OFFBOARD_HANDLES_JSON)) console.log(handle)')
|
|
69
|
+
bunx brains-ops user:offboard "$GITHUB_WORKSPACE" "${handles[@]}"
|
|
70
|
+
|
|
71
|
+
- name: Apply offboarding plan
|
|
72
|
+
if: inputs.apply
|
|
73
|
+
env:
|
|
74
|
+
OFFBOARD_CONFIRM: ${{ inputs.confirm }}
|
|
75
|
+
OFFBOARD_HANDLES_JSON: ${{ steps.handles.outputs.json }}
|
|
76
|
+
run: |
|
|
77
|
+
mapfile -t handles < <(node -e 'for (const handle of JSON.parse(process.env.OFFBOARD_HANDLES_JSON)) console.log(handle)')
|
|
78
|
+
bunx brains-ops user:offboard "$GITHUB_WORKSPACE" "${handles[@]}" \
|
|
79
|
+
--apply --confirm "$OFFBOARD_CONFIRM"
|
|
80
|
+
|
|
81
|
+
- name: Validate changed paths
|
|
82
|
+
if: inputs.apply
|
|
83
|
+
run: |
|
|
84
|
+
unexpected="$(git status --short | awk '{print $2}' | grep -Ev '^(cohorts/|users/|views/users\.md$)' || true)"
|
|
85
|
+
if [ -n "$unexpected" ]; then
|
|
86
|
+
echo "Offboarding changed unexpected paths:" >&2
|
|
87
|
+
echo "$unexpected" >&2
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
- name: Commit desired-state removal
|
|
92
|
+
if: inputs.apply
|
|
93
|
+
env:
|
|
94
|
+
OFFBOARD_HANDLES: ${{ inputs.handles }}
|
|
95
|
+
run: |
|
|
96
|
+
git add --all -- cohorts users views/users.md
|
|
97
|
+
if git diff --cached --quiet; then
|
|
98
|
+
echo "No desired-state changes remain to commit."
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
git config user.name "github-actions[bot]"
|
|
103
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
104
|
+
git commit -m "chore(ops): sunset pilot brains" \
|
|
105
|
+
-m "Retired handles: $OFFBOARD_HANDLES"
|
|
106
|
+
git pull --rebase origin main
|
|
107
|
+
git push origin HEAD:main
|
|
@@ -35,11 +35,31 @@ jobs:
|
|
|
35
35
|
|
|
36
36
|
- name: Commit generated outputs
|
|
37
37
|
run: |
|
|
38
|
-
|
|
38
|
+
# A newly added user's generated directory is untracked, and
|
|
39
|
+
# `git diff` cannot see untracked files — without intent-to-add
|
|
40
|
+
# the new config is silently dropped here and every later deploy
|
|
41
|
+
# skips that user (their files never appear in a commit range).
|
|
42
|
+
git add --intent-to-add -- users
|
|
43
|
+
|
|
44
|
+
if git diff --quiet -- users; then
|
|
39
45
|
exit 0
|
|
40
46
|
fi
|
|
47
|
+
|
|
48
|
+
git fetch origin "${{ github.ref_name }}"
|
|
49
|
+
if git diff --quiet "origin/${{ github.ref_name }}" -- users; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
patch_file="$(mktemp)"
|
|
54
|
+
git diff --binary -- users > "$patch_file"
|
|
55
|
+
git reset --hard "origin/${{ github.ref_name }}"
|
|
56
|
+
git apply --3way --index "$patch_file"
|
|
57
|
+
|
|
58
|
+
if git diff --cached --quiet -- users; then
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
|
|
41
62
|
git config user.name "github-actions[bot]"
|
|
42
63
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
43
|
-
git add views users
|
|
44
64
|
git commit -m "chore(ops): reconcile pilot outputs"
|
|
45
|
-
git push
|
|
65
|
+
git push origin HEAD:${{ github.ref_name }}
|