# .gitea/workflows/ci.yml — Wisp CI/CD (Gitea Actions, GitHub-Actions syntax) # --------------------------------------------------------------------------- # Runner model: a single self-hosted act_runner ON the briggen.dev server, with # the host Docker socket mounted into job containers (see /root/act_runner/ # config.yaml). Because the runner is co-located with the host Docker daemon + # the Traefik `proxy` network, deploy needs NO ssh keys and NO image registry — # `docker compose` simply builds and (re)creates the `wisp` container on the host. # # Triggers: # push to master -> test, then deploy-web, then build-apk # pull_request -> test only # # Required repo secrets (Settings -> Actions -> Secrets) — used by the build-apk # job: ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, # ANDROID_KEY_PASSWORD. # --------------------------------------------------------------------------- name: CI on: push: branches: [master] tags: ['v*'] pull_request: concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: # ---- test: typecheck + unit tests ---------------------------------------- # Runs on the default runner image (has Node, which actions/checkout needs); # Bun is installed in a step rather than via a Node-less bun container. test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Bun run: | curl -fsSL https://bun.sh/install | bash echo "$HOME/.bun/bin" >> "$GITHUB_PATH" - run: bun install --frozen-lockfile - run: bun run typecheck - run: bun run test # ---- deploy-web: build + (re)start the nginx web container on the host ---- # Runs on the server runner; the mounted host Docker socket means `docker # compose` builds the image and recreates the `wisp` service in place, joined # to the existing Traefik `proxy` network. No SSH, no registry. deploy-web: needs: test if: github.event_name == 'push' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build + (re)start wisp on the host run: docker compose -f deploy/wisp.compose.yml --project-directory . up -d --build --remove-orphans - name: Health check run: | for i in $(seq 1 10); do if docker exec wisp wget -qO- http://localhost:8080/healthz | grep -q ok; then echo "healthy"; exit 0 fi sleep 2 done echo "wisp container did not become healthy" >&2; exit 1 # ---- build-apk: signed release APK, served at /wisp.apk ------------------ # Socket-safe: the whole Android build runs inside `docker buildx build` # (context streamed to the host BuildKit — no host bind-mounts), signed via # BuildKit --secret from the ANDROID_* repo secrets. We then extract the APK # with docker create/cp and copy it into the running `wisp` web container so # it is downloadable at https://wisp.briggen.dev/wisp.apk. build-apk: # Auto-build the APK on every push to master (arm64-only keeps it ~minutes). # Runs after deploy-web so the web updates promptly, then the APK follows and # is published to /srv/wisp/wisp.apk (served at /wisp.apk via the compose mount). needs: [test, deploy-web] if: github.event_name == 'push' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest timeout-minutes: 120 steps: - uses: actions/checkout@v4 - name: Stage signing secrets as files run: | umask 077 mkdir -p "$RUNNER_TEMP/sec" printf '%s' "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" > "$RUNNER_TEMP/sec/ks_b64" printf '%s' "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" > "$RUNNER_TEMP/sec/ks_pass" printf '%s' "${{ secrets.ANDROID_KEY_ALIAS }}" > "$RUNNER_TEMP/sec/ks_alias" printf '%s' "${{ secrets.ANDROID_KEY_PASSWORD }}" > "$RUNNER_TEMP/sec/ks_keypass" - name: Build signed APK image run: | docker buildx build \ --secret id=ks_b64,src="$RUNNER_TEMP/sec/ks_b64" \ --secret id=ks_pass,src="$RUNNER_TEMP/sec/ks_pass" \ --secret id=ks_alias,src="$RUNNER_TEMP/sec/ks_alias" \ --secret id=ks_keypass,src="$RUNNER_TEMP/sec/ks_keypass" \ -f docker/android.Dockerfile -t wisp-android:ci --load . - name: Extract APK + publish to the host (served at /wisp.apk via the compose volume) run: | cid=$(docker create wisp-android:ci) docker cp "$cid:/workspace/android/app/build/outputs/apk/release/app-release.apk" ./app-release.apk docker rm "$cid" ls -lh app-release.apk # Stream the APK into a host-mounted dir so it survives web container # recreation (the wisp container mounts /srv/wisp/wisp.apk read-only). docker run --rm -i -v /srv/wisp:/out alpine:3 sh -c 'mkdir -p /out && cat > /out/wisp.apk' < ./app-release.apk - name: Cleanup staged secrets if: always() run: rm -rf "$RUNNER_TEMP/sec" - name: Upload APK artifact uses: actions/upload-artifact@v3 continue-on-error: true with: name: wisp-release-apk path: app-release.apk