Repository navigation
519 lines (458 loc) · 18.6 KB
/
Copy pathbuild.dev.yml
File metadata and controls
519 lines (458 loc) · 18.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
name: Dev Build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
on:
push:
branches:
- main
- feature/wip
- feature/add-web
- feature/web
workflow_dispatch:
inputs:
skip_nix_cache:
description: 'Skip nix cache (genesis mode — full rebuild, no pre-seeding)'
type: boolean
default: false
permissions:
contents: write
packages: write
id-token: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
go-unit:
name: Go Unit Tests
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Install go-task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
# cmd/serve.go imports the gitignored api/swagger/ package, so the
# docs must exist before cmd/ compiles (see AGENTS.md).
- name: Generate swagger docs
run: task swagger:generate
# Short mode: tests that need a logged-in agent binary, a built image
# or the network skip themselves. Docker-backed container tests live
# in ./test/... and run in the Docker Test job below.
- name: Vet and unit-test cmd/ and internal/
run: |
go vet ./cmd/... ./internal/...
go test -short -timeout 900s ./cmd/... ./internal/...
secrets:
name: Detect Secrets
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
# gitleaks CLI directly — bypasses gitleaks-action's org-license
# requirement (the license gate lives in the action wrapper, not the
# scanner). `detect` exits 1 if leaks are found.
- name: Run gitleaks
run: |
docker run --rm -v "$PWD:/repo" -w /repo \
ghcr.io/gitleaks/gitleaks:latest \
detect --source=. --verbose --redact
docker-build:
name: Docker Build (${{ matrix.arch }})
needs: secrets
strategy:
fail-fast: false
# Serialize amd64 + arm64 to avoid stressing GHCR + cache.nixos.org
# with two concurrent multi-GB push/pull pipelines from the same
# repo. Doubles wall-clock; halves peak network pressure.
max-parallel: 1
matrix:
include:
# amd64 temporarily disabled while we debug the post-base
# cache-publish hang on arm64. Re-enable once arm64 is healthy.
# - runner: ubuntu-24.04
# arch: amd64
# platform: linux/amd64
- runner: blacksmith-4vcpu-ubuntu-2404-arm
arch: arm64
platform: linux/arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
- name: Set lowercase image name
run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
sudo docker image prune -af
df -h
- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Install go-task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
# task cell:build = swagger:generate → CGO_ENABLED=0 go build with ldflags
# for GitCommit + BuildDate. Single source of truth shared with local dev
# (`task cell:build` works the same on a developer machine).
- name: Build cell binary
run: task cell:build
# Cache push/pull goes through `cell nix-store {push,pull}` — a
# cell subcommand using `pkg/v1/stream.NewLayer` from
# go-containerregistry. Push splits the tar into ~1 GB chunks,
# each uploaded as a separate OCI layer (CELL-297). Pull
# extracts all non-base layers in order.
# Cache the nix-store as a GHCR image. Lives in the SAME
# `devcell-sh/devcell` GHCR package as the runtime images, with tag
# prefix `nix-cache-` so it doesn't collide with stack tags.
- name: Hydrate /nix volume from GHCR cache
if: inputs.skip_nix_cache != true
run: |
HASH=${{ github.sha }}
./bin/cell nix-store pull \
--image "${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:nix-cache-${{ matrix.arch }}-${HASH}" \
--fallback "${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:nix-cache-${{ matrix.arch }}-latest" \
--volume "devcell-nix-store-${{ matrix.arch }}" \
|| echo "WARN: cache pull failed, continuing with empty volume"
# Build both stacks sequentially in the same job so the nix-store
# volume accumulates derivations from both — single tar dump at job
# end carries everything needed by docker-test. `cell build`
# reuses store paths across the two invocations (nix is
# content-addressed), so ultimate after base is incremental.
- name: Build thin image (base stack)
env:
DEVCELL_NIX_VOLUME: devcell-nix-store-${{ matrix.arch }}
DEVCELL_NIX_MAX_JOBS: "4"
run: |
BASE_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-base"
./bin/cell build --stack base --image "$BASE_TAG" --debug
echo "BASE_TAG=$BASE_TAG" >> "$GITHUB_ENV"
- name: Build thin image (ultimate stack)
env:
DEVCELL_NIX_VOLUME: devcell-nix-store-${{ matrix.arch }}
DEVCELL_NIX_MAX_JOBS: "4"
run: |
ULT_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-ultimate"
./bin/cell build --stack ultimate --image "$ULT_TAG" --debug
echo "ULT_TAG=$ULT_TAG" >> "$GITHUB_ENV"
- name: Push to GHCR (both stacks)
run: |
docker push "$BASE_TAG"
docker push "$ULT_TAG"
# Single publish after both stacks: the volume now carries the
# full base+ultimate closure. Runs even when a prior step failed
# (unless cancelled) so a partial build still refreshes the cache.
- name: Publish nix cache
if: ${{ !cancelled() && inputs.skip_nix_cache != true }}
timeout-minutes: 120
env:
ARCH: ${{ matrix.arch }}
HASH: ${{ github.sha }}
STAGE: post-ultimate
DEVCELL_NIX_PUSH_DEBUG: "1"
run: task nix-cache:publish
docker-test:
name: Docker Test (${{ matrix.arch }})
needs: docker-build
strategy:
fail-fast: false
matrix:
include:
# amd64 disabled in step with docker-build above.
# - runner: ubuntu-24.04
# arch: amd64
- runner: blacksmith-4vcpu-ubuntu-2404-arm
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
- name: Set lowercase image name
run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Install go-task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Build cell so we can use `cell nix-store pull` below — same
# binary docker-build's publish step uses, so any encoding
# divergence between push and pull is structurally impossible.
- name: Build cell binary
run: task cell:build
- name: Hydrate /nix volume from GHCR cache
run: |
HASH=${{ github.sha }}
./bin/cell nix-store pull \
--image "${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:nix-cache-${{ matrix.arch }}-${HASH}" \
--fallback "${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:nix-cache-${{ matrix.arch }}-latest" \
--volume "devcell-nix-store-${{ matrix.arch }}" \
|| echo "WARN: cache pull failed, tests will use empty volume"
- name: Pull test images (base + ultimate)
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-base
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-ultimate
- name: Run container tests
# Both `DEVCELL_USER_IMAGE` and `DEVCELL_TEST_IMAGE` point at the
# ultimate image (every module + tool baked in) so module-aware tests
# (kicad, plex, gui) find what they need. `DEVCELL_TEST_BASE_IMAGE`
# points at the smaller base image for entrypoint-only tests
# (TestEntrypoint_DebugTimestamps etc.). Setting `DEVCELL_USER_IMAGE`
# without `DEVCELL_USER_IMAGE_THIN` works because UserImageTagThin()
# falls back to DEVCELL_USER_IMAGE as-is (no `-thin` suffix appended)
# per CELL-286 prep.
# `DEVCELL_NIX_VOLUME` points cell at the per-arch volume we just
# hydrated from the GHCR cache, so `cell shell` / `cell claude` skip
# the lazy-build path entirely instead of falling back to the
# default-named volume (which would be empty).
env:
DEVCELL_NIX_VOLUME: devcell-nix-store-${{ matrix.arch }}
DEVCELL_TEST_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-ultimate
DEVCELL_USER_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-ultimate
DEVCELL_TEST_BASE_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:v0.0.0-${{ matrix.arch }}-base
DEVCELL_TEST_PROJECT_DIR: ${{ runner.temp }}/devcell-test
MCP_SECRET_TEST_PASSWORD: fake-secret-ci-value
MCP_SECRET_GITHUB_TOKEN: fake-token-ci-value
run: go test -v -timeout 1200s ./test/...
examples-test:
name: Examples E2E (${{ matrix.arch }})
needs: docker-build
strategy:
fail-fast: false
matrix:
include:
- runner: blacksmith-4vcpu-ubuntu-2404-arm
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v5
- name: Set lowercase image name
run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Install go-task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build cell binary
run: task cell:build
- name: Hydrate /nix volume from GHCR cache
run: |
HASH=${{ github.sha }}
./bin/cell nix-store pull \
--image "${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:nix-cache-${{ matrix.arch }}-${HASH}" \
--fallback "${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}:nix-cache-${{ matrix.arch }}-latest" \
--volume "devcell-nix-store" \
|| echo "WARN: cache pull failed, examples will build from scratch"
- name: Run examples E2E tests
env:
DEVCELL_TEST_PROJECT_DIR: ${{ runner.temp }}/devcell-test
run: go test -v -timeout 1800s -run 'TestExamples' ./test/...
docker-manifest:
name: Docker Manifests
needs: [docker-build]
if: always() && needs.docker-build.result == 'success'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Set lowercase image name
run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> $GITHUB_ENV
env:
IMAGE_NAME: ${{ github.repository }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create GHCR manifests (per stack + ultimate as canonical)
run: |
R=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LC }}
# amd64 inputs commented out in step with docker-build matrix
# above — re-add the `${R}:v0.0.0-amd64-*` arguments when amd64
# is re-enabled. arm64-only manifests are still valid OCI
# indexes; tags resolve cleanly on arm64 hosts.
docker buildx imagetools create \
-t "${R}:v0.0.0-base" \
"${R}:v0.0.0-arm64-base"
docker buildx imagetools create \
-t "${R}:v0.0.0-ultimate" \
-t "${R}:v0.0.0" \
-t "${R}:latest" \
-t "${R}:dev" \
"${R}:v0.0.0-arm64-ultimate"
cell-build:
name: Cell CLI Dev Build
needs: secrets
runs-on: blacksmith-4vcpu-ubuntu-2404
env:
RELEASE_VERSION: v0.0.0
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache-dependency-path: go.sum
- name: Set SHORT_SHA
run: echo "SHORT_SHA=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_ENV
- name: Delete existing tag
run: |
git config --global user.email "dmitry@atd.sh"
git config --global user.name "Dmitry Kireev"
git tag -d "${{ env.RELEASE_VERSION }}" || echo "No local tag to delete"
git push origin :refs/tags/${{ env.RELEASE_VERSION }} || echo "No remote tag to delete"
- name: Add tag
run: |
git tag -a ${{ env.RELEASE_VERSION }} -m "Development release"
git push origin ${{ github.ref_name }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser
version: "~> v2"
args: release --clean -f .goreleaser.dev.yaml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
SHORT_SHA: ${{ env.SHORT_SHA }}
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release edit "${{ env.RELEASE_VERSION }}" --draft=false --latest=false --prerelease
e2e-install:
name: E2E Install (${{ matrix.arch }})
needs: [cell-build, docker-manifest]
strategy:
fail-fast: false
matrix:
include:
# amd64 disabled in step with docker-build matrix above.
# - runner: ubuntu-24.04
# arch: amd64
- runner: blacksmith-4vcpu-ubuntu-2404-arm
arch: arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
- name: Download cell binary
run: |
TARBALL="cell-linux-${{ matrix.arch }}.tar.gz"
URL="https://github.com/DimmKirr/devcell/releases/download/v0.0.0/${TARBALL}"
echo "Downloading: ${URL}"
curl -fsSL -o "${TARBALL}" "${URL}"
tar xzf "${TARBALL}"
chmod +x cell
sudo mv cell /usr/local/bin/cell
- name: Verify cell binary
run: |
cell --help
echo "--- cell binary OK ---"
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run cell claude --version (full pipeline)
run: |
# Simulate a new user in a fresh project dir
mkdir -p /tmp/e2e-project && cd /tmp/e2e-project
# --plain-text: disable spinners for CI
# Flow: scaffold devcell.toml → pull base image → build user image → start container → print claude version
OUTPUT=$(cell --plain-text claude --version 2>&1) || true
echo "$OUTPUT"
# Assert cell version string is present
if echo "$OUTPUT" | grep -qE "cell\s+v?[0-9]+\.[0-9]+\.[0-9]+"; then
echo "--- PASS: cell version found ---"
else
echo "--- FAIL: cell version string not found in output ---"
exit 1
fi
# Assert the image was built (user image should exist now)
if echo "$OUTPUT" | grep -qiE "(building|built|image)"; then
echo "--- PASS: image build occurred ---"
else
echo "--- WARN: no build output detected (image may have been cached) ---"
fi
brew-install:
name: Brew Install (${{ matrix.arch }})
needs: [cell-build]
strategy:
fail-fast: false
matrix:
include:
# amd64 disabled in step with docker-build matrix above.
# - runner: ubuntu-24.04
# arch: amd64
- runner: blacksmith-4vcpu-ubuntu-2404-arm
arch: arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 15
steps:
- name: Install Homebrew
run: |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" < /dev/null
echo "/home/linuxbrew/.linuxbrew/bin" >> $GITHUB_PATH
- name: Brew install devcell-dev
run: |
brew tap devcell-sh/tap
# Homebrew 4.5+ refuses to load casks from third-party taps without
# explicit trust. `brew trust devcell-sh/tap` trusts every cask in
# the tap so non-interactive installs succeed.
brew trust devcell-sh/tap
brew install --cask devcell-dev || true
# Verify binary was actually linked despite potential broken pipe
if ! command -v cell &>/dev/null; then
echo "Binary not found, retrying..."
brew install --cask devcell-dev
fi
- name: Verify version
run: |
INSTALLED=$(cell --version)
echo "Installed: ${INSTALLED}"
if echo "${INSTALLED}" | grep -qE "^cell version [0-9]+\.[0-9]+\.[0-9]+"; then
echo "--- PASS: cell version OK ---"
else
echo "--- FAIL: unexpected version output ---"
exit 1
fi