#!/bin/sh # InventoryHQ installer for macOS and Linux. # # curl -fsSL https://ihq.monolith.ng/setup | sh # wget -qO- https://ihq.monolith.ng/setup | sh # # Options go after `-s --`: # # curl -fsSL https://ihq.monolith.ng/setup | sh -s -- --port 8080 --offline # # --port N port for the console (default 80) # --hostname NAME name for this machine's own address (default inventory-hq) # --offline no cloud backups or remote updates # --channel NAME stable (default) or beta # --bundle FILE install from a downloaded bundle instead of fetching one # --dry-run download and check everything, install nothing # --no-open do not open the console in a browser afterwards # # What it does: asks https://inventory-hq-api.monolith.ng for the newest release, downloads it, checks # its SHA-256, then hands over to the installer inside it, which sets up # Node.js, the background service, the update schedule and the database. Safe # to run again: it repairs an install and keeps its data. # # Run as your normal user, not root. It asks for your password (sudo) only for # the few steps that need it. set -eu FLEET_URL="https://inventory-hq-api.monolith.ng" CHANNEL="stable" BUNDLE="" DRY_RUN=0 OPEN_BROWSER=1 PASS_THROUGH="" # ---------------------------------------------------------------- output ---- if [ -t 1 ]; then BLUE='\033[0;34m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; BOLD='\033[1m'; RESET='\033[0m' else BLUE=''; GREEN=''; YELLOW=''; RED=''; BOLD=''; RESET='' fi log() { printf '%b==>%b %s\n' "$BLUE" "$RESET" "$*"; } ok() { printf '%b OK%b %s\n' "$GREEN" "$RESET" "$*"; } warn() { printf '%b !!%b %s\n' "$YELLOW" "$RESET" "$*" >&2; } die() { printf '%bERROR%b %s\n' "$RED" "$RESET" "$*" >&2; exit 1; } # ------------------------------------------------------------- arguments ---- while [ $# -gt 0 ]; do case "$1" in --channel) [ $# -ge 2 ] || die "--channel needs a value"; CHANNEL="$2"; shift 2 ;; --bundle) [ $# -ge 2 ] || die "--bundle needs a file"; BUNDLE="$2"; shift 2 ;; --dry-run) DRY_RUN=1; shift ;; --no-open) OPEN_BROWSER=0; shift ;; --port|--hostname|--remote-client-url) [ $# -ge 2 ] || die "$1 needs a value" PASS_THROUGH="$PASS_THROUGH $1 $2"; shift 2 ;; --offline) PASS_THROUGH="$PASS_THROUGH --offline"; shift ;; -h|--help) sed -n '2,28p' "$0" 2>/dev/null | sed 's/^# \{0,1\}//' || true; exit 0 ;; *) die "Unknown option: $1 (see --help)" ;; esac done case "$CHANNEL" in stable|beta) ;; *) die "--channel must be stable or beta" ;; esac # ----------------------------------------------------------- this machine --- OS="$(uname -s)" case "$OS" in Linux) PLATFORM=linux ;; Darwin) PLATFORM=darwin ;; *) die "This script is for macOS and Linux. On Windows, open PowerShell and run: irm https://ihq.monolith.ng/setup | iex" ;; esac if [ "$(id -u)" -eq 0 ]; then die "Do not run this as root. Run it as the user who will use InventoryHQ; it asks for sudo when needed." fi if [ "$PLATFORM" = linux ] && ! command -v apt-get >/dev/null 2>&1; then die "Linux support is for Debian-based systems (Ubuntu, Debian, Raspberry Pi OS). apt-get was not found." fi ROOT="${INVENTORY_HQ_ROOT:-$HOME/inventory-hq}" # Download with whichever tool exists. fetch_stdout() { if command -v curl >/dev/null 2>&1; then curl -fsSL "$1" elif command -v wget >/dev/null 2>&1; then wget -qO- "$1" else die "Need curl or wget to download InventoryHQ."; fi } download_with_progress() { if command -v curl >/dev/null 2>&1; then curl -fL --progress-bar "$1" -o "$2" else wget -q --show-progress -O "$2" "$1" 2>/dev/null || wget -qO "$2" "$1" fi } sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}' else die "Need sha256sum or shasum to check the download."; fi } # unzip comes with macOS. On Debian it is one package away. if ! command -v unzip >/dev/null 2>&1; then if [ "$PLATFORM" = linux ]; then log "Installing unzip" sudo apt-get update -qq && sudo apt-get install -y -qq unzip else die "unzip was not found." fi fi TMP="$(mktemp -d "${TMPDIR:-/tmp}/inventory-hq.XXXXXX")" trap 'rm -rf "$TMP"' EXIT INT TERM printf '\n%bInventoryHQ setup%b (%s, %s)\n\n' "$BOLD" "$RESET" "$PLATFORM" "$(uname -m)" # --------------------------------------------------------------- the bundle - ZIP="$TMP/inventory-hq.zip" if [ -n "$BUNDLE" ]; then [ -f "$BUNDLE" ] || die "No such file: $BUNDLE" log "Using the bundle you gave: $BUNDLE" cp "$BUNDLE" "$ZIP" VERSION="$(unzip -p "$ZIP" VERSION 2>/dev/null | tr -d '[:space:]' || true)" SHA256="$(sha256_of "$ZIP")" [ -n "$VERSION" ] || VERSION="unknown" else log "Asking $FLEET_URL for the newest $CHANNEL release" META="$(fetch_stdout "$FLEET_URL/v1/public/releases/latest?component=local-client&channel=$CHANNEL&format=env")" || die "Could not reach $FLEET_URL. Check this machine's internet connection." VERSION=""; SHA256=""; URL="" # Read line by line, never eval: whatever the server says is data. while IFS='=' read -r key value; do case "$key" in VERSION) VERSION="$value" ;; SHA256) SHA256="$value" ;; URL) URL="$value" ;; esac done </dev/null || true if [ "$DRY_RUN" -eq 1 ]; then ok "Dry run: release $VERSION is downloaded and verified, and its installer is present." printf ' It would be installed to %s.\n' "$ROOT" exit 0 fi # ---------------------------------------------------------------- stage it -- # The installer applies whatever sits in staging/dist.zip. Copied under a # temporary name and renamed, so a timer never sees a half-written file. mkdir -p "$ROOT/staging" cp "$ZIP" "$ROOT/staging/.dist.zip.part" printf '%s dist.zip\n' "$SHA256" >"$ROOT/staging/.dist.zip.sha256.part" mv "$ROOT/staging/.dist.zip.sha256.part" "$ROOT/staging/dist.zip.sha256" mv "$ROOT/staging/.dist.zip.part" "$ROOT/staging/dist.zip" log "Installing $VERSION to $ROOT" # shellcheck disable=SC2086 # PASS_THROUGH is a list of flags built above "$INSTALLER" $PASS_THROUGH # ------------------------------------------------------------------- done -- PORT="$(sed -n 's/^PORT=\([0-9]*\).*/\1/p' "$ROOT/shared/.env" 2>/dev/null | tail -1)" PORT="${PORT:-80}" SUFFIX=""; [ "$PORT" = "80" ] || SUFFIX=":$PORT" URL_HOME="http://localhost$SUFFIX/" printf '\n%bInventoryHQ %s is installed.%b\n' "$GREEN" "$VERSION" "$RESET" printf ' Open it here: %s\n' "$URL_HOME" printf ' Next: create your admin account in the page that opens.\n\n' if [ "$OPEN_BROWSER" -eq 1 ]; then if [ "$PLATFORM" = darwin ]; then open "$URL_HOME" >/dev/null 2>&1 || true elif command -v xdg-open >/dev/null 2>&1 && [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]; then xdg-open "$URL_HOME" >/dev/null 2>&1 || true fi fi