forked from kdlbs/kandev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile.universal
More file actions
220 lines (208 loc) · 10.4 KB
/
Copy pathDockerfile.universal
File metadata and controls
220 lines (208 loc) · 10.4 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
# Kandev Universal Dockerfile
#
# Extends the vanilla kandev image with language toolchains and developer
# tools so agents can build/test code in the most common stacks without
# extra setup. Built and published alongside vanilla in the release
# workflow. Inclusion policy: see docs/images.md.
#
# Build:
# docker build -f Dockerfile.universal -t kandev:latest-universal .
#
# Run (drop-in replacement for the vanilla image):
# docker run -p 38429:38429 -v kandev-data:/data ghcr.io/kdlbs/kandev:universal
#
# The BASE_IMAGE arg lets the release workflow point at a freshly built
# vanilla image inside the same run (so universal-of-X.Y.Z is built on top
# of vanilla-X.Y.Z, not on whatever :latest happens to be).
ARG BASE_IMAGE=ghcr.io/kdlbs/kandev:latest
FROM ${BASE_IMAGE}
USER root
# Pinned versions. Bump these deliberately; CI's size gate will catch growth.
#
# The Playwright Chromium apt deps below are hand-derived against Playwright
# 1.48.0. When that target moves, the apt-get install line may need to
# add/drop packages - check the upstream list at
# https://github.com/microsoft/playwright/blob/v<version>/packages/playwright-core/src/server/registry/nativeDeps.ts
# or run `npx playwright@<version> install-deps --dry-run` to diff. (Tracked
# only via this comment, not an ARG, since the apt package names are hardcoded
# below and a build-arg override would silently disagree with what's installed.)
ARG GO_VERSION=1.26.3
ARG GOLANGCI_LINT_VERSION=1.62.0
ARG PNPM_VERSION=9.15.9
# Single RUN to minimize image layers and clean apt lists in the same layer.
# Tools grouped by purpose; comments explain the why for each cluster so
# future maintainers can prune deliberately rather than guess.
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
# Tarball fetcher for the toolchain install steps below (Go,
# golangci-lint, rustup, yq). Not provided by the vanilla base image.
curl \
# Native compile (CGO, pip wheels with C extensions, native npm modules)
build-essential \
pkg-config \
python3-dev \
libssl-dev \
# Useful CLI tools agents reach for. Small, broadly applicable.
ripgrep \
fd-find \
jq \
# Playwright Chromium runtime deps. Browsers themselves are NOT
# preinstalled - users run `pnpm exec playwright install chromium`
# once and the download persists at ~/.cache/ms-playwright on the PV.
# This list mirrors `npx playwright@1.48.0 install-deps chromium`.
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libpango-1.0-0 \
libcairo2 \
libasound2 \
fonts-liberation \
fonts-noto-color-emoji \
; \
rm -rf /var/lib/apt/lists/*
# Go toolchain - tarball install to /usr/local/go (the standard layout the
# go.dev installer creates). Add to PATH below.
#
# Integrity: go.dev's `?mode=json&include=all` endpoint lists every released
# version with a per-file `sha256` field. We fetch that index, pick the SHA
# for our tarball, and verify before extraction. (The `.sha256` sidecar URLs
# that look like they should work redirect to the HTML downloads page, so
# they cannot be used here.) This is TOFU on first fetch; if go.dev itself
# is compromised both the JSON and the tarball are bad.
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
amd64) goarch=amd64 ;; \
arm64) goarch=arm64 ;; \
*) echo "unsupported arch: $arch" >&2; exit 1 ;; \
esac; \
tarball="go${GO_VERSION}.linux-${goarch}.tar.gz"; \
expected="$(curl -fsSL 'https://go.dev/dl/?mode=json&include=all' \
| jq -r --arg v "go${GO_VERSION}" --arg f "${tarball}" \
'first(.[] | select(.version == $v) | .files[] | select(.filename == $f) | .sha256)')"; \
if [ -z "$expected" ]; then \
echo "no upstream sha256 for ${tarball} at version ${GO_VERSION}" >&2; \
exit 1; \
fi; \
curl -fsSL "https://go.dev/dl/${tarball}" -o /tmp/go.tar.gz; \
echo "${expected} /tmp/go.tar.gz" | sha256sum -c -; \
tar -C /usr/local -xzf /tmp/go.tar.gz; \
rm /tmp/go.tar.gz
# golangci-lint - prebuilt binary, no compilation needed.
# Integrity: each release publishes a single `*-checksums.txt` file with
# `<sha256> <filename>` lines for every artifact. Grep for our tarball,
# pipe to `sha256sum -c -`. Download into /tmp and cd there so the relative
# filename in the checksum line resolves.
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
amd64) lintarch=amd64 ;; \
arm64) lintarch=arm64 ;; \
*) echo "unsupported arch: $arch" >&2; exit 1 ;; \
esac; \
tarball="golangci-lint-${GOLANGCI_LINT_VERSION}-linux-${lintarch}.tar.gz"; \
base="https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_LINT_VERSION}"; \
curl -fsSL "${base}/${tarball}" -o "/tmp/${tarball}"; \
curl -fsSL "${base}/golangci-lint-${GOLANGCI_LINT_VERSION}-checksums.txt" -o /tmp/golangci-lint-checksums.txt; \
(cd /tmp && grep " ${tarball}$" golangci-lint-checksums.txt | sha256sum -c -); \
tar -xz -C /tmp -f "/tmp/${tarball}"; \
mv "/tmp/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-${lintarch}/golangci-lint" /usr/local/bin/; \
rm -rf "/tmp/${tarball}" "/tmp/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-${lintarch}" /tmp/golangci-lint-checksums.txt
# Rust toolchain via rustup. RUSTUP_HOME/CARGO_HOME live in /usr/local so the
# install is system-wide and read-only for non-root users at runtime -
# matching the rest of the image. Users who need writable per-user state
# (e.g. `cargo install <tool>`, `rustup toolchain install nightly`) should
# override CARGO_HOME and RUSTUP_HOME to a path on the PV, e.g.:
# CARGO_HOME=~/.cargo RUSTUP_HOME=~/.rustup cargo install <tool>
#
# `--profile minimal` keeps the install lean (rustc + cargo + rust-std); we
# then explicitly add clippy and rustfmt, which agents working on Rust
# projects almost always reach for. `--profile default` would also work but
# pulls in rust-docs which we don't need.
#
# Integrity: download rustup-init for our arch and verify its SHA-256
# against the upstream .sha256 sidecar before executing - matches the
# pattern used for golangci-lint/yq above, rather than `curl | sh`.
# (Go uses the JSON release index instead - go.dev doesn't actually
# publish per-tarball .sha256 sidecars despite the URL pattern looking
# like it would work.)
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
amd64) triplet=x86_64-unknown-linux-gnu ;; \
arm64) triplet=aarch64-unknown-linux-gnu ;; \
*) echo "unsupported arch: $arch" >&2; exit 1 ;; \
esac; \
base="https://static.rust-lang.org/rustup/dist/${triplet}"; \
curl -fsSL "${base}/rustup-init" -o /tmp/rustup-init; \
curl -fsSL "${base}/rustup-init.sha256" -o /tmp/rustup-init.sha256; \
(cd /tmp && sha256sum -c rustup-init.sha256); \
chmod +x /tmp/rustup-init; \
/tmp/rustup-init -y --default-toolchain stable --no-modify-path --profile minimal; \
/usr/local/cargo/bin/rustup component add --toolchain stable clippy rustfmt; \
rm /tmp/rustup-init /tmp/rustup-init.sha256; \
chmod -R a+rX "$RUSTUP_HOME" "$CARGO_HOME"
# pnpm via npm - install system-wide so users don't need to bootstrap it
# themselves. Drops shims into /usr/local/bin.
#
# No separate sha256 step: npm downloads from registry.npmjs.org and verifies
# the tarball against the registry's `integrity` field (SHA-512) before
# installing. The other tools above pin upstream `.sha256` sidecars because
# they're served from CDNs that don't expose package integrity at the API level;
# npm does. Decision is deliberate, not an oversight.
RUN set -eux; \
npm install -g --prefix /usr/local "pnpm@${PNPM_VERSION}"; \
pnpm --version
# fd is installed as `fdfind` on Debian to avoid clashing with the legacy
# fd package - symlink so agents calling plain `fd` still work.
RUN ln -s /usr/bin/fdfind /usr/local/bin/fd
# yq is not in Debian stable; install the prebuilt binary from upstream
# (Mike Farah's yq, the Go implementation - the one most scripts assume).
#
# Integrity: mikefarah/yq publishes a `checksums` file with one row per
# artifact and multiple hash columns, plus a `checksums_hashes_order` file
# that lists which hash algorithm corresponds to each column. We compute the
# SHA-256 column index dynamically (defensive against upstream reordering)
# and verify the binary against that column before installing.
ARG YQ_VERSION=4.44.3
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
target="yq_linux_${arch}"; \
base="https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}"; \
curl -fsSL "${base}/checksums" -o /tmp/yq-checksums; \
curl -fsSL "${base}/checksums_hashes_order" -o /tmp/yq-hashes-order; \
sha256_col="$(awk '/^SHA-256$/ {print NR + 1; exit}' /tmp/yq-hashes-order)"; \
if [ -z "$sha256_col" ]; then echo "SHA-256 not in upstream checksums_hashes_order" >&2; exit 1; fi; \
expected="$(awk -v t="$target" -v c="$sha256_col" '$1==t {print $c}' /tmp/yq-checksums)"; \
if [ -z "$expected" ]; then echo "no checksum row for $target" >&2; exit 1; fi; \
curl -fsSL "${base}/${target}" -o /tmp/yq; \
echo "${expected} /tmp/yq" | sha256sum -c -; \
mv /tmp/yq /usr/local/bin/yq; \
chmod +x /usr/local/bin/yq; \
rm /tmp/yq-checksums /tmp/yq-hashes-order
# Append Go's toolchain bin and cargo's bin to whatever PATH the base image
# set. Appending rather than hardcoding the full list keeps the agent CLI
# dir (/data/.npm-global/bin) first - as the base image already arranges -
# and stays correct if the base image's PATH ever changes.
#
# Note: $GOPATH/bin (defaults to ~/go/bin, i.e. /data/home/go/bin in this
# image) is NOT added here - it's user-specific and lives on the PV. Users
# who `go install` tools and want them on PATH should add it to their shell
# profile (e.g. `export PATH=$PATH:$(go env GOPATH)/bin`).
ENV PATH=$PATH:/usr/local/go/bin:/usr/local/cargo/bin
# Drop back to the unprivileged user. Matches the vanilla image's runtime
# user; the docker-entrypoint.sh in the base image handles root → kandev
# privilege drop via gosu for users who run the container as root.
USER kandev