@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
|
@@ -7,6 +7,11 @@ 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:
|
|
@@ -82,51 +87,29 @@ jobs:
|
|
|
82
87
|
- name: Install operator tooling
|
|
83
88
|
run: bun install
|
|
84
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
|
+
|
|
85
95
|
- name: Decrypt user secrets
|
|
86
96
|
id: user_secrets
|
|
87
|
-
env:
|
|
88
|
-
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
|
|
89
97
|
run: bun deploy/scripts/decrypt-user-secrets.ts "$HANDLE"
|
|
90
98
|
|
|
91
99
|
- name: Reconcile selected user config
|
|
92
|
-
|
|
93
|
-
SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
|
|
94
|
-
run: |
|
|
95
|
-
export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
|
|
96
|
-
bunx brains-ops onboard "$GITHUB_WORKSPACE" "$HANDLE"
|
|
100
|
+
run: bunx brains-ops onboard "$GITHUB_WORKSPACE" "$HANDLE"
|
|
97
101
|
|
|
98
102
|
- name: Resolve generated user config
|
|
99
103
|
id: user_config
|
|
100
104
|
run: bun deploy/scripts/resolve-user-config.ts
|
|
101
105
|
|
|
102
106
|
- name: Validate selected secrets
|
|
103
|
-
|
|
104
|
-
SHARED_AI_API_KEY: ${{ secrets[steps.user_secrets.outputs.shared_ai_api_key_secret_name] }}
|
|
105
|
-
SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
|
|
106
|
-
SHARED_MCP_AUTH_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_mcp_auth_token_secret_name] }}
|
|
107
|
-
HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
|
|
108
|
-
HCLOUD_SSH_KEY_NAME: ${{ secrets.HCLOUD_SSH_KEY_NAME }}
|
|
109
|
-
HCLOUD_SERVER_TYPE: ${{ secrets.HCLOUD_SERVER_TYPE }}
|
|
110
|
-
HCLOUD_LOCATION: ${{ secrets.HCLOUD_LOCATION }}
|
|
111
|
-
KAMAL_SSH_PRIVATE_KEY: ${{ secrets.KAMAL_SSH_PRIVATE_KEY }}
|
|
112
|
-
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
|
|
113
|
-
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
|
114
|
-
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
|
115
|
-
CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
|
|
116
|
-
PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
|
|
117
|
-
run: |
|
|
118
|
-
export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
|
|
119
|
-
export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
|
|
120
|
-
export MCP_AUTH_TOKEN="${MCP_AUTH_TOKEN:-$SHARED_MCP_AUTH_TOKEN}"
|
|
121
|
-
bun deploy/scripts/validate-secrets.ts
|
|
107
|
+
run: bun deploy/scripts/validate-secrets.ts
|
|
122
108
|
|
|
123
109
|
- name: Seed content repo
|
|
124
110
|
env:
|
|
125
111
|
CONTENT_REPO: ${{ steps.user_config.outputs.content_repo }}
|
|
126
|
-
|
|
127
|
-
run: |
|
|
128
|
-
export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
|
|
129
|
-
bun deploy/scripts/sync-content-repo.ts
|
|
112
|
+
run: bun deploy/scripts/sync-content-repo.ts
|
|
130
113
|
|
|
131
114
|
- name: Log in to GHCR
|
|
132
115
|
uses: docker/login-action@v3
|
|
@@ -135,27 +118,31 @@ jobs:
|
|
|
135
118
|
username: ${{ github.actor }}
|
|
136
119
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
137
120
|
|
|
138
|
-
- name: Wait for
|
|
121
|
+
- name: Wait for image tag
|
|
139
122
|
run: |
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
142
128
|
if docker manifest inspect "$IMAGE_TAG" >/dev/null 2>&1; then
|
|
143
129
|
exit 0
|
|
144
130
|
fi
|
|
145
|
-
echo "
|
|
146
|
-
sleep
|
|
131
|
+
echo "Image tag not ready yet ($attempt/60): $IMAGE_TAG"
|
|
132
|
+
sleep 20
|
|
147
133
|
done
|
|
148
134
|
echo "Timed out waiting for $IMAGE_TAG" >&2
|
|
149
135
|
exit 1
|
|
150
136
|
|
|
137
|
+
- name: Verify runtime image packages
|
|
138
|
+
run: bun deploy/scripts/verify-runtime-image.ts
|
|
139
|
+
|
|
151
140
|
- name: Install Kamal
|
|
152
141
|
run: |
|
|
153
142
|
gem install --user-install kamal
|
|
154
143
|
ruby -r rubygems -e 'puts Gem.user_dir + "/bin"' >> "$GITHUB_PATH"
|
|
155
144
|
|
|
156
145
|
- name: Write Kamal SSH key
|
|
157
|
-
env:
|
|
158
|
-
KAMAL_SSH_PRIVATE_KEY: ${{ secrets.KAMAL_SSH_PRIVATE_KEY }}
|
|
159
146
|
run: bun deploy/scripts/write-ssh-key.ts
|
|
160
147
|
|
|
161
148
|
- name: Configure SSH client
|
|
@@ -172,38 +159,26 @@ jobs:
|
|
|
172
159
|
chmod 600 ~/.ssh/config
|
|
173
160
|
|
|
174
161
|
- name: Write .kamal/secrets
|
|
175
|
-
|
|
176
|
-
SHARED_AI_API_KEY: ${{ secrets[steps.user_secrets.outputs.shared_ai_api_key_secret_name] }}
|
|
177
|
-
SHARED_GIT_SYNC_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_git_sync_token_secret_name] }}
|
|
178
|
-
SHARED_MCP_AUTH_TOKEN: ${{ secrets[steps.user_secrets.outputs.shared_mcp_auth_token_secret_name] }}
|
|
179
|
-
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
|
|
180
|
-
CERTIFICATE_PEM: ${{ secrets.CERTIFICATE_PEM }}
|
|
181
|
-
PRIVATE_KEY_PEM: ${{ secrets.PRIVATE_KEY_PEM }}
|
|
182
|
-
run: |
|
|
183
|
-
export AI_API_KEY="${AI_API_KEY:-$SHARED_AI_API_KEY}"
|
|
184
|
-
export GIT_SYNC_TOKEN="${GIT_SYNC_TOKEN:-$SHARED_GIT_SYNC_TOKEN}"
|
|
185
|
-
export MCP_AUTH_TOKEN="${MCP_AUTH_TOKEN:-$SHARED_MCP_AUTH_TOKEN}"
|
|
186
|
-
bun deploy/scripts/write-kamal-secrets.ts
|
|
162
|
+
run: bun deploy/scripts/write-kamal-secrets.ts
|
|
187
163
|
|
|
188
164
|
- name: Provision server
|
|
189
165
|
id: provision
|
|
190
166
|
env:
|
|
191
|
-
HCLOUD_TOKEN: ${{ secrets.HCLOUD_TOKEN }}
|
|
192
|
-
HCLOUD_SSH_KEY_NAME: ${{ secrets.HCLOUD_SSH_KEY_NAME }}
|
|
193
|
-
HCLOUD_SERVER_TYPE: ${{ secrets.HCLOUD_SERVER_TYPE }}
|
|
194
|
-
HCLOUD_LOCATION: ${{ secrets.HCLOUD_LOCATION }}
|
|
195
167
|
INSTANCE_NAME: ${{ steps.user_config.outputs.instance_name }}
|
|
196
168
|
run: bun deploy/scripts/provision-server.ts
|
|
197
169
|
|
|
198
170
|
- name: Update Cloudflare DNS
|
|
199
171
|
env:
|
|
200
|
-
|
|
201
|
-
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
|
172
|
+
CF_ZONE_ID: ${{ steps.user_config.outputs.cloudflare_zone_id }}
|
|
202
173
|
BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
|
|
174
|
+
WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
|
|
203
175
|
PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
|
|
204
176
|
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
205
177
|
run: |
|
|
206
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
|
|
207
182
|
BRAIN_DOMAIN="$PREVIEW_DOMAIN" bun deploy/scripts/update-dns.ts
|
|
208
183
|
|
|
209
184
|
- name: Validate SSH key
|
|
@@ -224,26 +199,62 @@ jobs:
|
|
|
224
199
|
echo "SSH never became ready for $SSH_USER@$SERVER_IP" >&2
|
|
225
200
|
exit 1
|
|
226
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
|
+
|
|
227
223
|
- name: Deploy
|
|
228
224
|
env:
|
|
229
225
|
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
230
|
-
VERSION:
|
|
226
|
+
VERSION: ${{ steps.user_config.outputs.image_tag }}
|
|
231
227
|
IMAGE_REPOSITORY: ${{ steps.user_config.outputs.image_repository }}
|
|
232
228
|
REGISTRY_USERNAME: ${{ steps.user_config.outputs.registry_username }}
|
|
233
229
|
BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
|
|
230
|
+
WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
|
|
234
231
|
PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
|
|
235
232
|
BRAIN_YAML_PATH: ${{ steps.user_config.outputs.brain_yaml_path }}
|
|
236
233
|
run: kamal setup --skip-push -c deploy/kamal/deploy.yml
|
|
237
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
|
+
|
|
238
242
|
- name: Verify origin TLS
|
|
239
243
|
env:
|
|
240
244
|
SERVER_IP: ${{ steps.provision.outputs.server_ip }}
|
|
241
245
|
BRAIN_DOMAIN: ${{ steps.user_config.outputs.brain_domain }}
|
|
246
|
+
WWW_DOMAIN: ${{ steps.user_config.outputs.www_domain }}
|
|
242
247
|
PREVIEW_DOMAIN: ${{ steps.user_config.outputs.preview_domain }}
|
|
243
248
|
run: |
|
|
244
|
-
curl -I -k --max-time 20 --resolve "$BRAIN_DOMAIN:443:$SERVER_IP" "https://$BRAIN_DOMAIN/health"
|
|
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
|
|
245
253
|
curl -I -k --max-time 20 --resolve "$PREVIEW_DOMAIN:443:$SERVER_IP" "https://$PREVIEW_DOMAIN/"
|
|
246
254
|
|
|
255
|
+
- name: Verify operational health
|
|
256
|
+
run: bunx brains-ops verify-user "$GITHUB_WORKSPACE" "$HANDLE"
|
|
257
|
+
|
|
247
258
|
- name: Upload generated config
|
|
248
259
|
uses: actions/upload-artifact@v4
|
|
249
260
|
with:
|
|
@@ -297,6 +308,12 @@ jobs:
|
|
|
297
308
|
|
|
298
309
|
- name: Commit generated config
|
|
299
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
|
+
|
|
300
317
|
if git diff --quiet -- users views; then
|
|
301
318
|
exit 0
|
|
302
319
|
fi
|
|
@@ -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,21 +35,27 @@ 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
|
|
41
47
|
|
|
42
48
|
git fetch origin "${{ github.ref_name }}"
|
|
43
|
-
if git diff --quiet "origin/${{ github.ref_name }}" --
|
|
49
|
+
if git diff --quiet "origin/${{ github.ref_name }}" -- users; then
|
|
44
50
|
exit 0
|
|
45
51
|
fi
|
|
46
52
|
|
|
47
53
|
patch_file="$(mktemp)"
|
|
48
|
-
git diff --binary --
|
|
54
|
+
git diff --binary -- users > "$patch_file"
|
|
49
55
|
git reset --hard "origin/${{ github.ref_name }}"
|
|
50
56
|
git apply --3way --index "$patch_file"
|
|
51
57
|
|
|
52
|
-
if git diff --cached --quiet --
|
|
58
|
+
if git diff --cached --quiet -- users; then
|
|
53
59
|
exit 0
|
|
54
60
|
fi
|
|
55
61
|
|