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 <noreply@anthropic.com>
This commit is contained in:
Shekhar Vaidya 2026-07-13 22:28:23 +05:30
parent 151f70d67f
commit 5eec0351e7
7 changed files with 99 additions and 0 deletions

1
.gitattributes vendored
View file

@ -1,2 +1,3 @@
aur/msdl-bin/PKGBUILD text eol=lf
aur/msdl-bin/.SRCINFO text eol=lf
backend/install.sh text eol=lf

View file

@ -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

View file

@ -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 |

48
backend/install.sh Normal file
View file

@ -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."

View file

@ -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"}`))

View file

@ -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 (
<div className="space-y-2">
<p className="text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600">1. Install</p>
@ -56,6 +63,12 @@ function InstallSteps() {
</button>
</div>
</div>
<div className="flex items-center justify-between gap-2 rounded-lg bg-white/4 border border-white/7 px-3 py-2">
<code className="text-[11px] font-mono text-zinc-400 truncate">curl -fsSL .../install.sh | bash</code>
<button onClick={handleCurlCopy} className="flex-shrink-0 text-zinc-500 hover:text-white transition-colors">
{curlCopied ? <Check size={12} className="text-green-400" /> : <Copy size={12} />}
</button>
</div>
<a
href={RELEASES_URL}
target="_blank"

View file

@ -116,6 +116,12 @@ export default function CliPage() {
<CodeBlock code="brew tap starkSV/msdl && brew install msdl" />
</div>
{/* No Homebrew — curl installer */}
<div className="rounded-xl border border-white/7 bg-[#111113] p-4 space-y-2">
<p className="text-[12px] text-zinc-400">No Homebrew? One-line installer (auto-detects OS/arch, incl. Termux):</p>
<CodeBlock code="curl -fsSL https://api.msdl.tech-latest.com/install.sh | bash" />
</div>
{/* Manual / other platforms */}
<div className="rounded-xl border border-white/7 bg-[#111113] p-4 space-y-3">
<div className="flex items-center justify-between">