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"