#!/bin/bash # NextNVR — Portable One-Line Deployment Script # Usage: curl -sSL https://git.lohmar.co.uk/cclohmar/NextNVR/raw/branch/main/setup.sh | bash # # Supports: Debian, Ubuntu, Fedora, RHEL, Rocky, Alma, Arch, Alpine # Requires: curl, sudo, systemd (or falls back to nohup for non-systemd) set -e GIT_URL="https://git.lohmar.co.uk/cclohmar/NextNVR.git" DEPLOY_DIR="/opt/nextnvr" SERVICE_NAME="nextnvr" GO_VERSION="1.24.5" GO_ARCH="amd64" # Detect ARM: use arm64 if running on aarch64. if [ "$(uname -m)" = "aarch64" ]; then GO_ARCH="arm64"; fi RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' log() { echo -e "${GREEN}[NextNVR]${NC} $1"; } warn() { echo -e "${YELLOW}[NextNVR]${NC} $1"; } err() { echo -e "${RED}[NextNVR]${NC} $1"; } info() { echo -e "${CYAN}[NextNVR]${NC} $1"; } echo "" echo "============================================" echo " NextNVR — Portable Deployment" echo " $(uname -m) / $(uname -s)" echo "============================================" echo "" # ── Check for sudo ── if ! command -v sudo &>/dev/null; then if [ "$(id -u)" -eq 0 ]; then # Running as root — create a no-op sudo. sudo() { "$@"; } else err "sudo is required. Install it first or run as root." exit 1 fi fi # ── 0. Detect distro & package manager ── detect_pkg_manager() { if command -v apt-get &>/dev/null; then PKG_INSTALL="sudo apt-get update -qq && sudo apt-get install -y -qq" PKG_SEARCH="apt-cache search" FFMPEG_PKG="ffmpeg" GIT_PKG="git" CURL_PKG="curl" DISTRO="debian" elif command -v dnf &>/dev/null; then PKG_INSTALL="sudo dnf install -y -q" PKG_SEARCH="dnf search" FFMPEG_PKG="ffmpeg-free" GIT_PKG="git" CURL_PKG="curl" DISTRO="fedora" elif command -v yum &>/dev/null; then PKG_INSTALL="sudo yum install -y -q" PKG_SEARCH="yum search" FFMPEG_PKG="ffmpeg" GIT_PKG="git" CURL_PKG="curl" DISTRO="rhel" elif command -v pacman &>/dev/null; then PKG_INSTALL="sudo pacman -S --noconfirm --needed" PKG_SEARCH="pacman -Ss" FFMPEG_PKG="ffmpeg" GIT_PKG="git" CURL_PKG="curl" DISTRO="arch" elif command -v apk &>/dev/null; then PKG_INSTALL="sudo apk add --no-cache" PKG_SEARCH="apk search" FFMPEG_PKG="ffmpeg" GIT_PKG="git" CURL_PKG="curl" DISTRO="alpine" else err "Could not detect package manager. Supported: apt, dnf, yum, pacman, apk." exit 1 fi log "Detected: ${DISTRO} ($(echo ${PKG_INSTALL} | awk '{print $2}'))" } detect_pkg_manager # ── 1. Install system dependencies ── log "Step 1/6: Installing system dependencies..." if ! command -v ffmpeg &>/dev/null; then info "Installing ffmpeg (${FFMPEG_PKG})..." eval "${PKG_INSTALL} ${FFMPEG_PKG}" log "ffmpeg installed." else log "ffmpeg already installed: $(ffmpeg -version 2>&1 | head -1)" fi for pkg in git curl; do pkgvar="$(echo ${pkg} | tr '[:lower:]' '[:upper:]')_PKG" if ! command -v ${pkg} &>/dev/null; then info "Installing ${pkg}..." eval "${PKG_INSTALL} ${!pkgvar}" fi done # ── 1b. Time synchronization ── log "Checking time sync..." # Try timedatectl (systemd). Fall back to checking ntp/chrony. TIME_SYNC_OK=false if command -v timedatectl &>/dev/null; then if timedatectl status 2>/dev/null | grep -q "System clock synchronized: yes"; then TIME_SYNC_OK=true else info "Enabling NTP via timedatectl..." sudo timedatectl set-ntp true 2>/dev/null || true TIME_SYNC_OK=true fi elif systemctl is-active --quiet chronyd 2>/dev/null; then TIME_SYNC_OK=true elif systemctl is-active --quiet ntpd 2>/dev/null; then TIME_SYNC_OK=true else warn "No time sync service detected. Trying to install chrony..." eval "${PKG_INSTALL} chrony 2>/dev/null" || eval "${PKG_INSTALL} chronyd 2>/dev/null" || true sudo systemctl enable --now chronyd 2>/dev/null || sudo systemctl enable --now chrony 2>/dev/null || true TIME_SYNC_OK=true fi if $TIME_SYNC_OK; then log "Time sync: OK" else warn "Time sync could not be verified. Timestamps may drift." fi log "Local time: $(date)" TZ=$(timedatectl show --property=Timezone --value 2>/dev/null || cat /etc/timezone 2>/dev/null || echo "unknown") log "Timezone: ${TZ}" # ── 2. Install Go (portable — no distro packages) ── log "Step 2/6: Setting up Go ${GO_VERSION}..." GO_TAR="go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" GO_URL="https://go.dev/dl/${GO_TAR}" GO_HOME="$HOME/.local/go" if [ -f "${GO_HOME}/bin/go" ]; then log "Go already installed: $(${GO_HOME}/bin/go version)" else info "Downloading Go ${GO_VERSION} for linux/${GO_ARCH}..." curl -sSL "${GO_URL}" -o /tmp/${GO_TAR} mkdir -p "${GO_HOME}" tar -C "$HOME/.local" -xzf /tmp/${GO_TAR} rm /tmp/${GO_TAR} log "Go ${GO_VERSION} installed." fi export PATH="${GO_HOME}/bin:$PATH" export GOPATH="$HOME/go" export GOCACHE="$HOME/.cache/go-build" # ── 3. Clone or update repository ── log "Step 3/6: Fetching NextNVR source..." if [ -d "${DEPLOY_DIR}/.git" ]; then info "Repository exists, pulling latest..." git -C "${DEPLOY_DIR}" pull origin main log "Source updated." else info "Cloning repository..." sudo mkdir -p "${DEPLOY_DIR}" sudo chown "$(whoami):$(id -gn)" "${DEPLOY_DIR}" git clone "${GIT_URL}" "${DEPLOY_DIR}" log "Source cloned." fi cd "${DEPLOY_DIR}" # ── 4. Build ── log "Step 4/6: Building NextNVR binary..." go mod tidy VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0") BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) CGO_ENABLED=0 go build \ -ldflags="-s -w -X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME}" \ -o nextnvr . chmod +x nextnvr log "Binary built: nextnvr v${VERSION} ($(du -h nextnvr | cut -f1))" # ── 5. Configuration & directories ── log "Step 5/6: Configuration..." if [ ! -f "${DEPLOY_DIR}/config.yaml" ]; then cp config.yaml "${DEPLOY_DIR}/config.yaml" log "Default config.yaml created." info "Edit it to match your cameras: nano ${DEPLOY_DIR}/config.yaml" else log "config.yaml already exists — preserved." fi sudo mkdir -p /mnt/recordings 2>/dev/null || mkdir -p /mnt/recordings sudo chown "$(whoami):$(id -gn)" /mnt/recordings 2>/dev/null || true # ── 6. Install service ── log "Step 6/6: Service installation..." RUN_USER="$(whoami)" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" HAS_SYSTEMD=false if command -v systemctl &>/dev/null; then HAS_SYSTEMD=true fi if $HAS_SYSTEMD; then sudo tee "${SERVICE_FILE}" > /dev/null </dev/null || true else warn "systemd not detected. Running NextNVR in background with nohup." # Kill any existing instance. pkill -f "nextnvr --config" 2>/dev/null || true sleep 1 nohup "${DEPLOY_DIR}/nextnvr" --config "${DEPLOY_DIR}/config.yaml" \ > "${DEPLOY_DIR}/nextnvr.log" 2>&1 & sleep 2 if pgrep -f "nextnvr --config" > /dev/null; then log "NextNVR started (PID $(pgrep -f 'nextnvr --config' | head -1))." info "Logs: tail -f ${DEPLOY_DIR}/nextnvr.log" else err "NextNVR failed to start. Check: ${DEPLOY_DIR}/nextnvr.log" fi fi # ── Done ── echo "" echo "============================================" echo -e " ${GREEN}NextNVR deployment complete!${NC}" echo "============================================" echo "" info " Web UI: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):8080" info " Config: ${DEPLOY_DIR}/config.yaml" info " Storage: /mnt/recordings/" echo "" if $HAS_SYSTEMD; then echo " Service:" echo " sudo systemctl status ${SERVICE_NAME}" echo " sudo systemctl restart ${SERVICE_NAME}" echo " sudo journalctl -u ${SERVICE_NAME} -f" else echo " Process:" echo " ps aux | grep nextnvr" echo " tail -f ${DEPLOY_DIR}/nextnvr.log" echo " To stop: pkill -f 'nextnvr --config'" fi echo ""