ci(release): publish releases on ermite from pushed tags
A pushed v* tag runs GoReleaser on ermite (git.bnema.dev) and on the GitHub mirror, which keeps a copy for zut update (zut#4). The daily auto-tag job is gone: the maintainer tags releases. install.sh and install.ps1 resolve and download releases from the forge. go.mod declares go 1.27 and CI reads it.
9 files · +76 -144
M .forgejo/workflows/ci.yml +1 -1
| @@ -23,7 +23,7 @@ jobs: | |||
| 23 | 23 | - name: set up go | |
| 24 | 24 | uses: actions/setup-go@v7 | |
| 25 | 25 | with: | |
| 26 | - | go-version: "1.26" | |
| 26 | + | go-version-file: go.mod | |
| 27 | 27 | cache: true | |
| 28 | 28 | ||
| 29 | 29 | - name: go vet | |
| @@ -23,7 +23,7 @@ jobs: | |||||
| 23 | - name: set up go | 23 | - name: set up go | ||
| 24 | uses: actions/setup-go@v7 | 24 | uses: actions/setup-go@v7 | ||
| 25 | with: | 25 | with: | ||
| 26 | - | go-version: "1.26" | 26 | + | go-version-file: go.mod |
| 27 | cache: true | 27 | cache: true | ||
| 28 | 28 | ||||
| 29 | - name: go vet | 29 | - name: go vet | ||
M .github/workflows/ci.yml +1 -1
| @@ -26,7 +26,7 @@ jobs: | |||
| 26 | 26 | - name: set up go | |
| 27 | 27 | uses: actions/setup-go@v7 | |
| 28 | 28 | with: | |
| 29 | - | go-version: "1.26" | |
| 29 | + | go-version-file: go.mod | |
| 30 | 30 | cache: true | |
| 31 | 31 | ||
| 32 | 32 | - name: go vet | |
| @@ -26,7 +26,7 @@ jobs: | |||||
| 26 | - name: set up go | 26 | - name: set up go | ||
| 27 | uses: actions/setup-go@v7 | 27 | uses: actions/setup-go@v7 | ||
| 28 | with: | 28 | with: | ||
| 29 | - | go-version: "1.26" | 29 | + | go-version-file: go.mod |
| 30 | cache: true | 30 | cache: true | ||
| 31 | 31 | ||||
| 32 | - name: go vet | 32 | - name: go vet | ||
M .github/workflows/release.yml +18 -65
| @@ -1,81 +1,34 @@ | |||
| 1 | 1 | name: release | |
| 2 | 2 | ||
| 3 | - | # Once each morning, create at most one tag for the unreleased conventional | |
| 4 | - | # commits on main. GitHub schedules in UTC, so both possible UTC offsets are | |
| 5 | - | # triggered and the first step keeps only the 07:00 Europe/Paris run. | |
| 3 | + | # Pushing a v* tag publishes the release. This workflow intentionally runs on | |
| 4 | + | # both ermite (publishes to git.bnema.dev) and the GitHub mirror (publishes a | |
| 5 | + | # copy on github.com, because installed `zut update` only reads GitHub). | |
| 6 | 6 | on: | |
| 7 | - | schedule: | |
| 8 | - | - cron: "0 5,6 * * *" | |
| 9 | - | workflow_dispatch: | |
| 7 | + | push: | |
| 8 | + | tags: ["v*"] | |
| 10 | 9 | ||
| 11 | 10 | permissions: | |
| 12 | 11 | contents: write | |
| 13 | 12 | ||
| 14 | - | concurrency: | |
| 15 | - | group: release | |
| 16 | - | cancel-in-progress: false | |
| 17 | - | ||
| 18 | 13 | jobs: | |
| 19 | - | tag: | |
| 14 | + | release: | |
| 20 | 15 | runs-on: ubuntu-latest | |
| 21 | - | outputs: | |
| 22 | - | tag: ${{ steps.version.outputs.tag }} | |
| 23 | 16 | steps: | |
| 24 | - | - name: Check the Paris release time | |
| 25 | - | id: release-time | |
| 26 | - | shell: bash | |
| 27 | - | env: | |
| 28 | - | EVENT_NAME: ${{ github.event_name }} | |
| 29 | - | TZ: Europe/Paris | |
| 30 | - | run: | | |
| 31 | - | if [ "$EVENT_NAME" = workflow_dispatch ] || [ "$(date +%H)" = 07 ]; then | |
| 32 | - | echo "run=true" >> "$GITHUB_OUTPUT" | |
| 33 | - | fi | |
| 34 | - | ||
| 35 | - | - name: Check out main | |
| 36 | - | if: steps.release-time.outputs.run == 'true' | |
| 17 | + | - name: Check out the tag | |
| 37 | 18 | uses: actions/checkout@v4 | |
| 38 | 19 | with: | |
| 39 | 20 | fetch-depth: 0 | |
| 40 | - | fetch-tags: true | |
| 41 | - | ref: main | |
| 42 | 21 | ||
| 43 | - | - name: Compute the next release tag | |
| 44 | - | if: steps.release-time.outputs.run == 'true' | |
| 45 | - | id: version | |
| 46 | - | shell: bash | |
| 47 | - | run: | | |
| 48 | - | set -euo pipefail | |
| 49 | - | if git log -1 --format=%B | grep -Fq '[release=skip]'; then | |
| 50 | - | echo "The latest commit requested that the release be skipped." | |
| 51 | - | exit 0 | |
| 52 | - | fi | |
| 53 | - | tag=$(bash .github/scripts/next-release-tag.sh) | |
| 54 | - | if [ -z "$tag" ]; then | |
| 55 | - | echo "No releasable conventional commits for this run." | |
| 56 | - | exit 0 | |
| 57 | - | fi | |
| 58 | - | echo "Releasing $tag from $(git rev-parse --short HEAD)" | |
| 59 | - | echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| 22 | + | - name: Set up Go | |
| 23 | + | uses: actions/setup-go@v7 | |
| 24 | + | with: | |
| 25 | + | go-version-file: go.mod | |
| 60 | 26 | ||
| 61 | - | - name: Create and push the tag | |
| 62 | - | if: steps.version.outputs.tag != '' | |
| 27 | + | - name: Run GoReleaser | |
| 28 | + | uses: goreleaser/goreleaser-action@v6 | |
| 29 | + | with: | |
| 30 | + | distribution: goreleaser | |
| 31 | + | version: "~> v2" | |
| 32 | + | args: release --clean | |
| 63 | 33 | env: | |
| 64 | - | TAG: ${{ steps.version.outputs.tag }} | |
| 65 | - | run: | | |
| 66 | - | git config user.name "github-actions[bot]" | |
| 67 | - | git config user.email "github-actions[bot]@users.noreply.github.com" | |
| 68 | - | git tag -a "$TAG" -m "release $TAG" | |
| 69 | - | git push origin "$TAG" | |
| 70 | - | ||
| 71 | - | release: | |
| 72 | - | needs: tag | |
| 73 | - | if: needs.tag.outputs.tag != '' | |
| 74 | - | permissions: | |
| 75 | - | contents: write | |
| 76 | - | uses: bnema/gh-actions/.github/workflows/go-release.yml@main | |
| 77 | - | with: | |
| 78 | - | go-version: "1.26" | |
| 79 | - | # The reusable workflow fetches tags before this command, ensuring | |
| 80 | - | # GoReleaser produces artifacts and notes for the newly created tag. | |
| 81 | - | pre-command: git checkout --detach "${{ needs.tag.outputs.tag }}" | |
| 34 | + | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| @@ -1,81 +1,34 @@ | |||||
| 1 | name: release | 1 | name: release | ||
| 2 | 2 | ||||
| 3 | - | # Once each morning, create at most one tag for the unreleased conventional | 3 | + | # Pushing a v* tag publishes the release. This workflow intentionally runs on |
| 4 | - | # commits on main. GitHub schedules in UTC, so both possible UTC offsets are | 4 | + | # both ermite (publishes to git.bnema.dev) and the GitHub mirror (publishes a |
| 5 | - | # triggered and the first step keeps only the 07:00 Europe/Paris run. | 5 | + | # copy on github.com, because installed `zut update` only reads GitHub). |
| 6 | on: | 6 | on: | ||
| 7 | - | schedule: | 7 | + | push: |
| 8 | - | - cron: "0 5,6 * * *" | 8 | + | tags: ["v*"] |
| 9 | - | workflow_dispatch: | |||
| 10 | 9 | ||||
| 11 | permissions: | 10 | permissions: | ||
| 12 | contents: write | 11 | contents: write | ||
| 13 | 12 | ||||
| 14 | - | concurrency: | |||
| 15 | - | group: release | |||
| 16 | - | cancel-in-progress: false | |||
| 17 | - | ||||
| 18 | jobs: | 13 | jobs: | ||
| 19 | - | tag: | 14 | + | release: |
| 20 | runs-on: ubuntu-latest | 15 | runs-on: ubuntu-latest | ||
| 21 | - | outputs: | |||
| 22 | - | tag: ${{ steps.version.outputs.tag }} | |||
| 23 | steps: | 16 | steps: | ||
| 24 | - | - name: Check the Paris release time | 17 | + | - name: Check out the tag |
| 25 | - | id: release-time | |||
| 26 | - | shell: bash | |||
| 27 | - | env: | |||
| 28 | - | EVENT_NAME: ${{ github.event_name }} | |||
| 29 | - | TZ: Europe/Paris | |||
| 30 | - | run: | | |||
| 31 | - | if [ "$EVENT_NAME" = workflow_dispatch ] || [ "$(date +%H)" = 07 ]; then | |||
| 32 | - | echo "run=true" >> "$GITHUB_OUTPUT" | |||
| 33 | - | fi | |||
| 34 | - | ||||
| 35 | - | - name: Check out main | |||
| 36 | - | if: steps.release-time.outputs.run == 'true' | |||
| 37 | uses: actions/checkout@v4 | 18 | uses: actions/checkout@v4 | ||
| 38 | with: | 19 | with: | ||
| 39 | fetch-depth: 0 | 20 | fetch-depth: 0 | ||
| 40 | - | fetch-tags: true | |||
| 41 | - | ref: main | |||
| 42 | 21 | ||||
| 43 | - | - name: Compute the next release tag | 22 | + | - name: Set up Go |
| 44 | - | if: steps.release-time.outputs.run == 'true' | 23 | + | uses: actions/setup-go@v7 |
| 45 | - | id: version | 24 | + | with: |
| 46 | - | shell: bash | 25 | + | go-version-file: go.mod |
| 47 | - | run: | | |||
| 48 | - | set -euo pipefail | |||
| 49 | - | if git log -1 --format=%B | grep -Fq '[release=skip]'; then | |||
| 50 | - | echo "The latest commit requested that the release be skipped." | |||
| 51 | - | exit 0 | |||
| 52 | - | fi | |||
| 53 | - | tag=$(bash .github/scripts/next-release-tag.sh) | |||
| 54 | - | if [ -z "$tag" ]; then | |||
| 55 | - | echo "No releasable conventional commits for this run." | |||
| 56 | - | exit 0 | |||
| 57 | - | fi | |||
| 58 | - | echo "Releasing $tag from $(git rev-parse --short HEAD)" | |||
| 59 | - | echo "tag=$tag" >> "$GITHUB_OUTPUT" | |||
| 60 | 26 | ||||
| 61 | - | - name: Create and push the tag | 27 | + | - name: Run GoReleaser |
| 62 | - | if: steps.version.outputs.tag != '' | 28 | + | uses: goreleaser/goreleaser-action@v6 |
| 29 | + | with: | |||
| 30 | + | distribution: goreleaser | |||
| 31 | + | version: "~> v2" | |||
| 32 | + | args: release --clean | |||
| 63 | env: | 33 | env: | ||
| 64 | - | TAG: ${{ steps.version.outputs.tag }} | 34 | + | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 65 | - | run: | | |||
| 66 | - | git config user.name "github-actions[bot]" | |||
| 67 | - | git config user.email "github-actions[bot]@users.noreply.github.com" | |||
| 68 | - | git tag -a "$TAG" -m "release $TAG" | |||
| 69 | - | git push origin "$TAG" | |||
| 70 | - | ||||
| 71 | - | release: | |||
| 72 | - | needs: tag | |||
| 73 | - | if: needs.tag.outputs.tag != '' | |||
| 74 | - | permissions: | |||
| 75 | - | contents: write | |||
| 76 | - | uses: bnema/gh-actions/.github/workflows/go-release.yml@main | |||
| 77 | - | with: | |||
| 78 | - | go-version: "1.26" | |||
| 79 | - | # The reusable workflow fetches tags before this command, ensuring | |||
| 80 | - | # GoReleaser produces artifacts and notes for the newly created tag. | |||
| 81 | - | pre-command: git checkout --detach "${{ needs.tag.outputs.tag }}" | |||
M .goreleaser.yaml +14 -5
| @@ -1,10 +1,19 @@ | |||
| 1 | - | # GoReleaser config — builds cross-platform binaries and GitHub Release | |
| 2 | - | # artifacts for zut. The release workflow creates conventional-commit-based | |
| 3 | - | # v0.x tags, then invokes GoReleaser for that immutable tag. | |
| 1 | + | # GoReleaser config — builds cross-platform binaries and release artifacts | |
| 2 | + | # for zut. The release workflow runs GoReleaser when a v* tag is pushed. | |
| 4 | 3 | version: 2 | |
| 5 | 4 | ||
| 6 | 5 | project_name: zut | |
| 7 | 6 | ||
| 7 | + | # Releases are published on ermite. In CI the runner sets GITHUB_API_URL and | |
| 8 | + | # GITHUB_SERVER_URL to an address the job can reach; the public URLs are the | |
| 9 | + | # fallback for a local run. The release workflow also runs on the GitHub | |
| 10 | + | # mirror, so `zut update` in older builds, which only reads GitHub, still | |
| 11 | + | # updates: there GoReleaser keeps its github.com defaults. | |
| 12 | + | github_urls: | |
| 13 | + | api: '{{ if eq (index .Env "GITHUB_SERVER_URL") "https://github.com" }}https://api.github.com{{ else }}{{ or (index .Env "GITHUB_API_URL") "https://git.bnema.dev/-/github/api" }}{{ end }}/' | |
| 14 | + | upload: '{{ if eq (index .Env "GITHUB_SERVER_URL") "https://github.com" }}https://uploads.github.com{{ else }}{{ or (index .Env "GITHUB_API_URL") "https://git.bnema.dev/-/github/api" }}/uploads{{ end }}/' | |
| 15 | + | download: '{{ or (index .Env "GITHUB_SERVER_URL") "https://git.bnema.dev" }}/' | |
| 16 | + | ||
| 8 | 17 | before: | |
| 9 | 18 | hooks: | |
| 10 | 19 | - go mod tidy | |
| @@ -54,7 +63,7 @@ git: | |||
| 54 | 63 | ||
| 55 | 64 | changelog: | |
| 56 | 65 | sort: asc | |
| 57 | - | use: github | |
| 66 | + | use: git | |
| 58 | 67 | filters: | |
| 59 | 68 | # Styling-only commits are neither releasable nor useful release notes. | |
| 60 | 69 | exclude: | |
| @@ -93,7 +102,7 @@ release: | |||
| 93 | 102 | Install with: | |
| 94 | 103 | ||
| 95 | 104 | ```bash | |
| 96 | - | curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash | |
| 105 | + | curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash | |
| 97 | 106 | ``` | |
| 98 | 107 | ||
| 99 | 108 | Or download a binary below, verify it with `checksums.txt`, and add it to your `$PATH`. | |
| @@ -1,10 +1,19 @@ | |||||
| 1 | - | # GoReleaser config — builds cross-platform binaries and GitHub Release | 1 | + | # GoReleaser config — builds cross-platform binaries and release artifacts |
| 2 | - | # artifacts for zut. The release workflow creates conventional-commit-based | 2 | + | # for zut. The release workflow runs GoReleaser when a v* tag is pushed. |
| 3 | - | # v0.x tags, then invokes GoReleaser for that immutable tag. | |||
| 4 | version: 2 | 3 | version: 2 | ||
| 5 | 4 | ||||
| 6 | project_name: zut | 5 | project_name: zut | ||
| 7 | 6 | ||||
| 7 | + | # Releases are published on ermite. In CI the runner sets GITHUB_API_URL and | |||
| 8 | + | # GITHUB_SERVER_URL to an address the job can reach; the public URLs are the | |||
| 9 | + | # fallback for a local run. The release workflow also runs on the GitHub | |||
| 10 | + | # mirror, so `zut update` in older builds, which only reads GitHub, still | |||
| 11 | + | # updates: there GoReleaser keeps its github.com defaults. | |||
| 12 | + | github_urls: | |||
| 13 | + | api: '{{ if eq (index .Env "GITHUB_SERVER_URL") "https://github.com" }}https://api.github.com{{ else }}{{ or (index .Env "GITHUB_API_URL") "https://git.bnema.dev/-/github/api" }}{{ end }}/' | |||
| 14 | + | upload: '{{ if eq (index .Env "GITHUB_SERVER_URL") "https://github.com" }}https://uploads.github.com{{ else }}{{ or (index .Env "GITHUB_API_URL") "https://git.bnema.dev/-/github/api" }}/uploads{{ end }}/' | |||
| 15 | + | download: '{{ or (index .Env "GITHUB_SERVER_URL") "https://git.bnema.dev" }}/' | |||
| 16 | + | ||||
| 8 | before: | 17 | before: | ||
| 9 | hooks: | 18 | hooks: | ||
| 10 | - go mod tidy | 19 | - go mod tidy | ||
| @@ -54,7 +63,7 @@ git: | |||||
| 54 | 63 | ||||
| 55 | changelog: | 64 | changelog: | ||
| 56 | sort: asc | 65 | sort: asc | ||
| 57 | - | use: github | 66 | + | use: git |
| 58 | filters: | 67 | filters: | ||
| 59 | # Styling-only commits are neither releasable nor useful release notes. | 68 | # Styling-only commits are neither releasable nor useful release notes. | ||
| 60 | exclude: | 69 | exclude: | ||
| @@ -93,7 +102,7 @@ release: | |||||
| 93 | Install with: | 102 | Install with: | ||
| 94 | 103 | ||||
| 95 | ```bash | 104 | ```bash | ||
| 96 | - | curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash | 105 | + | curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash |
| 97 | ``` | 106 | ``` | ||
| 98 | 107 | ||||
| 99 | Or download a binary below, verify it with `checksums.txt`, and add it to your `$PATH`. | 108 | Or download a binary below, verify it with `checksums.txt`, and add it to your `$PATH`. | ||
M CONTRIBUTING.md +10 -0
| @@ -145,6 +145,16 @@ Documentation-only pull requests do not need the Go suite unless they modify exe | |||
| 145 | 145 | ||
| 146 | 146 | If a validation command cannot pass because of an unrelated repository or environment problem, describe the exact command and failure in the pull request. Do not silently omit it. | |
| 147 | 147 | ||
| 148 | + | ## Releases | |
| 149 | + | ||
| 150 | + | The maintainer publishes a release by pushing a signed version tag to the forge: | |
| 151 | + | ||
| 152 | + | ```sh | |
| 153 | + | git tag -s vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z | |
| 154 | + | ``` | |
| 155 | + | ||
| 156 | + | The tag triggers the release workflow, which runs GoReleaser and publishes the archives and `checksums.txt` on git.bnema.dev. The GitHub mirror receives the same tag and publishes a copy, because installed `zut update` builds read releases from GitHub only. | |
| 157 | + | ||
| 148 | 158 | ## Pull request shape | |
| 149 | 159 | ||
| 150 | 160 | Open the pull request against `main` and include: | |
| @@ -145,6 +145,16 @@ Documentation-only pull requests do not need the Go suite unless they modify exe | |||||
| 145 | 145 | ||||
| 146 | If a validation command cannot pass because of an unrelated repository or environment problem, describe the exact command and failure in the pull request. Do not silently omit it. | 146 | If a validation command cannot pass because of an unrelated repository or environment problem, describe the exact command and failure in the pull request. Do not silently omit it. | ||
| 147 | 147 | ||||
| 148 | + | ## Releases | |||
| 149 | + | ||||
| 150 | + | The maintainer publishes a release by pushing a signed version tag to the forge: | |||
| 151 | + | ||||
| 152 | + | ```sh | |||
| 153 | + | git tag -s vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z | |||
| 154 | + | ``` | |||
| 155 | + | ||||
| 156 | + | The tag triggers the release workflow, which runs GoReleaser and publishes the archives and `checksums.txt` on git.bnema.dev. The GitHub mirror receives the same tag and publishes a copy, because installed `zut update` builds read releases from GitHub only. | |||
| 157 | + | ||||
| 148 | ## Pull request shape | 158 | ## Pull request shape | ||
| 149 | 159 | ||||
| 150 | Open the pull request against `main` and include: | 160 | Open the pull request against `main` and include: | ||
M README.md +5 -5
| @@ -25,19 +25,19 @@ Yet another coding agent harness, lightweight and written (vibe-slopped) in go. | |||
| 25 | 25 | ### One-liner (macOS, Linux) | |
| 26 | 26 | ||
| 27 | 27 | ```bash | |
| 28 | - | curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash | |
| 28 | + | curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash | |
| 29 | 29 | ``` | |
| 30 | 30 | ||
| 31 | - | Detects your OS and architecture, downloads the latest release from GitHub, verifies the SHA-256 against the release's `checksums.txt`, extracts the binary, and drops it in `/usr/local/bin`, `~/.local/bin`, or `~/bin`, whichever is writable first. Pass a version or prefix to pin: | |
| 31 | + | Detects your OS and architecture, downloads the latest release from git.bnema.dev, verifies the SHA-256 against the release's `checksums.txt`, extracts the binary, and drops it in `/usr/local/bin`, `~/.local/bin`, or `~/bin`, whichever is writable first. Pass a version or prefix to pin: | |
| 32 | 32 | ||
| 33 | 33 | ```bash | |
| 34 | - | curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin | |
| 34 | + | curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash -s -- v0.1.0 ~/bin | |
| 35 | 35 | ``` | |
| 36 | 36 | ||
| 37 | 37 | ### One-liner (Windows, PowerShell) | |
| 38 | 38 | ||
| 39 | 39 | ```powershell | |
| 40 | - | iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex | |
| 40 | + | iwr -useb https://git.bnema.dev/zut/raw/main/install.ps1 | iex | |
| 41 | 41 | ``` | |
| 42 | 42 | ||
| 43 | 43 | Drops `zut.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards. | |
| @@ -67,7 +67,7 @@ development build. | |||
| 67 | 67 | ||
| 68 | 68 | ### Prebuilt binaries | |
| 69 | 69 | ||
| 70 | - | Every release on the [releases page](https://github.com/bnema/zut/releases) ships archives for Linux, macOS, and Windows on amd64 and arm64 (except windows/arm64), plus a `checksums.txt` file. Download, verify, `chmod +x`, and drop on your `$PATH`. | |
| 70 | + | Every release on the [releases page](https://git.bnema.dev/zut/releases) ships archives for Linux, macOS, and Windows on amd64 and arm64 (except windows/arm64), plus a `checksums.txt` file. Download, verify, `chmod +x`, and drop on your `$PATH`. | |
| 71 | 71 | ||
| 72 | 72 | ## Authenticate | |
| 73 | 73 | ||
| @@ -25,19 +25,19 @@ Yet another coding agent harness, lightweight and written (vibe-slopped) in go. | |||||
| 25 | ### One-liner (macOS, Linux) | 25 | ### One-liner (macOS, Linux) | ||
| 26 | 26 | ||||
| 27 | ```bash | 27 | ```bash | ||
| 28 | - | curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash | 28 | + | curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash |
| 29 | ``` | 29 | ``` | ||
| 30 | 30 | ||||
| 31 | - | Detects your OS and architecture, downloads the latest release from GitHub, verifies the SHA-256 against the release's `checksums.txt`, extracts the binary, and drops it in `/usr/local/bin`, `~/.local/bin`, or `~/bin`, whichever is writable first. Pass a version or prefix to pin: | 31 | + | Detects your OS and architecture, downloads the latest release from git.bnema.dev, verifies the SHA-256 against the release's `checksums.txt`, extracts the binary, and drops it in `/usr/local/bin`, `~/.local/bin`, or `~/bin`, whichever is writable first. Pass a version or prefix to pin: |
| 32 | 32 | ||||
| 33 | ```bash | 33 | ```bash | ||
| 34 | - | curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin | 34 | + | curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash -s -- v0.1.0 ~/bin |
| 35 | ``` | 35 | ``` | ||
| 36 | 36 | ||||
| 37 | ### One-liner (Windows, PowerShell) | 37 | ### One-liner (Windows, PowerShell) | ||
| 38 | 38 | ||||
| 39 | ```powershell | 39 | ```powershell | ||
| 40 | - | iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex | 40 | + | iwr -useb https://git.bnema.dev/zut/raw/main/install.ps1 | iex |
| 41 | ``` | 41 | ``` | ||
| 42 | 42 | ||||
| 43 | Drops `zut.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards. | 43 | Drops `zut.exe` into `$HOME\bin` and adds it to the user PATH if missing. Open a fresh terminal afterwards. | ||
| @@ -67,7 +67,7 @@ development build. | |||||
| 67 | 67 | ||||
| 68 | ### Prebuilt binaries | 68 | ### Prebuilt binaries | ||
| 69 | 69 | ||||
| 70 | - | Every release on the [releases page](https://github.com/bnema/zut/releases) ships archives for Linux, macOS, and Windows on amd64 and arm64 (except windows/arm64), plus a `checksums.txt` file. Download, verify, `chmod +x`, and drop on your `$PATH`. | 70 | + | Every release on the [releases page](https://git.bnema.dev/zut/releases) ships archives for Linux, macOS, and Windows on amd64 and arm64 (except windows/arm64), plus a `checksums.txt` file. Download, verify, `chmod +x`, and drop on your `$PATH`. |
| 71 | 71 | ||||
| 72 | ## Authenticate | 72 | ## Authenticate | ||
| 73 | 73 | ||||
M go.mod +3 -3
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | module github.com/bnema/zut | |
| 2 | 2 | ||
| 3 | - | go 1.26 | |
| 3 | + | go 1.27 | |
| 4 | 4 | ||
| 5 | 5 | require ( | |
| 6 | 6 | github.com/alecthomas/chroma/v2 v2.23.1 | |
| @@ -11,12 +11,12 @@ require ( | |||
| 11 | 11 | golang.org/x/image v0.43.0 | |
| 12 | 12 | golang.org/x/mod v0.35.0 | |
| 13 | 13 | golang.org/x/net v0.57.0 | |
| 14 | + | golang.org/x/sys v0.47.0 | |
| 15 | + | golang.org/x/term v0.45.0 | |
| 14 | 16 | ) | |
| 15 | 17 | ||
| 16 | 18 | require ( | |
| 17 | 19 | github.com/dlclark/regexp2 v1.11.5 // indirect | |
| 18 | 20 | github.com/kylelemons/godebug v1.1.0 // indirect | |
| 19 | 21 | github.com/rivo/uniseg v0.2.0 // indirect | |
| 20 | - | golang.org/x/sys v0.47.0 | |
| 21 | - | golang.org/x/term v0.45.0 | |
| 22 | 22 | ) | |
| @@ -1,6 +1,6 @@ | |||||
| 1 | module github.com/bnema/zut | 1 | module github.com/bnema/zut | ||
| 2 | 2 | ||||
| 3 | - | go 1.26 | 3 | + | go 1.27 |
| 4 | 4 | ||||
| 5 | require ( | 5 | require ( | ||
| 6 | github.com/alecthomas/chroma/v2 v2.23.1 | 6 | github.com/alecthomas/chroma/v2 v2.23.1 | ||
| @@ -11,12 +11,12 @@ require ( | |||||
| 11 | golang.org/x/image v0.43.0 | 11 | golang.org/x/image v0.43.0 | ||
| 12 | golang.org/x/mod v0.35.0 | 12 | golang.org/x/mod v0.35.0 | ||
| 13 | golang.org/x/net v0.57.0 | 13 | golang.org/x/net v0.57.0 | ||
| 14 | + | golang.org/x/sys v0.47.0 | |||
| 15 | + | golang.org/x/term v0.45.0 | |||
| 14 | ) | 16 | ) | ||
| 15 | 17 | ||||
| 16 | require ( | 18 | require ( | ||
| 17 | github.com/dlclark/regexp2 v1.11.5 // indirect | 19 | github.com/dlclark/regexp2 v1.11.5 // indirect | ||
| 18 | github.com/kylelemons/godebug v1.1.0 // indirect | 20 | github.com/kylelemons/godebug v1.1.0 // indirect | ||
| 19 | github.com/rivo/uniseg v0.2.0 // indirect | 21 | github.com/rivo/uniseg v0.2.0 // indirect | ||
| 20 | - | golang.org/x/sys v0.47.0 | |||
| 21 | - | golang.org/x/term v0.45.0 | |||
| 22 | ) | 22 | ) | ||
M install.ps1 +11 -25
| @@ -1,23 +1,18 @@ | |||
| 1 | 1 | # zut installer for Windows (PowerShell). | |
| 2 | 2 | # | |
| 3 | 3 | # Usage (in PowerShell): | |
| 4 | - | # iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex | |
| 4 | + | # iwr -useb https://git.bnema.dev/zut/raw/main/install.ps1 | iex | |
| 5 | 5 | # | |
| 6 | 6 | # Or with arguments: | |
| 7 | 7 | # $env:ZUT_VERSION = "v0.1.0" | |
| 8 | 8 | # $env:ZUT_PREFIX = "$HOME\bin" | |
| 9 | - | # iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex | |
| 9 | + | # iwr -useb https://git.bnema.dev/zut/raw/main/install.ps1 | iex | |
| 10 | 10 | # | |
| 11 | - | # Detects architecture, downloads the matching .zip from the GitHub | |
| 11 | + | # Detects architecture, downloads the matching .zip from the forge | |
| 12 | 12 | # release, verifies the sha256 against checksums.txt, extracts zut.exe, | |
| 13 | 13 | # and moves it into $ZUT_PREFIX (defaults to $HOME\bin, added to PATH | |
| 14 | 14 | # via the User environment if missing). | |
| 15 | 15 | # | |
| 16 | - | # $env:GITHUB_TOKEN is optional for the public repo. Set it to a PAT | |
| 17 | - | # with `contents:read` scope if you hit GitHub API rate limits (or if | |
| 18 | - | # you are installing from a private fork); the script then uses it for | |
| 19 | - | # the version lookup and every download. | |
| 20 | - | ||
| 21 | 16 | ||
| 22 | 17 | [CmdletBinding()] | |
| 23 | 18 | param( | |
| @@ -31,11 +26,6 @@ $owner = "bnema" | |||
| 31 | 26 | $repo = "zut" | |
| 32 | 27 | $binary = "zut" | |
| 33 | 28 | ||
| 34 | - | # Build Authorization header list once; used on every HTTP call so the | |
| 35 | - | # script works against private repos when $env:GITHUB_TOKEN is set. | |
| 36 | - | $headers = @{} | |
| 37 | - | if ($env:GITHUB_TOKEN) { $headers["Authorization"] = "Bearer $($env:GITHUB_TOKEN)" } | |
| 38 | - | ||
| 39 | 29 | if (-not $Version) { $Version = "latest" } | |
| 40 | 30 | if (-not $Prefix) { $Prefix = Join-Path $HOME "bin" } | |
| 41 | 31 | ||
| @@ -60,7 +50,7 @@ if ($arch -eq "arm64") { | |||
| 60 | 50 | ||
| 61 | 51 | # ---- resolve version ---- | |
| 62 | 52 | # | |
| 63 | - | # Resolve "latest" through the GitHub releases API. This works the same | |
| 53 | + | # Resolve "latest" through the forge's GitHub-compatible releases API. This works the same | |
| 64 | 54 | # on Windows PowerShell 5.1 and PowerShell 7+, unlike scraping the | |
| 65 | 55 | # /releases/latest redirect target: on PS7 the final URL lives at | |
| 66 | 56 | # $resp.BaseResponse.RequestMessage.RequestUri while on PS5.1 it is | |
| @@ -69,12 +59,8 @@ if ($arch -eq "arm64") { | |||
| 69 | 59 | # to scrape. | |
| 70 | 60 | ||
| 71 | 61 | if ($Version -eq "latest") { | |
| 72 | - | $apiUrl = "https://api.github.com/repos/$owner/$repo/releases/latest" | |
| 73 | - | # GitHub's API wants a User-Agent; Invoke-RestMethod sets one, but be | |
| 74 | - | # explicit so corporate proxies that strip it don't trip a 403. | |
| 75 | - | $apiHeaders = @{} + $headers | |
| 76 | - | if (-not $apiHeaders.ContainsKey("User-Agent")) { $apiHeaders["User-Agent"] = "zut-installer" } | |
| 77 | - | $apiHeaders["Accept"] = "application/vnd.github+json" | |
| 62 | + | $apiUrl = "https://git.bnema.dev/-/github/api/repos/$owner/$repo/releases/latest" | |
| 63 | + | $apiHeaders = @{ "User-Agent" = "zut-installer"; "Accept" = "application/json" } | |
| 78 | 64 | ||
| 79 | 65 | try { | |
| 80 | 66 | $api = Invoke-RestMethod -UseBasicParsing -Headers $apiHeaders -Uri $apiUrl | |
| @@ -84,7 +70,7 @@ if ($Version -eq "latest") { | |||
| 84 | 70 | if ($status -eq 404) { | |
| 85 | 71 | Die "no published release found for $owner/$repo (the repo may have no releases yet)" | |
| 86 | 72 | } elseif ($status -eq 401 -or $status -eq 403) { | |
| 87 | - | Die "GitHub API request was rejected ($status). If the repo is private, set `$env:GITHUB_TOKEN to a PAT with contents:read; otherwise you may be rate-limited (try again later or set `$env:GITHUB_TOKEN)." | |
| 73 | + | Die "forge API request was rejected ($status); try again later" | |
| 88 | 74 | } else { | |
| 89 | 75 | Die "could not resolve latest version: $($_.Exception.Message)" | |
| 90 | 76 | } | |
| @@ -92,7 +78,7 @@ if ($Version -eq "latest") { | |||
| 92 | 78 | ||
| 93 | 79 | $Version = $api.tag_name | |
| 94 | 80 | if (-not $Version) { | |
| 95 | - | Die "could not resolve latest version: GitHub API returned no tag_name for $owner/$repo" | |
| 81 | + | Die "could not resolve latest version: forge API returned no tag_name for $owner/$repo" | |
| 96 | 82 | } | |
| 97 | 83 | } | |
| 98 | 84 | ||
| @@ -102,7 +88,7 @@ $verNum = $Version.TrimStart("v") | |||
| 102 | 88 | # ---- download + verify + extract ---- | |
| 103 | 89 | ||
| 104 | 90 | $archive = "${binary}_${verNum}_windows_${arch}.zip" | |
| 105 | - | $baseUrl = "https://github.com/$owner/$repo/releases/download/$Version" | |
| 91 | + | $baseUrl = "https://git.bnema.dev/$repo/releases/download/$Version" | |
| 106 | 92 | $archiveUrl = "$baseUrl/$archive" | |
| 107 | 93 | $checksumUrl = "$baseUrl/checksums.txt" | |
| 108 | 94 | ||
| @@ -110,11 +96,11 @@ $tmp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("zut-install-" + | |||
| 110 | 96 | ||
| 111 | 97 | try { | |
| 112 | 98 | Msg "downloading $archive" | |
| 113 | - | Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri $archiveUrl -OutFile (Join-Path $tmp $archive) | |
| 99 | + | Invoke-WebRequest -UseBasicParsing -Uri $archiveUrl -OutFile (Join-Path $tmp $archive) | |
| 114 | 100 | ||
| 115 | 101 | Msg "verifying checksum" | |
| 116 | 102 | $checksumFile = Join-Path $tmp "checksums.txt" | |
| 117 | - | Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri $checksumUrl -OutFile $checksumFile | |
| 103 | + | Invoke-WebRequest -UseBasicParsing -Uri $checksumUrl -OutFile $checksumFile | |
| 118 | 104 | $expected = Get-Content -LiteralPath $checksumFile | ForEach-Object { | |
| 119 | 105 | $line = $_.Trim() | |
| 120 | 106 | if ($line) { | |
| @@ -1,23 +1,18 @@ | |||||
| 1 | # zut installer for Windows (PowerShell). | 1 | # zut installer for Windows (PowerShell). | ||
| 2 | # | 2 | # | ||
| 3 | # Usage (in PowerShell): | 3 | # Usage (in PowerShell): | ||
| 4 | - | # iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex | 4 | + | # iwr -useb https://git.bnema.dev/zut/raw/main/install.ps1 | iex |
| 5 | # | 5 | # | ||
| 6 | # Or with arguments: | 6 | # Or with arguments: | ||
| 7 | # $env:ZUT_VERSION = "v0.1.0" | 7 | # $env:ZUT_VERSION = "v0.1.0" | ||
| 8 | # $env:ZUT_PREFIX = "$HOME\bin" | 8 | # $env:ZUT_PREFIX = "$HOME\bin" | ||
| 9 | - | # iwr -useb https://raw.githubusercontent.com/bnema/zut/main/install.ps1 | iex | 9 | + | # iwr -useb https://git.bnema.dev/zut/raw/main/install.ps1 | iex |
| 10 | # | 10 | # | ||
| 11 | - | # Detects architecture, downloads the matching .zip from the GitHub | 11 | + | # Detects architecture, downloads the matching .zip from the forge |
| 12 | # release, verifies the sha256 against checksums.txt, extracts zut.exe, | 12 | # release, verifies the sha256 against checksums.txt, extracts zut.exe, | ||
| 13 | # and moves it into $ZUT_PREFIX (defaults to $HOME\bin, added to PATH | 13 | # and moves it into $ZUT_PREFIX (defaults to $HOME\bin, added to PATH | ||
| 14 | # via the User environment if missing). | 14 | # via the User environment if missing). | ||
| 15 | # | 15 | # | ||
| 16 | - | # $env:GITHUB_TOKEN is optional for the public repo. Set it to a PAT | |||
| 17 | - | # with `contents:read` scope if you hit GitHub API rate limits (or if | |||
| 18 | - | # you are installing from a private fork); the script then uses it for | |||
| 19 | - | # the version lookup and every download. | |||
| 20 | - | ||||
| 21 | 16 | ||||
| 22 | [CmdletBinding()] | 17 | [CmdletBinding()] | ||
| 23 | param( | 18 | param( | ||
| @@ -31,11 +26,6 @@ $owner = "bnema" | |||||
| 31 | $repo = "zut" | 26 | $repo = "zut" | ||
| 32 | $binary = "zut" | 27 | $binary = "zut" | ||
| 33 | 28 | ||||
| 34 | - | # Build Authorization header list once; used on every HTTP call so the | |||
| 35 | - | # script works against private repos when $env:GITHUB_TOKEN is set. | |||
| 36 | - | $headers = @{} | |||
| 37 | - | if ($env:GITHUB_TOKEN) { $headers["Authorization"] = "Bearer $($env:GITHUB_TOKEN)" } | |||
| 38 | - | ||||
| 39 | if (-not $Version) { $Version = "latest" } | 29 | if (-not $Version) { $Version = "latest" } | ||
| 40 | if (-not $Prefix) { $Prefix = Join-Path $HOME "bin" } | 30 | if (-not $Prefix) { $Prefix = Join-Path $HOME "bin" } | ||
| 41 | 31 | ||||
| @@ -60,7 +50,7 @@ if ($arch -eq "arm64") { | |||||
| 60 | 50 | ||||
| 61 | # ---- resolve version ---- | 51 | # ---- resolve version ---- | ||
| 62 | # | 52 | # | ||
| 63 | - | # Resolve "latest" through the GitHub releases API. This works the same | 53 | + | # Resolve "latest" through the forge's GitHub-compatible releases API. This works the same |
| 64 | # on Windows PowerShell 5.1 and PowerShell 7+, unlike scraping the | 54 | # on Windows PowerShell 5.1 and PowerShell 7+, unlike scraping the | ||
| 65 | # /releases/latest redirect target: on PS7 the final URL lives at | 55 | # /releases/latest redirect target: on PS7 the final URL lives at | ||
| 66 | # $resp.BaseResponse.RequestMessage.RequestUri while on PS5.1 it is | 56 | # $resp.BaseResponse.RequestMessage.RequestUri while on PS5.1 it is | ||
| @@ -69,12 +59,8 @@ if ($arch -eq "arm64") { | |||||
| 69 | # to scrape. | 59 | # to scrape. | ||
| 70 | 60 | ||||
| 71 | if ($Version -eq "latest") { | 61 | if ($Version -eq "latest") { | ||
| 72 | - | $apiUrl = "https://api.github.com/repos/$owner/$repo/releases/latest" | 62 | + | $apiUrl = "https://git.bnema.dev/-/github/api/repos/$owner/$repo/releases/latest" |
| 73 | - | # GitHub's API wants a User-Agent; Invoke-RestMethod sets one, but be | 63 | + | $apiHeaders = @{ "User-Agent" = "zut-installer"; "Accept" = "application/json" } |
| 74 | - | # explicit so corporate proxies that strip it don't trip a 403. | |||
| 75 | - | $apiHeaders = @{} + $headers | |||
| 76 | - | if (-not $apiHeaders.ContainsKey("User-Agent")) { $apiHeaders["User-Agent"] = "zut-installer" } | |||
| 77 | - | $apiHeaders["Accept"] = "application/vnd.github+json" | |||
| 78 | 64 | ||||
| 79 | try { | 65 | try { | ||
| 80 | $api = Invoke-RestMethod -UseBasicParsing -Headers $apiHeaders -Uri $apiUrl | 66 | $api = Invoke-RestMethod -UseBasicParsing -Headers $apiHeaders -Uri $apiUrl | ||
| @@ -84,7 +70,7 @@ if ($Version -eq "latest") { | |||||
| 84 | if ($status -eq 404) { | 70 | if ($status -eq 404) { | ||
| 85 | Die "no published release found for $owner/$repo (the repo may have no releases yet)" | 71 | Die "no published release found for $owner/$repo (the repo may have no releases yet)" | ||
| 86 | } elseif ($status -eq 401 -or $status -eq 403) { | 72 | } elseif ($status -eq 401 -or $status -eq 403) { | ||
| 87 | - | Die "GitHub API request was rejected ($status). If the repo is private, set `$env:GITHUB_TOKEN to a PAT with contents:read; otherwise you may be rate-limited (try again later or set `$env:GITHUB_TOKEN)." | 73 | + | Die "forge API request was rejected ($status); try again later" |
| 88 | } else { | 74 | } else { | ||
| 89 | Die "could not resolve latest version: $($_.Exception.Message)" | 75 | Die "could not resolve latest version: $($_.Exception.Message)" | ||
| 90 | } | 76 | } | ||
| @@ -92,7 +78,7 @@ if ($Version -eq "latest") { | |||||
| 92 | 78 | ||||
| 93 | $Version = $api.tag_name | 79 | $Version = $api.tag_name | ||
| 94 | if (-not $Version) { | 80 | if (-not $Version) { | ||
| 95 | - | Die "could not resolve latest version: GitHub API returned no tag_name for $owner/$repo" | 81 | + | Die "could not resolve latest version: forge API returned no tag_name for $owner/$repo" |
| 96 | } | 82 | } | ||
| 97 | } | 83 | } | ||
| 98 | 84 | ||||
| @@ -102,7 +88,7 @@ $verNum = $Version.TrimStart("v") | |||||
| 102 | # ---- download + verify + extract ---- | 88 | # ---- download + verify + extract ---- | ||
| 103 | 89 | ||||
| 104 | $archive = "${binary}_${verNum}_windows_${arch}.zip" | 90 | $archive = "${binary}_${verNum}_windows_${arch}.zip" | ||
| 105 | - | $baseUrl = "https://github.com/$owner/$repo/releases/download/$Version" | 91 | + | $baseUrl = "https://git.bnema.dev/$repo/releases/download/$Version" |
| 106 | $archiveUrl = "$baseUrl/$archive" | 92 | $archiveUrl = "$baseUrl/$archive" | ||
| 107 | $checksumUrl = "$baseUrl/checksums.txt" | 93 | $checksumUrl = "$baseUrl/checksums.txt" | ||
| 108 | 94 | ||||
| @@ -110,11 +96,11 @@ $tmp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("zut-install-" + | |||||
| 110 | 96 | ||||
| 111 | try { | 97 | try { | ||
| 112 | Msg "downloading $archive" | 98 | Msg "downloading $archive" | ||
| 113 | - | Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri $archiveUrl -OutFile (Join-Path $tmp $archive) | 99 | + | Invoke-WebRequest -UseBasicParsing -Uri $archiveUrl -OutFile (Join-Path $tmp $archive) |
| 114 | 100 | ||||
| 115 | Msg "verifying checksum" | 101 | Msg "verifying checksum" | ||
| 116 | $checksumFile = Join-Path $tmp "checksums.txt" | 102 | $checksumFile = Join-Path $tmp "checksums.txt" | ||
| 117 | - | Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri $checksumUrl -OutFile $checksumFile | 103 | + | Invoke-WebRequest -UseBasicParsing -Uri $checksumUrl -OutFile $checksumFile |
| 118 | $expected = Get-Content -LiteralPath $checksumFile | ForEach-Object { | 104 | $expected = Get-Content -LiteralPath $checksumFile | ForEach-Object { | ||
| 119 | $line = $_.Trim() | 105 | $line = $_.Trim() | ||
| 120 | if ($line) { | 106 | if ($line) { | ||
M install.sh +13 -39
| @@ -3,8 +3,8 @@ | |||
| 3 | 3 | # zut installer. | |
| 4 | 4 | # | |
| 5 | 5 | # Usage: | |
| 6 | - | # curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash | |
| 7 | - | # curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin | |
| 6 | + | # curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash | |
| 7 | + | # curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash -s -- v0.1.0 ~/bin | |
| 8 | 8 | # | |
| 9 | 9 | # Positional arguments: | |
| 10 | 10 | # $1 version — release tag (e.g. v0.1.0). Defaults to "latest". | |
| @@ -16,19 +16,15 @@ | |||
| 16 | 16 | # Environment overrides: | |
| 17 | 17 | # ZUT_VERSION same as $1 | |
| 18 | 18 | # ZUT_PREFIX same as $2 | |
| 19 | - | # GITHUB_TOKEN personal access token — required while the repo is | |
| 20 | - | # private, ignored once it goes public. Must have at | |
| 21 | - | # least `contents:read` scope on the zut repository. | |
| 22 | 19 | # | |
| 23 | 20 | # The script detects your OS and architecture, downloads the matching | |
| 24 | - | # archive from the GitHub release, verifies the sha256 against the | |
| 21 | + | # archive from the forge release, verifies the sha256 against the | |
| 25 | 22 | # release's checksums.txt, extracts the binary, and moves it into the | |
| 26 | 23 | # prefix directory. No sudo unless you explicitly pick a prefix that | |
| 27 | 24 | # needs it. | |
| 28 | 25 | ||
| 29 | 26 | set -euo pipefail | |
| 30 | 27 | ||
| 31 | - | OWNER="bnema" | |
| 32 | 28 | REPO="zut" | |
| 33 | 29 | BINARY="zut" | |
| 34 | 30 | ||
| @@ -42,22 +38,6 @@ die() { printf "\033[31merror:\033[0m %s\n" "$*" >&2; exit 1; } | |||
| 42 | 38 | command -v curl >/dev/null 2>&1 || die "curl is required" | |
| 43 | 39 | command -v tar >/dev/null 2>&1 || die "tar is required" | |
| 44 | 40 | ||
| 45 | - | # CURL_AUTH is prepended to every curl invocation so private-repo | |
| 46 | - | # downloads work while $GITHUB_TOKEN is set. Empty array when the repo | |
| 47 | - | # is public. | |
| 48 | - | # | |
| 49 | - | # Note the "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" pattern at every call | |
| 50 | - | # site: bash 3.2 (the default /bin/bash on macOS) treats an | |
| 51 | - | # unquoted empty-array expansion as an unbound variable under | |
| 52 | - | # `set -u` and aborts with "CURL_AUTH[@]: unbound variable". The | |
| 53 | - | # guard expands to nothing when the array is empty and to the | |
| 54 | - | # array's contents when it isn't. Bash 4+ doesn't need this, but | |
| 55 | - | # the installer's primary audience is `curl | bash` on macOS. | |
| 56 | - | CURL_AUTH=() | |
| 57 | - | if [ -n "${GITHUB_TOKEN:-}" ]; then | |
| 58 | - | CURL_AUTH=(-H "Authorization: Bearer $GITHUB_TOKEN") | |
| 59 | - | fi | |
| 60 | - | ||
| 61 | 41 | # ---- detect OS + arch ---- | |
| 62 | 42 | ||
| 63 | 43 | uname_s=$(uname -s) | |
| @@ -81,18 +61,12 @@ esac | |||
| 81 | 61 | # ---- resolve version ---- | |
| 82 | 62 | ||
| 83 | 63 | if [ "$VERSION" = "latest" ]; then | |
| 84 | - | # Private-repo friendly: hit the api, grab tag_name. Falls back to | |
| 85 | - | # following the /releases/latest redirect on public repos. | |
| 86 | - | if [ ${#CURL_AUTH[@]} -gt 0 ]; then | |
| 87 | - | VERSION=$(curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" \ | |
| 88 | - | "https://api.github.com/repos/${OWNER}/${REPO}/releases/latest" \ | |
| 89 | - | | sed -nE 's/.*"tag_name": *"([^"]+)".*/\1/p' | head -n1) | |
| 90 | - | else | |
| 91 | - | VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \ | |
| 92 | - | "https://github.com/${OWNER}/${REPO}/releases/latest" \ | |
| 93 | - | | sed -E 's|.*/tag/([^/]+).*|\1|') | |
| 94 | - | fi | |
| 95 | - | [ -n "$VERSION" ] || die "could not resolve latest version (set GITHUB_TOKEN if the repo is private)" | |
| 64 | + | # The forge answers /releases/latest with a redirect to | |
| 65 | + | # /releases/tag/<tag>; read the tag from the final URL. | |
| 66 | + | VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \ | |
| 67 | + | "https://git.bnema.dev/${REPO}/releases/latest" \ | |
| 68 | + | | sed -E 's|.*/tag/([^/]+).*|\1|') | |
| 69 | + | [ -n "$VERSION" ] || die "could not resolve latest version" | |
| 96 | 70 | fi | |
| 97 | 71 | ||
| 98 | 72 | case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac | |
| @@ -126,7 +100,7 @@ mkdir -p "$PREFIX" | |||
| 126 | 100 | # ---- download + verify + extract ---- | |
| 127 | 101 | ||
| 128 | 102 | ARCHIVE="${BINARY}_${VER_NUM}_${OS}_${ARCH}.tar.gz" | |
| 129 | - | BASE_URL="https://github.com/${OWNER}/${REPO}/releases/download/${VERSION}" | |
| 103 | + | BASE_URL="https://git.bnema.dev/${REPO}/releases/download/${VERSION}" | |
| 130 | 104 | ARCHIVE_URL="${BASE_URL}/${ARCHIVE}" | |
| 131 | 105 | CHECKSUMS_URL="${BASE_URL}/checksums.txt" | |
| 132 | 106 | ||
| @@ -134,11 +108,11 @@ TMP=$(mktemp -d) | |||
| 134 | 108 | trap 'rm -rf "$TMP"' EXIT | |
| 135 | 109 | ||
| 136 | 110 | msg "downloading ${ARCHIVE}" | |
| 137 | - | curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" -o "$TMP/$ARCHIVE" "$ARCHIVE_URL" \ | |
| 138 | - | || die "download failed: $ARCHIVE_URL (set GITHUB_TOKEN if the repo is private)" | |
| 111 | + | curl -fsSL -o "$TMP/$ARCHIVE" "$ARCHIVE_URL" \ | |
| 112 | + | || die "download failed: $ARCHIVE_URL" | |
| 139 | 113 | ||
| 140 | 114 | msg "verifying checksum" | |
| 141 | - | curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" -o "$TMP/checksums.txt" "$CHECKSUMS_URL" \ | |
| 115 | + | curl -fsSL -o "$TMP/checksums.txt" "$CHECKSUMS_URL" \ | |
| 142 | 116 | || die "download failed: $CHECKSUMS_URL" | |
| 143 | 117 | ||
| 144 | 118 | expected=$(grep " ${ARCHIVE}\$" "$TMP/checksums.txt" | awk '{print $1}' || true) | |
| @@ -3,8 +3,8 @@ | |||||
| 3 | # zut installer. | 3 | # zut installer. | ||
| 4 | # | 4 | # | ||
| 5 | # Usage: | 5 | # Usage: | ||
| 6 | - | # curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash | 6 | + | # curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash |
| 7 | - | # curl -fsSL https://raw.githubusercontent.com/bnema/zut/main/install.sh | bash -s -- v0.1.0 ~/bin | 7 | + | # curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash -s -- v0.1.0 ~/bin |
| 8 | # | 8 | # | ||
| 9 | # Positional arguments: | 9 | # Positional arguments: | ||
| 10 | # $1 version — release tag (e.g. v0.1.0). Defaults to "latest". | 10 | # $1 version — release tag (e.g. v0.1.0). Defaults to "latest". | ||
| @@ -16,19 +16,15 @@ | |||||
| 16 | # Environment overrides: | 16 | # Environment overrides: | ||
| 17 | # ZUT_VERSION same as $1 | 17 | # ZUT_VERSION same as $1 | ||
| 18 | # ZUT_PREFIX same as $2 | 18 | # ZUT_PREFIX same as $2 | ||
| 19 | - | # GITHUB_TOKEN personal access token — required while the repo is | |||
| 20 | - | # private, ignored once it goes public. Must have at | |||
| 21 | - | # least `contents:read` scope on the zut repository. | |||
| 22 | # | 19 | # | ||
| 23 | # The script detects your OS and architecture, downloads the matching | 20 | # The script detects your OS and architecture, downloads the matching | ||
| 24 | - | # archive from the GitHub release, verifies the sha256 against the | 21 | + | # archive from the forge release, verifies the sha256 against the |
| 25 | # release's checksums.txt, extracts the binary, and moves it into the | 22 | # release's checksums.txt, extracts the binary, and moves it into the | ||
| 26 | # prefix directory. No sudo unless you explicitly pick a prefix that | 23 | # prefix directory. No sudo unless you explicitly pick a prefix that | ||
| 27 | # needs it. | 24 | # needs it. | ||
| 28 | 25 | ||||
| 29 | set -euo pipefail | 26 | set -euo pipefail | ||
| 30 | 27 | ||||
| 31 | - | OWNER="bnema" | |||
| 32 | REPO="zut" | 28 | REPO="zut" | ||
| 33 | BINARY="zut" | 29 | BINARY="zut" | ||
| 34 | 30 | ||||
| @@ -42,22 +38,6 @@ die() { printf "\033[31merror:\033[0m %s\n" "$*" >&2; exit 1; } | |||||
| 42 | command -v curl >/dev/null 2>&1 || die "curl is required" | 38 | command -v curl >/dev/null 2>&1 || die "curl is required" | ||
| 43 | command -v tar >/dev/null 2>&1 || die "tar is required" | 39 | command -v tar >/dev/null 2>&1 || die "tar is required" | ||
| 44 | 40 | ||||
| 45 | - | # CURL_AUTH is prepended to every curl invocation so private-repo | |||
| 46 | - | # downloads work while $GITHUB_TOKEN is set. Empty array when the repo | |||
| 47 | - | # is public. | |||
| 48 | - | # | |||
| 49 | - | # Note the "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" pattern at every call | |||
| 50 | - | # site: bash 3.2 (the default /bin/bash on macOS) treats an | |||
| 51 | - | # unquoted empty-array expansion as an unbound variable under | |||
| 52 | - | # `set -u` and aborts with "CURL_AUTH[@]: unbound variable". The | |||
| 53 | - | # guard expands to nothing when the array is empty and to the | |||
| 54 | - | # array's contents when it isn't. Bash 4+ doesn't need this, but | |||
| 55 | - | # the installer's primary audience is `curl | bash` on macOS. | |||
| 56 | - | CURL_AUTH=() | |||
| 57 | - | if [ -n "${GITHUB_TOKEN:-}" ]; then | |||
| 58 | - | CURL_AUTH=(-H "Authorization: Bearer $GITHUB_TOKEN") | |||
| 59 | - | fi | |||
| 60 | - | ||||
| 61 | # ---- detect OS + arch ---- | 41 | # ---- detect OS + arch ---- | ||
| 62 | 42 | ||||
| 63 | uname_s=$(uname -s) | 43 | uname_s=$(uname -s) | ||
| @@ -81,18 +61,12 @@ esac | |||||
| 81 | # ---- resolve version ---- | 61 | # ---- resolve version ---- | ||
| 82 | 62 | ||||
| 83 | if [ "$VERSION" = "latest" ]; then | 63 | if [ "$VERSION" = "latest" ]; then | ||
| 84 | - | # Private-repo friendly: hit the api, grab tag_name. Falls back to | 64 | + | # The forge answers /releases/latest with a redirect to |
| 85 | - | # following the /releases/latest redirect on public repos. | 65 | + | # /releases/tag/<tag>; read the tag from the final URL. |
| 86 | - | if [ ${#CURL_AUTH[@]} -gt 0 ]; then | 66 | + | VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \ |
| 87 | - | VERSION=$(curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" \ | 67 | + | "https://git.bnema.dev/${REPO}/releases/latest" \ |
| 88 | - | "https://api.github.com/repos/${OWNER}/${REPO}/releases/latest" \ | 68 | + | | sed -E 's|.*/tag/([^/]+).*|\1|') |
| 89 | - | | sed -nE 's/.*"tag_name": *"([^"]+)".*/\1/p' | head -n1) | 69 | + | [ -n "$VERSION" ] || die "could not resolve latest version" |
| 90 | - | else | |||
| 91 | - | VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \ | |||
| 92 | - | "https://github.com/${OWNER}/${REPO}/releases/latest" \ | |||
| 93 | - | | sed -E 's|.*/tag/([^/]+).*|\1|') | |||
| 94 | - | fi | |||
| 95 | - | [ -n "$VERSION" ] || die "could not resolve latest version (set GITHUB_TOKEN if the repo is private)" | |||
| 96 | fi | 70 | fi | ||
| 97 | 71 | ||||
| 98 | case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac | 72 | case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac | ||
| @@ -126,7 +100,7 @@ mkdir -p "$PREFIX" | |||||
| 126 | # ---- download + verify + extract ---- | 100 | # ---- download + verify + extract ---- | ||
| 127 | 101 | ||||
| 128 | ARCHIVE="${BINARY}_${VER_NUM}_${OS}_${ARCH}.tar.gz" | 102 | ARCHIVE="${BINARY}_${VER_NUM}_${OS}_${ARCH}.tar.gz" | ||
| 129 | - | BASE_URL="https://github.com/${OWNER}/${REPO}/releases/download/${VERSION}" | 103 | + | BASE_URL="https://git.bnema.dev/${REPO}/releases/download/${VERSION}" |
| 130 | ARCHIVE_URL="${BASE_URL}/${ARCHIVE}" | 104 | ARCHIVE_URL="${BASE_URL}/${ARCHIVE}" | ||
| 131 | CHECKSUMS_URL="${BASE_URL}/checksums.txt" | 105 | CHECKSUMS_URL="${BASE_URL}/checksums.txt" | ||
| 132 | 106 | ||||
| @@ -134,11 +108,11 @@ TMP=$(mktemp -d) | |||||
| 134 | trap 'rm -rf "$TMP"' EXIT | 108 | trap 'rm -rf "$TMP"' EXIT | ||
| 135 | 109 | ||||
| 136 | msg "downloading ${ARCHIVE}" | 110 | msg "downloading ${ARCHIVE}" | ||
| 137 | - | curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" -o "$TMP/$ARCHIVE" "$ARCHIVE_URL" \ | 111 | + | curl -fsSL -o "$TMP/$ARCHIVE" "$ARCHIVE_URL" \ |
| 138 | - | || die "download failed: $ARCHIVE_URL (set GITHUB_TOKEN if the repo is private)" | 112 | + | || die "download failed: $ARCHIVE_URL" |
| 139 | 113 | ||||
| 140 | msg "verifying checksum" | 114 | msg "verifying checksum" | ||
| 141 | - | curl -fsSL "${CURL_AUTH[@]+"${CURL_AUTH[@]}"}" -o "$TMP/checksums.txt" "$CHECKSUMS_URL" \ | 115 | + | curl -fsSL -o "$TMP/checksums.txt" "$CHECKSUMS_URL" \ |
| 142 | || die "download failed: $CHECKSUMS_URL" | 116 | || die "download failed: $CHECKSUMS_URL" | ||
| 143 | 117 | ||||
| 144 | expected=$(grep " ${ARCHIVE}\$" "$TMP/checksums.txt" | awk '{print $1}' || true) | 118 | expected=$(grep " ${ARCHIVE}\$" "$TMP/checksums.txt" | awk '{print $1}' || true) | ||