-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathContainerfile.tools
More file actions
316 lines (287 loc) · 16.1 KB
/
Copy pathContainerfile.tools
File metadata and controls
316 lines (287 loc) · 16.1 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
# Base pinned by digest for reproducibility (the `trixie` tag is rolling).
# Refresh: docker buildx imagetools inspect debian:trixie --format '{{.Manifest.Digest}}'
FROM --platform=linux/amd64 debian:trixie@sha256:fe7312b5f05bf5f43fad76bcd8945642e4e47a68aefd1b73f447615899d0fac1
ENV DEBIAN_FRONTEND=noninteractive
ARG USERNAME="bull"
ENV USER=$USERNAME
# IMPORTANT: these ARG defaults are the single source of truth ONLY for the dev
# container, which builds this file passing just USERNAME. `make container-tools`
# overrides FLUTTER_VERSION (from .fvmrc) and the ANDROID_* args (from
# android/gradle.properties); the FVM CI install pins FVM separately. Keep these
# defaults in sync with .fvmrc / android/gradle.properties so the dev container
# and the reproducible release build never diverge on toolchain versions.
ARG FVM_VERSION="4.1.1"
ARG FLUTTER_VERSION="3.44.2"
ARG ANDROID_CMDLINE_TOOLS_VERSION="14742923"
# Rust is pinned explicitly via RUST_VERSION. RUSTUP_TOOLCHAIN env (set below)
# forces cargo/rustc invocations that respect it through this version. It does
# NOT cover cargokit or bdk_dart's native-assets build hook, both of which
# invoke `rustup run <toolchain>` directly — an explicit `rustup run` always
# wins over the env var. Those are covered separately below by a rustup shim
# that rewrites the "stable" token to this pinned version, and by pre-installing
# bdk_dart's own pinned channel (BDK_RUST_VERSION). This is essential for
# reproducibility — upstream stable drifts every 6 weeks.
ARG RUST_VERSION="1.95.0"
# Android versions (passed via --build-arg from Makefile, defaults as fallback)
ARG JVM_TARGET="21"
ARG ANDROID_API_LEVEL="36"
ARG ANDROID_BUILD_TOOLS="36.0.0"
ARG ANDROID_NDK="29.0.14206865"
ENV ANDROID_HOME=/opt/android-sdk
ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools
# Common base tools (shared across all target platforms)
RUN apt-get update && apt-get install -y \
sudo \
ca-certificates \
curl \
git \
unzip \
xz-utils \
zip \
wget \
make \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Android build deps: JDK (needed by sdkmanager and Gradle)
RUN apt-get update && apt-get install -y \
openjdk-${JVM_TARGET}-jdk-headless \
&& rm -rf /var/lib/apt/lists/*
# Linux desktop Flutter build deps:
# clang, lld, cmake, ninja, pkg-config — Flutter Linux toolchain
# libgtk-3-dev, liblzma-dev, libstdc++-12-dev — Flutter Linux runtime libs
# libsecret-1-dev — required by flutter_secure_storage_linux plugin
# libsecret-tools — provides `secret-tool`, used by init-keyring.sh to bootstrap
# a default keyring on first run
# libcurl4-openssl-dev — required by sentry-native (sentry_flutter) HTTP transport
# dbus, gnome-keyring — Secret Service provider for libsecret at runtime
# (bootstrapped by .devcontainer/init-keyring.sh on container start)
# xdg-desktop-portal, xdg-desktop-portal-gtk — D-Bus FileChooser portal used
# by the file_picker plugin's saveFile on Linux (CSV transaction / BIP329
# label export). Without the portal frontend + a backend (gtk), saveFile
# finds no org.freedesktop.portal.FileChooser service and silently returns
# null, so nothing is written.
# fonts-noto-* — Indic/etc. glyph coverage so non-Latin locales render
# (Flutter Linux falls back to system fonts via fontconfig; without these
# scripts like Devanagari/Bengali can't render. cjk is for Chinese/Japanese/Korean
# glyphs shipped separately from core)
RUN apt-get update && apt-get install -y \
clang \
lld \
cmake \
ninja-build \
pkg-config \
libgtk-3-dev \
liblzma-dev \
libstdc++-12-dev \
libsecret-1-dev \
libsecret-tools \
libcurl4-openssl-dev \
dbus \
gnome-keyring \
xdg-desktop-portal \
xdg-desktop-portal-gtk \
libsqlite3-dev \
fonts-noto-core \
fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*
# Workaround for CVE-2024-32020 hardlink check failing on overlayfs:
# git >= 2.45.1 verifies inode equality after hardlinking during local clone;
# overlayfs returns different inodes so the check aborts. Pub does many local
# clones from its bare cache, so we force --no-hardlinks for `git clone`.
RUN mv /usr/bin/git /usr/bin/git.real && \
printf '#!/bin/sh\nif [ "$1" = "clone" ]; then shift; exec /usr/bin/git.real clone --no-hardlinks "$@"; fi\nexec /usr/bin/git.real "$@"\n' > /usr/bin/git && \
chmod +x /usr/bin/git
# Create user
RUN adduser --disabled-password --gecos '' $USER
RUN adduser $USER sudo
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER $USER
# Install Rust (pinned via RUST_VERSION arg).
# rustup-init is fetched from the immutable archive path and checked against
# the SHA256 rustup publishes next to it, rather than piping sh.rustup.rs into
# a shell: everything downstream — the FFI crypto in bull_sdk, payjoin and tor
# — is built by whatever this step installs, so it has to be tamper-evident.
# The base image is pinned to linux/amd64, so a single x86_64 digest is right.
# Refresh: curl -fsSL https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/x86_64-unknown-linux-gnu/rustup-init.sha256
ARG RUSTUP_VERSION="1.29.0"
ARG RUSTUP_INIT_SHA256="4acc9acc76d5079515b46346a485974457b5a79893cfb01112423c89aeb5aa10"
RUN curl --proto '=https' --tlsv1.2 -fsSL \
https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/x86_64-unknown-linux-gnu/rustup-init \
-o /tmp/rustup-init \
&& echo "${RUSTUP_INIT_SHA256} /tmp/rustup-init" | sha256sum -c - \
&& chmod +x /tmp/rustup-init \
&& /tmp/rustup-init -y --no-modify-path --default-toolchain ${RUST_VERSION} \
&& rm /tmp/rustup-init
ENV PATH="/home/$USER/.cargo/bin:${PATH}"
# Pre-install clippy and rustfmt for the pinned toolchain.
RUN rustup component add clippy rustfmt
# Force every cargo/rustc invocation through the pinned RUST_VERSION,
# regardless of what plugins/build scripts request via +channel or
# rust-toolchain.toml. Blocks rustup auto-install of other toolchains.
ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
ENV RUSTUP_AUTO_INSTALL=0
# Use the system git binary for cargo's git dependency fetches instead of the
# built-in libgit2. libgit2's TLS backend intermittently fails on some git
# hosts with `SSL error: unknown error; class=Ssl (16)`; the system git (built
# against OpenSSL, with proper CA certs) handles these reliably. cargokit pulls
# bull_sdk's Rust git deps (bbqr-rust, ark-wallet-dart) this way.
ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
# Add Android Rust targets
RUN rustup target add aarch64-linux-android
RUN rustup target add armv7-linux-androideabi
RUN rustup target add x86_64-linux-android
RUN rustup target add i686-linux-android
RUN rustc --version && cargo --version
# cargokit's hard-coded `rustup run stable` bypass (which would otherwise build
# with floating upstream stable instead of RUST_VERSION) is handled by the
# rustup shim installed near the end of this file — it rewrites the "stable"
# argument to RUST_VERSION. It is NOT handled here by `rustup toolchain link
# stable`, because rustup >=1.28 rejects linking a reserved channel name. See
# that shim's comment for the full rationale.
# bdk_dart builds via native_toolchain_rust (a Dart native-assets build hook,
# not cargokit) and reads its own rust-toolchain.toml, invoking
# `rustup run <channel> cargo build` directly — same bypass as cargokit above,
# but with its own pinned channel. Pre-install it (and its Android targets) so
# the build never fetches a toolchain from the network. Keep in sync with the
# `channel` value AND `components` in bdk-dart's native/rust-toolchain.toml
# (bdk_dart is a transitive git dependency pulled in via bull_sdk — see its
# resolved-ref in pubspec.lock) — the components are pre-added
# here so rustup does not silently fetch them from the network at build time
# when native_toolchain_rust honours that toolchain file.
# Default here is a fallback; the makefile passes --build-arg BDK_RUST_VERSION
# from its own BDK_RUST_VERSION variable (the single source of truth, also used
# by `make verify-rustc-pins`). Keep the two in sync if you edit either.
ARG BDK_RUST_VERSION="1.85.1"
RUN rustup toolchain install ${BDK_RUST_VERSION}
RUN rustup component add --toolchain ${BDK_RUST_VERSION} clippy rustfmt
RUN rustup target add --toolchain ${BDK_RUST_VERSION} aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
# Belt-and-suspenders verification: abort if the installed Rust does not match
# EXPECTED_RUST_VERSION when provided.
ARG EXPECTED_RUST_VERSION=""
RUN if [ -n "$EXPECTED_RUST_VERSION" ]; then \
INSTALLED=$(rustc --version | awk '{print $2}'); \
if [ "$INSTALLED" != "$EXPECTED_RUST_VERSION" ]; then \
echo "ERROR: Expected Rust $EXPECTED_RUST_VERSION but installed is $INSTALLED"; \
echo "Pass --build-arg RUST_VERSION=$EXPECTED_RUST_VERSION to align"; \
exit 1; \
fi; \
fi
# Install FVM from its pinned release tarball rather than piping fvm.app's
# install.sh into bash: that script is itself unversioned, so only the FVM
# version was pinned and never the installer fetching it. The layout below is
# what install.sh produces — it copies the contents of the archive's fvm/
# directory into ${INSTALL_BASE}/bin, which is the directory added to PATH.
# Refresh: sha256sum of the linux-x64 asset on the FVM release for this version.
ARG FVM_SHA256="2d7fdfc3e77591806efe38fab23098a79567a89a9570de713c43f30cc54348d7"
RUN curl -fsSL \
https://github.com/leoafarias/fvm/releases/download/${FVM_VERSION}/fvm-${FVM_VERSION}-linux-x64.tar.gz \
-o /tmp/fvm.tar.gz \
&& echo "${FVM_SHA256} /tmp/fvm.tar.gz" | sha256sum -c - \
&& mkdir -p /home/$USER/fvm/bin /tmp/fvm-extract \
&& tar -xzf /tmp/fvm.tar.gz -C /tmp/fvm-extract \
&& cp -a /tmp/fvm-extract/fvm/. /home/$USER/fvm/bin/ \
&& chmod +x /home/$USER/fvm/bin/fvm \
&& rm -rf /tmp/fvm.tar.gz /tmp/fvm-extract
ENV PATH="/home/$USER/fvm/bin:${PATH}"
# Install Flutter via FVM.
# Run the Flutter tool in non-interactive "bot" mode for the whole build: its
# bot_detector treats the `CI` env var as CI, so it skips the analytics prompt,
# animated spinners and any stdin waits that can hang a non-TTY `podman build`.
# GitHub Actions sets this automatically; a local/container build does not.
ENV CI=true
ENV FVM_USE_GIT_CACHE=false
RUN fvm install ${FLUTTER_VERSION} --verbose --no-setup
RUN fvm global ${FLUTTER_VERSION}
ENV PATH="/home/$USER/fvm/default/bin:${PATH}"
# Download Android cmdline-tools
# (USER root instead of sudo: setuid binaries are unreliable under rootless
# podman user namespaces — sudo fails with "account validation failure" —
# and are unnecessary at build time since USER switching achieves the same.)
USER root
# The zip is versioned but Google serves it from a _latest.zip URL, so verify
# the digest: this archive provides sdkmanager, which installs the rest of the
# Android SDK used to build the release APK.
# Refresh: sha256sum of the downloaded zip for this ANDROID_CMDLINE_TOOLS_VERSION.
ARG ANDROID_CMDLINE_TOOLS_SHA256="04453066b540409d975c676d781da1477479dde3761310f1a7eb92a1dfb15af7"
RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip -O /tmp/android-cmdline-tools.zip \
&& echo "${ANDROID_CMDLINE_TOOLS_SHA256} /tmp/android-cmdline-tools.zip" | sha256sum -c -
# Set up Android SDK
RUN mkdir -p ${ANDROID_HOME}/cmdline-tools
RUN unzip -q /tmp/android-cmdline-tools.zip -d ${ANDROID_HOME}/cmdline-tools
RUN mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest
RUN rm /tmp/android-cmdline-tools.zip
RUN chown -R $USER ${ANDROID_HOME}
USER $USER
# Install Android SDK components
RUN yes | sdkmanager --sdk_root=${ANDROID_HOME} --licenses
RUN sdkmanager --sdk_root=${ANDROID_HOME} "platform-tools"
RUN sdkmanager --sdk_root=${ANDROID_HOME} "platforms;android-${ANDROID_API_LEVEL}"
RUN sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;${ANDROID_BUILD_TOOLS}"
RUN sdkmanager --sdk_root=${ANDROID_HOME} "ndk;${ANDROID_NDK}"
# AGP resolves an NDK/CMake version per-subproject: flutter_zxing pins NDK
# 27.0.12077973 for its vendored C++ build, and any plugin without an explicit
# ndkVersion falls back to the Flutter Gradle plugin's default (28.2.13676358
# for this Flutter version), which also needs CMake 3.22.1. Without these
# pre-installed, AGP silently fetches ~2GB from dl.google.com mid-build —
# content-pinned so it doesn't change output bytes, but it makes the build
# depend on Google continuing to serve them and adds an unpinned network step.
# Keep these two NDK versions in sync with `android.ndkVersion` overrides in
# the plugin dependency tree and the Flutter Gradle plugin's default per
# .fvmrc; keep CMake in sync with whatever that NDK/AGP combination requests.
RUN sdkmanager --sdk_root=${ANDROID_HOME} "ndk;27.0.12077973"
RUN sdkmanager --sdk_root=${ANDROID_HOME} "ndk;28.2.13676358"
RUN sdkmanager --sdk_root=${ANDROID_HOME} "cmake;3.22.1"
# Pre-cache Flutter engine artifacts for the platforms we build for.
RUN fvm flutter precache --android --linux
# Disable telemetry
RUN fvm flutter config --no-analytics
RUN fvm dart --disable-analytics
# Wrap rustup with a thin shim that does two things before delegating to the
# real binary.
#
# (1) Version pin. cargokit (payjoin_flutter, tor, bull_sdk's rust_builder)
# hard-codes `rustup run stable cargo build` and `rustup toolchain install
# stable` — its toolchain option is a fixed stable/beta/nightly enum, so it
# cannot name a version. An explicit `rustup run stable` outranks both
# RUSTUP_TOOLCHAIN and any rust-toolchain.toml (rustup's documented override
# precedence), so without intervention cargokit silently builds with whatever
# upstream stable is current on build day — that is how shipped .so files ended
# up embedding rustc newer than RUST_VERSION. rustup >=1.28 forbids `rustup
# toolchain link stable` (reserved channel name), so we cannot alias "stable"
# to the pinned toolchain directory; instead the shim rewrites the argument
# "stable" -> RUST_VERSION before calling the real rustup. bdk_dart's explicit
# `rustup run 1.85.1` has no "stable" token and passes through untouched. The
# match is exact-equality per argument, so substrings like "stableish" and
# args with spaces are preserved.
#
# (2) flock. Multiple Rust plugins build in parallel via cargokit and
# concurrent rustup calls race in ~/.rustup/downloads/ — one process renames
# the .partial file while another is still looking for it, causing ENOENT. The
# shim makes every rustup call acquire an exclusive flock first.
#
# The shim MUST be #!/bin/bash, not #!/bin/sh. cargokit injects per-target
# cross-compiler env vars whose names contain dashes (e.g.
# CC_aarch64-linux-android, AR_aarch64-linux-android, CXX_aarch64-linux-android).
# POSIX sh (dash) discards environment variables with non-identifier names at
# startup, so a /bin/sh wrapper would strip these before exec-ing cargo. cc-rs
# then finds no CC and falls back to the legacy GCC-style "aarch64-linux-android-clang"
# tool name, which NDK r23+ does not ship — failing the Android build with
# 'failed to find tool "aarch64-linux-android-clang"'. bash preserves
# dash-named env vars across exec, so the compiler env reaches cargo intact.
RUN mv /home/$USER/.cargo/bin/rustup /home/$USER/.cargo/bin/rustup.real && \
printf '#!/bin/bash\nargs=()\nfor a in "$@"; do [ "$a" = stable ] && a=%s; args+=("$a"); done\nexec flock /tmp/rustup.lock /home/%s/.cargo/bin/rustup.real "${args[@]}"\n' "$RUST_VERSION" "$USER" > /home/$USER/.cargo/bin/rustup && \
chmod +x /home/$USER/.cargo/bin/rustup
# Persistent bash history (backed by a named volume mounted at ~/.history).
# Written via printf rather than a RUN heredoc — heredocs require BuildKit,
# and podman's classic imagebuilder parses each heredoc line as a separate
# Containerfile instruction (fails on the first `export` line).
RUN printf '%s\n' \
'' \
'# Persist bash history across container restarts/rebuilds via mounted volume' \
'export HISTFILE="$HOME/.history/bash_history"' \
'export HISTSIZE=-1' \
'export HISTFILESIZE=-1' \
'export HISTCONTROL=ignoredups' \
'shopt -s histappend' \
'export PROMPT_COMMAND="history -a; ${PROMPT_COMMAND:-}"' \
>> /home/$USER/.bashrc