mirror of
https://github.com/eduard256/Strix
synced 2026-04-21 13:37:27 +00:00
Worker scripts with JSON event streaming: - detect.sh: system/Docker/Frigate/go2rtc detection - prepare.sh: Docker and Compose installation - strix.sh: deploy Strix standalone - strix-frigate.sh: deploy Strix + Frigate with HW autodetect - proxmox-lxc-create.sh: create Ubuntu LXC on Proxmox - install.sh: animated frontend with owl display
400 lines
13 KiB
Bash
Executable file
400 lines
13 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# Strix -- prepare.sh (worker)
|
|
#
|
|
# Silent backend worker that prepares the system for Strix deployment.
|
|
# Detects OS, installs Docker and Docker Compose if missing.
|
|
#
|
|
# Protocol:
|
|
# - Every action is reported as a single-line JSON to stdout.
|
|
# - Types: check, ok, miss, install, error, done
|
|
# - Field "msg" is always human-readable.
|
|
# - Field "data" is optional, carries machine-readable details.
|
|
# - Last line is always: {"type":"done","ok":true} or {"type":"done","ok":false,"error":"..."}
|
|
# - All internal command output goes to /dev/null or stderr (never stdout).
|
|
# - Exit code: 0 = success, 1 = failure.
|
|
#
|
|
# Usage:
|
|
# bash scripts/prepare.sh
|
|
# result=$(bash scripts/prepare.sh)
|
|
# =============================================================================
|
|
|
|
set -uo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# JSON helpers (no jq dependency)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Emit a JSON event line to stdout.
|
|
# Usage: emit "type" "msg" '{"key":"val"}'
|
|
emit() {
|
|
local type="$1"
|
|
local msg="$2"
|
|
local data="${3:-}"
|
|
|
|
# Escape double quotes in msg
|
|
msg="${msg//\\/\\\\}"
|
|
msg="${msg//\"/\\\"}"
|
|
|
|
if [[ -n "$data" ]]; then
|
|
printf '{"type":"%s","msg":"%s","data":%s}\n' "$type" "$msg" "$data"
|
|
else
|
|
printf '{"type":"%s","msg":"%s"}\n' "$type" "$msg"
|
|
fi
|
|
}
|
|
|
|
# Emit final done event and exit.
|
|
emit_done() {
|
|
local ok="$1"
|
|
local error="${2:-}"
|
|
|
|
if [[ "$ok" == "true" ]]; then
|
|
printf '{"type":"done","ok":true}\n'
|
|
exit 0
|
|
else
|
|
error="${error//\\/\\\\}"
|
|
error="${error//\"/\\\"}"
|
|
printf '{"type":"done","ok":false,"error":"%s"}\n' "$error"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# OS detection
|
|
# ---------------------------------------------------------------------------
|
|
detect_os() {
|
|
emit "check" "Detecting operating system"
|
|
|
|
local kernel
|
|
kernel=$(uname -s 2>/dev/null || echo "unknown")
|
|
|
|
case "$kernel" in
|
|
Linux)
|
|
local os_id="unknown"
|
|
local os_ver="unknown"
|
|
local os_name="Unknown Linux"
|
|
|
|
if [[ -f /etc/os-release ]]; then
|
|
# shellcheck disable=SC1091
|
|
. /etc/os-release
|
|
os_id="${ID:-unknown}"
|
|
os_ver="${VERSION_ID:-unknown}"
|
|
os_name="${PRETTY_NAME:-${ID} ${VERSION_ID}}"
|
|
fi
|
|
|
|
local arch
|
|
arch=$(uname -m 2>/dev/null || echo "unknown")
|
|
local arch_label="$arch"
|
|
case "$arch" in
|
|
x86_64) arch_label="amd64" ;;
|
|
aarch64) arch_label="arm64" ;;
|
|
armv7l) arch_label="armv7" ;;
|
|
esac
|
|
|
|
OS_TYPE="linux"
|
|
OS_ID="$os_id"
|
|
OS_VER="$os_ver"
|
|
OS_NAME="$os_name"
|
|
OS_ARCH="$arch_label"
|
|
|
|
emit "ok" "${os_name} (${arch_label})" \
|
|
"{\"os\":\"linux\",\"id\":\"${os_id}\",\"ver\":\"${os_ver}\",\"arch\":\"${arch_label}\"}"
|
|
;;
|
|
|
|
Darwin)
|
|
local mac_ver
|
|
mac_ver=$(sw_vers -productVersion 2>/dev/null || echo "unknown")
|
|
|
|
local arch
|
|
arch=$(uname -m 2>/dev/null || echo "unknown")
|
|
local arch_label="$arch"
|
|
case "$arch" in
|
|
x86_64) arch_label="amd64" ;;
|
|
arm64) arch_label="arm64" ;;
|
|
esac
|
|
|
|
OS_TYPE="mac"
|
|
OS_ID="macos"
|
|
OS_VER="$mac_ver"
|
|
OS_NAME="macOS ${mac_ver}"
|
|
OS_ARCH="$arch_label"
|
|
|
|
emit "ok" "macOS ${mac_ver} (${arch_label})" \
|
|
"{\"os\":\"mac\",\"id\":\"macos\",\"ver\":\"${mac_ver}\",\"arch\":\"${arch_label}\"}"
|
|
;;
|
|
|
|
*)
|
|
emit "error" "Unsupported OS: ${kernel}" \
|
|
"{\"kernel\":\"${kernel}\"}"
|
|
emit_done "false" "Unsupported operating system: ${kernel}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Root check (Linux only)
|
|
# ---------------------------------------------------------------------------
|
|
check_root() {
|
|
if [[ "$OS_TYPE" == "mac" ]]; then
|
|
return
|
|
fi
|
|
|
|
emit "check" "Checking root privileges"
|
|
|
|
if [[ "$(id -u)" -eq 0 ]]; then
|
|
emit "ok" "Running as root"
|
|
else
|
|
emit "error" "Root privileges required. Run with sudo."
|
|
emit_done "false" "Not running as root"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# curl (required for Docker install and compose download)
|
|
# ---------------------------------------------------------------------------
|
|
ensure_curl() {
|
|
emit "check" "Checking curl"
|
|
|
|
if command -v curl &>/dev/null; then
|
|
emit "ok" "curl available"
|
|
return 0
|
|
fi
|
|
|
|
emit "miss" "curl not found"
|
|
emit "install" "Installing curl"
|
|
|
|
local pkg_mgr="unknown"
|
|
if command -v apt-get &>/dev/null; then
|
|
pkg_mgr="apt"
|
|
emit "check" "Updating apt package lists"
|
|
if ! apt-get update -qq &>/dev/null; then
|
|
emit "error" "apt-get update failed"
|
|
emit_done "false" "Failed to update package lists"
|
|
fi
|
|
emit "ok" "Package lists updated"
|
|
emit "install" "Installing curl via apt"
|
|
apt-get install -y -qq curl &>/dev/null
|
|
elif command -v yum &>/dev/null; then
|
|
pkg_mgr="yum"
|
|
emit "install" "Installing curl via yum"
|
|
yum install -y -q curl &>/dev/null
|
|
elif command -v dnf &>/dev/null; then
|
|
pkg_mgr="dnf"
|
|
emit "install" "Installing curl via dnf"
|
|
dnf install -y -q curl &>/dev/null
|
|
elif command -v apk &>/dev/null; then
|
|
pkg_mgr="apk"
|
|
emit "install" "Installing curl via apk"
|
|
apk add --no-cache curl &>/dev/null
|
|
elif command -v pacman &>/dev/null; then
|
|
pkg_mgr="pacman"
|
|
emit "install" "Installing curl via pacman"
|
|
pacman -Sy --noconfirm curl &>/dev/null
|
|
elif command -v zypper &>/dev/null; then
|
|
pkg_mgr="zypper"
|
|
emit "install" "Installing curl via zypper"
|
|
zypper install -y curl &>/dev/null
|
|
else
|
|
emit "error" "No supported package manager found" "{\"tried\":\"apt,yum,dnf,apk,pacman,zypper\"}"
|
|
emit_done "false" "Cannot install curl: no supported package manager"
|
|
fi
|
|
|
|
if command -v curl &>/dev/null; then
|
|
emit "ok" "curl installed via ${pkg_mgr}"
|
|
return 0
|
|
fi
|
|
|
|
emit "error" "curl installation failed via ${pkg_mgr}"
|
|
emit_done "false" "curl installation failed"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Docker
|
|
# ---------------------------------------------------------------------------
|
|
check_docker() {
|
|
emit "check" "Checking Docker"
|
|
|
|
if command -v docker &>/dev/null; then
|
|
local ver
|
|
ver=$(docker --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1 || echo "unknown")
|
|
emit "ok" "Docker ${ver}" "{\"version\":\"${ver}\"}"
|
|
return 0
|
|
fi
|
|
|
|
emit "miss" "Docker not found"
|
|
return 1
|
|
}
|
|
|
|
install_docker_linux() {
|
|
emit "install" "Downloading Docker install script from get.docker.com"
|
|
|
|
local tmp_script="/tmp/get-docker.sh"
|
|
if ! curl -fsSL https://get.docker.com -o "$tmp_script" 2>/dev/null; then
|
|
emit "error" "Failed to download get.docker.com"
|
|
emit_done "false" "Docker download failed"
|
|
fi
|
|
|
|
emit "ok" "Docker install script downloaded"
|
|
emit "install" "Running Docker install script (this may take a minute)"
|
|
|
|
if sh "$tmp_script" &>/dev/null; then
|
|
rm -f "$tmp_script"
|
|
emit "ok" "Docker install script completed"
|
|
else
|
|
rm -f "$tmp_script"
|
|
emit "error" "Docker install script failed"
|
|
emit_done "false" "Docker installation failed"
|
|
fi
|
|
|
|
# Enable and start via systemd
|
|
if command -v systemctl &>/dev/null; then
|
|
emit "check" "Enabling Docker service"
|
|
systemctl enable docker &>/dev/null || true
|
|
systemctl start docker &>/dev/null || true
|
|
|
|
if systemctl is-active docker &>/dev/null; then
|
|
emit "ok" "Docker service started"
|
|
else
|
|
emit "error" "Docker service failed to start"
|
|
emit_done "false" "Docker service failed to start"
|
|
fi
|
|
fi
|
|
|
|
# Verify docker binary works
|
|
if command -v docker &>/dev/null; then
|
|
local ver
|
|
ver=$(docker --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1 || echo "unknown")
|
|
emit "ok" "Docker ${ver} installed" "{\"version\":\"${ver}\"}"
|
|
return 0
|
|
fi
|
|
|
|
emit "error" "Docker binary not found after install"
|
|
emit_done "false" "Docker installation failed"
|
|
}
|
|
|
|
install_docker_mac() {
|
|
emit "check" "Checking Docker Desktop for Mac"
|
|
|
|
# Docker Desktop should already be installed on Mac.
|
|
# We can't silently install it -- it requires GUI interaction.
|
|
emit "error" "Docker not found. Install Docker Desktop from https://docker.com/products/docker-desktop"
|
|
emit_done "false" "Docker Desktop not installed on Mac"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Docker Compose
|
|
# ---------------------------------------------------------------------------
|
|
check_compose() {
|
|
emit "check" "Checking Docker Compose"
|
|
|
|
# Plugin (v2): docker compose
|
|
if docker compose version &>/dev/null; then
|
|
local ver
|
|
ver=$(docker compose version --short 2>/dev/null || echo "unknown")
|
|
COMPOSE_CMD="docker compose"
|
|
emit "ok" "Docker Compose ${ver} (plugin)" "{\"version\":\"${ver}\",\"type\":\"plugin\"}"
|
|
return 0
|
|
fi
|
|
|
|
# Standalone: docker-compose
|
|
if command -v docker-compose &>/dev/null; then
|
|
local ver
|
|
ver=$(docker-compose --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1 || echo "unknown")
|
|
COMPOSE_CMD="docker-compose"
|
|
emit "ok" "Docker Compose ${ver} (standalone)" "{\"version\":\"${ver}\",\"type\":\"standalone\"}"
|
|
return 0
|
|
fi
|
|
|
|
emit "miss" "Docker Compose not found"
|
|
return 1
|
|
}
|
|
|
|
install_compose_linux() {
|
|
emit "install" "Installing Docker Compose plugin"
|
|
|
|
local installed=false
|
|
|
|
# Try package manager first
|
|
if command -v apt-get &>/dev/null; then
|
|
apt-get update -qq &>/dev/null && apt-get install -y -qq docker-compose-plugin &>/dev/null && installed=true
|
|
elif command -v yum &>/dev/null; then
|
|
yum install -y -q docker-compose-plugin &>/dev/null && installed=true
|
|
elif command -v dnf &>/dev/null; then
|
|
dnf install -y -q docker-compose-plugin &>/dev/null && installed=true
|
|
fi
|
|
|
|
# Fallback: download binary
|
|
if [[ "$installed" == false ]]; then
|
|
emit "install" "Downloading Docker Compose binary"
|
|
|
|
local compose_ver="v2.29.1"
|
|
local compose_arch
|
|
case "$OS_ARCH" in
|
|
amd64) compose_arch="x86_64" ;;
|
|
arm64) compose_arch="aarch64" ;;
|
|
*) compose_arch="$(uname -m)" ;;
|
|
esac
|
|
|
|
mkdir -p /usr/local/lib/docker/cli-plugins &>/dev/null
|
|
if curl -fsSL "https://github.com/docker/compose/releases/download/${compose_ver}/docker-compose-linux-${compose_arch}" \
|
|
-o /usr/local/lib/docker/cli-plugins/docker-compose &>/dev/null; then
|
|
chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
|
|
installed=true
|
|
fi
|
|
fi
|
|
|
|
# Verify
|
|
if [[ "$installed" == true ]] && docker compose version &>/dev/null; then
|
|
local ver
|
|
ver=$(docker compose version --short 2>/dev/null || echo "unknown")
|
|
COMPOSE_CMD="docker compose"
|
|
emit "ok" "Docker Compose ${ver} installed" "{\"version\":\"${ver}\"}"
|
|
return 0
|
|
fi
|
|
|
|
emit "error" "Docker Compose installation failed"
|
|
emit_done "false" "Docker Compose installation failed"
|
|
}
|
|
|
|
install_compose_mac() {
|
|
# On Mac, Docker Compose comes with Docker Desktop
|
|
emit "error" "Docker Compose not found. It should be included with Docker Desktop."
|
|
emit_done "false" "Docker Compose missing on Mac"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
main() {
|
|
# 1. Detect OS
|
|
detect_os
|
|
|
|
# 2. Root check
|
|
check_root
|
|
|
|
# 3. curl (needed for Docker install, always present on Mac)
|
|
if [[ "$OS_TYPE" == "linux" ]]; then
|
|
ensure_curl
|
|
fi
|
|
|
|
# 4. Docker
|
|
if ! check_docker; then
|
|
case "$OS_TYPE" in
|
|
linux) install_docker_linux ;;
|
|
mac) install_docker_mac ;;
|
|
esac
|
|
fi
|
|
|
|
# 5. Docker Compose
|
|
if ! check_compose; then
|
|
case "$OS_TYPE" in
|
|
linux) install_compose_linux ;;
|
|
mac) install_compose_mac ;;
|
|
esac
|
|
fi
|
|
|
|
# 6. All good
|
|
emit_done "true"
|
|
}
|
|
|
|
main
|