ci: Forgejo build workflow — per-repo straw-build image, gated auto-publish
Build the Straw APK in CI from a dedicated, ephemeral build container (git.sulkta.com/sulkta-infra/straw-build — Android SDK/NDK + Rust + cargo-ndk, see ci/Dockerfile) instead of the persistent crafting-table. The runner spins the container up per job and tears it down after. On push to main (after the build passes + the signer fingerprint is verified against the canonical key) it publishes to fdroid.sulkta.com: APK into the Lucy repo + index re-sign via the host docker socket, then the signed repo streamed to Rackham web168 over a scoped forced-command deploy key. Keystore + deploy key are Forgejo repo secrets. Build steps run under `ionice -c3 nice` so they can't I/O-starve the live DBs on Lucy.
This commit is contained in:
parent
845a8b9cc7
commit
5e89056f62
2 changed files with 178 additions and 0 deletions
115
.forgejo/workflows/build.yml
Normal file
115
.forgejo/workflows/build.yml
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# Straw APK — build on Forgejo Actions, and (gated on a green build) publish to
|
||||
# fdroid.sulkta.com so the in-app updater picks it up.
|
||||
#
|
||||
# Runs in the dedicated git.sulkta.com/sulkta-infra/straw-build image (Android
|
||||
# SDK/NDK + Rust + cargo-ndk, see ci/Dockerfile). The signing key comes from
|
||||
# the STRAW_SIGNING_KEYSTORE_B64 secret (the same androiddebugkey the whole
|
||||
# series is signed with — vaulted as "Sulkta — Straw fdroid signing keystore").
|
||||
#
|
||||
# Publish path mirrors scripts/publish-fdroid.sh: drop the APK into the Lucy
|
||||
# fdroid repo + re-sign the index (both via the host docker socket, keystore
|
||||
# never leaves Lucy), then stream the signed repo to Rackham web168 over a
|
||||
# scoped, forced-command deploy key (STRAW_FDROID_RACKHAM_KEY).
|
||||
name: build-apk
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'strawApp/**'
|
||||
- 'rust/**'
|
||||
- 'buildSrc/**'
|
||||
- 'gradle/**'
|
||||
- 'gradlew'
|
||||
- '*.gradle.kts'
|
||||
- 'gradle.properties'
|
||||
- 'ci/Dockerfile'
|
||||
- '.forgejo/workflows/build.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: lucy-rust
|
||||
container:
|
||||
image: git.sulkta.com/sulkta-infra/straw-build:latest
|
||||
steps:
|
||||
- name: Checkout straw
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: straw
|
||||
|
||||
# strawcore is consumed by rust/strawcore via `path = "../../../strawcore"`,
|
||||
# i.e. a sibling of the straw checkout — so it MUST live next to it.
|
||||
- name: Checkout strawcore (sibling)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Sulkta-OSS/strawcore
|
||||
ref: main
|
||||
path: strawcore
|
||||
|
||||
- name: Decode signing keystore
|
||||
env:
|
||||
KS_B64: ${{ secrets.STRAW_SIGNING_KEYSTORE_B64 }}
|
||||
run: echo "$KS_B64" | base64 -d > "$GITHUB_WORKSPACE/straw.keystore"
|
||||
|
||||
- name: Assemble debug APK
|
||||
working-directory: straw
|
||||
env:
|
||||
STRAW_KEYSTORE_FILE: ${{ github.workspace }}/straw.keystore
|
||||
STRAW_KEYSTORE_PASS: android
|
||||
STRAW_KEY_ALIAS: androiddebugkey
|
||||
STRAW_KEY_PASS: android
|
||||
# Keep the 4-ABI cross-compile off the container rootfs.
|
||||
CARGO_TARGET_DIR: ${{ github.workspace }}/cargo-target
|
||||
# ionice idle class + low nice so the build yields disk/CPU to the
|
||||
# live DBs on Lucy — a 5GB build I/O-starved the MAS Postgres
|
||||
# checkpoint and dropped Matrix for ~2min on 2026-06-19.
|
||||
run: ionice -c3 nice -n 19 ./gradlew :strawApp:assembleDebug --no-daemon --stacktrace
|
||||
|
||||
- name: Verify signer + stage APK
|
||||
working-directory: straw
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VC=$(grep STRAW_VERSION_CODE buildSrc/src/main/kotlin/ProjectConfig.kt | grep -o '[0-9]\+')
|
||||
APK=$(ls strawApp/build/outputs/apk/debug/*.apk | head -1)
|
||||
NAME="com.sulkta.straw.debug_${VC}.apk"
|
||||
cp "$APK" "$GITHUB_WORKSPACE/$NAME"
|
||||
echo "Built vc=$VC -> $NAME"
|
||||
# The whole series is signed with SHA-1 bb9ca96b...; fail loudly if a
|
||||
# build ever produces a different signer (would break in-place updates).
|
||||
FP=$("$ANDROID_HOME/build-tools/34.0.0/apksigner" verify --print-certs "$APK" | grep -i 'SHA-1' | grep -o '[0-9a-f]\{40\}')
|
||||
echo "signer SHA-1: $FP"
|
||||
if [ "$FP" != "bb9ca96b10ebbc1ac48e037a21f350415d18915f" ]; then
|
||||
echo "::error::APK signer $FP != canonical key — refusing to publish"; exit 1
|
||||
fi
|
||||
echo "STRAW_VC=$VC" >> "$GITHUB_ENV"
|
||||
echo "STRAW_APK=$NAME" >> "$GITHUB_ENV"
|
||||
|
||||
# ---- Publish: only on a real push to main, AFTER the build above passed ----
|
||||
- name: Publish to fdroid (Lucy repo + index re-sign, via host docker socket)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
FDROID=git.sulkta.com/sulkta-infra/fdroid-sulkta:latest
|
||||
# Stream the APK into the host-mounted fdroid repo (helper runs as the
|
||||
# fdroid uid so ownership matches).
|
||||
docker run --rm -i -u 1000:1000 -v /mnt/cache/appdata/fdroid-sulkta/repo:/repo \
|
||||
--entrypoint sh "$FDROID" -c "cat > /repo/${STRAW_APK}" < "$GITHUB_WORKSPACE/${STRAW_APK}"
|
||||
# Re-sign the index (the index keystore lives only on Lucy).
|
||||
docker run --rm -u 1000:1000 -v /mnt/cache/appdata/fdroid-sulkta:/repo "$FDROID" update
|
||||
|
||||
- name: Publish to Rackham (rsync signed repo to web168)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
RACKHAM_KEY: ${{ secrets.STRAW_FDROID_RACKHAM_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p ~/.ssh && echo "$RACKHAM_KEY" > ~/.ssh/id_deploy && chmod 600 ~/.ssh/id_deploy
|
||||
ssh-keyscan -H 142.44.213.229 >> ~/.ssh/known_hosts 2>/dev/null
|
||||
FDROID=git.sulkta.com/sulkta-infra/fdroid-sulkta:latest
|
||||
# tar the signed repo out of the host-mounted volume and stream it to
|
||||
# the forced-command deploy key on Rackham (which untars + sudo-rsyncs).
|
||||
docker run --rm -u 1000:1000 -v /mnt/cache/appdata/fdroid-sulkta:/src \
|
||||
--entrypoint sh "$FDROID" -c 'tar c -C /src repo' \
|
||||
| ssh -i ~/.ssh/id_deploy kayos@142.44.213.229
|
||||
echo "Published vc=${STRAW_VC} to fdroid.sulkta.com"
|
||||
63
ci/Dockerfile
Normal file
63
ci/Dockerfile
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Sulkta straw-build — reproducible Android + Rust build image for the Straw APK.
|
||||
#
|
||||
# Pushed to git.sulkta.com/sulkta-infra/straw-build:latest and used as the job
|
||||
# `container:` in .forgejo/workflows/build.yml. It bakes the toolchain that
|
||||
# otherwise lives only in bind-mounts on the long-running crafting-table, so a
|
||||
# FRESH Forgejo CI job container is fully self-contained (no host /caches
|
||||
# dependency, no per-machine signing key).
|
||||
#
|
||||
# Toolchain pinned to exactly what builds vc=72 successfully:
|
||||
# JDK 21 · NDK 27.2.12479018 · build-tools 34.0.0 · platforms android-36
|
||||
# Rust stable + 4 Android targets · cargo-ndk · clang/libclang (rquickjs bindgen)
|
||||
FROM eclipse-temurin:21-jdk-jammy
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
ANDROID_SDK_ROOT=/opt/android-sdk \
|
||||
ANDROID_HOME=/opt/android-sdk \
|
||||
ANDROID_NDK_HOME=/opt/android-sdk/ndk/27.2.12479018 \
|
||||
CARGO_HOME=/opt/cargo \
|
||||
RUSTUP_HOME=/opt/rustup \
|
||||
PATH=/opt/cargo/bin:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:/usr/bin:/bin
|
||||
|
||||
# Base OS deps: clang/libclang for rquickjs bindgen, a C toolchain for the
|
||||
# QuickJS C sources, unzip for the SDK zips, git+ca-certs for checkout.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git curl unzip ca-certificates clang libclang-dev build-essential pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Android cmdline-tools + the SDK packages the build needs.
|
||||
ARG CMDLINE_TOOLS_ZIP=commandlinetools-linux-11076708_latest.zip
|
||||
RUN mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" \
|
||||
&& curl -fsSL -o /tmp/cmdtools.zip "https://dl.google.com/android/repository/${CMDLINE_TOOLS_ZIP}" \
|
||||
&& unzip -q /tmp/cmdtools.zip -d "$ANDROID_SDK_ROOT/cmdline-tools" \
|
||||
&& mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest" \
|
||||
&& rm /tmp/cmdtools.zip \
|
||||
&& yes | sdkmanager --licenses >/dev/null 2>&1 \
|
||||
&& sdkmanager --install \
|
||||
"platform-tools" \
|
||||
"platforms;android-36" \
|
||||
"build-tools;34.0.0" \
|
||||
"ndk;27.2.12479018" >/dev/null \
|
||||
&& rm -rf "$ANDROID_SDK_ROOT/.temp" /tmp/*
|
||||
|
||||
# Rust toolchain + the four Android targets + cargo-ndk.
|
||||
RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal \
|
||||
&& rustup target add \
|
||||
aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android \
|
||||
&& cargo install cargo-ndk --locked \
|
||||
&& rm -rf /opt/cargo/registry/cache /opt/cargo/registry/src
|
||||
|
||||
# Sanity: fail the image build early if anything's missing.
|
||||
RUN java -version && cargo --version && cargo ndk --version || true \
|
||||
&& test -d "$ANDROID_NDK_HOME" && test -d "$ANDROID_SDK_ROOT/build-tools/34.0.0"
|
||||
|
||||
# Publish tooling (appended last so the heavy toolchain layers stay cached):
|
||||
# docker CLI to talk to the runner's host socket for the fdroid steps, and
|
||||
# openssh-client to stream the signed repo to Rackham. The build steps don't
|
||||
# touch the socket; only the gated publish step does.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
docker.io openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# The signing keystore is NOT baked — it's injected per-build from the Forgejo
|
||||
# secret STRAW_SIGNING_KEYSTORE_B64 → STRAW_KEYSTORE_FILE.
|
||||
Loading…
Add table
Add a link
Reference in a new issue