packaging.md

Packaging and releases

Packaging is part of the baseline when the project is meant to be installed, not an afterthought.

Read mise.md for build task templates before adding package tasks. For CLI, TUI, and MCP binaries, also read cli.md for version-policy guidance.

Choose the artifact

  • Plain CLI/MCP server: cross-compiled binaries are usually enough.
  • System service: add systemd/OpenRC files and consider nFPM packages.
  • Desktop/Wails app: use nFPM for Linux packages, desktop files, icons, and WebKit runtime dependencies; use Wails/NSIS for Windows installers when needed.
  • Library: no binary packaging unless examples/tools are part of the repo.

Version gating

Package tasks should fail on ambiguous versions. Prefer exact tags for release packages. For Git-tagged releases, a package task can gate on an exact tag:

version=$(git describe --tags --exact-match 2>/dev/null) || {
  echo "Error: package builds require an exact vMAJOR.MINOR.PATCH tag" >&2
  exit 1
}
version=${version#v}

For jj-backed projects, use the project's agreed jj/git-derived release command instead. Do not infer package versions from Go BuildInfo unless the project explicitly chose that policy. For development builds, VERSION=dev is fine; for packages, be stricter.

nFPM baseline

# yaml-language-server: $schema=https://nfpm.goreleaser.com/static/schema.json
name: example
arch: ${GOARCH}
version: ${VERSION}
release: ${RELEASE:-1}
section: utils
priority: optional
maintainer: Amolith <amolith@secluded.site>
description: Short human description
homepage: https://example.invalid
license: LicenseRef-MutuaL-1.2

contents:
  - src: build/pkg/example
    dst: /usr/bin/example

For desktop apps, add:

contents:
  - src: build/pkg/example
    dst: /usr/bin/example
  - src: packaging/example.desktop
    dst: /usr/share/applications/example.desktop
  - src: dist/example.png
    dst: /usr/share/icons/hicolor/512x512/apps/example.png

overrides:
  deb:
    depends:
      - libwebkit2gtk-4.1-0
    recommends:
      - xdg-utils
  rpm:
    depends:
      - (webkit2gtk4.1 or libwebkit2gtk-4_1-0)
  apk:
    depends:
      - webkit2gtk-4.1
  archlinux:
    depends:
      - webkit2gtk-4.1

mise package tasks

Use hidden preparation tasks for version, icon conversion, package build, and optional compression. Visible tasks should be simple: pkg, pkg:deb, pkg:rpm, pkg:apk, pkg:arch.

Minimal visible tasks look like this:

[tasks."pkg:deb"]
description = "Build a deb package"
env = { RELEASE = "1" }
run = "nfpm package -f packaging/nfpm.yaml -p deb -t build/pkg"

[tasks."pkg:rpm"]
description = "Build an rpm package"
env = { RELEASE = "1" }
run = "nfpm package -f packaging/nfpm.yaml -p rpm -t build/pkg"

UPX is optional. Use it for Linux binaries when size matters; do not assume it works well for every OS or binary format.

Cross-compilation

  • CGO_ENABLED=0 makes plain Go binaries easy to cross-compile.
  • For CLI, TUI, and MCP release binaries, prefer netgo, osusergo, optional project-specific static_build, -d -s -w -buildid=, and -extldflags=-static when dependencies are pure Go.
  • Treat Go VCS build metadata as a policy choice, not the package version source. If a package needs exact version or commit strings, derive them in the package task and inject them. If a release intentionally relies on Go BuildInfo, make that explicit and consider -buildvcs=true.
  • Wails/Linux builds require native CGO and WebKitGTK.
  • Windows Wails cross-builds from Linux need mingw-w64; NSIS packages need makensis.
  • macOS desktop builds require macOS unless a project has proven otherwise.

Document these constraints in task descriptions and preconditions rather than letting users discover linker failures.