When the remote Dagger engine (stunnel/port 8774) is unreachable, Dagger falls back to a local engine which requires a Docker daemon. The job container does not have /var/run/docker.sock mounted, so the fallback was failing with "connect: no such file or directory". Add a docker:27-dind service to the CI job and set DOCKER_HOST=tcp://docker:2375 so Dagger can start a local engine when the remote engine is unavailable. Also guard the Firebase and Play Store steps in deploy.yml so they are skipped gracefully when the relevant secrets are not configured. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
260 lines
11 KiB
YAML
260 lines
11 KiB
YAML
name: Deploy
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 * * * *' # every hour on the hour
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
test-android-firebase:
|
|
name: Android Instrumented Tests (Firebase Test Lab)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Check runner tools
|
|
run: |
|
|
command -v dagger >/dev/null 2>&1 || { echo "ERROR: dagger is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
command -v task >/dev/null 2>&1 || { echo "ERROR: task is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
dpkg -s stunnel4 netcat-openbsd >/dev/null 2>&1 || { echo "ERROR: stunnel4/netcat-openbsd are not installed in the runner image. Add them to .forgejo/Dockerfile."; exit 1; }
|
|
|
|
- name: Setup Dagger Remote Engine (via stunnel)
|
|
env:
|
|
DAGGER_STUNNEL_URL: ${{ secrets.DAGGER_STUNNEL_URL }}
|
|
DAGGER_CA_CERT: ${{ secrets.DAGGER_CA_CERT }}
|
|
DAGGER_CLIENT_CERT: ${{ secrets.DAGGER_CLIENT_CERT }}
|
|
DAGGER_CLIENT_KEY: ${{ secrets.DAGGER_CLIENT_KEY }}
|
|
run: scripts/setup_dagger_remote.sh
|
|
|
|
- name: Run Android Tests on Firebase Test Lab
|
|
if: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY != '' }}
|
|
env:
|
|
FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_TEST_LAB_SERVICE_ACCOUNT_KEY }}
|
|
FIREBASE_PROJECT_ID: ${{ vars.FIREBASE_PROJECT_ID }}
|
|
DAGGER_NO_NAG: "1"
|
|
run: task test-android-firebase
|
|
|
|
- name: Cleanup TLS credentials
|
|
if: always()
|
|
run: rm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pid
|
|
|
|
deploy-playstore:
|
|
name: Build & Deploy to Play Store
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Check runner tools
|
|
run: |
|
|
command -v dagger >/dev/null 2>&1 || { echo "ERROR: dagger is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
command -v task >/dev/null 2>&1 || { echo "ERROR: task is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
dpkg -s stunnel4 netcat-openbsd >/dev/null 2>&1 || { echo "ERROR: stunnel4/netcat-openbsd are not installed in the runner image. Add them to .forgejo/Dockerfile."; exit 1; }
|
|
|
|
- name: Setup Dagger Remote Engine (via stunnel)
|
|
env:
|
|
DAGGER_STUNNEL_URL: ${{ secrets.DAGGER_STUNNEL_URL }}
|
|
DAGGER_CA_CERT: ${{ secrets.DAGGER_CA_CERT }}
|
|
DAGGER_CLIENT_CERT: ${{ secrets.DAGGER_CLIENT_CERT }}
|
|
DAGGER_CLIENT_KEY: ${{ secrets.DAGGER_CLIENT_KEY }}
|
|
run: scripts/setup_dagger_remote.sh
|
|
|
|
- name: Publish Android to Play Store
|
|
if: ${{ secrets.PLAY_STORE_CONFIG_JSON != '' }}
|
|
env:
|
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }}
|
|
DAGGER_NO_NAG: "1"
|
|
run: task publish-android
|
|
|
|
- name: Cleanup TLS credentials
|
|
if: always()
|
|
run: rm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pid
|
|
|
|
deploy-apk:
|
|
name: Build & Deploy APK to Server
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Check runner tools
|
|
run: |
|
|
command -v dagger >/dev/null 2>&1 || { echo "ERROR: dagger is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
command -v task >/dev/null 2>&1 || { echo "ERROR: task is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
dpkg -s stunnel4 netcat-openbsd >/dev/null 2>&1 || { echo "ERROR: stunnel4/netcat-openbsd are not installed in the runner image. Add them to .forgejo/Dockerfile."; exit 1; }
|
|
|
|
- name: Setup Dagger Remote Engine (via stunnel)
|
|
env:
|
|
DAGGER_STUNNEL_URL: ${{ secrets.DAGGER_STUNNEL_URL }}
|
|
DAGGER_CA_CERT: ${{ secrets.DAGGER_CA_CERT }}
|
|
DAGGER_CLIENT_CERT: ${{ secrets.DAGGER_CLIENT_CERT }}
|
|
DAGGER_CLIENT_KEY: ${{ secrets.DAGGER_CLIENT_KEY }}
|
|
run: scripts/setup_dagger_remote.sh
|
|
|
|
- name: Build & Deploy APK to server
|
|
if: ${{ secrets.SSH_PRIVATE_KEY != '' }}
|
|
env:
|
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
SSH_USER: ${{ secrets.SSH_USER }}
|
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
DAGGER_NO_NAG: "1"
|
|
run: task deploy-apk
|
|
|
|
- name: Cleanup TLS credentials
|
|
if: always()
|
|
run: rm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pid
|
|
|
|
build-linux:
|
|
name: Build Linux Release
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Check runner tools
|
|
run: |
|
|
command -v dagger >/dev/null 2>&1 || { echo "ERROR: dagger is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
command -v task >/dev/null 2>&1 || { echo "ERROR: task is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
dpkg -s stunnel4 netcat-openbsd >/dev/null 2>&1 || { echo "ERROR: stunnel4/netcat-openbsd are not installed in the runner image. Add them to .forgejo/Dockerfile."; exit 1; }
|
|
|
|
- name: Setup Dagger Remote Engine (via stunnel)
|
|
env:
|
|
DAGGER_STUNNEL_URL: ${{ secrets.DAGGER_STUNNEL_URL }}
|
|
DAGGER_CA_CERT: ${{ secrets.DAGGER_CA_CERT }}
|
|
DAGGER_CLIENT_CERT: ${{ secrets.DAGGER_CLIENT_CERT }}
|
|
DAGGER_CLIENT_KEY: ${{ secrets.DAGGER_CLIENT_KEY }}
|
|
run: scripts/setup_dagger_remote.sh
|
|
|
|
- name: Build & Deploy Linux to server
|
|
if: ${{ secrets.SSH_PRIVATE_KEY != '' }}
|
|
env:
|
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
SSH_USER: ${{ secrets.SSH_USER }}
|
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
|
DAGGER_NO_NAG: "1"
|
|
run: task deploy-linux
|
|
|
|
- name: Cleanup TLS credentials
|
|
if: always()
|
|
run: rm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pid
|
|
|
|
publish-website:
|
|
name: Publish Website Build History
|
|
runs-on: ubuntu-latest
|
|
needs: [build-linux, deploy-playstore, deploy-apk]
|
|
if: |
|
|
always() &&
|
|
(needs.build-linux.result == 'success' || needs.deploy-playstore.result == 'success' || needs.deploy-apk.result == 'success')
|
|
timeout-minutes: 60
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Check runner tools
|
|
run: |
|
|
command -v dagger >/dev/null 2>&1 || { echo "ERROR: dagger is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
command -v task >/dev/null 2>&1 || { echo "ERROR: task is not installed in the runner image. Add it to .forgejo/Dockerfile."; exit 1; }
|
|
dpkg -s stunnel4 netcat-openbsd >/dev/null 2>&1 || { echo "ERROR: stunnel4/netcat-openbsd are not installed in the runner image. Add them to .forgejo/Dockerfile."; exit 1; }
|
|
|
|
- name: Setup Dagger Remote Engine (via stunnel)
|
|
env:
|
|
DAGGER_STUNNEL_URL: ${{ secrets.DAGGER_STUNNEL_URL }}
|
|
DAGGER_CA_CERT: ${{ secrets.DAGGER_CA_CERT }}
|
|
DAGGER_CLIENT_CERT: ${{ secrets.DAGGER_CLIENT_CERT }}
|
|
DAGGER_CLIENT_KEY: ${{ secrets.DAGGER_CLIENT_KEY }}
|
|
run: scripts/setup_dagger_remote.sh
|
|
|
|
- name: Generate build history and deploy website
|
|
if: ${{ secrets.SSH_PRIVATE_KEY != '' }}
|
|
env:
|
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
SSH_USER: ${{ secrets.SSH_USER }}
|
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
|
DAGGER_NO_NAG: "1"
|
|
run: task publish-website
|
|
|
|
- name: Cleanup TLS credentials
|
|
if: always()
|
|
run: rm -rf /tmp/dagger-tls /tmp/stunnel-dagger.conf /tmp/stunnel.pid
|
|
|
|
label-deploy-health:
|
|
name: Update Deploy Health Label
|
|
runs-on: ubuntu-latest
|
|
needs: [test-android-firebase, deploy-playstore, deploy-apk, build-linux]
|
|
if: always() && vars.DEPLOY_HEALTH_ISSUE != ''
|
|
timeout-minutes: 5
|
|
|
|
steps:
|
|
- name: Set CI/Full-Pass or CI/Full-Fail label on tracking issue
|
|
env:
|
|
FORGEJO_TOKEN: ${{ github.token }}
|
|
FORGEJO_URL: ${{ github.server_url }}
|
|
DEPLOY_HEALTH_ISSUE: ${{ vars.DEPLOY_HEALTH_ISSUE }}
|
|
ALL_SUCCEEDED: ${{ needs.test-android-firebase.result == 'success' && needs.deploy-playstore.result == 'success' && needs.deploy-apk.result == 'success' && needs.build-linux.result == 'success' }}
|
|
run: |
|
|
python3 - << 'PYEOF'
|
|
import os, json, urllib.request, urllib.error
|
|
|
|
issue = os.environ.get("DEPLOY_HEALTH_ISSUE", "").strip()
|
|
if not issue:
|
|
print("DEPLOY_HEALTH_ISSUE not set; skipping")
|
|
raise SystemExit(0)
|
|
|
|
token = os.environ["FORGEJO_TOKEN"]
|
|
url_base = os.environ["FORGEJO_URL"].rstrip("/")
|
|
succeeded = os.environ.get("ALL_SUCCEEDED", "false").lower() == "true"
|
|
add_label = "CI/Full-Pass" if succeeded else "CI/Full-Fail"
|
|
remove_label = "CI/Full-Fail" if succeeded else "CI/Full-Pass"
|
|
|
|
headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
|
|
api = f"{url_base}/api/v1/repos/guettli/sharedinbox"
|
|
|
|
def api_get(path):
|
|
req = urllib.request.Request(f"{api}{path}", headers=headers)
|
|
with urllib.request.urlopen(req) as r:
|
|
return json.loads(r.read())
|
|
|
|
def api_put(path, body):
|
|
data = json.dumps(body).encode()
|
|
req = urllib.request.Request(f"{api}{path}", data=data, headers=headers, method="PUT")
|
|
try:
|
|
with urllib.request.urlopen(req) as r:
|
|
return json.loads(r.read())
|
|
except urllib.error.HTTPError as e:
|
|
print(f"PUT {path} failed: {e.read().decode()}")
|
|
raise
|
|
|
|
repo_labels = api_get("/labels")
|
|
label_map = {l["name"]: l["id"] for l in repo_labels}
|
|
|
|
if add_label not in label_map:
|
|
print(f"Label '{add_label}' not found in repo — create it first")
|
|
raise SystemExit(1)
|
|
|
|
current = api_get(f"/issues/{issue}/labels")
|
|
keep_ids = [l["id"] for l in current if l["name"] not in ("CI/Full-Pass", "CI/Full-Fail")]
|
|
keep_ids.append(label_map[add_label])
|
|
|
|
api_put(f"/issues/{issue}/labels", {"labels": keep_ids})
|
|
print(f"Set '{add_label}' on issue #{issue}")
|
|
PYEOF
|