Reconcile no-Actions local release workflow with accepted Hop implementation

Hop-State: A_06FN5GEZTNDD5V7A7PXHBR0
Hop-Proposal: R_06FN5GDDA8WVH1V6JW0FBY0
Hop-Task: T_06FN3MBF98GWD4NA5PA1RWG
Hop-Attempt: AT_06FN5FPYNDP5C49S0G52NQG
This commit is contained in:
Hop
2026-07-11 12:57:45 -07:00
parent aad987e762
commit 011f6fa102
8 changed files with 156 additions and 158 deletions
-67
View File
@@ -1,67 +0,0 @@
name: CI
on:
push:
branches:
- main
pull_request:
permissions:
code: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: https://gitea.com/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Go
uses: https://gitea.com/actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod
cache: true
- name: Test with race detection
run: go test -race ./...
- name: Vet
run: go vet ./...
- name: Cross-compile supported targets
shell: bash
run: |
set -euo pipefail
for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64 windows/arm64; do
os=${target%/*}
arch=${target#*/}
output="dist-smoke/hop_${os}_${arch}"
if [[ "$os" == windows ]]; then output="${output}.exe"; fi
GOOS="$os" GOARCH="$arch" CGO_ENABLED=0 go build -trimpath -o "$output" ./cmd/hop
done
- name: Validate POSIX installer syntax
run: sh -n scripts/install.sh
- name: Smoke-test POSIX installer
run: sh scripts/test-install.sh
powershell-installer:
runs-on: windows-latest
steps:
- name: Check out repository
uses: https://gitea.com/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Set up Go
uses: https://gitea.com/actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod
cache: true
- name: Validate PowerShell installer syntax
shell: pwsh
run: |
$tokens = $null
$errors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile(
(Resolve-Path scripts/install.ps1), [ref]$tokens, [ref]$errors
)
if ($errors.Count -gt 0) {
$errors | Format-List | Out-String | Write-Error
exit 1
}
- name: Smoke-test PowerShell installer
shell: pwsh
run: ./scripts/test-install.ps1
-59
View File
@@ -1,59 +0,0 @@
name: Release
on:
push:
tags:
- "v*"
permissions:
code: read
releases: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Check out full history
uses: https://gitea.com/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Set up Go
uses: https://gitea.com/actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod
cache: true
- name: Require a public-release license
run: test -f LICENSE
- name: Test with race detection
run: go test -race ./...
- name: Vet
run: go vet ./...
- name: Build and upload a draft Gitea Release
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
version=2.17.0
case "$(uname -m)" in
x86_64|amd64)
arch=x86_64
expected=dde10e2d5a13cef969c0eec00c74f359c0ac306d702b1bd291ad9337b4e54c1d
;;
aarch64|arm64)
arch=arm64
expected=75f93fc0e25d10d8535ffd0e4abcf39d6784a2467ba453d479ae513729a9ebbf
;;
*)
echo "Unsupported release runner architecture: $(uname -m)" >&2
exit 1
;;
esac
archive="goreleaser_Linux_${arch}.tar.gz"
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT
curl -fsSL --proto '=https' --tlsv1.2 \
-o "$tmp_dir/$archive" \
"https://github.com/goreleaser/goreleaser/releases/download/v${version}/${archive}"
printf '%s %s\n' "$expected" "$tmp_dir/$archive" | sha256sum -c -
tar -xzf "$tmp_dir/$archive" -C "$tmp_dir" goreleaser
"$tmp_dir/goreleaser" release --clean
+1
View File
@@ -5,6 +5,7 @@
# Local build and verification artifacts. # Local build and verification artifacts.
/hop /hop
/dist/
/SHA256SUMS /SHA256SUMS
/coverage.out /coverage.out
*.test *.test
+2
View File
@@ -148,6 +148,8 @@ No project is modified during installation. A project receives its local
Release installer URLs become available after the corresponding Gitea Release Release installer URLs become available after the corresponding Gitea Release
is published. Until the first public release exists, use the source build. is published. Until the first public release exists, use the source build.
Hop does not require Gitea Actions: maintainers build and upload draft releases
from a trusted local machine with `scripts/release-local.sh`.
## Getting started ## Getting started
+18
View File
@@ -65,3 +65,21 @@ func TestProductDocumentationUsesCanonicalGiteaHost(t *testing.T) {
} }
} }
} }
func TestDistributionDoesNotRequireGiteaActions(t *testing.T) {
root := filepath.Clean(filepath.Join("..", ".."))
workflows, err := filepath.Glob(filepath.Join(root, ".gitea", "workflows", "*"))
if err != nil {
t.Fatal(err)
}
if len(workflows) != 0 {
t.Fatalf("Gitea Actions workflows remain enabled in source: %v", workflows)
}
contents, err := os.ReadFile(filepath.Join(root, "wiki", "Release-Checklist.md"))
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(contents), ".gitea/workflows/") {
t.Fatal("release checklist still depends on a Gitea Actions workflow")
}
}
+105
View File
@@ -0,0 +1,105 @@
#!/bin/sh
set -eu
fail() {
printf 'hop release: %s\n' "$*" >&2
exit 1
}
usage() {
cat <<'EOF'
Usage: scripts/release-local.sh --snapshot
scripts/release-local.sh --publish
--snapshot Test and build all release archives locally without uploading.
--publish Test, build, and upload a draft release to githop.xyz.
Requires a clean signed tag, LICENSE, and GITEA_TOKEN.
EOF
}
[ "$#" -eq 1 ] || { usage >&2; exit 2; }
mode=$1
case "$mode" in
--snapshot | --publish) ;;
*) usage >&2; exit 2 ;;
esac
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$root"
for command in curl git go tar; do
command -v "$command" >/dev/null 2>&1 || fail "$command is required"
done
if [ "$mode" = --publish ]; then
[ -f LICENSE ] || fail "LICENSE is required before publishing"
[ -n "${GITEA_TOKEN:-}" ] || fail "set a scoped GITEA_TOKEN in the local environment"
[ -z "$(git status --porcelain)" ] || fail "the Git worktree must be clean"
tag=$(git describe --tags --exact-match HEAD 2>/dev/null) ||
fail "HEAD must have an exact release tag"
case "$tag" in
v[0-9]*) ;;
*) fail "release tag must start with v followed by a number" ;;
esac
[ "$(git cat-file -t "$tag")" = tag ] || fail "$tag must be an annotated tag"
git verify-tag "$tag" >/dev/null 2>&1 || fail "$tag must have a valid signature"
git ls-remote --exit-code origin "refs/tags/$tag" >/dev/null 2>&1 ||
fail "$tag must be pushed to origin before publishing"
fi
printf 'Running local release validation...\n'
go test -race ./...
go vet ./...
sh -n scripts/install.sh
sh scripts/test-install.sh
git diff --check
goreleaser_version=2.17.0
case "$(uname -s)/$(uname -m)" in
Darwin/arm64)
archive=goreleaser_Darwin_arm64.tar.gz
expected=58912a80159199c0fd5c8484e4c868bf87414129655d6d87cd1cd84ee645736c
;;
Darwin/x86_64)
archive=goreleaser_Darwin_x86_64.tar.gz
expected=f37e89fb844ddfd23cffb97e30d91f972c42da68232a676bfba2beacea300543
;;
Linux/arm64 | Linux/aarch64)
archive=goreleaser_Linux_arm64.tar.gz
expected=75f93fc0e25d10d8535ffd0e4abcf39d6784a2467ba453d479ae513729a9ebbf
;;
Linux/x86_64 | Linux/amd64)
archive=goreleaser_Linux_x86_64.tar.gz
expected=dde10e2d5a13cef969c0eec00c74f359c0ac306d702b1bd291ad9337b4e54c1d
;;
*) fail "unsupported release host: $(uname -s)/$(uname -m)" ;;
esac
tmp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t hop-release)
cleanup() {
rm -rf "$tmp_dir"
}
trap cleanup EXIT HUP INT TERM
printf 'Downloading verified GoReleaser %s...\n' "$goreleaser_version"
curl -fsSL --retry 3 --proto '=https' --tlsv1.2 \
-o "$tmp_dir/$archive" \
"https://github.com/goreleaser/goreleaser/releases/download/v${goreleaser_version}/${archive}"
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "$tmp_dir/$archive" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
actual=$(shasum -a 256 "$tmp_dir/$archive" | awk '{print $1}')
else
fail "sha256sum or shasum is required"
fi
[ "$actual" = "$expected" ] || fail "GoReleaser checksum verification failed"
tar -xzf "$tmp_dir/$archive" -C "$tmp_dir" goreleaser
"$tmp_dir/goreleaser" check
if [ "$mode" = --snapshot ]; then
"$tmp_dir/goreleaser" release --snapshot --clean
printf 'Local snapshot complete: %s/dist\n' "$root"
else
"$tmp_dir/goreleaser" release --clean
printf 'Draft release uploaded to https://githop.xyz/GnosysLabs/Hop/releases\n'
fi
+25 -28
View File
@@ -1,7 +1,8 @@
# Release checklist # Release checklist
Hop releases are built on `githop.xyz` by Gitea Actions and uploaded by Hop releases are built on a trusted maintainer machine and uploaded by
GoReleaser as draft Gitea Releases. GoReleaser as draft Gitea Releases. Gitea Actions and act runners are not
required; the server only stores source, tags, release metadata, and assets.
The canonical repository is `githop.xyz/GnosysLabs/Hop`. The canonical repository is `githop.xyz/GnosysLabs/Hop`.
@@ -9,15 +10,12 @@ The canonical repository is `githop.xyz/GnosysLabs/Hop`.
- Create the `GnosysLabs/Hop` repository and set its default branch to `main`. - Create the `GnosysLabs/Hop` repository and set its default branch to `main`.
- Configure `origin` as `https://githop.xyz/GnosysLabs/Hop.git`. - Configure `origin` as `https://githop.xyz/GnosysLabs/Hop.git`.
- Run a currently patched Gitea 1.26.x or newer, enable Actions, and enable - Keep Gitea Actions disabled when the instance does not have dedicated runner
Actions for the repository. Hop's least-privilege workflow uses the 1.26 job capacity; Hop's release process does not depend on it.
token permission model. - Create a narrowly scoped maintainer access token that can write releases.
- Register trusted, isolated `ubuntu-latest` and `windows-latest` act runners. Export it as `GITEA_TOKEN` only for the local publish command, then unset it.
- Allow the repository job token `code: read` and `releases: write`. - When upgrading GoReleaser, update its pinned version and four archive
- Ensure `${{ secrets.GITEA_TOKEN }}` can create releases in this repository. checksums in `scripts/release-local.sh` from the official checksum file.
- Keep third-party Actions pinned to reviewed commit SHAs. When upgrading
GoReleaser, update its version and both hard-coded Linux archive checksums in
`.gitea/workflows/release.yml` from the official release checksum file.
- Permit release attachment MIME types for `.tar.gz`, `.zip`, and `.txt` in - Permit release attachment MIME types for `.tar.gz`, `.zip`, and `.txt` in
Gitea's `[attachment] ALLOWED_TYPES` configuration. Gitea's `[attachment] ALLOWED_TYPES` configuration.
- Enable the repository wiki, then push the files in `wiki/` to its wiki Git - Enable the repository wiki, then push the files in `wiki/` to its wiki Git
@@ -25,24 +23,19 @@ The canonical repository is `githop.xyz/GnosysLabs/Hop`.
## Public-launch gates ## Public-launch gates
- Choose and add a `LICENSE`. The release workflow intentionally fails without - Choose and add a `LICENSE`. The local publishing script intentionally fails
one; this is a product/legal decision, not a build default. without one; this is a product/legal decision, not a build default.
- Add `SECURITY.md` with a monitored private disclosure address. - Add `SECURITY.md` with a monitored private disclosure address.
- Create an offline-controlled release-signing key, publish its public key, and - Create an offline-controlled release-signing key, publish its public key, and
add detached signing for `checksums.txt` before general availability. add detached signing for `checksums.txt` before general availability.
- Confirm the `githop.xyz/GnosysLabs/Hop` Go import path serves valid `go-import` - Confirm the `githop.xyz/GnosysLabs/Hop` Go import path serves valid `go-import`
metadata. metadata.
- Back up the Gitea database, repositories, release attachments, and Actions - Back up the Gitea database, repositories, and release attachments.
secrets.
## Validate before tagging ## Validate before tagging
```bash ```bash
go test -race ./... scripts/release-local.sh --snapshot
go vet ./...
sh -n scripts/install.sh
goreleaser check
goreleaser release --snapshot --clean
``` ```
Inspect `dist/` and test at least one archive on each operating system family. Inspect `dist/` and test at least one archive on each operating system family.
@@ -51,15 +44,19 @@ Confirm `hop version` reports the snapshot/tag-injected version and
## Create a release ## Create a release
1. Update release notes and the expected version. 1. Update release notes. The signed Git tag is the version source and is
2. Create a signed semantic-version tag such as `v0.1.0-alpha.1`. injected into the binaries automatically.
3. Push the tag to `githop.xyz`. 2. Run `scripts/release-local.sh --snapshot` and inspect the artifacts.
4. The `.gitea/workflows/release.yml` workflow runs race tests and vet, builds 3. Create a signed semantic-version tag such as `v0.1.0-alpha.1` and push it.
six platform archives, generates `checksums.txt`, and uploads a draft. 4. Export a locally stored, scoped token: `export GITEA_TOKEN=...`.
5. Download the draft assets and independently verify checksums, version output, 5. Run `scripts/release-local.sh --publish`. It reruns race tests, vet, installer
checks, builds six platform archives, generates `checksums.txt`, and uploads
a draft without executing build work on the Gitea server.
6. Immediately run `unset GITEA_TOKEN`.
7. Download the draft assets and independently verify checksums, version output,
skill installation, and a disposable Hop project. skill installation, and a disposable Hop project.
6. Publish the Gitea draft only after those checks pass. 8. Publish the Gitea draft only after those checks pass.
7. Test both one-command installers against the now-published release. 9. Test both one-command installers against the now-published release.
## Expected assets ## Expected assets
+5 -4
View File
@@ -33,11 +33,12 @@ sign `checksums.txt` with an offline-controlled release key and publish the
public key independently. Checksum signing is listed as a launch gate in the public key independently. Checksum signing is listed as a launch gate in the
[release checklist](Release-Checklist). [release checklist](Release-Checklist).
## Runner trust ## Release-machine trust
Release jobs execute on Gitea act runners. Only trusted, isolated runners should Release builds execute on a maintainer machine, not on the Gitea server. Use a
receive release tags or release tokens. Do not run secret-bearing release jobs trusted, patched machine; keep the release token out of shell history and source
from unreviewed fork pull requests. files; scope it to the Hop repository; export it only for the publish command;
and unset it immediately afterward. Releases upload as drafts for review.
## Filesystem safety ## Filesystem safety