<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hant-TW">
	<id>https://tommy0804.ddns.net/index.php?action=history&amp;feed=atom&amp;title=Firmware_CI%2FCD_on_GitHub_Actions</id>
	<title>Firmware CI/CD on GitHub Actions - 修訂紀錄</title>
	<link rel="self" type="application/atom+xml" href="https://tommy0804.ddns.net/index.php?action=history&amp;feed=atom&amp;title=Firmware_CI%2FCD_on_GitHub_Actions"/>
	<link rel="alternate" type="text/html" href="https://tommy0804.ddns.net/index.php?title=Firmware_CI/CD_on_GitHub_Actions&amp;action=history"/>
	<updated>2026-10-13T00:14:32Z</updated>
	<subtitle>本wiki上此頁面的修訂紀錄</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://tommy0804.ddns.net/index.php?title=Firmware_CI/CD_on_GitHub_Actions&amp;diff=811&amp;oldid=prev</id>
		<title>Tommy：​New page: Firmware CI/CD on GitHub Actions</title>
		<link rel="alternate" type="text/html" href="https://tommy0804.ddns.net/index.php?title=Firmware_CI/CD_on_GitHub_Actions&amp;diff=811&amp;oldid=prev"/>
		<updated>2026-10-12T13:25:05Z</updated>

		<summary type="html">&lt;p&gt;New page: Firmware CI/CD on GitHub Actions&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新頁面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;#039;&amp;#039;&amp;#039;Firmware CI/CD on GitHub Actions&amp;#039;&amp;#039;&amp;#039; &amp;amp;mdash; the golden procedure for building, testing, reviewing and releasing firmware on hosted runners, with flashing and on-device tests left to the bench.&lt;br /&gt;
&lt;br /&gt;
[[File:cicd_golden.png|1200px|thumb|center|Every change builds, tests, lints and simulates; every pull request is reviewed; every tag releases; the bench does what hosted runners cannot.]]&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! workflow !! trigger !! runs !! output&lt;br /&gt;
|-&lt;br /&gt;
| Build || push to main, pull request, manual || one job per variant; toolchain from a container built from the repo&amp;#039;s Dockerfile with a layer cache, or a minimal SDK cached on the plain runner when the image does not fit the runner disk; a fan-in job gives the matrix one check name || run artefacts per variant named &amp;lt;code&amp;gt;&amp;amp;lt;variant&amp;amp;gt;-&amp;amp;lt;sha&amp;amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;retention-days&amp;lt;/code&amp;gt; 14–30 (release artefacts 90); size report&lt;br /&gt;
|-&lt;br /&gt;
| Unit tests || push, pull request || host tests in their own test container, coverage, JUnit || JUnit published as a check, coverage artefact&lt;br /&gt;
|-&lt;br /&gt;
| Fast lints || push, pull request || scripts that read only the source tree (layering, dependency manifest), no container, no submodules, seconds; also dry-run any release-only generator here, so one broken since the last release fails on a pull request, not on tag day || pass/fail&lt;br /&gt;
|-&lt;br /&gt;
| Simulation regression || push, pull request || host build of the algorithm model, fixed scenarios; needs the submodule token only for the submodule it uses (&amp;lt;code&amp;gt;path:&amp;lt;/code&amp;gt; input), not all of them || pass/fail&lt;br /&gt;
|-&lt;br /&gt;
| Automated review || pull request opened or updated, not drafts || agent reads the diff against the repo rules file || review comment; not a required check, because it skips drafts and doc-only changes&lt;br /&gt;
|-&lt;br /&gt;
| Release || tag &amp;lt;code&amp;gt;vX.Y.Z&amp;lt;/code&amp;gt; (manual dry-run optional) || version from the tag into the build; all variants, bootloader package, update images signed with the release key; changelog since the previous tag; dependency manifest checked against the tree, then snapshotted || draft GitHub Release: images, update set, bootloader, manifest, changelog, snapshot, one zip&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! recipe !! how&lt;br /&gt;
|-&lt;br /&gt;
| private submodules || mint a token per run with &amp;lt;code&amp;gt;actions/create-github-app-token&amp;lt;/code&amp;gt; (App client ID as a repository variable, private key as a secret); rewrite &amp;lt;code&amp;gt;https://github.com/&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;git@github.com:&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;https://x-access-token:${TOKEN}@github.com/&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;git config --global --add url.&amp;amp;lt;token url&amp;amp;gt;.insteadOf&amp;lt;/code&amp;gt; (twice, &amp;lt;code&amp;gt;--add&amp;lt;/code&amp;gt; so the second mapping does not replace the first); &amp;lt;code&amp;gt;git submodule sync&amp;lt;/code&amp;gt;, then &amp;lt;code&amp;gt;git submodule update --init --depth 1&amp;lt;/code&amp;gt;; a personal token works too but expires, and renewing it means &amp;lt;code&amp;gt;gh secret set&amp;lt;/code&amp;gt;, not only the account&lt;br /&gt;
|-&lt;br /&gt;
| container || &amp;lt;code&amp;gt;docker/build-push-action&amp;lt;/code&amp;gt; from the repo Dockerfile with &amp;lt;code&amp;gt;cache-from/cache-to: type=gha&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;load: true&amp;lt;/code&amp;gt;, then &amp;lt;code&amp;gt;docker run --user $(id -u):$(id -g) -v $GITHUB_WORKSPACE:/work&amp;lt;/code&amp;gt;; if the image does not fit the 14 GB runner disk, install a minimal toolchain on the plain runner and cache it with &amp;lt;code&amp;gt;actions/cache&amp;lt;/code&amp;gt; keyed on its version&lt;br /&gt;
|-&lt;br /&gt;
| required check || a job is required only when branch protection or a ruleset lists its name; a matrix needs one fan-in job (&amp;lt;code&amp;gt;needs&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;if: always()&amp;lt;/code&amp;gt;, fail unless every result is success); a required workflow must not use path filters or skip drafts, or such pull requests wait forever&lt;br /&gt;
|-&lt;br /&gt;
| test report || publish the JUnit XML with a test-reporter action (&amp;lt;code&amp;gt;java-junit&amp;lt;/code&amp;gt;, fail on error) under job permission &amp;lt;code&amp;gt;checks: write&amp;lt;/code&amp;gt;; upload coverage and the XML with &amp;lt;code&amp;gt;actions/upload-artifact&amp;lt;/code&amp;gt;, retention 7–30 days&lt;br /&gt;
|-&lt;br /&gt;
| release || checkout with &amp;lt;code&amp;gt;fetch-depth: 0&amp;lt;/code&amp;gt;; &amp;lt;code&amp;gt;VERSION=${GITHUB_REF#refs/tags/}&amp;lt;/code&amp;gt;; changelog = &amp;lt;code&amp;gt;git log $(git describe --tags --abbrev=0 ${VERSION}^)..${VERSION} --pretty=&amp;#039;- %s (%h)&amp;#039;&amp;lt;/code&amp;gt;; artefacts &amp;lt;code&amp;gt;&amp;amp;lt;project&amp;amp;gt;-&amp;amp;lt;variant&amp;amp;gt;-&amp;amp;lt;version&amp;amp;gt;.&amp;amp;lt;ext&amp;amp;gt;&amp;lt;/code&amp;gt; plus one zip; publish with a release action as a draft, prerelease when the version has a suffix, under &amp;lt;code&amp;gt;contents: write&amp;lt;/code&amp;gt;; write the signing-key PEM from the secret to &amp;lt;code&amp;gt;$RUNNER_TEMP&amp;lt;/code&amp;gt;, and without the secret sign with the dev key and emit a warning; the same key signs the images and is built into the bootloader package, or release bootloaders would accept dev-signed images; dry-run = &amp;lt;code&amp;gt;workflow_dispatch&amp;lt;/code&amp;gt; with a &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt; input, the version step takes the tag when &amp;lt;code&amp;gt;github.event_name == &amp;#039;push&amp;#039;&amp;lt;/code&amp;gt; else the input, and the draft-release step runs only &amp;lt;code&amp;gt;if: startsWith(github.ref, &amp;#039;refs/tags/&amp;#039;)&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| automated review || &amp;lt;code&amp;gt;uses: anthropics/claude-code-action@&amp;amp;lt;commit-sha&amp;amp;gt;&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;claude_code_oauth_token: ${{ secrets.&amp;amp;lt;review token&amp;amp;gt; }}&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;additional_permissions: actions: read&amp;lt;/code&amp;gt; (the job permission alone does not let the agent read CI results); job permissions &amp;lt;code&amp;gt;contents: read, pull-requests: write, id-token: write, actions: read&amp;lt;/code&amp;gt;; &amp;lt;code&amp;gt;prompt:&amp;lt;/code&amp;gt; points at the rules file (CLAUDE.md / REVIEW.md); &amp;lt;code&amp;gt;claude_args&amp;lt;/code&amp;gt; restricts the model and the allowed tools to pull-request reading commands; skip drafts; one concurrency group per pull request&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! secret or variable !! purpose !! rule&lt;br /&gt;
|-&lt;br /&gt;
| App client ID (variable) + App private key (secret) || mints the short-lived submodule token || preferred: nothing expires unnoticed&lt;br /&gt;
|-&lt;br /&gt;
| personal access token (secret) || same, when no App exists || expires; renew in the repo secret&lt;br /&gt;
|-&lt;br /&gt;
| release signing key (secret) || signs the update images || secret only; a device accepts only images signed with the key its bootloader carries&lt;br /&gt;
|-&lt;br /&gt;
| review-agent token (secret) || the automated review action || secret only; the action that reads it is pinned to a commit SHA, never a floating tag&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;GITHUB_TOKEN&amp;lt;/code&amp;gt; || creates the draft release || needs &amp;lt;code&amp;gt;permissions: contents: write&amp;lt;/code&amp;gt; on that job&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Every workflow also sets &amp;lt;code&amp;gt;concurrency&amp;lt;/code&amp;gt; per ref with cancel-in-progress, &amp;lt;code&amp;gt;timeout-minutes&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;permissions: contents: read&amp;lt;/code&amp;gt; by default, and path filters on non-required workflows. Hosted runners build, test, lint, review and sign; flashing and on-device tests are done on the bench, or by a self-hosted runner on the bench machine.&lt;br /&gt;
&lt;br /&gt;
[[Category:Software and Tools]]&lt;/div&gt;</summary>
		<author><name>Tommy</name></author>
	</entry>
</feed>