diff --git a/flake.nix b/flake.nix index 96786def3..76c44211b 100644 --- a/flake.nix +++ b/flake.nix @@ -259,6 +259,8 @@ cat go.mod | ${pkgs.ripgrep}/bin/rg "\t" | ${pkgs.ripgrep}/bin/rg -v indirect | ${pkgs.gawk}/bin/awk '{print $1}' | ${pkgs.findutils}/bin/xargs go get -u go mod tidy '') + + (pkgs.callPackage ./nix/release-issue { }) ]; shellHook = '' diff --git a/nix/release-issue/default.nix b/nix/release-issue/default.nix new file mode 100644 index 000000000..448f0dafe --- /dev/null +++ b/nix/release-issue/default.nix @@ -0,0 +1,14 @@ +{ pkgs, ... }: +pkgs.writeShellApplication { + name = "headscale-release-issue"; + + runtimeInputs = with pkgs; [ + gh + coreutils + gnused + ]; + + text = '' + export HEADSCALE_RELEASE_CHECKLIST=${./release-checklist.md} + '' + builtins.readFile ./release-issue.sh; +} diff --git a/nix/release-issue/release-checklist.md b/nix/release-issue/release-checklist.md new file mode 100644 index 000000000..4774f0826 --- /dev/null +++ b/nix/release-issue/release-checklist.md @@ -0,0 +1,36 @@ + + +# Release vX.Y.Z + +This release is based on the work in [Milestone vX.Y.Z](https://github.com/juanfont/headscale/milestone/N). + +## Prep (once, before first beta) + +- [ ] Update all Go dependencies +- [ ] Update all Nix dependencies +- [ ] Bump minimum Tailscale version +- [ ] Bump headscale version in `mkdocs.yml` +- [ ] Review & tidy changelog + +## Per build (copy this block for each beta / rc / stable) + +### vX.Y.Z-beta.N + +- [ ] Tag & push +- [ ] Verify CI / release artifacts +- [ ] Announce on Discord +- [ ] Comment on issues fixed in this build + + + +## Done + +- [ ] Stable released +- [ ] Changelog finalized +- [ ] Close this issue diff --git a/nix/release-issue/release-issue.sh b/nix/release-issue/release-issue.sh new file mode 100644 index 000000000..0d78d3241 --- /dev/null +++ b/nix/release-issue/release-issue.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Wrapped by nix/release-issue/default.nix. Do not invoke directly. +set -euo pipefail + +TEMPLATE="${HEADSCALE_RELEASE_CHECKLIST:?HEADSCALE_RELEASE_CHECKLIST must be set by the Nix wrapper}" + +usage() { + cat >&2 <<'EOF' +Usage: headscale-release-issue --version X.Y.Z [--milestone N] [--dry-run] + +Opens a GitHub release-tracking issue from the embedded checklist template. +Attaches the milestone (when given), adds the "no-stale-bot" label, and locks +the issue for comments so the checklist is not derailed by drive-by replies. + +Flags: + --version X.Y.Z Stable release version (required). Pre-release suffixes + are rejected — release-tracking issues are stable-only; + individual betas/rcs are tracked inside the issue body. + --milestone N Milestone number to associate with. Optional; when + omitted no --milestone is passed to gh and the + /milestone/N URL in the body keeps its literal "N". + --dry-run Print the gh commands that would run, do not open or lock. + -h, --help Show this help. +EOF +} + +VERSION="" +MILESTONE="" +DRY_RUN=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --version) + [[ $# -ge 2 ]] || { echo "--version requires a value" >&2; exit 2; } + VERSION="$2"; shift 2 ;; + --milestone) + [[ $# -ge 2 ]] || { echo "--milestone requires a value" >&2; exit 2; } + MILESTONE="$2"; shift 2 ;; + --dry-run) DRY_RUN=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "unknown argument: $1" >&2; usage; exit 2 ;; + esac +done + +if [[ -z "$VERSION" ]]; then + echo "--version is required" >&2 + usage + exit 2 +fi + +if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "--version must be a stable X.Y.Z (no pre-release suffix), got: $VERSION" >&2 + exit 2 +fi + +if [[ -n "$MILESTONE" ]] && ! [[ "$MILESTONE" =~ ^[1-9][0-9]*$ ]]; then + echo "--milestone must be a positive integer, got: $MILESTONE" >&2 + exit 2 +fi + +[[ -r "$TEMPLATE" ]] || { echo "template not readable: $TEMPLATE" >&2; exit 1; } + +BODY=$(sed "s/vX\.Y\.Z/v${VERSION}/g" "$TEMPLATE") + +MILESTONE_TITLE="" +if [[ -n "$MILESTONE" ]]; then + BODY=$(printf '%s\n' "$BODY" | sed "s|/milestone/N|/milestone/${MILESTONE}|g") + if ! MILESTONE_TITLE=$(gh api "repos/{owner}/{repo}/milestones/${MILESTONE}" --jq .title); then + echo "could not resolve milestone #${MILESTONE} title via gh api" >&2 + exit 1 + fi +fi + +TITLE="Release v${VERSION}" + +CREATE_ARGS=(--title "$TITLE" --body "$BODY" --label no-stale-bot) +if [[ -n "$MILESTONE_TITLE" ]]; then + CREATE_ARGS+=(--milestone "$MILESTONE_TITLE") +fi + +if [[ "$DRY_RUN" == "1" ]]; then + printf 'Would run: gh issue create --title %q --label no-stale-bot' "$TITLE" + [[ -n "$MILESTONE_TITLE" ]] && printf -- ' --milestone %q' "$MILESTONE_TITLE" + printf ' --body \n' + printf 'Then: gh issue lock \n\n' + printf -- '--- rendered body ---\n%s\n--- end body ---\n' "$BODY" + exit 0 +fi + +URL=$(gh issue create "${CREATE_ARGS[@]}") +echo "$URL" +gh issue lock "$URL"