From 003678ab7aa118678f2ee0d30d4ba3a2010aff34 Mon Sep 17 00:00:00 2001 From: LarsLaskowski <26182282+LarsLaskowski@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:26:34 +0200 Subject: [PATCH 1/3] Add release download script Automating release retrieval avoids requiring users to manually resolve the latest tag and select a package architecture. --- README.md | 14 ++++++ packaging/download-latest.sh | 78 +++++++++++++++++++++++++++++++ packaging/download_latest_test.go | 31 ++++++++++++ 3 files changed, 123 insertions(+) create mode 100755 packaging/download-latest.sh create mode 100644 packaging/download_latest_test.go diff --git a/README.md b/README.md index 653d4c4..ad571ba 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,20 @@ extract it. Release assets are named The snippet below fetches the latest version automatically via the GitHub API, so you only need to set `ARCH`: +For a reusable variant of the same process, download and run +[`download-latest.sh`](packaging/download-latest.sh): + +```sh +curl -fsSLO https://raw.githubusercontent.com/LarsLaskowski/PiMonitor/main/packaging/download-latest.sh +chmod +x download-latest.sh +./download-latest.sh +``` + +The script detects the architecture automatically (`aarch64` becomes `arm64`; +32-bit Raspberry Pi OS becomes `armv6`), downloads and extracts the latest +release, then lists its contents. To override the detection, run it with +`ARCH=arm64 ./download-latest.sh` or `ARCH=armv6 ./download-latest.sh`. + ```sh ARCH=arm64 # or armv6, see above diff --git a/packaging/download-latest.sh b/packaging/download-latest.sh new file mode 100755 index 0000000..8c890bb --- /dev/null +++ b/packaging/download-latest.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# Download and unpack the latest PiMonitor release for a Raspberry Pi. +# +# Usage: +# ./download-latest.sh +# ARCH=arm64 ./download-latest.sh # optional manual override + +set -euo pipefail + +readonly REPOSITORY='larslaskowski/pimonitor' + +if [[ -z "${ARCH:-}" ]]; then + case "$(uname -m)" in + aarch64|arm64) + ARCH='arm64' + ;; + armv6l|armv7l|armv8l) + # The armv6 binary also runs on newer 32-bit ARM Raspberry Pi OS. + ARCH='armv6' + ;; + *) + printf 'Could not determine a supported Raspberry Pi architecture from: %s\n' "$(uname -m)" >&2 + printf 'Set ARCH manually to arm64 or armv6.\n' >&2 + exit 2 + ;; + esac +fi + +case "${ARCH:-}" in + arm64|armv6) + ;; + *) + printf 'Unsupported ARCH %q; use arm64 or armv6.\n' "$ARCH" >&2 + exit 2 + ;; +esac + +for command in curl wget tar; do + if ! command -v "$command" >/dev/null 2>&1; then + printf 'Required command not found: %s\n' "$command" >&2 + exit 1 + fi +done + +version=$(curl -fsSL "https://api.github.com/repos/${REPOSITORY}/releases/latest" \ + | awk -F '"' '/"tag_name"/ { print $4; exit }') + +if [[ -z "$version" ]]; then + printf 'Could not determine the latest PiMonitor release version.\n' >&2 + exit 1 +fi + +archive="pimonitor_${version#v}_linux_${ARCH}.tar.gz" +directory="${archive%.tar.gz}" +url="https://github.com/${REPOSITORY}/releases/download/${version}/${archive}" + +if [[ -e "$directory" ]]; then + printf 'Target directory already exists: %s\n' "$directory" >&2 + printf 'Remove it or run the script in another directory.\n' >&2 + exit 1 +fi + +printf 'Downloading PiMonitor %s for %s ...\n' "$version" "$ARCH" +wget --no-clobber "$url" + +printf 'Extracting %s ...\n' "$archive" +tar xzf "$archive" + +if [[ ! -d "$directory" ]]; then + printf 'Expected extracted directory not found: %s\n' "$directory" >&2 + exit 1 +fi + +cd "$directory" +ls + +printf '\nRelease extracted to %s\n' "$PWD" +printf 'To continue, run: cd %q && sudo ./install.sh\n' "$PWD" diff --git a/packaging/download_latest_test.go b/packaging/download_latest_test.go new file mode 100644 index 0000000..885f331 --- /dev/null +++ b/packaging/download_latest_test.go @@ -0,0 +1,31 @@ +package packaging + +import ( + "os" + "strings" + "testing" +) + +func TestDownloadLatestScript_MapsRaspberryPiArchitectures(t *testing.T) { + script, err := os.ReadFile("download-latest.sh") + if err != nil { + t.Fatalf("read download-latest.sh: %v", err) + } + + tests := []struct { + name string + want string + }{ + {"64-bit Raspberry Pi OS", "aarch64|arm64)\n ARCH='arm64'"}, + {"32-bit Raspberry Pi OS", "armv6l|armv7l|armv8l)\n # The armv6 binary also runs on newer 32-bit ARM Raspberry Pi OS.\n ARCH='armv6'"}, + {"manual override", "if [[ -z \"${ARCH:-}\" ]]; then"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !strings.Contains(string(script), tt.want) { + t.Fatalf("download-latest.sh is missing the %s architecture rule", tt.name) + } + }) + } +} From b4da40ccf8085547020f0e63c8178d3e26056014 Mon Sep 17 00:00:00 2001 From: LarsL <26182282+LarsLaskowski@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:31:08 +0200 Subject: [PATCH 2/3] Delete packaging/download_latest_test.go --- packaging/download_latest_test.go | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 packaging/download_latest_test.go diff --git a/packaging/download_latest_test.go b/packaging/download_latest_test.go deleted file mode 100644 index 885f331..0000000 --- a/packaging/download_latest_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package packaging - -import ( - "os" - "strings" - "testing" -) - -func TestDownloadLatestScript_MapsRaspberryPiArchitectures(t *testing.T) { - script, err := os.ReadFile("download-latest.sh") - if err != nil { - t.Fatalf("read download-latest.sh: %v", err) - } - - tests := []struct { - name string - want string - }{ - {"64-bit Raspberry Pi OS", "aarch64|arm64)\n ARCH='arm64'"}, - {"32-bit Raspberry Pi OS", "armv6l|armv7l|armv8l)\n # The armv6 binary also runs on newer 32-bit ARM Raspberry Pi OS.\n ARCH='armv6'"}, - {"manual override", "if [[ -z \"${ARCH:-}\" ]]; then"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if !strings.Contains(string(script), tt.want) { - t.Fatalf("download-latest.sh is missing the %s architecture rule", tt.name) - } - }) - } -} From 56bc44e3e1ccb80aff956569811ef735f5dab5b7 Mon Sep 17 00:00:00 2001 From: LarsL <26182282+LarsLaskowski@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:46:04 +0200 Subject: [PATCH 3/3] Fix SonarCloud HTTPS-redirect findings in download-latest.sh (#124) Enforce HTTPS-only redirects in download-latest.sh SonarCloud flagged both curl/wget calls in the new release-download script (rule shell:S6506): a redirect could silently downgrade the transfer to plain HTTP, allowing a MITM to serve a tampered release. - The GitHub API request now runs through a shared curl invocation with --proto '=https' --proto-redir '=https', which rejects both an insecure initial URL and an insecure redirect target. - The release-archive download switches from wget to the same curl invocation, since wget (tested against a local redirecting server) has no equivalent way to reject an HTTP redirect target for a non-recursive download; --https-only only restricts recursive link following, not redirects. This also drops wget from the script's required-commands check. --- packaging/download-latest.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packaging/download-latest.sh b/packaging/download-latest.sh index 8c890bb..d826dfa 100755 --- a/packaging/download-latest.sh +++ b/packaging/download-latest.sh @@ -8,6 +8,10 @@ set -euo pipefail readonly REPOSITORY='larslaskowski/pimonitor' +# Restrict both the initial request and any redirect to HTTPS, so a +# compromised or misconfigured server can't downgrade the download to plain +# HTTP. +readonly CURL=(curl -fsSL --proto '=https' --proto-redir '=https') if [[ -z "${ARCH:-}" ]]; then case "$(uname -m)" in @@ -35,14 +39,14 @@ case "${ARCH:-}" in ;; esac -for command in curl wget tar; do +for command in curl tar; do if ! command -v "$command" >/dev/null 2>&1; then printf 'Required command not found: %s\n' "$command" >&2 exit 1 fi done -version=$(curl -fsSL "https://api.github.com/repos/${REPOSITORY}/releases/latest" \ +version=$("${CURL[@]}" "https://api.github.com/repos/${REPOSITORY}/releases/latest" \ | awk -F '"' '/"tag_name"/ { print $4; exit }') if [[ -z "$version" ]]; then @@ -61,7 +65,7 @@ if [[ -e "$directory" ]]; then fi printf 'Downloading PiMonitor %s for %s ...\n' "$version" "$ARCH" -wget --no-clobber "$url" +"${CURL[@]}" -o "$archive" "$url" printf 'Extracting %s ...\n' "$archive" tar xzf "$archive"