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>
This commit is contained in:
2026-10-25 22:38:50 +01:00
co-authored by Claude Opus 5.5
parent c9932932bb
commit 1ee394cfdf
12 changed files with 182 additions and 93 deletions
+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)