From 5eec0351e7f2214fb16623ed61641c9a44692a0b Mon Sep 17 00:00:00 2001 From: Shekhar Vaidya Date: Mon, 13 Jul 2026 22:28:23 +0530 Subject: [PATCH] feat: add curl | bash installer for Linux/macOS without Homebrew Adds a GET /install.sh endpoint on the backend (embedded via Go's embed package, so the script stays a real, shellcheck-able .sh file rather than a Go string literal). Auto-detects OS/arch (including Termux on Android via $PREFIX) and always resolves the latest GitHub release via the /releases/latest/download/ redirect, so unlike the winget/brew/AUR manifests it needs no per-release maintenance. Verified end-to-end in an ephemeral Ubuntu container (both the normal /usr/local/bin path and the Termux $PREFIX path), and locally against a running backend instance. Documented alongside the existing winget/Homebrew instructions in README.md, the release notes template, the web CLI page, and the per-product CliHandoff card. Co-Authored-By: Claude Sonnet 4.6 --- .gitattributes | 1 + .github/workflows/cli-release.yml | 6 ++++ README.md | 6 ++++ backend/install.sh | 48 ++++++++++++++++++++++++++ backend/main.go | 19 ++++++++++ frontend/src/components/CliHandoff.tsx | 13 +++++++ frontend/src/pages/CliPage.tsx | 6 ++++ 7 files changed, 99 insertions(+) create mode 100644 backend/install.sh diff --git a/.gitattributes b/.gitattributes index ff02d63..fef99c2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ aur/msdl-bin/PKGBUILD text eol=lf aur/msdl-bin/.SRCINFO text eol=lf +backend/install.sh text eol=lf diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml index 2562eb1..873e38d 100644 --- a/.github/workflows/cli-release.yml +++ b/.github/workflows/cli-release.yml @@ -73,6 +73,12 @@ jobs: brew install msdl ``` + **macOS/Linux (no Homebrew):** + ```bash + curl -fsSL https://api.msdl.tech-latest.com/install.sh | bash + ``` + Auto-detects OS/arch (including Termux on Android) and installs the latest release. + **Linux/macOS (direct download):** ```bash # Linux x86_64 diff --git a/README.md b/README.md index ebc438b..87a730a 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,12 @@ brew tap starkSV/msdl brew install msdl ``` +**macOS / Linux (no Homebrew):** +```bash +curl -fsSL https://api.msdl.tech-latest.com/install.sh | bash +``` +Auto-detects OS/arch (including Termux on Android) and installs the latest release. + **Direct download:** Grab the latest binary from [GitHub Releases](https://github.com/starkSV/windows-iso-downloader/releases/latest) and rename it: | Platform | File | Rename to | diff --git a/backend/install.sh b/backend/install.sh new file mode 100644 index 0000000..6447876 --- /dev/null +++ b/backend/install.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Installs the msdl CLI. Usage: curl -fsSL https://api.msdl.tech-latest.com/install.sh | bash +set -euo pipefail + +REPO="starkSV/windows-iso-downloader" + +os="$(uname -s)" +arch="$(uname -m)" + +case "$os" in + Linux) platform="linux" ;; + Darwin) platform="darwin" ;; + *) + echo "msdl: unsupported OS: $os (Windows users: winget install starkSV.msdl)" >&2 + exit 1 + ;; +esac + +case "$arch" in + x86_64|amd64) goarch="amd64" ;; + aarch64|arm64) goarch="arm64" ;; + *) + echo "msdl: unsupported architecture: $arch" >&2 + exit 1 + ;; +esac + +asset="msdl-${platform}-${goarch}" +url="https://github.com/${REPO}/releases/latest/download/${asset}" + +tmp="$(mktemp)" +echo "Downloading ${asset}..." +curl -fsSL "$url" -o "$tmp" +chmod +x "$tmp" + +if [ -n "${PREFIX:-}" ] && [ -d "${PREFIX}/bin" ]; then + # Termux (or similar $PREFIX-based environment) -- no sudo available or needed + mv "$tmp" "${PREFIX}/bin/msdl" + echo "Installed to ${PREFIX}/bin/msdl" +elif [ -w /usr/local/bin ]; then + mv "$tmp" /usr/local/bin/msdl + echo "Installed to /usr/local/bin/msdl" +else + sudo mv "$tmp" /usr/local/bin/msdl + echo "Installed to /usr/local/bin/msdl" +fi + +echo "Run 'msdl --help' to get started." diff --git a/backend/main.go b/backend/main.go index af0dfa6..d2ea1b4 100644 --- a/backend/main.go +++ b/backend/main.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + _ "embed" "encoding/json" "fmt" "html" @@ -27,6 +28,9 @@ import ( "golang.org/x/sync/singleflight" ) +//go:embed install.sh +var installScript []byte + // Package-level rand source — avoids global mutex contention under concurrent load. // Go 1.20+ auto-seeds the global rand, but a local source is faster at high QPS. var rng = rand.New(rand.NewSource(time.Now().UnixNano())) @@ -1701,6 +1705,20 @@ func handleCLIVersion(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{"latest": latestCLIVersion}) } +// --- /install.sh endpoint --- + +// handleInstallScript serves the Linux/macOS install script embedded at +// build time from install.sh. Auto-detects OS/arch and always resolves the +// latest GitHub release, so it needs no per-release maintenance. +func handleInstallScript(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + w.Header().Set("Content-Type", "text/x-sh; charset=utf-8") + w.Write(installScript) +} + // --- /metrics endpoint --- // loadTelemetryFromRedis reads all telemetry counters from Redis. @@ -1865,6 +1883,7 @@ func main() { mux.HandleFunc("/metrics", handleMetrics) mux.HandleFunc("/telemetry", handleTelemetry) mux.HandleFunc("/cli/version", handleCLIVersion) + mux.HandleFunc("/install.sh", handleInstallScript) mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok"}`)) diff --git a/frontend/src/components/CliHandoff.tsx b/frontend/src/components/CliHandoff.tsx index b6adf23..0c4cd26 100644 --- a/frontend/src/components/CliHandoff.tsx +++ b/frontend/src/components/CliHandoff.tsx @@ -16,6 +16,7 @@ const RELEASES_URL = 'https://minxl.ink/msdl-github-release' function InstallSteps() { const [copied, setCopied] = useState(false) const [brewCopied, setBrewCopied] = useState(false) + const [curlCopied, setCurlCopied] = useState(false) function handleCopy() { navigator.clipboard.writeText('winget install starkSV.msdl') @@ -29,6 +30,12 @@ function InstallSteps() { setTimeout(() => setBrewCopied(false), 2000) } + function handleCurlCopy() { + navigator.clipboard.writeText('curl -fsSL https://api.msdl.tech-latest.com/install.sh | bash') + setCurlCopied(true) + setTimeout(() => setCurlCopied(false), 2000) + } + return (

1. Install

@@ -56,6 +63,12 @@ function InstallSteps() {
+
+ curl -fsSL .../install.sh | bash + +
+ {/* No Homebrew — curl installer */} +
+

No Homebrew? One-line installer (auto-detects OS/arch, incl. Termux):

+ +
+ {/* Manual / other platforms */}