forked from schubergphilis/claude-docker
-
Notifications
You must be signed in to change notification settings - Fork 0
185 lines (161 loc) · 7.96 KB
/
Copy pathci.yml
File metadata and controls
185 lines (161 loc) · 7.96 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
# yamllint disable rule:line-length
---
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch: {}
concurrency:
group: ci-${{ github.ref }}
# Cancel superseded PR builds, but never cancel a run on main —
# main runs validate merged code and must complete.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
validate:
name: Validate
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: ShellCheck shell scripts
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
with:
severity: warning
scandir: .
version: v0.11.0
- name: Unit tests — update_pins.py helpers
# stdlib unittest on the runner's preinstalled python3 — no install,
# no third-party action (the suite imports only the standard library).
run: python3 -m unittest discover -s tests -p 'test_*.py' -v
- name: Hadolint Dockerfile
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: Dockerfile
config: .hadolint.yaml
failure-threshold: warning
- name: npm-pinned tools supply-chain audit
# For EVERY npm-pinned tool enforce two policies: (1) tarball signed by
# npm's keyring over dist.integrity (`npm audit signatures`), (2) soak
# since publish (default 7 days) so loud supply-chain attacks get yanked
# before we ingest. The soak gate is enforced by update_pins.py --audit,
# which is the single source of truth for soak logic (including the window
# default).
# The tool list is provided by update_pins.py --list-npm-tools so CI never
# re-implements generator logic.
run: |
set -euo pipefail
python3 update_pins.py --audit
# Capture the tool list into a variable BEFORE looping: a process
# substitution `done < <(cmd)` does NOT propagate cmd's non-zero exit
# under `set -e`, so a fail-closed --list-npm-tools (e.g. a missing pin)
# would silently yield an empty loop and skip every signature check
# (fail-open). `$(...)` assignment, by contrast, DOES abort the step
# under `set -e`, so the here-string consumer below is fail-closed.
tools=$(python3 update_pins.py --list-npm-tools)
while IFS=$'\t' read -r name pkg env var ver; do
[ -n "$ver" ] || { echo "::error::no pinned version for $name"; exit 1; }
scratch=$(mktemp -d)
( cd "$scratch" && npm init -y >/dev/null \
&& npm install --ignore-scripts --no-audit --no-fund --silent "${pkg}@${ver}" \
&& npm audit signatures )
done <<< "$tools"
- name: Markdown lint (advisory)
uses: DavidAnson/markdownlint-cli2-action@6bf21b07787794f89a243495939cd651942aeabe # v24.1.0
continue-on-error: true
with:
globs: "**/*.md"
- name: Broken relative links (advisory, offline)
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
continue-on-error: true
with:
args: --offline --include-fragments --no-progress .
docker-build:
name: Docker build (validate, no push)
needs: [validate]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Build (amd64, no push)
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
# Load into the local daemon so the smoke test below can run the
# image. Build is amd64-only (single-arch load is fine); arm64
# coverage relies on the maintainers' local builds for now.
load: true
tags: opencode:ci
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=min
- name: Smoke test — opencode --version matches Dockerfile pin
# Catches the failure mode where the JS launcher is installed
# but the native-binary postinstall isn't run, leaving `opencode` as a
# stub that errors at exec time. Asserts the actual reported version
# matches the OPENCODE_VERSION pin in pins/opencode.env.
run: |
set -euo pipefail
EXPECTED=$(python3 update_pins.py --list-npm-tools | awk -F'\t' '$1=="opencode"{print $5}')
ACTUAL=$(docker run --rm opencode:ci opencode --version)
echo "Expected: ${EXPECTED} Actual: ${ACTUAL}"
[[ "$ACTUAL" == "${EXPECTED}" ]]
- name: Cross-platform smoke (Linux)
# Drives smoke.sh across a matrix of cells, all reusing the already-loaded
# opencode:ci image from the build step above. Do NOT move this to a
# separate job — a separate job gets a blank local daemon and cannot reuse
# the image loaded by the build step.
#
# Cell inventory:
# 1. runner-uid/cold/no-optins — baseline privilege-drop to runner UID
# 2. runner-uid/cold/aws — AWS credential opt-in
# 3. runner-uid/cold/glab — GitLab credential opt-in
# 4. runner-uid/cold/aws+glab+tfe — all three opt-ins combined
# 5. runner-uid/warm — warm volume (HOST_UID-owned /root traversal)
# 6. uid-501/cold/no-optins — macOS-style UID (<1000, below Ubuntu floor)
# 7. uid-0/cold/no-optins — root-legacy path (HOST_UID=0 → exec directly)
# 8. runner-uid/ephemeral — --ephemeral mode (no named volumes)
# 9. runner-uid/ro/no-optins — read-only workspace (EROFS robustness)
run: |
set -euo pipefail
export IMAGE=opencode:ci
RUNNER_UID=$(id -u)
run_cell() {
echo "--- Cell: $* ---"
IMAGE=opencode:ci bash smoke/smoke.sh "$@"
}
# 1. Baseline: runner UID, cold volume, no opt-ins.
run_cell --uid="${RUNNER_UID}" --volstate=cold --ephemeral=0
# 2. AWS opt-in.
run_cell --uid="${RUNNER_UID}" --optins=aws --volstate=cold --ephemeral=0
# 3. GitLab opt-in.
run_cell --uid="${RUNNER_UID}" --optins=glab --volstate=cold --ephemeral=0
# 4. All three opt-ins combined.
run_cell --uid="${RUNNER_UID}" --optins=aws,glab,tfe --volstate=cold --ephemeral=0
# 5. Warm volume: first run populates the named volume, second run
# traverses the now HOST_UID-owned 0700 /root (exercises CAP_DAC_READ_SEARCH).
run_cell --uid="${RUNNER_UID}" --volstate=warm --ephemeral=0
# 6. macOS-style UID 501 (below Ubuntu's default UID_MIN=1000).
run_cell --uid=501 --volstate=cold --ephemeral=0
# 7. Root-legacy path: HOST_UID=0 → entrypoint skips useradd+runuser and
# exec's claude directly as container root.
run_cell --uid=0 --volstate=cold --ephemeral=0
# 8. Ephemeral mode: no named volumes.
run_cell --uid="${RUNNER_UID}" --ephemeral=1
# 9. Read-only workspace: EROFS robustness — chown on :ro mounts must not
# abort the entrypoint; assert no spurious WARN on stderr.
run_cell --uid="${RUNNER_UID}" --volstate=cold --ro=1 --ephemeral=0
# 10. Non-runner GID: exercises the entrypoint's `useradd -g $HOST_GID`
# path with a GID that differs from the runner's (asserts the
# dropped process lands in exactly that primary group).
run_cell --uid=501 --gid=501 --volstate=cold --ephemeral=0