straw/.forgejo/workflows/build.yml
Cobb 457166e3b0
All checks were successful
build-apk / build-and-publish (push) Successful in 7m29s
gitleaks / scan (push) Successful in 42s
vc=88: deferred-hygiene sweep (audit #2 leftovers, no behavior change)
M-2: route every SharedPreferences write (Settings/History/Subs/Resume) through
one PrefsWriter — a per-store single-thread dispatcher — so the on-disk apply()
order matches the in-memory CAS order. Previously a Main-thread toggle and an
IO-thread import could land apply() out of order, and ResumePositions detached
ordering entirely via a fresh globalScope.launch per write; a stale value could
then win the next cold-start load. Each write reads the live StateFlow so disk
converges to the latest in-memory state regardless of enqueue order.

L-14: Settings storage-usage sampling (File.length() x4 + Coil diskCache.size)
moved off the composition/Main thread into a LaunchedEffect on Dispatchers.IO.

L-2 / L-4..L-8 / L-15 / L-16: dead code + stale comments from the vc=85 SB/RYD
to Rust migration. Http.kt trimmed to STRAW_USER_AGENT; reconciled the
network_security_config / feed.rs / SubscriptionFeedViewModel / net.rs / CI
comments with reality; recencyScore overflow-guarded; ci/Dockerfile now
pre-installs build-tools 36 (AGP 9.2.1's actual floor, was auto-fetched).

Verified: headless compileDebugKotlin green on the straw-build image.
2026-06-21 20:03:45 -07:00

132 lines
6.6 KiB
YAML

# 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
# Run every step with bash, not the runner's default dash — the steps use
# `set -euo pipefail` (pipefail is a bashism; dash errors "Illegal option").
# The straw-build image ships /usr/bin/bash.
defaults:
run:
shell: bash
container:
image: git.sulkta.com/sulkta-infra/straw-build:latest
steps:
# We clone with plain git instead of actions/checkout@v4: that action is
# a Node action, and the straw-build job container ships the Android +
# Rust toolchain but NOT node — so checkout@v4 dies with
# `exec: "node": not found`. git is in the image, both repos are public,
# and a shell clone also sidesteps the runner's flaky data.forgejo.org
# action fetch. strawcore must be a SIBLING of straw because
# rust/strawcore depends on it via `path = "../../../strawcore"`.
- name: Checkout straw + strawcore (sibling, no JS actions)
run: |
set -euo pipefail
git clone https://git.sulkta.com/Sulkta-OSS/straw.git straw
git -C straw checkout --detach "${{ github.sha }}"
git clone --depth 1 https://git.sulkta.com/Sulkta-OSS/strawcore.git strawcore
echo "straw: $(git -C straw rev-parse --short HEAD)"
echo "strawcore: $(git -C strawcore rev-parse --short HEAD)"
- 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).
# apksigner lives under build-tools/<ver>/. AGP (compileSdk 36) needs
# build-tools 36; older straw-build images pre-installed only 34.0.0 and
# AGP auto-fetched 36 at build time, so this sort -V | tail -1 resolves to
# 36.0.0's apksigner. (ci/Dockerfile now pre-installs 36 too — see there.)
# apksigner is a shell wrapper that needs `java` on PATH; the image
# sets JAVA_HOME but doesn't put its bin on PATH for run steps (gradle
# uses JAVA_HOME directly, so the build itself is fine).
export PATH="$JAVA_HOME/bin:$PATH"
APKSIGNER=$(ls "$ANDROID_HOME"/build-tools/*/apksigner | sort -V | tail -1)
FP=$("$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"