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"