From fd28391d2bf9b3a3f6584a360b5b10bf59c12fcf Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Wed, 5 Aug 2026 12:45:48 -0400 Subject: [PATCH] feat(install): install without reaching github.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New `SOCKET_PATCH_BASE_URL` points the archive downloads at any releases base that answers GitHub's two asset paths, `/latest/download/` and `/download/v/`: curl -fsSL https://install.socket.dev/patch \ | SOCKET_PATCH_BASE_URL=https://install.socket.dev/SocketDev/socket-patch/releases sh install.socket.dev relays exactly those paths from the GitHub release (SocketDev/depscan#23909), which is why one template covers both origins and the script needs no branching for it. Whichever origin is used, the archive is still verified against the SHA256SUMS fetched from that same origin — the change moves who serves the bytes, not how they are checked. A new socket-patch release needs no publish for any of this: the origin resolves "latest" per request against the upstream release. Also adds `SOCKET_PATCH_INSTALL_DIR`, which wins over both defaults. It is what unprivileged installs into a toolchain-managed prefix need, and it is what makes the script testable without writing to a system path — used by the two new CI steps below. The default origin stays GitHub in this commit. Flipping it is one line, held until the relay is verified in prod: a script that defaults to a host which does not answer yet is a broken installer for everyone running it from a git checkout or the raw GitHub URL. CI gains two steps. One installs through a non-default base (GitHub's own, the same URL shape the relay serves) so the template is covered unconditionally. The other installs through install.socket.dev and asserts the installed version matches what that host reports as latest — skipping itself with a notice until the host resolves, so it is inert until the relay ships rather than red from merge. Verified locally against the real relay logic: latest and pinned installs both succeeded through it, checksums verified, and `socket-patch --update` worked through the same host with no CLI changes via SOCKET_UPDATE_BASE_URL (--dry-run resolved latest; `--update 3.2.0` downloaded and swapped). Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 33 ++++++++++++++++++++++++ CHANGELOG.md | 12 +++++++++ README.md | 9 +++++++ docs/installer-hosting.md | 53 ++++++++++++++++++++++++++++++++++++--- scripts/install.sh | 42 ++++++++++++++++++++++++++----- 5 files changed, 139 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0c5570..2cdb34d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,39 @@ jobs: command -v socket-patch socket-patch --version + - name: Shell — run the installer against an alternate origin + # Exercises SOCKET_PATCH_BASE_URL (and SOCKET_PATCH_INSTALL_DIR) with a + # base that is not the default. Uses GitHub's own releases base, which + # is the same URL shape install.socket.dev serves, so the template the + # script builds is covered regardless of whether the Socket relay is + # deployed yet. The dedicated Socket-origin check is the next step. + run: | + SOCKET_PATCH_BASE_URL=https://github.com/SocketDev/socket-patch/releases \ + SOCKET_PATCH_INSTALL_DIR="$RUNNER_TEMP/alt-origin" \ + sh scripts/install.sh + "$RUNNER_TEMP/alt-origin/socket-patch" --version + + - name: Shell — install through install.socket.dev, once it exists + # The whole point of the relay is that a client never has to reach + # github.com. That is only assertable against the deployed host, so this + # step skips itself until the host resolves rather than being red from + # the day it merges (same posture as the installer-drift workflow). + run: | + if ! curl -sfI -m 20 https://install.socket.dev/patch/latest >/dev/null 2>&1; then + echo "::notice::install.socket.dev/patch/latest does not answer yet — skipping the Socket-origin install." + exit 0 + fi + latest=$(curl -fsSL -m 20 https://install.socket.dev/patch/latest) + echo "install.socket.dev reports latest=$latest" + SOCKET_PATCH_BASE_URL=https://install.socket.dev/SocketDev/socket-patch/releases \ + SOCKET_PATCH_INSTALL_DIR="$RUNNER_TEMP/socket-origin" \ + sh scripts/install.sh + installed=$("$RUNNER_TEMP/socket-origin/socket-patch" --version | awk '{print $NF}') + if [ "$installed" != "$latest" ]; then + echo "::error::install.socket.dev says latest is $latest but installed $installed" >&2 + exit 1 + fi + - name: Shell — the installer URL is consistent across the docs # The README, the script's own usage comment, and the hosting runbook # all name the canonical URL. Keeping them in lockstep is the whole diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c26c6..ee841ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -337,6 +337,18 @@ into the new version's section — see docs/releasing.md. ### Changed +- **`install.sh` can install without reaching github.com.** New + `SOCKET_PATCH_BASE_URL` points the archive downloads at any releases base that + answers GitHub's two asset paths — notably + `https://install.socket.dev/SocketDev/socket-patch/releases`, which relays them + from the GitHub release, so one URL template covers either origin. A new + release needs no publish for this: the origin resolves "latest" per request. + `socket-patch --update` can use the same host today through the + `SOCKET_UPDATE_BASE_URL` override it already has. Also new: + `SOCKET_PATCH_INSTALL_DIR` to choose the install directory explicitly instead + of taking `/usr/local/bin` or `~/.local/bin`. The default download origin is + still GitHub — see `docs/installer-hosting.md`. + - **The documented one-liner installs from `https://install.socket.dev/patch`.** The previous URL was `raw.githubusercontent.com`, which asks users to trust a third-party CDN for a script they pipe into a shell and is the first URL a diff --git a/README.md b/README.md index c06960b..74f4ad8 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,15 @@ against the release's `SHA256SUMS`, and installs to `/usr/local/bin` or `~/.loca Use `sudo sh` instead of `sh` if `/usr/local/bin` requires root. Pin a version with `SOCKET_PATCH_VERSION=3.3.0 sh` instead of plain `sh`. +On a network that blocks or distrusts `github.com`, set `SOCKET_PATCH_BASE_URL` so the +archives come from Socket too — `install.socket.dev` relays them from the GitHub release, +checksums included: + +```bash +curl -fsSL https://install.socket.dev/patch \ + | SOCKET_PATCH_BASE_URL=https://install.socket.dev/SocketDev/socket-patch/releases sh +``` + `install.socket.dev` serves a copy of [`scripts/install.sh`](scripts/install.sh) from this repository — read it before you run it, either there or at [install.socket.dev/patch](https://install.socket.dev/patch). If you would rather not diff --git a/docs/installer-hosting.md b/docs/installer-hosting.md index e1439d4..07ad96e 100644 --- a/docs/installer-hosting.md +++ b/docs/installer-hosting.md @@ -21,6 +21,45 @@ it stays stable if the artifacts ever move. The GitHub URL still works and still serves the same bytes. Anyone who would rather not add a dependency on the Socket domain can keep using it. +## Installing without reaching github.com + +By default the script downloads archives from the GitHub release. Point it +somewhere else with `SOCKET_PATCH_BASE_URL` — a releases base that answers +GitHub's two asset paths, `/latest/download/` and +`/download/v/`: + +```sh +curl -fsSL https://install.socket.dev/patch \ + | SOCKET_PATCH_BASE_URL=https://install.socket.dev/SocketDev/socket-patch/releases sh +``` + +`install.socket.dev` relays those exact paths from the GitHub release, which is +why one template covers both origins and the script needs no branching. It also +exposes a cleaner shape for humans and for scripts that want the version: + +| Endpoint | Serves | +|---|---| +| `install.socket.dev/patch/latest` | the latest version as plain text (`3.4.0`) | +| `install.socket.dev/patch/dl/v3.4.0/` | that release's asset, immutably cached | +| `install.socket.dev/patch/dl/latest/` | the same asset from whatever is latest | + +**A new release needs no publish for any of this.** "Latest" is resolved per +request against the upstream release, so cutting 3.4.0 makes it installable from +`install.socket.dev` immediately — nothing runs at release time. + +`socket-patch --update` can use the same host today, with no changes to the CLI, +via the endpoint override it already has: + +```sh +SOCKET_UPDATE_BASE_URL=https://install.socket.dev socket-patch --update +``` + +One caveat worth knowing before standardizing on that: a non-default +`SOCKET_UPDATE_BASE_URL` intentionally downgrades the downloaded binary's +version self-check from hard-fail to a warning, because the override is meant +for mirrors that may repackage. Making Socket's host a first-class endpoint set +that keeps the strict check is a CLI change, not a hosting one. + ## What the trust model actually is Unchanged by the hosting move, and worth being precise about: @@ -83,9 +122,15 @@ mangled publish is caught even when the hash somehow matches expectations. through a package manager or a release archive. A `patch.ps1` object on the same host would be the natural addition — the hosting side already supports it, nothing here does yet. -- **Objects must stay flat.** `gcs-bucket-server` interpolates the object name - into the GCS JSON API URL unencoded, so only bucket-root keys resolve - (`patch`, `patch.sha256`, `index.html`). A nested path like - `/patch/3.3.0/install.sh` would 404 until that is fixed on the depscan side. +- **Objects must stay flat** — for the *bucket-backed* paths only (`patch`, + `patch.sha256`, `index.html`). `gcs-bucket-server` interpolates the object name + into the GCS JSON API URL unencoded, so only bucket-root keys resolve. This + does not affect `/patch/dl/**`, which is relayed by a separate service and + never touches the bucket. +- **The default download origin is still GitHub.** The `SOCKET_PATCH_BASE_URL` + mechanism ships first; flipping the default to `install.socket.dev` is a + one-line change, deliberately held until the relay is verified in prod. A + script that defaults to a host which does not answer yet is a broken installer + for everyone running it from a git checkout or the raw GitHub URL. [depscan]: https://github.com/SocketDev/depscan diff --git a/scripts/install.sh b/scripts/install.sh index 00f5b15..f2bc28e 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -11,10 +11,33 @@ set -eu # # Override the version that gets installed by exporting SOCKET_PATCH_VERSION: # curl -fsSL https://install.socket.dev/patch | SOCKET_PATCH_VERSION=3.0.0 sh +# +# Override where the archives come from with SOCKET_PATCH_BASE_URL — a releases +# base that answers GitHub's two asset paths, `/latest/download/` +# and `/download/v/`. Use it to install without reaching +# github.com at all: +# +# … | SOCKET_PATCH_BASE_URL=https://install.socket.dev/SocketDev/socket-patch/releases sh +# +# install.socket.dev relays those exact paths from the GitHub release, which is +# why one template covers both origins. Whichever origin is used, the archive is +# still verified against the SHA256SUMS fetched from that same origin. +# +# Override where the binary is installed with SOCKET_PATCH_INSTALL_DIR. REPO="SocketDev/socket-patch" BINARY="socket-patch" VERSION="${SOCKET_PATCH_VERSION:-latest}" +# Releases base. Default is GitHub; see the SOCKET_PATCH_BASE_URL note above for +# installing through install.socket.dev instead. Trailing slashes are trimmed so +# a base with one does not produce `//download`. +RELEASES_BASE="${SOCKET_PATCH_BASE_URL:-https://github.com/${REPO}/releases}" +while :; do + case "$RELEASES_BASE" in + */) RELEASES_BASE="${RELEASES_BASE%/}" ;; + *) break ;; + esac +done # Detect platform OS="$(uname -s)" @@ -88,8 +111,13 @@ else exit 1 fi -# Pick install directory -if [ -w /usr/local/bin ]; then +# Pick install directory. An explicit SOCKET_PATCH_INSTALL_DIR wins over both +# defaults — needed for unprivileged installs into a toolchain-managed prefix, +# and for testing the script without writing to a system path. +if [ -n "${SOCKET_PATCH_INSTALL_DIR:-}" ]; then + INSTALL_DIR="$SOCKET_PATCH_INSTALL_DIR" + mkdir -p "$INSTALL_DIR" +elif [ -w /usr/local/bin ]; then INSTALL_DIR="/usr/local/bin" else INSTALL_DIR="${HOME}/.local/bin" @@ -100,12 +128,14 @@ fi TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT -# Pick the release path. "latest" resolves on GitHub's side; tagged versions are -# served from /releases/download/v/. +# Pick the release path. "latest" is resolved by the origin (GitHub redirects; +# install.socket.dev resolves it against the upstream release), so the script +# never has to know the version number. Tagged versions are served from +# /download/v/. if [ "$VERSION" = "latest" ]; then - BASE_URL="https://github.com/${REPO}/releases/latest/download" + BASE_URL="${RELEASES_BASE}/latest/download" else - BASE_URL="https://github.com/${REPO}/releases/download/v${VERSION#v}" + BASE_URL="${RELEASES_BASE}/download/v${VERSION#v}" fi ARCHIVE="${BINARY}-${TARGET}.tar.gz"