halkyon/.forgejo/workflows/ci.yaml
Tizian 「ティツィアーン」 39de2c3faa
Some checks failed
CI / check (push) Failing after 25m23s
CI / deploy (push) Has been cancelled
ci: deploy a green main again
The hackathon's judging is over, so learn.halkyon.eu follows main once
more. Pushed together with build 21's release, as released apps up to
build 20 can't sign in against the new server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-22 18:11:14 +02:00

164 lines
8 KiB
YAML

# CI: every push of a branch runs the checks of the pre-push hook (tool/check.dart, every app's and
# shared package's); a push to main is then deployed to learn.halkyon.eu (deploy/redeploy.sh).
# Both jobs run on the runner on learn.halkyon.eu's server, directly on the host as an
# unprivileged user (deploy/runner/). A commit that passed the checks before isn't checked again
# (see "Checked before?").
#
# Pushes only, no pull requests: a pull request from a fork would run its own code on
# the production server's runner.
name: CI
on:
push:
# Branches only, not tags: a release pushes main and then its tag, both on the same
# commit, and the tag's run would check that commit a second time while main's still
# runs ("Checked before?" knows only runs that finished). A tag deploys nothing.
branches: ['**']
# Runs the workflow again from the web UI, e.g. to retry a failed deploy.
workflow_dispatch:
jobs:
check:
runs-on: sahaquiel
timeout-minutes: 45
env:
# The Flutter the app is developed with; move it together with the local one.
FLUTTER_VERSION: 3.47.5
# The Serverpod CLI, for the generated-code check: the servers' Serverpod version
# (every package pins it); move it together with them.
SERVERPOD_VERSION: 4.0.4
steps:
# Before anything else runs, the job proves it can neither see nor reach production
# (the runner's sandbox, deploy/runner/). If it can, nothing else should run.
- name: Sandbox
run: |
fail() { echo "The runner's sandbox leaks: $*" >&2; exit 1; }
# Only the runner's own processes are visible. A container's process shows up
# (in a process namespace of its own) if it runs under the runner's user ID,
# and then its environment and files are readable too.
[ "$(ps -e -o uid= | sort -u | wc -l)" -eq 1 ] || fail "other users' processes are visible"
namespaces=$(for p in /proc/[0-9]*; do readlink "$p/ns/pid"; done 2>/dev/null | sort -u | wc -l)
[ "$namespaces" -le 1 ] || fail "processes of $namespaces process namespaces are visible"
# deploy's home (.env, the checkout, the dumps) and the Docker socket are out of
# reach.
[ ! -e /home/deploy ] || fail "/home/deploy is visible"
{ [ ! -r /run/docker.sock ] && [ ! -w /run/docker.sock ]; } || fail "the Docker socket is usable"
# Docker's networks, where the database listens, are unreachable. Caddy listens on
# every address of the host, the Docker bridge's included.
if timeout 5 bash -c 'exec 3<>/dev/tcp/172.17.0.1/443' 2>/dev/null; then
fail "Docker's network is reachable"
fi
echo "This job can neither see nor reach production."
# A commit that passed this job before isn't checked again: a fast-forward merge
# brings the commit a branch's push checked to main unchanged, and only its deploy
# is new. The marks live in the runner's home; only pushes run this workflow, so
# only those who may push can leave one. Every later step of this job asks.
- name: Checked before?
run: |
if [ -e "$HOME/checked/${{ github.sha }}" ]; then
echo "${{ github.sha }} passed these checks before; they aren't run again."
echo 'CHECKED_BEFORE=true' >> "$FORGEJO_ENV"
fi
- name: Check out
if: env.CHECKED_BEFORE != 'true'
env:
TOKEN: ${{ github.token }}
run: |
git init -q .
# The job's token as a header from the environment, so it never shows in a
# command line; plain git, so the host needs no Node.js for actions/checkout.
export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=http.extraHeader \
GIT_CONFIG_VALUE_0="Authorization: Basic $(printf 'ci:%s' "$TOKEN" | base64 -w0)"
git fetch -q --depth 1 "${{ github.server_url }}/${{ github.repository }}.git" "${{ github.sha }}"
git checkout -q -f FETCH_HEAD
git clean -q -ffdx
- name: Flutter
if: env.CHECKED_BEFORE != 'true'
run: |
sdk="$HOME/sdk/flutter-$FLUTTER_VERSION"
if [ ! -x "$sdk/bin/flutter" ]; then
rm -rf "$sdk"
git clone -q --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$sdk"
fi
# No usage reports to Google, every time: a cancelled job may have left an SDK
# behind before switching them off.
"$sdk/bin/flutter" config --no-analytics >/dev/null
"$sdk/bin/dart" --disable-analytics >/dev/null
echo "$sdk/bin" >> "$FORGEJO_PATH"
"$sdk/bin/flutter" --version
# Exactly the committed pubspec.lock, or a failure here with pub's own message:
# CI never resolves something else on its own.
#
# The second try works around pub's https://github.com/dart-lang/pub/issues/4912
# (Dart v3.13): when the runner's cached copy of a git dependency's repository (the
# editor fork, pure_zstd) lacks the locked commit, the first run resolves that
# package without its dependencies and fails, but fetches the commit, so the
# second goes through. Drop it once the Dart of FLUTTER_VERSION has pub's fix
# (ebba0dc, pull request 4914).
- name: Dependencies
if: env.CHECKED_BEFORE != 'true'
run: flutter pub get --enforce-lockfile || flutter pub get --enforce-lockfile
# The generated code (each server's lib/src/generated, its client package) is
# committed, so it has to be exactly what the generator makes of the models and
# endpoints: a model changed without regenerating fails here. Every server package,
# the apps' and the shared module, through Melos' `generate` script.
- name: Generated code
if: env.CHECKED_BEFORE != 'true'
run: |
bin="${XDG_STATE_HOME:-$HOME/.local/state}/Dart/install/bin"
if ! "$bin/serverpod" --no-analytics version 2>/dev/null | grep -qx "Serverpod version: $SERVERPOD_VERSION"; then
dart install "serverpod_cli@$SERVERPOD_VERSION"
fi
PATH="$bin:$PATH" dart run melos run generate
if [ -n "$(git status --porcelain)" ]; then
git status --short
echo 'The generated code differs from what the generator makes now: run `serverpod generate` and commit.' >&2
exit 1
fi
# The test run mode's passwords, which passwords.yaml holds on a developer's machine
# and which is never committed. Without them the server tests stop without a word.
# Throwaway and new for every job: they only guard the test database the job creates
# in its own checkout.
- name: Test passwords
if: env.CHECKED_BEFORE != 'true'
run: |
for name in SERVERPOD_DATABASE_PASSWORD \
SERVERPOD_PASSWORD_serverSideSessionKeyHashPepper \
SERVERPOD_PASSWORD_emailSignInPepper \
SERVERPOD_PASSWORD_totpKey \
SERVERPOD_PASSWORD_recoveryCodeKey \
SERVERPOD_PASSWORD_signInDecoyKey \
SERVERPOD_PASSWORD_serviceSecret; do
echo "$name=$(openssl rand -hex 32)" >> "$FORGEJO_ENV"
done
- name: Checks
if: env.CHECKED_BEFORE != 'true'
run: dart run tool/check.dart
# Reached only when every step above passed. Marks older than 90 days go.
- name: Remember the commit
if: env.CHECKED_BEFORE != 'true'
run: |
mkdir -p "$HOME/checked"
touch "$HOME/checked/${{ github.sha }}"
find "$HOME/checked" -type f -mtime +90 -delete
deploy:
needs: check
if: github.ref == 'refs/heads/main'
runs-on: sahaquiel
timeout-minutes: 30
steps:
# The key can run nothing but deploy/redeploy.sh, and only from that server itself.
- name: Deploy to learn.halkyon.eu
run: >-
ssh -i "$HOME/.ssh/deploy_trigger" -o IdentitiesOnly=yes -o BatchMode=yes
-o StrictHostKeyChecking=yes -o UserKnownHostsFile="$HOME/.ssh/known_hosts"
deploy@127.0.0.1 "${{ github.sha }}"