ci: restore Forgejo build+publish + gitleaks workflows (infra-clean)
The OSS public-scrub removed .forgejo/workflows/ wholesale — it only needed to parameterize the two infra literals in the publish step. Restored both workflows; the publish target (root@host) and its SSH host-key now come from Forgejo repo secrets (STRAW_PUBLISH_TARGET / STRAW_LUCY_HOSTKEY) instead of literals, so the public workflow carries zero infra topology. Everything else was already clean: signing keystore + deploy key are secrets, the actual publish is a forced-command on the host (not in this YAML), and the runner label / build image / signer fingerprint are non-sensitive. Restores push-triggered CI: build assembleDebug in the straw-build image, verify signer bb9ca96b, publish to fdroid. This push also builds vc=91.
This commit is contained in:
parent
e4a8751942
commit
e34fbd457b
2 changed files with 178 additions and 0 deletions
138
.forgejo/workflows/build.yml
Normal file
138
.forgejo/workflows/build.yml
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# 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 is ROOTLESS (#444): the signed APK is handed to a forced-command
|
||||
# script on the Lucy host (key STRAW_FDROID_LUCY_KEY, pinned to that one script
|
||||
# via restrict,command=). The host does the fdroid index re-sign + the Rackham
|
||||
# rsync — the signing keystore never enters this CI container, and this job no
|
||||
# longer needs the host docker socket. The host script re-verifies the APK
|
||||
# signer before publishing.
|
||||
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 (rootless, no docker socket): hand the signed APK to the
|
||||
# Lucy host forced-command. The host re-verifies the signer, re-signs
|
||||
# the fdroid index (keystore stays on Lucy), and rsyncs to Rackham. ----
|
||||
- name: Publish to fdroid via Lucy host forced-command
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
LUCY_KEY: ${{ secrets.STRAW_FDROID_LUCY_KEY }}
|
||||
# Publish target + its host-key are Forgejo secrets, NOT literals, so
|
||||
# this public workflow carries no infra topology — only references.
|
||||
PUBLISH_TARGET: ${{ secrets.STRAW_PUBLISH_TARGET }}
|
||||
LUCY_HOSTKEY: ${{ secrets.STRAW_LUCY_HOSTKEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
install -d -m700 "$HOME/.ssh"
|
||||
printf '%s\n' "$LUCY_KEY" > "$HOME/.ssh/id_lucy"
|
||||
chmod 600 "$HOME/.ssh/id_lucy"
|
||||
# Pin the publish host key (no TOFU) — from a secret, not a literal.
|
||||
printf '%s\n' "$LUCY_HOSTKEY" > "$HOME/.ssh/known_hosts_lucy"
|
||||
# Hand off the signed APK on stdin. Host exit codes: 0 = published,
|
||||
# 3 = already published (benign no-op on a same-commit re-run),
|
||||
# anything else = real failure.
|
||||
set +e
|
||||
ssh -i "$HOME/.ssh/id_lucy" -o IdentitiesOnly=yes -o BatchMode=yes \
|
||||
-o StrictHostKeyChecking=yes -o UserKnownHostsFile="$HOME/.ssh/known_hosts_lucy" \
|
||||
"$PUBLISH_TARGET" "publish ${STRAW_APK}" < "$GITHUB_WORKSPACE/${STRAW_APK}"
|
||||
rc=$?
|
||||
set -e
|
||||
case "$rc" in
|
||||
0) echo "Published vc=${STRAW_VC} to fdroid.sulkta.com (rootless — no docker socket)";;
|
||||
3) echo "vc=${STRAW_VC} already published — nothing to do";;
|
||||
*) echo "::error::fdroid publish failed (rc=$rc)"; exit "$rc";;
|
||||
esac
|
||||
40
.forgejo/workflows/gitleaks.yml
Normal file
40
.forgejo/workflows/gitleaks.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# .forgejo/workflows/gitleaks.yml
|
||||
#
|
||||
# Sulkta canonical gitleaks workflow. Drop a copy into every public repo at
|
||||
# `.forgejo/workflows/gitleaks.yml` after the Forgejo act_runner is registered
|
||||
# (task #295).
|
||||
#
|
||||
# Pairs with the pre-receive hook installed on every bare repo — that one is
|
||||
# the strict enforcement layer (rejects the push); this one provides the
|
||||
# per-PR red ✗ that branch-protection rules can require before merge.
|
||||
#
|
||||
# Layer 1 (this workflow): visible per-PR status, can be a required check.
|
||||
# Layer 2 (pre-receive hook): strict enforcement at the server.
|
||||
# Layer 3 (johnny5 cron sweep): nightly full-history sweep across all repos.
|
||||
|
||||
name: gitleaks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Full history — gitleaks needs depth to scan a commit range.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: install gitleaks
|
||||
run: |
|
||||
curl -sSL -o gl.tar.gz \
|
||||
https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz
|
||||
tar xzf gl.tar.gz gitleaks
|
||||
chmod +x gitleaks
|
||||
./gitleaks version
|
||||
|
||||
- name: scan
|
||||
run: |
|
||||
./gitleaks detect --source . --no-banner --redact --verbose
|
||||
Loading…
Add table
Add a link
Reference in a new issue