Publishing releases
This is the maintainer runbook. Releases are automated with GoReleaser — tagging a version publishes binaries, packages, container images and the Helm chart in one pass.
What a release produces
| Target | Artifact | Automated |
|---|---|---|
| GitHub Releases | archives, checksums, provenance attestation | yes |
| Install scripts | install.sh / install.ps1 on GitHub Pages |
yes |
| Docker Hub + GHCR | multi-arch linux/amd64 and linux/arm64 images |
yes |
| Homebrew | cask in Thre4dripper/homebrew-tap |
yes |
| Helm chart | oci://ghcr.io/thre4dripper/charts |
yes |
System-level installs are served by the install script rather than by
per-distribution registries or package files. The script reads the same GitHub
release archives listed above, so there is nothing extra to publish for Linux,
macOS or Windows. Tidefetch is deliberately not published to Winget, Scoop,
AUR, Nix, MacPorts, Chocolatey or hosted APT/RPM repositories, and no .deb,
.rpm or .apk is produced — each of those needs its own repository,
credential and review cycle per release.
One-time setup
Repository secrets
Add these under Settings → Secrets and variables → Actions:
| Secret | Purpose |
|---|---|
HOMEBREW_TAP_TOKEN |
PAT with contents:write on homebrew-tap — optional, skips the cask without it |
DOCKERHUB_USERNAME |
Docker Hub account name — optional, GHCR-only without it |
DOCKERHUB_TOKEN |
Docker Hub access token with Read & Write scope |
GITHUB_TOKEN is provided automatically and covers GHCR, GitHub Releases, the
Helm chart push and provenance attestation. There is no signing key to create,
rotate or leak.
GITHUB_TOKEN is provided automatically and covers GHCR, GitHub Releases and
the Helm chart push.
Companion repositories
Create this once:
Thre4dripper/homebrew-tap
Supply chain: provenance attestation
Every release archive, checksums.txt and the container image get a signed
build provenance attestation.
This is always on and needs no secret — signing keys are minted per run from the
workflow's OIDC identity and recorded in a public transparency log.
An attestation proves which workflow built the file, from which commit. A stolen credential cannot forge that, whereas a GPG key stored in CI secrets can sign anything once copied. That is why this project attests rather than GPG-signs.
Anyone can verify a download:
gh attestation verify tidefetch_linux_amd64.tar.gz --repo Thre4dripper/tidefetch
gh attestation verify oci://ghcr.io/thre4dripper/tidefetch:0.2.0 --repo Thre4dripper/tidefetch
GitHub Pages
The install scripts are served from the product site. Enable Settings → Pages
→ Source: GitHub Actions once, then the site workflow publishes
install.sh and install.ps1 to the site root on every push to main that
touches site/**, docs/** or scripts/install.*.
The first release
The install script and the binaries it downloads are hosted independently, so there is no bootstrap problem — but the order matters, because the script is useless until a release exists for it to fetch.
Enable GitHub Pages and merge to
main. Thesiteworkflow publisheshttps://thre4dripper.github.io/tidefetch/install.sh. At this point the URL resolves but the script has nothing to install yet.Push the first tag:
git tag v0.2.0 && git push origin v0.2.0. Thereleaseworkflow builds the archives andchecksums.txtand publishes the release.Verify from a clean machine or container:
docker run --rm -it debian:stable-slim sh -c \ 'apt-get update -qq && apt-get install -y -qq curl ca-certificates >/dev/null && curl -fsSL https://thre4dripper.github.io/tidefetch/install.sh | sh && tidefetch version'
Users only need curl (or wget) and tar, which ship on effectively every
macOS and Linux system. Nothing has to be preinstalled.
Release cycle
You choose the version number. Everything else is automated, and the workflow refuses anything malformed or backwards.
push to main → "next" draft release is rebuilt
(unreleased commits + real binaries to test)
ready to ship → push a tag, or run the workflow with a version
→ validate → build → publish → draft cleared
The "next" draft release
The next workflow keeps a single draft release called Next (unreleased) in
sync with main. On every push it lists the commits since the last tag and
attaches freshly built binaries for all seven platforms.
It is a draft release rather than a pull request for two reasons: a PR cannot
carry build artifacts, and it would have to modify a file just to exist. Drafts
are invisible to users and excluded from /releases/latest, so the install
script keeps serving the last real release.
Use it to download and test main before committing to a version number. When
you release, the draft is deleted automatically because its contents just
shipped.
Cutting a release
Either push a tag:
git tag v0.3.0
git push origin v0.3.0
Or go to Actions → Release → Run workflow, type v0.3.0 in the version box
and untick dry run. The workflow creates and pushes the tag for you.
Leaving the version empty with dry run ticked builds everything and attaches it to the workflow run without publishing. That path needs no secrets, so it is the safest way to prove the pipeline works.
What gets rejected
The validate job runs before anything is built:
| Input | Result |
|---|---|
0.3.0 |
rejected — needs the v prefix |
v0.3 |
rejected — not MAJOR.MINOR.PATCH |
v0.2.0 when v0.2.0 exists |
rejected — already released |
v0.1.0 when v0.2.0 exists |
rejected — older than the latest release |
v0.3.0 or v1.0.0-rc.1 |
accepted |
Choosing the number
Tags are SemVer with a v prefix, which is what Go modules and the install
script expect.
| Change | Bump |
|---|---|
| Bug fixes only | patch — 0.2.0 → 0.2.1 |
| New features, nothing broken | minor — 0.2.0 → 0.3.0 |
| Breaking change to flags, config or API | minor while pre-1.0, major after |
Stay on 0.x while CLI flags, the config file format or the web API may still
change — under SemVer, 0.x explicitly means "anything may break". Cut v1.0.0
once those are stable.
Pre-releases
git tag v1.0.0-rc.1 && git push origin v1.0.0-rc.1
GoReleaser marks it as a prerelease automatically (prerelease: auto), and
GitHub excludes prereleases from /releases/latest, so the install script keeps
serving the last stable version. Testers opt in explicitly:
curl -fsSL https://thre4dripper.github.io/tidefetch/install.sh \
| TIDEFETCH_VERSION=v1.0.0-rc.1 sh
Version strings in the repository
cmd/tidefetch/main.go, packaging/helm/tidefetch/Chart.yaml,
web/package.json and site/package.json carry a version, but none of them are
authoritative — the tag is. GoReleaser stamps the real version through
-ldflags, and the Helm chart is packaged with --version from the tag. The
Makefile derives its version from git describe, so local builds are always
self-describing.
Dry run
Validate the pipeline without publishing anything:
make release-check # goreleaser check + build snapshot
goreleaser release --snapshot --clean --skip=publish
Artifacts land in dist/. Check the archives and the generated Homebrew cask
before tagging:
tar -tzf dist/tidefetch_linux_amd64.tar.gz
cat dist/homebrew/Casks/tidefetch.rb
Manual steps
Unraid Community Applications
The template at packaging/unraid/tidefetch.xml is fetched from main, so it
updates automatically. Only submit to the Community Applications repository
when the template is added or renamed.
Verifying a published release
curl -fsSL https://thre4dripper.github.io/tidefetch/install.sh | sh && tidefetch version
brew install thre4dripper/tap/tidefetch && tidefetch version
docker run --rm ghcr.io/thre4dripper/tidefetch:0.3.0 version
helm show chart oci://ghcr.io/thre4dripper/charts/tidefetch | grep version
Check the multi-arch manifest:
docker buildx imagetools inspect ghcr.io/thre4dripper/tidefetch:0.3.0
Rolling back
Container tags are immutable per version — point deployments at the previous tag. For a bad package release, publish a patch version rather than deleting artifacts; package managers cache aggressively and deletions break clients that already resolved the version.