Skip to content
/ bits Public

Commit 5eb6aee

Browse files
committed
Improve CI
1 parent 45e7f6f commit 5eb6aee

21 files changed

Lines changed: 1007 additions & 140 deletions

File tree

.claude/skills/commit-all/SKILL.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
---
22
name: commit-all
33
description: Create atomic commits from all unstaged changes
4-
allowed-tools: Bash(git status, git diff*, git add, git commit, git log)
4+
allowed-tools: Bash(just fmt, git status, git diff*, git add, git commit, git log)
55
---
66

77
# Commit All Unstaged Changes
88

99
Create atomic commits from all unstaged changes.
1010

11+
## Step 1: Format Code
12+
13+
!`just fmt`
14+
1115
## Current State
1216

1317
!`git status --short`

.claude/skills/commit/SKILL.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
---
22
name: commit
33
description: Create atomic commits from session changes only
4-
allowed-tools: Bash(git status, git diff*, git add, git commit, git log)
4+
allowed-tools: Bash(just fmt, git status, git diff*, git add, git commit, git log)
55
---
66

77
# Commit Session Changes
88

99
Create atomic commits for **only the changes we made together** in this session.
1010

11-
## Step 1: Identify Session Changes
11+
## Step 1: Format Code
12+
13+
!`just fmt`
14+
15+
## Step 2: Identify Session Changes
1216

1317
Review your tool use history from this conversation. List the files you edited
1418
using Edit or Write tools. These are "our changes."
1519

16-
## Step 2: Cross-Reference with Git
20+
## Step 3: Cross-Reference with Git
1721

1822
!`git status --short`
1923

@@ -26,7 +30,7 @@ Only consider files that:
2630

2731
Ignore any pre-existing uncommitted changes that weren't part of our work.
2832

29-
## Step 3: Propose Atomic Commits
33+
## Step 4: Propose Atomic Commits
3034

3135
Group related changes into logical commits. For each proposed commit, show:
3236

@@ -39,7 +43,7 @@ Commit 2: <message>
3943
- path/to/other.clj
4044
```
4145

42-
## Step 4: Wait for Approval
46+
## Step 5: Wait for Approval
4347

4448
Present the plan and ask: "Ready to create these commits?"
4549

.claude/skills/sync-deps/SKILL.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
name: sync-deps
3+
description: Synchronise Nix deps hash after changing deps.edn
4+
---
5+
6+
# Sync Dependencies Hash
7+
8+
Update the fixed-output derivation hash in `pkgs/bits-uberjar/default.nix` after
9+
changing `deps.edn`.
10+
11+
## Background
12+
13+
The uberjar build uses a fixed-output derivation (FOD) to cache Maven/Clojure
14+
dependencies. When `deps.edn` changes, the hash becomes stale and builds fail
15+
with a hash mismatch error containing the correct hash.
16+
17+
## Process
18+
19+
1. Read `pkgs/bits-uberjar/default.nix`
20+
21+
2. Edit `depsHash` to an invalid value to force recomputation:
22+
23+
```nix
24+
depsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
25+
```
26+
27+
3. Clear devenv cache: `rm -f .devenv/nix-eval-cache.db`
28+
29+
4. Run: `devenv build outputs.bits-uberjar`
30+
31+
The build fails with a hash mismatch. Find the `got:` line containing the
32+
correct hash (format: `sha256-...=`).
33+
34+
5. Edit `depsHash` with the correct hash from the error output.
35+
36+
6. Verify: `devenv build outputs.bits-uberjar`
37+
38+
7. If verification fails, restore: `git checkout pkgs/bits-uberjar/default.nix`
39+
40+
$ARGUMENTS

.forgejo/scripts/attic-push.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
set -eu
3+
4+
# Push gcroots and their closures to Attic
5+
# This ensures future CI runs can fetch from Attic instead of cache.nixos.org
6+
for path in .devenv/gc/*; do
7+
if [[ -L $path ]]; then
8+
# Resolve symlink and push the actual store path with its closure
9+
store_path=$(readlink -f "$path")
10+
attic push invetica:invetica "$store_path" || true
11+
fi
12+
done

.forgejo/scripts/attic-setup.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
set -eu
3+
4+
usage() {
5+
echo >&2 "Usage: ATTIC_TOKEN=<token> attic-setup.sh"
6+
}
7+
8+
if [[ -z ${ATTIC_TOKEN:-} ]]; then
9+
usage
10+
exit 22 # EINVAL
11+
fi
12+
13+
attic login invetica https://attic.lan.invetica.co.uk "$ATTIC_TOKEN"
14+
attic use invetica:invetica

.forgejo/scripts/build.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
set -eu
3+
4+
usage() {
5+
echo >&2 "Usage: build.sh <output-name>"
6+
}
7+
8+
if [[ $# -ne 1 ]]; then
9+
usage
10+
exit 22 # EINVAL
11+
fi
12+
13+
output=$1
14+
15+
devenv build "outputs.$output"

.forgejo/scripts/filter-devenv.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env bash
2+
3+
# Filter devenv spinner noise from output
4+
grep -v '^[⠋⠙⠹⠸⠼⠴⠦⠧⠇✖]' || true

.forgejo/scripts/tag-image.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env zsh
2+
set -eu
3+
4+
usage() {
5+
echo >&2 "Usage: tag-image.sh <image-path> <arch>"
6+
}
7+
8+
if [[ $# -ne 2 ]]; then
9+
usage
10+
exit 22 # EINVAL
11+
fi
12+
13+
image_path=$1
14+
arch=$2
15+
16+
podman load <"$image_path"
17+
18+
version=$(podman images bits --format '{{.Tag}}' | head -1)
19+
echo "version=$version" >>"$GITHUB_OUTPUT"
20+
echo "Version: $version"
21+
22+
image="ghcr.io/jcf/bits"
23+
podman tag "bits:$version" "$image:$version-$arch"
24+
echo "image=$image" >>"$GITHUB_OUTPUT"

.forgejo/workflows/bootstrap.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Bootstrap
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: [main, morph]
7+
paths:
8+
- "pkgs/bits-ci/**"
9+
- ".forgejo/workflows/bootstrap.yml"
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
build:
17+
name: Build
18+
runs-on: nixos
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Configure Attic cache
24+
env:
25+
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
26+
run: .forgejo/scripts/attic-setup.sh
27+
28+
- name: Build CI container
29+
id: build
30+
run: |
31+
set -o pipefail
32+
image_path=$(.forgejo/scripts/build.sh bits-ci-amd64 2>&1 | .forgejo/scripts/filter-devenv.sh | head -1)
33+
echo "image_path=$image_path" >> "$GITHUB_OUTPUT"
34+
35+
- name: Push closure to Attic cache
36+
run: nix-store -qR "${{ steps.build.outputs.image_path }}" | xargs attic push invetica:invetica
37+
38+
- name: Push to Forgejo
39+
env:
40+
REGISTRY_AUTH_FILE: ${{ runner.temp }}/auth.json
41+
run: |
42+
echo "${{ secrets.FORGEJO_PKG_TOKEN }}" | skopeo login git.lan.invetica.co.uk -u jcf --password-stdin
43+
version="$(date +%Y%m%d)-$(git rev-parse --short HEAD)"
44+
image="git.lan.invetica.co.uk/jcf/bits/bits-ci"
45+
skopeo copy \
46+
"docker-archive:${{ steps.build.outputs.image_path }}" \
47+
"docker://$image:$version"
48+
skopeo copy \
49+
"docker-archive:${{ steps.build.outputs.image_path }}" \
50+
"docker://$image:latest"
51+
echo ""
52+
echo "✨ Images pushed!"
53+
echo ""
54+
echo "$image:$version"
55+
echo "$image:latest"

.forgejo/workflows/ci.yml

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
name: CI
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: [main, morph]
7+
paths-ignore:
8+
- .forgejo/workflows/bootstrap.yml
9+
- pkgs/bits-ci/**
10+
pull_request:
11+
branches: [main, morph]
12+
paths-ignore:
13+
- .forgejo/workflows/bootstrap.yml
14+
- pkgs/bits-ci/**
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
19+
20+
permissions:
21+
packages: read
22+
23+
env:
24+
CI: true
25+
IMAGE: git.lan.invetica.co.uk/jcf/bits/bits-ci:20260225-9a8b11c
26+
27+
jobs:
28+
check:
29+
name: Check
30+
runs-on: nixos
31+
container:
32+
image: ${{ env.IMAGE }}
33+
credentials:
34+
username: jcf
35+
password: ${{ github.token }}
36+
steps:
37+
- uses: actions/checkout@v4
38+
39+
- name: Check
40+
run: just check
41+
42+
test:
43+
name: Test
44+
runs-on: nixos
45+
container:
46+
image: ${{ env.IMAGE }}
47+
credentials:
48+
username: jcf
49+
password: ${{ github.token }}
50+
services:
51+
postgres:
52+
image: postgres:17
53+
env:
54+
POSTGRES_USER: bits
55+
POSTGRES_PASSWORD: please
56+
POSTGRES_DB: bits_test
57+
steps:
58+
- uses: actions/checkout@v4
59+
60+
- name: Cache Clojure deps
61+
uses: actions/cache@v4
62+
with:
63+
path: |
64+
~/.m2/repository
65+
~/.gitlibs
66+
~/.deps.clj
67+
key: maven-${{ hashFiles('deps.edn') }}
68+
restore-keys: maven-
69+
70+
- name: Wait for PostgreSQL
71+
run: |
72+
for i in $(seq 1 30); do
73+
pg_isready -h postgres -p 5432 -U bits && exit 0
74+
sleep 1
75+
done
76+
echo "Postgres failed to start"
77+
exit 1
78+
79+
- name: Generate CSS
80+
run: tailwindcss --input resources/tailwind.css --output resources/public/app.css
81+
82+
- name: Run tests
83+
env:
84+
CLUSTER_KEYSTORE_PASSWORD: correct-horse-battery-staple
85+
DATABASE_URL: postgres://bits:please@postgres:5432/bits_test
86+
run: just os=linux-x86_64 test --reporter documentation
87+
88+
build:
89+
name: Build ${{ matrix.arch }}
90+
runs-on: nixos
91+
needs: [check, test]
92+
if: github.event_name == 'push'
93+
94+
strategy:
95+
matrix:
96+
include:
97+
- arch: amd64
98+
output: bits-container-amd64
99+
- arch: arm64
100+
output: bits-container-arm64
101+
102+
steps:
103+
- uses: actions/checkout@v4
104+
105+
- name: Configure Attic cache
106+
env:
107+
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
108+
run: .forgejo/scripts/attic-setup.sh
109+
110+
- name: Build container
111+
id: build
112+
run: |
113+
set -o pipefail
114+
image_path=$(.forgejo/scripts/build.sh "${{ matrix.output }}" 2>&1 | .forgejo/scripts/filter-devenv.sh | head -1)
115+
echo "image_path=$image_path" >> "$GITHUB_OUTPUT"
116+
117+
- name: Push to Attic cache
118+
run: attic push invetica:invetica "${{ steps.build.outputs.image_path }}"
119+
120+
- name: Load and tag image
121+
id: tag
122+
run: .forgejo/scripts/tag-image.sh "${{ steps.build.outputs.image_path }}" "${{ matrix.arch }}"
123+
124+
- name: Push to ghcr.io
125+
run: |
126+
echo "${{ secrets.GH_TOKEN }}" | podman login ghcr.io -u jcf --password-stdin
127+
podman push "${{ steps.tag.outputs.image }}:${{ steps.tag.outputs.version }}-${{ matrix.arch }}"
128+
129+
outputs:
130+
version: ${{ steps.tag.outputs.version }}
131+
image: ${{ steps.tag.outputs.image }}
132+
133+
manifest:
134+
name: Create manifest
135+
runs-on: nixos
136+
needs: [build]
137+
if: github.event_name == 'push'
138+
139+
steps:
140+
- name: Login to ghcr.io
141+
run: echo "${{ secrets.GH_TOKEN }}" | podman login ghcr.io -u jcf --password-stdin
142+
143+
- name: Create multi-arch manifest
144+
run: |
145+
image="${{ needs.build.outputs.image }}"
146+
version="${{ needs.build.outputs.version }}"
147+
148+
# Create versioned manifest
149+
podman manifest create "$image:$version" \
150+
"$image:$version-amd64" \
151+
"$image:$version-arm64"
152+
podman manifest push "$image:$version" "docker://$image:$version"
153+
154+
# Create latest manifest
155+
podman manifest create "$image:latest" \
156+
"$image:$version-amd64" \
157+
"$image:$version-arm64"
158+
podman manifest push "$image:latest" "docker://$image:latest"

0 commit comments

Comments
 (0)