#!/usr/bin/env bash # install-sdl.sh - SDL Installer Script set -euo pipefail # ------------------------------------------------------------ # Functions # ------------------------------------------------------------ resolve_dirs() { local json="$1" declare -A raw resolved while IFS='=' read -r key val; do raw["$key"]="$val" done < <(jq -r '.DIRS | to_entries[] | "\(.key)=\(.value)"' "$json") if [[ -z "${raw[SDL_HOME]:-}" ]]; then echo "ERROR: DIRS.SDL_HOME is not defined in config.json" exit 1 fi resolved["SDL_HOME"]="${raw[SDL_HOME]/#\~/$HOME}" local changed=true while $changed; do changed=false for key in "${!raw[@]}"; do [[ -n "${resolved[$key]:-}" ]] && continue local val="${raw[$key]}" for rkey in "${!resolved[@]}"; do val="${val/$rkey/${resolved[$rkey]:-}}" done if [[ "$val" != *SDL_* ]]; then resolved["$key"]="$val" changed=true fi done done for key in "${!raw[@]}"; do [[ -z "${resolved[$key]:-}" ]] && { echo "ERROR: Unresolved DIR: $key=${raw[$key]}" exit 1 } done for key in "${!resolved[@]}"; do export "$key=${resolved[$key]}" printf " %-12s %s\n" "$key:" "${resolved[$key]}" mkdir -p "${resolved[$key]}" done } use_systemd() { # Check if systemd user mode is available and service exists if systemctl --user status >/dev/null 2>&1; then # Check if our service file exists if systemctl --user list-unit-files | grep -q "^sdl-mgr.service"; then return 0 # Use systemd fi fi return 1 # Use manual scripts } # ------------------------------------------------------------ # Main # ------------------------------------------------------------ # ------------------------------------------------------------ # Require non root user # ------------------------------------------------------------ if [[ $EUID -eq 0 ]]; then echo "ERROR: install-sdl.sh must be run as a non-root user" exit 1 fi # ------------------------------------------------------------ # Requirements # ------------------------------------------------------------ for cmd in awk curl grep jq sed sha256sum tar unzip; do command -v "$cmd" >/dev/null || { echo "ERROR: $cmd is required" exit 1 } done # ------------------------------------------------------------ # Resolve SDL_ROOT # ------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SDL_ROOT="$(cd "$SCRIPT_DIR" && pwd)" CONFIG_FILE="$SDL_ROOT/config.json" META_FILE="$SDL_ROOT/metadata.json" [[ -f "$CONFIG_FILE" ]] || { echo "ERROR: config.json not found"; exit 1; } [[ -f "$META_FILE" ]] || { echo "ERROR: metadata.json not found"; exit 1; } # ------------------------------------------------------------ # Hostname detection # ------------------------------------------------------------ HOSTNAME="$(hostname)" # ------------------------------------------------------------ # OS / platform detection # ------------------------------------------------------------ OS_ID="unknown" OS_LIKE="" OS_NAME="" OS_VERSION="" OS_FAMILY="unknown" OS_ARCH="unknown" OS_PLATFORM="unknown" if [[ -f /etc/os-release ]]; then # shellcheck disable=SC1091 . /etc/os-release OS_ID="${ID:-unknown}" OS_LIKE="${ID_LIKE:-}" OS_NAME="${NAME:-unknown}" OS_VERSION="${VERSION_ID:-}" fi case "$OS_ID" in debian|ubuntu|raspbian|zorin) OS_FAMILY="debian" OS_PLATFORM="linux" ;; rhel|rocky|almalinux|centos|fedora) OS_FAMILY="rhel" OS_PLATFORM="linux" ;; *) if [[ "$OS_LIKE" == *debian* ]]; then OS_FAMILY="debian" elif [[ "$OS_LIKE" == *rhel* ]]; then OS_FAMILY="rhel" fi ;; esac # ------------------------------------------------------------ # CPU architecture detection # ------------------------------------------------------------ ARCH_RAW="$(uname -m)" case "$ARCH_RAW" in x86_64) OS_ARCH="x64" ;; aarch64|arm64) OS_ARCH="arm64" ;; armv7l) OS_ARCH="armv7l" ;; *) echo "ERROR: Unsupported architecture: $ARCH_RAW" exit 1 ;; esac SDL_PLATFORM="${OS_FAMILY}:${OS_ARCH}" # ------------------------------------------------------------ # SELinux detection (warn-only) # ------------------------------------------------------------ SELINUX_MODE="disabled" if command -v getenforce >/dev/null 2>&1; then SELINUX_MODE="$(getenforce | tr '[:upper:]' '[:lower:]')" fi # ------------------------------------------------------------ # systemd user service check # ------------------------------------------------------------ if ! systemctl --user status >/dev/null 2>&1; then echo "ERROR: systemd user services are not available" echo " SDL requires systemd --user support" exit 1 fi SDL_VERSION="$(jq -r '.METADATA.version' "$META_FILE")" SDL_VERSION_DATE="$(jq -r '.METADATA.version_date' "$META_FILE")" SDL_COPYRIGHT="$(jq -r '.METADATA.copyright' "$META_FILE")" SDL_AUTHOR="$(jq -r '.METADATA.author' "$META_FILE")" SDL_LICENSE="$(jq -r '.METADATA.license' "$META_FILE")" SDL_HOMEPAGE="$(jq -r '.METADATA.homepage' "$META_FILE")" SDL_REPO="$(jq -r '.METADATA.git_repo' "$META_FILE")" # ------------------------------------------------------------ # NodeJS detection # ------------------------------------------------------------ NODE_VERSION="$(jq -r '.NODEJS.VERSION' "$CONFIG_FILE")" NODE_PLATFORMS="$(jq -r '.NODEJS.TARGETS[]' "$CONFIG_FILE" | tr '\n' ' ')" NODE_PLATFORMS="${NODE_PLATFORMS%% }" echo "####################################" echo "Software Defined Laboratory (SDL)" echo "####################################" echo " Version: $SDL_VERSION" echo " Version Date: $SDL_VERSION_DATE" echo " Copyright: (C) $SDL_COPYRIGHT" echo " Author: $SDL_AUTHOR" echo " License: $SDL_LICENSE" echo " Homepage: $SDL_HOMEPAGE" echo " Repository: $SDL_REPO" echo "" echo "NodeJS:" echo " Version: $NODE_VERSION" echo " Platforms: " for np in $NODE_PLATFORMS; do echo " $np" done echo "" echo "Target System:" echo " Hostname: $HOSTNAME" echo " OS: $OS_NAME $OS_VERSION" echo " Family: $OS_FAMILY" echo " Platform: $OS_PLATFORM" echo " Arch: $OS_ARCH" echo " systemd: user services available" echo " SELinux: $SELINUX_MODE" echo "" if [[ "$OS_PLATFORM" == "win32" ]]; then echo "ERROR: win32 host install not supported by this installer" exit 1 fi echo "Directories:" echo " SDL_ROOT: $SDL_ROOT" resolve_dirs "$CONFIG_FILE" # ------------------------------------------------------------ # Build sdl-mgr tarball to SDL_DIST # Always rebuild sdl-mgr_version.tgz from source # ------------------------------------------------------------ echo "" echo "Generating: $SDL_DIST/sdl-mgr_$SDL_VERSION.tgz" tar -czf "$SDL_DIST/sdl-mgr_$SDL_VERSION.tgz" -C "$SDL_ROOT" sdl-mgr sha256sum "$SDL_DIST/sdl-mgr_$SDL_VERSION.tgz" | awk '{sub(".*/","",$2); print}' > "$SDL_DIST/sdl-mgr_$SDL_VERSION.tgz.sha256" # ------------------------------------------------------------ # Build sdl-wkr tarball to SDL_DIST # Always rebuild sdl-wkr_version.tgz from source # ------------------------------------------------------------ echo "" echo "Generating: $SDL_DIST/sdl-wkr_$SDL_VERSION.tgz" tar -czf "$SDL_DIST/sdl-wkr_$SDL_VERSION.tgz" -C "$SDL_ROOT" sdl-wkr sha256sum "$SDL_DIST/sdl-wkr_$SDL_VERSION.tgz" | awk '{sub(".*/","",$2); print}' > "$SDL_DIST/sdl-wkr_$SDL_VERSION.tgz.sha256" # ------------------------------------------------------------ # Copy install-sdl-wkr.sh to SDL_DIST # ------------------------------------------------------------ echo "" echo "Copying: $SDL_DIST/install-sdl-wkr.sh" cp "$SDL_ROOT/sdl-wkr/scripts/install-sdl-wkr.sh" "$SDL_DIST/install-sdl-wkr.sh" sha256sum "$SDL_DIST/install-sdl-wkr.sh" | awk '{sub(".*/","",$2); print}' > "$SDL_DIST/install-sdl-wkr.sh.sha256" # ------------------------------------------------------------ # Fetch NodeJS Tarballs / Zips to SDL_DIST # ------------------------------------------------------------ echo "" echo "Fetching NodeJS Binaries to $SDL_DIST" SHASUM_URL="https://nodejs.org/dist/v${NODE_VERSION}/SHASUMS256.txt" curl -sSL "$SHASUM_URL" -o "$SDL_DIST/NODE_SHASUMS256.txt" # Download NodeJS tarball for np in $NODE_PLATFORMS; do IFS=":" read -r OS ARCH <<< "$np" case "$OS" in linux) EXT="tar.xz" ;; darwin) EXT="tar.gz" ;; win) EXT="zip" ;; *) echo "ERROR: Unknown NodeJS target OS: $OS"; exit 1 ;; esac PLATFORM="${OS}-${ARCH}" NODE_DIR="node-v${NODE_VERSION}-${PLATFORM}" NODE_TARBALL="${NODE_DIR}.$EXT" NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/${NODE_TARBALL}" echo " $np: $NODE_TARBALL" if [ ! -e "$SDL_DIST/$NODE_TARBALL" ]; then curl -sSL "$NODE_URL" -o "$SDL_DIST/$NODE_TARBALL" EXPECTED_SHA="$(grep " $NODE_TARBALL\$" "$SDL_DIST/NODE_SHASUMS256.txt" | awk '{print $1}')" if [[ -z "$EXPECTED_SHA" ]]; then echo "Error: SHA256 not found for $NODE_TARBALL" exit 1 fi ACTUAL_SHA="$(sha256sum "$SDL_DIST/$NODE_TARBALL" | awk '{print $1}')" if [[ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]]; then echo "SHA mismatch $NODE_TARBALL" rm -f "$SDL_DIST/$NODE_TARBALL" exit 1 fi echo "$EXPECTED_SHA $NODE_TARBALL" > "$SDL_DIST/$NODE_TARBALL.sha256" fi done unset OS ARCH # ------------------------------------------------------------ # Extract sdl-mgr-VERSION tarball from SDL_DIST to SDL_MGR # Symlink to SDL_MGR/current # ------------------------------------------------------------ echo "" echo "Extracting: $SDL_DIST/sdl-mgr_$SDL_VERSION.tgz to $SDL_MGR/sdl-mgr_$SDL_VERSION" rm -rf "$SDL_MGR/sdl-mgr_$SDL_VERSION" tar -xzf "$SDL_DIST/sdl-mgr_$SDL_VERSION.tgz" -C "$SDL_MGR" mv "$SDL_MGR/sdl-mgr" "$SDL_MGR/sdl-mgr_$SDL_VERSION" # rm "$SDL_MGR/current" ln -sfn "$SDL_MGR/sdl-mgr_$SDL_VERSION" "$SDL_MGR/current" # ------------------------------------------------------------ # Copy SDL_MGR/current/conf/efault-sdl-mgr-config.json to SDL_CONF/sdl-mgr-config.json # ------------------------------------------------------------ if [ ! -e "$SDL_CONF/sdl-mgr-config.json" ]; then echo "" echo "Copying: $SDL_MGR/current/conf/default-sdl-mgr-config.json to $SDL_CONF/sdl-mgr-config.json" cp "$SDL_MGR/current/conf/default-sdl-mgr-config.json" "$SDL_CONF/sdl-mgr-config.json" fi # ------------------------------------------------------------ # Extract NodeJS tarball from SDL_DIST to SDL_NODEJS # Symlink to SDL_NODEJS/current # ------------------------------------------------------------ # Check if NodeJS is already installed NODE_BIN="$SDL_NODEJS/current/bin/node" NPM_BIN="$SDL_NODEJS/current/bin/npm" # echo "NodeJS binary: $NODE_BIN" if [ -e "$NODE_BIN" ]; then NODE_BIN_VERSION="$("$NODE_BIN" --version)" # echo "NodeJS bin version: $NODE_BIN_VERSION" # echo "NodeJS version: v$NODE_VERSION" case "$OS_PLATFORM" in linux) EXT="tar.xz"; TAR_OPTS="-xJf" ;; darwin) EXT="tar.gz"; TAR_OPTS="-xzf" ;; win) EXT="zip" ;; *) echo "ERROR: Unsupported OS platform: $OS_PLATFORM"; exit 1 ;; esac if [ "$NODE_BIN_VERSION" != "v$NODE_VERSION" ]; then echo "" echo "Installing NodeJS: node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}" echo "Extracting: $SDL_DIST/node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}.$EXT" if [ $OS_PLATFORM == "win32" ]; then echo "ERROR: win32 host install not supported by this installer" exit 1 # unzip "$SDL_DIST/node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}.$EXT" -d "$SDL_NODEJS" else tar $TAR_OPTS "$SDL_DIST/node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}.$EXT" -C "$SDL_NODEJS" fi # rm "$SDL_NODEJS/current" ln -sfn "$SDL_NODEJS/node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}" "$SDL_NODEJS/current" else echo "" echo "NodeJS $OS_PLATFORM-$OS_ARCH v$NODE_VERSION already installed" fi else echo "" echo "Installing NodeJS $NODE_VERSION" echo "Extracting: $SDL_DIST/node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}.tar.xz" tar -xJf "$SDL_DIST/node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}.tar.xz" -C "$SDL_NODEJS" ln -sfn "$SDL_NODEJS/node-v${NODE_VERSION}-${OS_PLATFORM}-${OS_ARCH}" "$SDL_NODEJS/current" fi # ------------------------------------------------------------ # Install sdl-mgr Node Dependencies # ------------------------------------------------------------ echo "" echo "Installing NodeJS Dependencies" cd "$SDL_MGR/current/app" export PATH="$SDL_NODEJS/current/bin:$PATH" $NPM_BIN install # ------------------------------------------------------------ # Copy Start / Stop Scripts # ------------------------------------------------------------ cp "$SDL_MGR/current/scripts/start-sdl-mgr.sh" "$SDL_HOME/scripts/" cp "$SDL_MGR/current/scripts/stop-sdl-mgr.sh" "$SDL_HOME/scripts/" cp "$SDL_MGR/current/scripts/status-sdl-mgr.sh" "$SDL_HOME/scripts/" # ------------------------------------------------------------ # Install Systemd Unit Files # Copy SDL_MGR/current/scripts/sdl-mgr.service to ~/.config/systemd/user # ------------------------------------------------------------ echo "" echo "Installing SystemD Unit Files" mkdir -p ~/.config/systemd/user cp "$SDL_MGR/current/scripts/sdl-mgr.service" ~/.config/systemd/user/ # ------------------------------------------------------------ # Start SDL Manager # ------------------------------------------------------------ systemctl --user stop sdl-mgr --no-pager sleep 2 systemctl --user daemon-reload --no-pager systemctl --user restart sdl-mgr --no-pager sleep 3 systemctl --user status sdl-mgr --no-pager echo "" echo "SDL Manager deployed:" echo " to: $SDL_HOME" echo "SDL Controls:" echo " systemctl --user status sdl-mgr" echo " systemctl --user start sdl-mgr" echo " systemctl --user stop sdl-mgr" echo "" echo "Removal Commands:" echo " WARNING: Backup data directory first!!!!" echo " systemctl --user disable sdl-mgr" echo " rm -rf $SDL_HOME"