#!/bin/sh set -eu SERVER="${COMPUTER_MONITOR_SERVER:-https://monitor.leikunx.com}" SERVER="${SERVER%/}" INSTALL_DIR="${COMPUTER_MONITOR_DIR:-$HOME/.computer-monitor}" INSTALL_STARTED=$(date +%s) STEP_TITLE='Check installation settings' TMP='' step() { STEP_TITLE=$2 STEP_STARTED=$(date +%s) printf '\n[%s/4] %s\n' "$1" "$STEP_TITLE" } complete_step() { printf ' Done in %ss.\n' "$(( $(date +%s) - STEP_STARTED ))" } finish() { INSTALL_STATUS=$? trap - 0 if [ -n "$TMP" ]; then rm -rf "$TMP"; fi if [ "$INSTALL_STATUS" -ne 0 ]; then printf '\n[!] Installation stopped: %s (after %ss).\n' "$STEP_TITLE" "$(( $(date +%s) - INSTALL_STARTED ))" >&2 printf ' Check the error above, then rerun the installation command.\n' >&2 fi exit "$INSTALL_STATUS" } trap finish 0 trap 'exit 130' 2 trap 'exit 143' 15 bun_archive() { case "$(uname -s)" in Darwin) OS=darwin ;; Linux) OS=linux ;; *) echo 'Use the PowerShell installer on Windows.' >&2; exit 1 ;; esac case "$(uname -m)" in x86_64) ARCH=x64; BASELINE=-baseline ;; arm64|aarch64) ARCH=aarch64; BASELINE='' ;; *) echo 'Unsupported Bun CPU architecture.' >&2; exit 1 ;; esac LIBC='' if [ "$OS" = linux ] && ldd --version 2>&1 | grep -qi musl; then LIBC=-musl; fi ARCHIVE="bun-$OS-$ARCH$LIBC$BASELINE.zip" } prepare_bun_extractor() { if command -v unzip >/dev/null 2>&1; then BUN_EXTRACTOR=unzip elif command -v python3 >/dev/null 2>&1 && python3 -I -c 'import zipfile, zlib' >/dev/null 2>&1; then BUN_EXTRACTOR=python3 printf ' Using Python 3 to unpack Bun (unzip is not installed).\n' else printf 'Bun needs unzip or Python 3 to unpack its download.\n' >&2 printf 'On Ubuntu/Debian, run: sudo apt-get update && sudo apt-get install -y unzip\n' >&2 printf 'On other systems, install unzip or Python 3 with your package manager, then rerun this installer.\n' >&2 exit 1 fi } extract_bun() { if [ "$BUN_EXTRACTOR" = unzip ]; then unzip -q "$TMP/$ARCHIVE" -d "$TMP/bun" else python3 -I - "$TMP/$ARCHIVE" "$TMP/bun/${ARCHIVE%.zip}/bun" <<'PY' import pathlib import shutil import sys import zipfile destination = pathlib.Path(sys.argv[2]) destination.parent.mkdir(parents=True, exist_ok=True) with zipfile.ZipFile(sys.argv[1]) as archive: with archive.open(destination.parent.name + "/bun") as source: with destination.open("wb") as output: shutil.copyfileobj(source, output) PY fi } install_bun() { bun_archive prepare_bun_extractor printf ' Downloading Bun %s for %s/%s. This may take a moment.\n' "$BUN_VERSION" "$OS" "$ARCH" BASE="https://github.com/oven-sh/bun/releases/download/bun-v$BUN_VERSION" curl --fail --silent --show-error --location --proto '=https' "$BASE/SHASUMS256.txt" -o "$TMP/SHASUMS256.txt" EXPECTED="$(awk -v archive="$ARCHIVE" '$2 == archive { print $1 }' "$TMP/SHASUMS256.txt")" [ -n "$EXPECTED" ] || { echo 'Bun download metadata is unavailable.' >&2; exit 1; } curl --fail --silent --show-error --location --proto '=https' "$BASE/$ARCHIVE" -o "$TMP/$ARCHIVE" if command -v sha256sum >/dev/null 2>&1; then ACTUAL="$(sha256sum "$TMP/$ARCHIVE" | awk '{print $1}')"; else ACTUAL="$(shasum -a 256 "$TMP/$ARCHIVE" | awk '{print $1}')"; fi [ "$EXPECTED" = "$ACTUAL" ] || { echo 'Bun checksum mismatch.' >&2; exit 1; } extract_bun BUN="$INSTALL_DIR/runtime/bun-$BUN_VERSION/bin/bun" mkdir -p "$(dirname "$BUN")" cp "$TMP/bun/${ARCHIVE%.zip}/bun" "$BUN" chmod 700 "$BUN" printf ' Download verified. Runtime installed for your user account.\n' } supported_bun() { [ -n "$BUN" ] && [ -x "$BUN" ] && "$BUN" --no-env-file -e 'process.exit(Bun.semver.satisfies(Bun.version, ">=1.4.2") ? 0 : 1)' >/dev/null 2>&1 } printf '\nšŸ–„ Computer Monitor\n CPU and memory, from every computer you own.\n' case "$SERVER" in https://*) DOWNLOAD_PROTOCOLS='=https' ;; http://localhost|http://localhost:*|http://127.0.0.1|http://127.0.0.1:*) DOWNLOAD_PROTOCOLS='=http,https' ;; *) echo 'Install server must use HTTPS (HTTP is allowed only on localhost).' >&2; exit 1 ;; esac umask 077 mkdir -p "$INSTALL_DIR/bin" TMP="$(mktemp -d)" step 1 'Prepare Bun' BUN_VERSION=1.4.2 BUN="$(command -v bun || true)" if ! supported_bun; then BUN="$INSTALL_DIR/runtime/bun-$BUN_VERSION/bin/bun" if ! supported_bun; then install_bun; fi fi supported_bun || { echo 'Bun could not run on this computer. Check OS/CPU compatibility.' >&2; exit 1; } printf ' Using Bun %s.\n' "$("$BUN" --version)" complete_step STEP_TITLE='Run shared installer' printf '\nHanding setup to the shared Bun installer…\n' curl --fail --silent --show-error --proto "$DOWNLOAD_PROTOCOLS" "$SERVER/install.mjs" -o "$TMP/install.mjs" curl --fail --silent --show-error --proto "$DOWNLOAD_PROTOCOLS" "$SERVER/install.mjs.sha256" -o "$TMP/install.mjs.sha256" "$BUN" --no-env-file -e 'const [file,sum]=process.argv.slice(1);const expected=(await Bun.file(sum).text()).trim().split(/\s+/)[0];const actual=new Bun.CryptoHasher("sha256").update(await Bun.file(file).arrayBuffer()).digest("hex");if(!/^[a-f0-9]{64}$/.test(expected)||actual!==expected)throw new Error("Installer checksum mismatch");' "$TMP/install.mjs" "$TMP/install.mjs.sha256" export COMPUTER_MONITOR_DIR="$INSTALL_DIR" "$BUN" --no-env-file "$TMP/install.mjs" --server "$SERVER" "$@"