Skip to content

Commit bc843b4

Browse files
gustavobertoiclaude
andcommitted
fix: apply self-update/README review findings (5 confirmed)
- install.sh: send the GitHub token on the asset + checksums downloads (not just API reads), so a PRIVATE-repo install actually works with GITHUB_TOKEN. curl drops the auth header on the cross-host CDN redirect (default since 7.58), so the token never leaks to storage. Verified end-to-end against the private v0.1.0 release. - README: note that the curl|sh one-liner needs a token while the repo is private (accurate token-row wording) and add an Uninstall section (binary + alias symlinks + XDG state). - selfupdate: IsDevBuild now matches the git-describe shape via an anchored regex instead of a bare "-g" substring, so valid release tags like v1.0.0-grpc.1 are not misclassified as dev builds (+ test). extractBinary uses bytes.NewReader instead of copying the whole archive through a string. make ci + shellcheck green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 55dc8e0 commit bc843b4

5 files changed

Lines changed: 34 additions & 7 deletions

File tree

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ The installer detects your OS/arch, downloads the matching archive from
4848
[GitHub Releases](https://github.com/open-source-cloud/devstack/releases),
4949
**verifies its SHA-256 checksum**, and installs to `$XDG_BIN_HOME` (or `~/.local/bin`).
5050

51+
> **Note:** once the repository's releases are public this works with no auth.
52+
> While the repo is private, export a token first — e.g.
53+
> `export GITHUB_TOKEN="$(gh auth token)"`.
54+
5155
<details>
5256
<summary>Installer options &amp; alternatives</summary>
5357

@@ -65,7 +69,7 @@ DEVSTACK_ALIASES="rq uranus" \
6569
| `DEVSTACK_INSTALL_DIR` | `$XDG_BIN_HOME` or `~/.local/bin` | Where to install the binary. |
6670
| `DEVSTACK_ALIASES` | *(none)* | Space-separated `argv[0]` alias symlinks. |
6771
| `DEVSTACK_NO_VERIFY` | `0` | Skip checksum verification (not recommended). |
68-
| `GITHUB_TOKEN` / `GH_TOKEN` | *(none)* | Auth for the GitHub API (higher rate limits). |
72+
| `GITHUB_TOKEN` / `GH_TOKEN` | *(none)* | GitHub auth — **required while the repo is private**; also raises API rate limits. |
6973

7074
**From source** (needs Go 1.25+): `make install` (builds a CGO-free static binary
7175
into `$XDG_BIN_HOME`). **Linux packages**: `.deb` / `.rpm` are attached to each release.
@@ -81,6 +85,18 @@ devstack self update # download + checksum-verify + atomically replace in pla
8185
`self update` refuses to overwrite a Homebrew/dpkg/rpm-managed binary, printing the
8286
right `brew`/`apt`/`dnf` command instead.
8387

88+
### Uninstall
89+
90+
```bash
91+
rm "$(command -v devstack)" # remove the binary
92+
rm -f "$HOME/.local/bin/rq" "$HOME/.local/bin/uranus" # remove any argv[0] alias symlinks you added
93+
```
94+
95+
From a source install, `make uninstall` removes the binary. Machine-global state
96+
(the SQLite ledger, alias registry, template cache) lives under your XDG data/
97+
config/cache dirs (`~/.local/share/devstack`, `~/.config/devstack`, …); a managed
98+
teardown (`workspace destroy`) is on the roadmap.
99+
84100
## Quickstart
85101

86102
`devstack` discovers your workspace by walking up from the current directory for a

install.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ have() { command -v "$1" >/dev/null 2>&1; }
3737

3838
if have curl; then
3939
dl() { curl -fsSL "$1"; }
40-
dl_to() { curl -fsSL -o "$2" "$1"; }
41-
# api adds the auth header (when a token is set) for GitHub API reads only.
40+
# dl_to and api send the token when set so PRIVATE-repo asset/API access works.
41+
# curl drops the Authorization header on the cross-host redirect to the asset
42+
# CDN (default since 7.58), so the token never leaks to storage.
43+
dl_to() { if [ -n "$TOKEN" ]; then curl -fsSL -H "Authorization: Bearer $TOKEN" -o "$2" "$1"; else curl -fsSL -o "$2" "$1"; fi; }
4244
api() { if [ -n "$TOKEN" ]; then curl -fsSL -H "Authorization: Bearer $TOKEN" "$1"; else curl -fsSL "$1"; fi; }
4345
elif have wget; then
4446
dl() { wget -qO- "$1"; }
45-
dl_to() { wget -qO "$2" "$1"; }
47+
dl_to() { if [ -n "$TOKEN" ]; then wget -qO "$2" --header="Authorization: Bearer $TOKEN" "$1"; else wget -qO "$2" "$1"; fi; }
4648
api() { if [ -n "$TOKEN" ]; then wget -qO- --header="Authorization: Bearer $TOKEN" "$1"; else wget -qO- "$1"; fi; }
4749
else
4850
die "need curl or wget to download devstack"

internal/selfupdate/selfupdate.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ import (
1515
"fmt"
1616
"io"
1717
"net/http"
18+
"regexp"
1819
"strings"
1920
"time"
2021

2122
"golang.org/x/mod/semver"
2223
)
2324

25+
// describeRe matches the `git describe` suffix (e.g. -11-gabc1234) so a real
26+
// release tag whose prerelease merely starts with 'g' (v1.0.0-grpc.1) is not
27+
// mistaken for a dev build.
28+
var describeRe = regexp.MustCompile(`-[0-9]+-g[0-9a-f]+`)
29+
2430
// Repo is the GitHub repository releases are pulled from.
2531
const Repo = "open-source-cloud/devstack"
2632

@@ -66,7 +72,7 @@ func Check(ctx context.Context, current string) (*CheckResult, error) {
6672
// `git describe` build like v0.1.0-11-gabc1234, or a -dirty tree) for which
6773
// semver comparison against a release tag is meaningless.
6874
func IsDevBuild(v string) bool {
69-
return v == "" || v == "dev" || strings.Contains(v, "-g") || strings.HasSuffix(v, "-dirty") || !semver.IsValid(v)
75+
return v == "" || v == "dev" || strings.HasSuffix(v, "-dirty") || describeRe.MatchString(v) || !semver.IsValid(v)
7076
}
7177

7278
// LatestTag resolves the newest release tag, trying /releases/latest first and

internal/selfupdate/selfupdate_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ func TestIsDevBuild(t *testing.T) {
2222
t.Errorf("IsDevBuild(%q) = false, want true", v)
2323
}
2424
}
25-
rel := []string{"v0.1.0", "v1.2.3", "v0.2.0"}
25+
// Valid release tags, including prereleases whose identifier merely starts
26+
// with 'g' — must NOT be treated as dev builds.
27+
rel := []string{"v0.1.0", "v1.2.3", "v0.2.0", "v1.0.0-rc.1", "v1.0.0-grpc.1", "v1.0.0-gke", "v1.0.0-go.1"}
2628
for _, v := range rel {
2729
if IsDevBuild(v) {
2830
t.Errorf("IsDevBuild(%q) = true, want false", v)

internal/selfupdate/update.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package selfupdate
22

33
import (
44
"archive/tar"
5+
"bytes"
56
"compress/gzip"
67
"context"
78
"crypto/sha256"
@@ -128,7 +129,7 @@ func checksumFor(sums, filename string) string {
128129

129130
// extractBinary returns the named file's bytes from a .tar.gz archive.
130131
func extractBinary(archive []byte, name string) ([]byte, error) {
131-
gz, err := gzip.NewReader(strings.NewReader(string(archive)))
132+
gz, err := gzip.NewReader(bytes.NewReader(archive))
132133
if err != nil {
133134
return nil, fmt.Errorf("gunzip release: %w", err)
134135
}

0 commit comments

Comments
 (0)