2 Commits
Author SHA1 Message Date
simonfrankenandClaude Opus 5.5 a8133b4884 Make GH_TOKEN the main setting in .env.template
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:42:48 +01:00
simonfrankenandClaude Opus 5.5 1ee394cfdf Move releases to GitHub, with one bridge release on Gitea
GitHub has no generic package registry, so releases become GitHub
release assets. The updater and install.sh read releases/latest/download
and releases/download/<v>/, plain redirects with no API rate limit.
make release publishes a draft with all assets, then makes it latest.
make release-gitea publishes the same version to the old Gitea registry
for binaries that still update from there, and refuses until GitHub
serves it. See D33.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:38:50 +01:00
12 changed files with 183 additions and 93 deletions
+59 -45
View File
@@ -1,23 +1,23 @@
---
name: release
description: Prepare and publish a submarine release. Finds the latest release, picks the next semver from the changes since, runs a release check, creates and pushes the git tag, hands over to the developer for `make release`, then creates the Gitea release with MCP. Use only when the user asks to release.
description: Prepare and publish a submarine release. Finds the latest release, picks the next semver from the changes since, runs a release check, creates and pushes the git tag, writes the release notes, hands over to the developer for `make release`, then verifies the GitHub release. Use only when the user asks to release.
disable-model-invocation: true
---
# Release submarine
A release is built on the developer's machine (`make release`, see
`docs/architecture.md#distribution`). `make release` publishes the binaries
of the **clean `vX.Y.Z` tag at HEAD** to the Gitea package registry. This
skill does everything around it. The developer runs `make release`
themselves, in the middle: you never run it and never read or print `.env`
or `GITEA_TOKEN`.
`docs/architecture.md#distribution`). `make release` creates the GitHub
release of the **clean `vX.Y.Z` tag at HEAD**, with the binaries as assets.
This skill does everything around it. The developer runs `make release`
themselves, in the middle: you never run it and never read or print `.env`,
`GH_TOKEN` or `GITEA_TOKEN`.
Pushing a tag and creating a release are outward-facing and hard to undo.
Do them only after the confirmation in step 5, and never force-push, move or
delete a tag without asking.
Gitea: owner `simonfranken`, repo `submarine`, `https://git.simon-franken.de`.
GitHub: owner `simonfranken`, repo `submarine`, remote `origin`.
## 1. Preconditions
@@ -29,7 +29,7 @@ git branch --show-current # main
git status --porcelain # empty: make release refuses a dirty tree
git rev-parse HEAD origin/main # equal: HEAD is pushed, nothing to release from a local-only commit
git tag --points-at HEAD # empty: HEAD is not released yet
grep -q '^GITEA_TOKEN=.\+' .env # exists: only test it, never print it
gh auth status >/dev/null 2>&1 || grep -q '^GH_TOKEN=.\+' .env # one holds: only test, never print
```
## 2. Latest release
@@ -38,11 +38,12 @@ grep -q '^GITEA_TOKEN=.\+' .env # exists: only test it, never print i
git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -1
```
Cross-check with the `mcp__gitea__get_latest_release` MCP tool (load it with
ToolSearch first). If the tag and the Gitea release differ (a tag without a
Cross-check with the `mcp__github__get_latest_release` MCP tool (load it with
ToolSearch first). If the tag and the GitHub release differ (a tag without a
release is normal for the one in progress, a release without a tag is not),
tell the user and ask before going on. With no tag at all, the first release
is `v0.1.0`.
is `v0.1.0`. While the Makefile has `release-gitea` (step 10), GitHub has
no releases yet: cross-check with `mcp__gitea__get_latest_release` instead.
## 3. Changes since
@@ -55,7 +56,7 @@ No commits since the last tag: stop, there is nothing to release. Read the
commits, and where a subject is not clear, the diff. Sort each change as:
- **breaking**: removes or changes a key, a command or flag, the on-disk or
registry contract (`install.sh`, asset names, `submarine-latest/latest/VERSION`,
release contract (`install.sh`, asset names, `latest/download/VERSION`,
`submarine update`), or behavior users rely on;
- **feature**: a new key, action, view or option, or a visible behavior;
- **fix / other**: bug fixes, docs, refactoring, tests, performance.
@@ -108,51 +109,64 @@ git ls-remote --tags origin vX.Y.Z # the tag is on the remote
If the push fails, report git's message. Do not retry with force. If the tag
is wrong after it was pushed, ask before deleting it.
## 7. Hand over: the developer runs `make release`
## 7. Write the release notes
Write them to `/tmp/submarine-vX.Y.Z-notes.md` (outside the repository:
`make release` refuses a dirty tree). They are for users of the tool, not a
commit dump: short, grouped under **Breaking changes** (only if any),
**New**, **Fixes** (leave out empty groups and pure refactoring/tests), each
item one line naming the key or behavior (`b` switches the branch of a
repository...). End with:
```
Install or update: `curl -fsSL https://raw.githubusercontent.com/simonfranken/submarine/main/install.sh | sh`, or `submarine update`.
Full changelog: https://github.com/simonfranken/submarine/compare/<last>...vX.Y.Z
```
Show the notes to the user; they can change them before the next step.
## 8. Hand over: the developer runs `make release`
HEAD is now the clean tag, which is what `make release` needs. Tell the
developer to run, in the repository:
```sh
make release
make release NOTES=/tmp/submarine-vX.Y.Z-notes.md
```
(it reads `GITEA_TOKEN` from `.env`, tests, builds, uploads, and points
`submarine-latest` at the version last). **Stop here and wait** until they
say it finished. If it failed, help with the output; do not run it yourself.
(it uses `gh`, tests, builds, creates the release as a draft with all
assets and publishes it last). **Stop here and wait** until they say it
finished. If it failed, help with the output; do not run it yourself. A
leftover draft from a failed run must be deleted (ask first) before a retry.
## 8. Verify the upload, create the Gitea release
## 9. Verify the release
Once the developer reports success:
```sh
curl -fsSL https://github.com/simonfranken/submarine/releases/latest/download/VERSION
```
must print `vX.Y.Z` (this is what `install.sh` and `submarine update`
read). Check with `mcp__github__get_release_by_tag` that it is not a draft
and has the assets `submarine_{linux,darwin}_{amd64,arm64}`,
`checksums.txt` and `VERSION`. If anything is missing, report it. Finish
with the link to the release and the version.
## 10. Bridge release (only while the Makefile has `release-gitea`)
The first GitHub release is also published to the old Gitea registry, so
binaries that still update from Gitea pick it up (D33). After step 9, and
after asking the user, push the tag to the `gitea` remote and hand over
again:
```sh
git push gitea main vX.Y.Z
make release-gitea # run by the developer, needs GITEA_TOKEN
curl -fsS https://git.simon-franken.de/api/packages/simonfranken/generic/submarine-latest/latest/VERSION
```
must print `vX.Y.Z` (the registry is public; `install.sh` reads it the same
way). If it doesn't, the upload did not complete: do not create the release,
report it.
Then create the release with `mcp__gitea__create_release` (load it with
ToolSearch first):
- `owner`: `simonfranken`, `repo`: `submarine`
- `tag_name`: `vX.Y.Z`
- `target`: the tag's commit, `git rev-parse vX.Y.Z^{commit}`
- `title`: `vX.Y.Z`
- `is_draft`: false, `is_pre_release`: false
- `body`: release notes, see below
Release notes are for users of the tool, not a commit dump: short, grouped
under **Breaking changes** (only if any), **New**, **Fixes** (leave out empty
groups and pure refactoring/tests), each item one line naming the key or
behavior (`b` switches the branch of a repository...). End with:
```
Install or update: `curl -fsSL https://git.simon-franken.de/simonfranken/submarine/raw/branch/main/install.sh | sh`, or `submarine update`.
Full changelog: https://git.simon-franken.de/simonfranken/submarine/compare/<last>...vX.Y.Z
```
The binaries live in the package registry, not on the release: attach
nothing. Finish with the link to the release and the version.
The last command must print `vX.Y.Z`. Then create a Gitea release for the
tag with `mcp__gitea__create_release`, its body saying that submarine moved
to https://github.com/simonfranken/submarine.
+9 -3
View File
@@ -1,8 +1,14 @@
# Copy to .env (gitignored); the Makefile loads it automatically.
# Access token with write:package scope. Required for `make release`.
GITEA_TOKEN=
# GitHub token for `make release` (gh): classic with the `repo` scope, or
# fine-grained with Contents read/write on simonfranken/submarine.
GH_TOKEN=
# Optional overrides (defaults shown).
# Optional override (default shown).
# GITHUB_REPO=simonfranken/submarine
# Bridge release only (`make release-gitea`, see docs/decisions.md D33):
# Gitea token with the write:package scope. Remove once the bridge is out.
GITEA_TOKEN=
# GITEA_URL=https://git.simon-franken.de
# GITEA_OWNER=simonfranken
+2 -2
View File
@@ -22,7 +22,7 @@ go run ./cmd/submarine [dir] # run on a repository (default: cwd)
go run ./cmd/submarine --mock # run on mock data
scripts/make-fixture.sh DIR # build a test superproject in DIR
make dist # cross-compile into dist/
GITEA_TOKEN=… make release # publish a tagged release (outward-facing; run by the developer)
make release NOTES=FILE # publish a tagged release to GitHub (outward-facing; run by the developer)
go vet ./...
go test ./...
gofmt -l . # must print nothing
@@ -32,7 +32,7 @@ gofmt -l . # must print nothing
Use the `release` skill (`/release`, `.claude/skills/release/SKILL.md`): it
picks the next semver, runs a check, tags and pushes, asks the developer to
run `make release`, then creates the Gitea release. Never run `make release`
run `make release`, then verifies the GitHub release. Never run `make release`
or push tags outside of it.
## Rules
+33 -9
View File
@@ -1,12 +1,16 @@
# Build and publish submarine. Releases are built locally and uploaded to the
# Gitea generic package registry (see docs/architecture.md#distribution).
# Build and publish submarine. Releases are built locally and uploaded as
# GitHub release assets (see docs/architecture.md#distribution).
#
# make dist cross-compile into dist/
# GITEA_TOKEN=... make release test, build and publish the tagged HEAD
# make dist cross-compile into dist/
# make release NOTES=FILE test, build and publish the tagged HEAD (gh)
# GITEA_TOKEN=... make release-gitea bridge only: the same version to the old Gitea registry
# Settings from .env (see .env.template); `make VAR=...` on the command line wins.
-include .env
export GITEA_TOKEN
export GITEA_TOKEN GH_TOKEN
GITHUB_REPO ?= simonfranken/submarine
RELEASES := https://github.com/$(GITHUB_REPO)/releases
GITEA_URL ?= https://git.simon-franken.de
GITEA_OWNER ?= simonfranken
@@ -17,7 +21,7 @@ LDFLAGS := -s -w -X main.version=$(VERSION)
PLATFORMS := linux_amd64 linux_arm64 darwin_amd64 darwin_arm64
BINARIES := $(addprefix dist/submarine_,$(PLATFORMS))
.PHONY: build test vet dist release clean FORCE
.PHONY: build test vet dist check-tag release release-gitea clean FORCE
build:
go build -ldflags '$(LDFLAGS)' -o submarine ./cmd/submarine
@@ -41,11 +45,31 @@ dist/submarine_%: FORCE
dist/checksums.txt: $(BINARIES)
cd dist && sha256sum $(notdir $(BINARIES)) > checksums.txt
release:
@test -n "$$GITEA_TOKEN" || { echo "GITEA_TOKEN is not set (needs write:package)"; exit 1; }
check-tag:
@echo '$(VERSION)' | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$$' || \
{ echo "VERSION '$(VERSION)' is not a clean tag: tag HEAD as vX.Y.Z with a clean tree"; exit 1; }
@test -z "$$(git status --porcelain)" || { echo "working tree is not clean"; exit 1; }
# A draft gets all assets first and is published last, so releases/latest
# never points at a release with missing files.
release: check-tag
@test -n "$(NOTES)" && test -f "$(NOTES)" || { echo "NOTES=FILE with the release notes is required"; exit 1; }
@command -v gh >/dev/null || { echo "gh (GitHub CLI) is required"; exit 1; }
@gh auth status >/dev/null 2>&1 || { echo "gh is not logged in: run gh auth login or set GH_TOKEN"; exit 1; }
$(MAKE) clean test vet dist VERSION=$(VERSION)
printf '%s\n' '$(VERSION)' > dist/VERSION
gh release create '$(VERSION)' --repo '$(GITHUB_REPO)' --verify-tag --draft \
--title '$(VERSION)' --notes-file '$(NOTES)' $(BINARIES) dist/checksums.txt dist/VERSION
gh release edit '$(VERSION)' --repo '$(GITHUB_REPO)' --draft=false --latest
@echo "released $(VERSION): $(RELEASES)/tag/$(VERSION)"
# Bridge release for binaries that still update from Gitea: same version,
# old registry layout. Refuses until GitHub serves this version, so a binary
# updated from Gitea finds its release on GitHub. Remove after the bridge.
release-gitea: check-tag
@test -n "$$GITEA_TOKEN" || { echo "GITEA_TOKEN is not set (needs write:package)"; exit 1; }
@test "$$(curl -fsSL '$(RELEASES)/latest/download/VERSION' | tr -d '[:space:]')" = '$(VERSION)' || \
{ echo "GitHub does not serve $(VERSION) as latest yet: run make release first"; exit 1; }
$(MAKE) clean test vet dist VERSION=$(VERSION)
@set -e; \
for f in $(BINARIES) dist/checksums.txt; do \
@@ -59,7 +83,7 @@ release:
"$(GITEA_URL)/api/v1/packages/$(GITEA_OWNER)/generic/submarine-latest/latest" >/dev/null || true; \
curl -fsS -X PUT -H "Authorization: token $$GITEA_TOKEN" \
--upload-file dist/VERSION "$(REGISTRY)/submarine-latest/latest/VERSION" >/dev/null; \
echo "released $(VERSION)"
echo "released $(VERSION) to Gitea"
clean:
rm -rf dist submarine
+1 -1
View File
@@ -38,7 +38,7 @@ the questions you actually have:
## Install
```sh
curl -fsSL https://git.simon-franken.de/simonfranken/submarine/raw/branch/main/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/simonfranken/submarine/main/install.sh | sh
```
Installs `sub` and `submarine` (linux/macOS, amd64/arm64) to `~/.local/bin`.
+1 -1
View File
@@ -93,7 +93,7 @@ func main() {
}
// latestRelease returns the version of a newer release, or "" if there is
// none or the registry can't be reached within a few seconds.
// none or GitHub can't be reached within a few seconds.
func latestRelease(ctx context.Context) string {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
+2 -2
View File
@@ -20,12 +20,12 @@ bare commit hash.
## Install
```sh
curl -fsSL https://git.simon-franken.de/simonfranken/submarine/raw/branch/main/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/simonfranken/submarine/main/install.sh | sh
```
Installs `submarine` and the alias `sub` (linux/macOS, amd64/arm64) to
`~/.local/bin`. Update with `submarine update`. Maintainers publish with
`GITEA_TOKEN=… make release` (see [architecture](architecture.md#distribution)).
`make release NOTES=…` (see [architecture](architecture.md#distribution)).
## Status
+17 -10
View File
@@ -23,7 +23,7 @@ internal/submodule/git/ provider using the git CLI (reads, fetch, watch)
switch.go Branches, SwitchLoss, Switch (stage the tip, restore, attach or fast-forward the branch)
gitdir.go resolves .git dirs and gitdir: files
internal/submodule/mock/ fixture provider with artificial latency
internal/update/ self-update from the package registry; NO UI imports
internal/update/ self-update from GitHub releases; NO UI imports
internal/tui/ Bubble Tea application
app.go root model: layout, message routing, loading
keys.go key bindings (also feed the help view)
@@ -203,17 +203,24 @@ No CI runner: releases are built and published from a developer machine.
- `make dist` cross-compiles `CGO_ENABLED=0` binaries for linux/darwin ×
amd64/arm64 into `dist/` plus `checksums.txt`; the version comes from
`git describe` and is baked in with `-X main.version`.
- `make release` with `GITEA_TOKEN` set, e.g. in `.env` (copy `.env.template`; token needs `write:package`; HEAD must be a
clean `vX.Y.Z` tag) runs tests, builds and uploads to the Gitea generic
registry `https://git.simon-franken.de/api/packages/simonfranken/generic`:
- `submarine/<version>/submarine_<os>_<arch>` and `checksums.txt`
- `submarine-latest/latest/VERSION`, replaced **last**, so a failed upload
never advertises a broken version.
- `make release NOTES=FILE` (needs `gh` logged in, or `GH_TOKEN` in `.env`,
see `.env.template`; HEAD must be a clean `vX.Y.Z` tag pushed to GitHub)
runs tests, builds and creates the GitHub release `vX.Y.Z` with the notes
and the assets `submarine_<os>_<arch>`, `checksums.txt` and `VERSION`. It
is created as a **draft** and published last, so `releases/latest` never
points at a release with missing assets. Clients read, without the API
(plain redirects, no rate limit), below
`https://github.com/simonfranken/submarine/releases`:
- `latest/download/VERSION`
- `download/<version>/submarine_<os>_<arch>` and `checksums.txt`
- Before the move to GitHub, releases lived in the Gitea generic registry
(`git.simon-franken.de/api/packages/simonfranken/generic`, see D33). It
stays online for old binaries. `make release-gitea` publishes the bridge
version there in the old layout and refuses until GitHub serves it.
- The `release` skill (`.claude/skills/release/SKILL.md`) drives a release:
latest tag → next semver from the commits since → check (`vet`, `test`,
`gofmt`, `make dist`) → tag and push → the developer runs `make release` →
the skill verifies the registry's `VERSION` and creates the Gitea release
with MCP (notes only; the binaries stay in the registry).
the skill verifies that `latest/download/VERSION` serves the new version.
- `install.sh` (`curl -fsSL …/raw/branch/main/install.sh | sh`) detects
OS/arch, verifies the sha256, installs `submarine` and a `sub` symlink to
`~/.local/bin` (`SUBMARINE_INSTALL_DIR`, `SUBMARINE_VERSION`,
@@ -221,7 +228,7 @@ No CI runner: releases are built and published from a developer machine.
- `submarine update [--check] [--force]` (package `internal/update`) reads
`VERSION`, downloads the asset for the running platform, verifies the
checksum and atomically replaces the real executable (symlinks resolved,
so `sub` works too). `SUBMARINE_UPDATE_URL` overrides the registry.
so `sub` works too). `SUBMARINE_UPDATE_URL` overrides the releases root.
- On every start the TUI looks for a newer release in the background (3 s
timeout, no cache, no blocking). `main` passes `tui.WithUpdateCheck` and
the TUI gets the result as a message; the footer then offers `q quit and
+29 -1
View File
@@ -201,7 +201,8 @@ package so versions stay clean.
corrupt or partial downloads, not a compromised registry (no signatures).
The update check is a deliberate, outside-the-provider network use: only
on `update` and a background check at startup that can be disabled (it
was once a day; see D27).
was once a day; see D27). The registry was replaced by GitHub releases in
D33; local builds, `install.sh` and the pointer file stay.
## Copy path with `c` via OSC 52
@@ -634,3 +635,30 @@ skipped. Contextual palettes that grey out impossible keys: larger, kept for
later (item 10 of the usability plan).
**Consequences.** `footer.Keys` drops `help.KeyMap` for `Tools`,
`HelpGroups` and `LegendKeys`; the footer renders the full help itself.
## D33 — Releases move to GitHub, with one bridge release on Gitea
**Context.** The repository moves from `git.simon-franken.de` (Gitea) to
`github.com/simonfranken/submarine`. Installed binaries only know the Gitea
registry (D21), and check it on every start (D27).
**Decision.**
- Releases are GitHub releases with the binaries, `checksums.txt` and
`VERSION` as assets. GitHub Packages has no generic registry. Clients read
`releases/latest/download/VERSION` and `releases/download/<v>/<asset>`:
plain redirects, not the API, so the check on every start is not rate
limited and needs no token.
- `make release` creates a draft, uploads all assets, then publishes: the
draft takes the role of D21's "pointer last".
- One bridge version, the first that reads from GitHub, is also published to
Gitea in the old layout (`make release-gitea`). Old binaries update to it
from Gitea, and from then on update from GitHub. `release-gitea` refuses
until GitHub serves the version, so no bridge binary finds GitHub empty.
- The Gitea registry stays online with the bridge as its latest version, and
the Gitea repository is archived, not deleted: a binary started for the
first time in months still finds its way over.
**Alternatives.** Keep Gitea and mirror to GitHub: two places to publish
to. Have the updater try both hosts: permanent code for a one-time move.
**Consequences.** `SUBMARINE_UPDATE_URL` and `SUBMARINE_BASE_URL` now take
a releases root, not a registry root. Versions before the bridge exist only
on Gitea; `SUBMARINE_VERSION=<old>` with the new `install.sh` doesn't find
them. `release-gitea` and the Gitea settings go once the bridge is out.
+6 -6
View File
@@ -1,14 +1,14 @@
#!/bin/sh
# Install submarine (and the `sub` alias).
# curl -fsSL https://git.simon-franken.de/simonfranken/submarine/raw/branch/main/install.sh | sh
# curl -fsSL https://raw.githubusercontent.com/simonfranken/submarine/main/install.sh | sh
#
# Environment:
# SUBMARINE_VERSION install this version (default: latest)
# SUBMARINE_INSTALL_DIR target directory (default: ~/.local/bin)
# SUBMARINE_BASE_URL registry root (default: the Gitea generic registry)
# SUBMARINE_BASE_URL releases root (default: the GitHub releases)
set -eu
base="${SUBMARINE_BASE_URL:-https://git.simon-franken.de/api/packages/simonfranken/generic}"
base="${SUBMARINE_BASE_URL:-https://github.com/simonfranken/submarine/releases}"
dir="${SUBMARINE_INSTALL_DIR:-$HOME/.local/bin}"
die() { echo "install: $*" >&2; exit 1; }
@@ -35,7 +35,7 @@ fi
version="${SUBMARINE_VERSION:-}"
if [ -z "$version" ]; then
version="$(curl -fsSL "$base/submarine-latest/latest/VERSION")" || die "cannot determine the latest version"
version="$(curl -fsSL "$base/latest/download/VERSION")" || die "cannot determine the latest version"
version="$(printf '%s' "$version" | tr -d '[:space:]')"
fi
case "$version" in
@@ -48,8 +48,8 @@ tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT INT TERM
echo "Installing submarine $version ($os/$arch) to $dir"
curl -fsSL -o "$tmp/$asset" "$base/submarine/$version/$asset" || die "download failed: $asset"
curl -fsSL -o "$tmp/checksums.txt" "$base/submarine/$version/checksums.txt" || die "download failed: checksums.txt"
curl -fsSL -o "$tmp/$asset" "$base/download/$version/$asset" || die "download failed: $asset"
curl -fsSL -o "$tmp/checksums.txt" "$base/download/$version/checksums.txt" || die "download failed: checksums.txt"
want="$(awk -v a="$asset" '$2 == a || $2 == "*" a { print $1 }' "$tmp/checksums.txt")"
[ -n "$want" ] || die "no checksum for $asset"
+12 -10
View File
@@ -1,5 +1,5 @@
// Package update implements self-update from the Gitea generic package
// registry. It has no UI imports; the network is only used on explicit calls.
// Package update implements self-update from GitHub release assets. It has no
// UI imports; the network is only used on explicit calls.
package update
import (
@@ -19,16 +19,18 @@ import (
"time"
)
// DefaultBaseURL is the registry root; see docs/architecture.md#distribution.
const DefaultBaseURL = "https://git.simon-franken.de/api/packages/simonfranken/generic"
// DefaultBaseURL is the releases root; see docs/architecture.md#distribution.
// Both paths below it are plain redirects to the assets, not API calls, so
// they are not rate limited.
const DefaultBaseURL = "https://github.com/simonfranken/submarine/releases"
// EnvBaseURL overrides the registry root (tests, manual verification).
// EnvBaseURL overrides the releases root (tests, manual verification).
const EnvBaseURL = "SUBMARINE_UPDATE_URL"
// maxBinary bounds a download so a bad server cannot fill the disk.
const maxBinary = 256 << 20
// Client talks to the registry.
// Client talks to the releases.
type Client struct {
BaseURL string
HTTP *http.Client
@@ -36,7 +38,7 @@ type Client struct {
GOARCH string
}
// New returns a client for the default (or $SUBMARINE_UPDATE_URL) registry.
// New returns a client for the default (or $SUBMARINE_UPDATE_URL) releases.
func New() *Client {
base := os.Getenv(EnvBaseURL)
if base == "" {
@@ -52,13 +54,13 @@ func New() *Client {
// Latest returns the newest released version, e.g. "v0.2.0".
func (c *Client) Latest(ctx context.Context) (string, error) {
b, err := c.get(ctx, c.BaseURL+"/submarine-latest/latest/VERSION", 1<<10)
b, err := c.get(ctx, c.BaseURL+"/latest/download/VERSION", 1<<10)
if err != nil {
return "", err
}
v := strings.TrimSpace(string(b))
if _, ok := parse(v); !ok {
return "", fmt.Errorf("registry returned an invalid version %q", v)
return "", fmt.Errorf("releases returned an invalid version %q", v)
}
return v, nil
}
@@ -120,7 +122,7 @@ func (c *Client) Install(ctx context.Context, version string) (string, error) {
// InstallTo is Install with an explicit target path.
func (c *Client) InstallTo(ctx context.Context, version, target string) error {
asset := fmt.Sprintf("submarine_%s_%s", c.GOOS, c.GOARCH)
base := c.BaseURL + "/submarine/" + version + "/"
base := c.BaseURL + "/download/" + version + "/"
sums, err := c.get(ctx, base+"checksums.txt", 1<<20)
if err != nil {
+12 -3
View File
@@ -32,16 +32,25 @@ func TestNewer(t *testing.T) {
}
}
// registry mimics GitHub releases: every asset URL redirects to storage.
func registry(t *testing.T, bin, sum string) *Client {
t.Helper()
mux := http.NewServeMux()
mux.HandleFunc("/submarine-latest/latest/VERSION", func(w http.ResponseWriter, _ *http.Request) {
redirect := func(from, to string) {
mux.HandleFunc(from, func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, to, http.StatusFound)
})
}
redirect("/latest/download/VERSION", "/assets/VERSION")
redirect("/download/v0.2.0/checksums.txt", "/assets/checksums.txt")
redirect("/download/v0.2.0/submarine_linux_arm64", "/assets/submarine_linux_arm64")
mux.HandleFunc("/assets/VERSION", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("v0.2.0\n"))
})
mux.HandleFunc("/submarine/v0.2.0/checksums.txt", func(w http.ResponseWriter, _ *http.Request) {
mux.HandleFunc("/assets/checksums.txt", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte(sum + " submarine_linux_arm64\n"))
})
mux.HandleFunc("/submarine/v0.2.0/submarine_linux_arm64", func(w http.ResponseWriter, _ *http.Request) {
mux.HandleFunc("/assets/submarine_linux_arm64", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte(bin))
})
srv := httptest.NewServer(mux)