Releasing
Nội dung này hiện chưa có sẵn bằng ngôn ngữ của bạn.
A release is a pushed tag. The gate runs on the maintainer’s machine first; the tag starts
.github/workflows/release.yml,
which runs scripts/release.sh --ci on a GitHub-hosted runner and creates the GitHub release.
The full procedure, with every command and a table of what to do when a stage fails, is the
release runbook. This
page is the map.
Versions and tags
Section titled “Versions and tags”- Git tags are
vX.Y.Z, orvX.Y.Z-<prerelease>such asv1.0.0-rc.1: semver without build metadata, checked exactly byscripts/release/version.mjs. The version must be newer than every otherv*tag, and a pushed tag is never moved. - Image tags drop the
v: alwaysX.Y.Z, plusX.YandXonly when this is the newest final release of that line, so a patch to an old line never moves a newer line’s tags. A prerelease gets its exact tag only. There is never alatest. - The chart’s version is the release’s version.
scripts/release/chart.shpackages the chart with--version X.Y.Z --app-version X.Y.Z; the committedcharts/kept/Chart.yamlstays0.0.0-dev(scripts/check-helm.shfails if it is edited by hand). Each published chart pins one image digest, and the release still refuses a chart version the registry already holds. - Commits follow Conventional Commits; the changelog is built from them.
What a release publishes
Section titled “What a release publishes”In this order, stopping at the first failure (D87, D186, D187):
- a multi-arch image (linux/amd64 and linux/arm64) built from
git archiveof the tagged commit, with an SPDX SBOM and minimal provenance in its index, pushed by digest, with no tag; - each architecture pulled by digest and smoked with
scripts/smoke-image.sh(arm64 natively, amd64 under emulation), and/versionchecked to report this version and commit; - the index digest and every manifest under it signed with cosign, then verified;
- the tags, all pointing at that one signed digest, each verified;
- the Helm chart as an OCI artifact beside the image, its image pinned by the signed digest, signed too;
- the release record
docs/releases/X.Y.Z.md: digests, tags, the chart and the verify commands; - the GitHub release
vX.Y.Z, withTHIRD-PARTY-NOTICES.txtandX.Y.Z.mdattached, marked a prerelease forX.Y.Z-…. Its page (scripts/release/compose-notes.mjs) is the section’s Highlights; What’s changed, with documentation and internal work folded away; Upgrade (the image on both registries, Compose, the chart, and whether the database changes); Contributors; the full changelog link; and the release record, folded; - the Docker Hub copy (
dockerhub.yml,scripts/mirror-dockerhub.sh): the same index copied by digest todocker.io/keptinventory/keptwith the same tags, and signed there with the same key; from 1.14.3 the Home Assistant app’s image goes todocker.io/keptinventory/kept-hathe same way. It needs theDOCKERHUB_USERNAMEandDOCKERHUB_TOKENsecrets; a release already out is copied with Run workflow ondockerhuband its version. - the Discord announcement (
discord.yml): the Highlights (else What’s changed, unfolded parts only) posted to #releases through theDISCORD_RELEASES_WEBHOOKsecret, pinging Release pings for a final release; Run workflow ondiscordwith a tag posts one again.
Nothing is tagged until both architectures passed and the signature verified, so a failed run
leaves only untagged digests behind. The default registry is ghcr.io/keptinventory/kept (the
chart under ghcr.io/keptinventory/charts/kept); the runbook records that this path was inferred
from the Dockerfile and is to be confirmed (its step W2).
Cutting one
Section titled “Cutting one”The short version of the runbook’s “Each release”:
-
Write 2–4 highlights (bullets led by a bold sentence, with their pull request or issue) in an ignored file under
.tmp/, then the changelog section, and commit it alone through the release pull request.ghsigned in adds the closed issues, who reported them and the Contributors line; without it they are left out with a warning:Terminal window node scripts/changelog.mjs --version X.Y.Z --from vPREVIOUS --highlights .tmp/highlights-X.Y.Z.md --write CHANGELOG.mdgit commit -m "chore(release): X.Y.Z" -- CHANGELOG.mdNo highlights file, no Highlights section: nothing is written for you. Running it again replaces the version’s section.
What’s new in Kept shows each highlight as a slide. A line
right under a bullet gives it a picture (.webp) or a short clip (.mp4);iconnames the app’s area (places,labels,import,paperwork,vehicles,help,map, …), shown when the media can’t be. The changelog keeps it as a comment, so GitHub, the release page and Discord show no broken image. Before the changelog step, make the media from the demo build with a recipe,scripts/whats-new/X.Y.Z.json(its format and an example:scripts/whats-new/README.md), then remove the previous release’s folder and recipe, so the tree holds one release’s media (a shallow clone gets the whole tree; history keeps the rest):Terminal window node scripts/record-whats-new.mjs --version X.Y.Znode scripts/whats-new-keep.mjs --version X.Y.Zgit add -A apps/web/public/whats-new scripts/whats-newCommit these with the changelog section, in the release pull request.
changelog.mjsstops when a highlight names a file that isn’t inapps/web/public/whats-new/X.Y.Z/with itsmedia.jsonentry, and so does the release image’s build. Clips stay under 400 KB, posters under 60 KB, pictures under 120 KB; they are fetched when the card opens, never precached. A highlight without media shows its area’s icon. -
Wait for the
ciworkflow to pass on that commit on main. It is the gate (D222): every step ofscripts/ci-local.shexcept perf, as required jobs. The release workflow’s preflight refuses a tag whose commit hasn’t passed it. -
Push the tag. The tag push starts the workflow:
Terminal window git tag -a vX.Y.Z -m "Kept X.Y.Z" && git push origin vX.Y.Z -
When the run is green, download the release record into
docs/releases/and commit it. -
Verify from outside, as a user would (verifying a release).
What the workflow does
Section titled “What the workflow does”release.yml runs only on a pushed tag in Kept’s version format, or by hand
(workflow_dispatch) for an existing tag after a failed run. Never on a branch push or a pull
request, so Actions minutes go to releases only. It is one job, release, on the arm64 runner
(ubuntu-24.04-arm) with a 90-minute timeout, and one release runs at a time.
- Preflight, before any build: the tag is a release version; signing is either fully set up or
fully absent (half is refused); an unsigned
1.0.0or later is refused; the GitHub release doesn’t exist yet; the tagged commit has a successfulcirun on main. scripts/release.sh --ci: the stages above. It also refuses a tagged commit that isn’t onorigin/mainand a version or chart version the registry already holds.- The GitHub release, after checking the notes for AI attribution (D173).
- Cleanup, always: the key file and the registry logins are removed.
The layer cache is Actions’ cache, scoped by ref: it helps a re-run of the same tag, not the next
release. The gate is ci.yml, run on every pull request and on main; the release only checks
that it passed for the tagged commit.
The laptop fallback
Section titled “The laptop fallback”When Actions can’t run, bash scripts/release.sh X.Y.Z does the same stages from the laptop, with
the cosign key file (KEPT_COSIGN_KEY, COSIGN_PASSWORD), a GHCR login and gh as the
repository owner (the runbook’s M1–M3). It never commits, tags or pushes: at the end it prints
those steps for you. It needs the pinned tools (bash scripts/tools.sh fetch) and 15 GB free.
Don’t push a tag from a laptop release while release.yml is in the tagged commit unless you mean
the workflow to run as well; it stops at “already published” in its first minutes.
The dry run
Section titled “The dry run”Rehearse the whole pipeline locally, with nothing published outside the machine:
bash scripts/release.sh 0.1.0 --dry-run# or as ci-local's opt-in step:KEPT_RELEASE_DRY_RUN=1 bash scripts/ci-local.sh --from release-dry-runIt builds HEAD from git archive, pushes to a throwaway registry:3.1.2 container on a spare port,
smokes both architectures, signs with a throwaway key pair, tags, publishes and signs the chart,
and must end with a verified signature and no container left behind. ci-local’s
release-dry-run step skips (loudly) unless KEPT_RELEASE_DRY_RUN=1 is set.
Related
Section titled “Related”- Signing: the key, what is signed, and why it isn’t keyless yet.
- The changelog.
- Runbooks and scripts: every ci-local step.