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.
/hop
/dist/
/SHA256SUMS
/coverage.out
*.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
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
+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
Hop releases are built on `githop.xyz` by Gitea Actions and uploaded by
GoReleaser as draft Gitea Releases.
Hop releases are built on a trusted maintainer machine and uploaded by
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`.
@@ -9,15 +10,12 @@ The canonical repository is `githop.xyz/GnosysLabs/Hop`.
- Create the `GnosysLabs/Hop` repository and set its default branch to `main`.
- Configure `origin` as `https://githop.xyz/GnosysLabs/Hop.git`.
- Run a currently patched Gitea 1.26.x or newer, enable Actions, and enable
Actions for the repository. Hop's least-privilege workflow uses the 1.26 job
token permission model.
- Register trusted, isolated `ubuntu-latest` and `windows-latest` act runners.
- Allow the repository job token `code: read` and `releases: write`.
- Ensure `${{ secrets.GITEA_TOKEN }}` can create releases in this repository.
- 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.
- Keep Gitea Actions disabled when the instance does not have dedicated runner
capacity; Hop's release process does not depend on it.
- Create a narrowly scoped maintainer access token that can write releases.
Export it as `GITEA_TOKEN` only for the local publish command, then unset it.
- When upgrading GoReleaser, update its pinned version and four archive
checksums in `scripts/release-local.sh` from the official checksum file.
- Permit release attachment MIME types for `.tar.gz`, `.zip`, and `.txt` in
Gitea's `[attachment] ALLOWED_TYPES` configuration.
- 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
- Choose and add a `LICENSE`. The release workflow intentionally fails without
one; this is a product/legal decision, not a build default.
- Choose and add a `LICENSE`. The local publishing script intentionally fails
without one; this is a product/legal decision, not a build default.
- Add `SECURITY.md` with a monitored private disclosure address.
- Create an offline-controlled release-signing key, publish its public key, and
add detached signing for `checksums.txt` before general availability.
- Confirm the `githop.xyz/GnosysLabs/Hop` Go import path serves valid `go-import`
metadata.
- Back up the Gitea database, repositories, release attachments, and Actions
secrets.
- Back up the Gitea database, repositories, and release attachments.
## Validate before tagging
```bash
go test -race ./...
go vet ./...
sh -n scripts/install.sh
goreleaser check
goreleaser release --snapshot --clean
scripts/release-local.sh --snapshot
```
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
1. Update release notes and the expected version.
2. Create a signed semantic-version tag such as `v0.1.0-alpha.1`.
3. Push the tag to `githop.xyz`.
4. The `.gitea/workflows/release.yml` workflow runs race tests and vet, builds
six platform archives, generates `checksums.txt`, and uploads a draft.
5. Download the draft assets and independently verify checksums, version output,
1. Update release notes. The signed Git tag is the version source and is
injected into the binaries automatically.
2. Run `scripts/release-local.sh --snapshot` and inspect the artifacts.
3. Create a signed semantic-version tag such as `v0.1.0-alpha.1` and push it.
4. Export a locally stored, scoped token: `export GITEA_TOKEN=...`.
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.
6. Publish the Gitea draft only after those checks pass.
7. Test both one-command installers against the now-published release.
8. Publish the Gitea draft only after those checks pass.
9. Test both one-command installers against the now-published release.
## 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
[release checklist](Release-Checklist).
## Runner trust
## Release-machine trust
Release jobs execute on Gitea act runners. Only trusted, isolated runners should
receive release tags or release tokens. Do not run secret-bearing release jobs
from unreviewed fork pull requests.
Release builds execute on a maintainer machine, not on the Gitea server. Use a
trusted, patched machine; keep the release token out of shell history and source
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