Public Access
The Site workflow's last step, and the release workflow after publishing, ask the web server over SSH to rebuild the site. The key CI holds is tied on the server to one forced command (restrict,command=...), so CI sends no command and a leaked key can only refresh the site. The server, the user, the key and the server's host key are Gitea secrets; with none of them set the step does nothing. scripts/site_refresh.sh is what both workflows run; scripts/site_deploy_keygen.sh makes the key and prints where each half goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
168 lines
8.9 KiB
YAML
168 lines
8.9 KiB
YAML
# CI and releases (docs/milestones/R1.md).
|
|
# A push to main: the host tests, with their coverage of lib/, and the README's badges
|
|
# published to the branch `badges`.
|
|
# A pull request: the same tests and coverage, then the firmware (from the build cache).
|
|
# A branch's pushes run nothing by themselves: its pull request runs, once.
|
|
# A tag v*: the tests, then the firmware built once, clean, signed and published as a
|
|
# Gitea release. The site is then rebuilt: its home page and Downloads name
|
|
# the latest release when they are built (issue #79).
|
|
# Run by hand: the release of a tag that exists already (the ones from before CI).
|
|
#
|
|
# The job runs in a plain Python image, as scripts/ci.sh does on a developer's machine, with the
|
|
# toolchains in a Docker volume the runner allows (container.valid_volumes: roro9stack-pio): that
|
|
# volume is the cache. No JavaScript actions, so the image needs no Node: the checkout is git.
|
|
#
|
|
# What the volume keeps between runs, and what makes each go stale (issue #74, R1.md):
|
|
# /pio/packages, /pio/platforms toolchains and the framework: by their versions in platformio.ini
|
|
# .../framework-arduinoespressif32-libs/.roro-sdkconfig.defaults
|
|
# the mark that the framework is already rebuilt with our SDK settings: the
|
|
# project's sdkconfig.defaults, which isn't in git, so that every fresh
|
|
# checkout rebuilt the framework (260 s). The platform checks its hash
|
|
# against platformio.ini's settings, and rebuilds if they differ. It is kept
|
|
# inside the libraries it describes, so it goes when they are reinstalled.
|
|
# /pio/ci/build-cache PlatformIO's build cache (SCons): objects by the signature of their
|
|
# sources and command line. For pull requests only: a release compiles
|
|
# its own sources from nothing.
|
|
# /pio/ci/ccache the host tests' objects (they're built for coverage, which the build
|
|
# cache can't keep: it would lose the .gcno files)
|
|
# /pio/ci/venv PlatformIO and gcovr: delete the folder to upgrade them
|
|
# To start from nothing (a slow run, about 7 minutes): delete /pio/ci and that .roro-sdkconfig.defaults file.
|
|
name: CI
|
|
on:
|
|
push:
|
|
branches: [main] # other branches are tested by their pull request: one run, not two
|
|
tags: ['v*']
|
|
# A change that touches nothing but the site and the documents it is built from runs the Site
|
|
# workflow only (a tag always runs this one: Gitea doesn't apply path filters to tags).
|
|
paths-ignore: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md']
|
|
pull_request:
|
|
paths-ignore: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md']
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: An existing tag to build and publish as a release
|
|
required: true
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu
|
|
# A pull request from a fork would run someone else's code on our runner: not without us (Q154).
|
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
|
container:
|
|
image: python:3.12-slim
|
|
volumes:
|
|
- roro9stack-pio:/pio
|
|
env:
|
|
PLATFORMIO_CORE_DIR: /pio
|
|
RORO_NO_DOCKER: 1
|
|
SDK_MARK: /pio/packages/framework-arduinoespressif32-libs/.roro-sdkconfig.defaults
|
|
CCACHE_DIR: /pio/ci/ccache
|
|
CCACHE_MAXSIZE: 1G
|
|
steps:
|
|
- name: Tools
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq --no-install-recommends git build-essential openssl ccache >/dev/null
|
|
mkdir -p /pio/ci
|
|
if [ ! -x /pio/ci/venv/bin/pio ]; then
|
|
python -m venv /pio/ci/venv
|
|
/pio/ci/venv/bin/pip install -q --no-cache-dir platformio gcovr
|
|
fi
|
|
ln -sf /pio/ci/venv/bin/pio /pio/ci/venv/bin/gcovr /usr/local/bin/
|
|
pio --version; df -h /pio | tail -1; du -sh /pio/ci/* 2>/dev/null || true
|
|
# The build cache only grows: start it again past 3 GB (a full set of objects is 160 MB, and each run adds about 40).
|
|
if [ "$(du -sm /pio/ci/build-cache 2>/dev/null | cut -f1)" -gt 3072 ] 2>/dev/null; then rm -rf /pio/ci/build-cache; fi
|
|
|
|
- name: Check out
|
|
run: |
|
|
find . -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
git config --global --add safe.directory '*'
|
|
git init -q .
|
|
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
|
|
git fetch -q --tags origin '+refs/heads/*:refs/remotes/origin/*' '+refs/pull/*/head:refs/remotes/pull/*'
|
|
git checkout -q --detach "${{ github.sha }}"
|
|
git describe --tags --always
|
|
|
|
- name: Host tests, and their coverage of lib/
|
|
if: github.event_name != 'workflow_dispatch'
|
|
run: |
|
|
export PATH="/usr/lib/ccache:$PATH" # gcc and g++ through ccache
|
|
scripts/coverage.sh
|
|
ccache -s | grep -E 'Hits|Misses' | head -2
|
|
|
|
# A pull request only: a tag's firmware is built once, by the release step below.
|
|
- name: The firmware
|
|
if: github.event_name == 'pull_request'
|
|
env:
|
|
PLATFORMIO_BUILD_CACHE_DIR: /pio/ci/build-cache
|
|
run: |
|
|
[ ! -f "$SDK_MARK" ] || cp "$SDK_MARK" sdkconfig.defaults
|
|
scripts/ci.sh builds
|
|
cp sdkconfig.defaults "$SDK_MARK" # what the framework in the volume is rebuilt with, now
|
|
|
|
# The README's badges are files on a branch of their own, replaced at each push to main and
|
|
# at each tag (the release badge says which tag is the latest)
|
|
- name: Publish the badges
|
|
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag')
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: |
|
|
rm -rf /tmp/badges && mkdir /tmp/badges
|
|
cp .pio/coverage/coverage.svg .pio/coverage/summary.json /tmp/badges/
|
|
scripts/coverage_badge.py --plain release "$(git describe --tags --abbrev=0)" /tmp/badges/release.svg
|
|
cd /tmp/badges
|
|
git init -q -b badges .
|
|
git add .
|
|
git -c user.name="roro9stack CI" -c user.email="ci@git.twis.la" commit -q -m "Coverage of ${{ github.ref_name }} at ${{ github.sha }}"
|
|
git push -q --force "$(echo "${{ github.server_url }}" | sed "s#://#://ci:${GITEA_TOKEN}@#")/${{ github.repository }}.git" badges
|
|
|
|
- name: Which release
|
|
id: release
|
|
run: |
|
|
if [ "${{ github.event_name }}" = workflow_dispatch ]; then
|
|
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
|
|
elif [ "${{ github.ref_type }}" = tag ]; then
|
|
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Build and sign the release
|
|
if: steps.release.outputs.tag != ''
|
|
env:
|
|
OTA_SIGNING_KEY: ${{ secrets.OTA_SIGNING_KEY }}
|
|
run: |
|
|
# The sources of the tag in a clone of their own; the tools are this commit's.
|
|
rm -rf /tmp/release-src dist
|
|
git clone -q . /tmp/release-src
|
|
git -C /tmp/release-src checkout -q --detach "refs/tags/${{ steps.release.outputs.tag }}"
|
|
# The key exists as a file only while this step runs, in a container that goes with the job.
|
|
umask 077
|
|
export RORO_OTA_KEY="$(mktemp)"
|
|
trap 'rm -f "$RORO_OTA_KEY"' EXIT
|
|
printf '%s\n' "$OTA_SIGNING_KEY" > "$RORO_OTA_KEY"
|
|
umask 022
|
|
# The framework rebuilt with our settings is reused if it matches (the platform checks);
|
|
# the release's own sources are compiled from nothing, with no build cache.
|
|
[ ! -f "$SDK_MARK" ] || cp "$SDK_MARK" /tmp/release-src/sdkconfig.defaults
|
|
scripts/release_build.sh /tmp/release-src dist
|
|
# An old tag has no SDK settings of its own, and no sdkconfig.defaults afterwards: nothing to mark.
|
|
[ ! -f /tmp/release-src/sdkconfig.defaults ] || [ ! -d "$(dirname "$SDK_MARK")" ] || cp /tmp/release-src/sdkconfig.defaults "$SDK_MARK"
|
|
|
|
- name: Publish the release
|
|
if: steps.release.outputs.tag != ''
|
|
env:
|
|
GITEA_API: ${{ github.server_url }}/api/v1
|
|
GITEA_REPO: ${{ github.repository }}
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
run: scripts/release_publish.py dist
|
|
|
|
# The site names the latest release on its home page and lists them all on Downloads, both
|
|
# read when it is built: so it is rebuilt now (issue #79, as the Site workflow does).
|
|
- name: Refresh the site
|
|
if: steps.release.outputs.tag != ''
|
|
env:
|
|
SITE_DEPLOY_KEY: ${{ secrets.SITE_DEPLOY_KEY }}
|
|
SITE_DEPLOY_HOST: ${{ secrets.SITE_DEPLOY_HOST }}
|
|
SITE_DEPLOY_USER: ${{ secrets.SITE_DEPLOY_USER }}
|
|
SITE_DEPLOY_KNOWN_HOSTS: ${{ secrets.SITE_DEPLOY_KNOWN_HOSTS }}
|
|
run: scripts/site_refresh.sh
|