name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # scripts/install_service.sh installs both of these for the host and # this file never did, so ./scripts/e2e.sh has failed on every commit # for as long as it has existed: tool/e2e_fixtures.py builds its JPEG # and MP4 fixtures with ffmpeg, and signal_bot/media_strip.py strips # metadata with exiftool. Verified rather than trusted, because an # apt exit code is not the same claim as "the binary resolves". # Every binary tool/e2e_fixtures.py shells out to, which is the whole # list rather than the first one that failed: ffmpeg builds the JPEG # and MP4 fixtures, heif-enc the HEIC one, and exiftool is what # signal_bot/media_strip.py strips metadata with. scripts/e2e.sh has # needed all three since it was written and this file installed none of # them, which is why CI has been red on every commit. # # Verified by resolving each binary, not by trusting apt's exit code -- # the same distinction install_service.sh makes for the host. - name: Install the tools the fixtures are built with run: | sudo apt-get update sudo apt-get install -y ffmpeg libimage-exiftool-perl \ libheif-examples libheif-plugin-x265 for tool in ffmpeg exiftool heif-enc; do command -v "$tool" || { echo "::error::$tool did not resolve"; exit 1; } done # Resolving heif-enc is not the same claim as being able to encode: # Ubuntu splits the HEVC encoder into its own plugin, so the binary # is there and answers "No HEVC encoder available". One real encode # fails here, naming the problem, instead of inside a fixture builder. ffmpeg -loglevel error -f lavfi -i color=c=red:s=32x32 -frames:v 1 -y /tmp/probe.png heif-enc -q 50 /tmp/probe.png -o /tmp/probe.heic test -s /tmp/probe.heic || { echo "::error::heif-enc produced nothing"; exit 1; } - name: Install uv uses: astral-sh/setup-uv@v5 - name: Set up Python run: uv python install 3.14 - name: Install dependencies run: | uv venv .venv uv pip install --python .venv/bin/python -e '.[dev]' # Mirrors the no-noqa pre-commit hook. A suppression must not be able # to reach main just because someone committed without hooks installed. - name: block suppressions run: | if grep -rnEi '#\s*(noqa|type:\s*ignore|ruff:\s*noqa)' \ --include='*.py' signal_bot fake_signal tests; then echo '::error::suppressions are not allowed; fix the finding or ask first' exit 1 fi # The name half of this check needs the operator's private data and so # can only run locally; the shape half -- phone numbers, uuids, Signal # group ids -- works anywhere and is what CI enforces on every push. - name: no personal data run: ./scripts/check_personal_data.sh --all - name: ruff format run: .venv/bin/ruff format --check . - name: ruff check (ALL rules) run: .venv/bin/ruff check . - name: mypy --strict run: .venv/bin/mypy signal_bot fake_signal - name: shellcheck # --severity=style is the floor CI enforces; the local pre-commit hook # uses the same flag so a version skew cannot pass locally and fail here. run: shellcheck -x --severity=style ./*.sh scripts/*.sh - name: pytest (100% branch coverage) run: .venv/bin/python -m pytest -q # The real bot against the fake Signal server. No account, no network. - name: end-to-end run: ./scripts/e2e.sh # Boot order reversed: the bot starts first and Signal appears later, # which is what a reboot looks like and what put the unit in `failed` # for eight hours on 2026-08-30. - name: cold start run: ./scripts/cold_start.sh # The management page. Its own job rather than more steps on `check`: it # needs a Node toolchain that the Python job has no use for, and the two # halves fail for unrelated reasons. web: runs-on: ubuntu-latest defaults: run: working-directory: web steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "24.20.0" - run: corepack enable - name: install (exact pins, from the lockfile) run: pnpm install --frozen-lockfile - name: tsc --noEmit and eslint run: pnpm lint - name: vitest (100% coverage, no excludes) run: pnpm coverage