#!/bin/bash # NextNVR — Deploy & Update Script # Usage: curl -sSLO https://git.lohmar.co.uk/cclohmar/NextNVR/raw/branch/main/deploy_nvr.sh # chmod +x deploy_nvr.sh && ./deploy_nvr.sh # # Greenfield: clones repo, prompts for storage, builds config, installs service. # Update: kills old instance, pulls latest, rebuilds, restarts (config preserved). 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" GO2RTC_VER="1.9.14" [ "$(uname -m)" = "aarch64" ] && GO_ARCH="arm64" 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 — Deploy / Update" echo " $(uname -m) / $(uname -s)" echo "============================================" echo "" # ── 0. Check sudo ── if ! command -v sudo &>/dev/null; then if [ "$(id -u)" -eq 0 ]; then sudo() { "$@"; } else err "sudo is required. Install it first or run as root." exit 1 fi fi # ── 1. Stop all existing instances ── log "Stopping any running NextNVR instances..." # Graceful stop via systemd first. if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then sudo systemctl stop "${SERVICE_NAME}" 2>/dev/null || true sleep 2 fi # Kill any remaining processes by PID (targeted, no wildcard pkill). for proc in "/opt/nextnvr/nextnvr" "go2rtc"; do for pid in $(pgrep -f "${proc}" 2>/dev/null || true); do sudo kill "${pid}" 2>/dev/null || true done done sleep 2 # Final check — force kill stragglers. for pid in $(pgrep -f "/opt/nextnvr/nextnvr" 2>/dev/null || true); do sudo kill -9 "${pid}" 2>/dev/null || true done sleep 1 log "All instances stopped." # ── 2. Detect greenfield vs update ── GREENFIELD=false if [ ! -d "${DEPLOY_DIR}" ]; then GREENFIELD=true log "Greenfield deployment detected." else log "Existing installation detected — update mode (config preserved)." fi # ── 3. System dependencies ── detect_pkg_manager() { if command -v apt-get &>/dev/null; then PKG="sudo apt-get install -y -qq"; DISTRO="debian" elif command -v dnf &>/dev/null; then PKG="sudo dnf install -y -q"; DISTRO="fedora" elif command -v yum &>/dev/null; then PKG="sudo yum install -y -q"; DISTRO="rhel" elif command -v pacman &>/dev/null; then PKG="sudo pacman -S --noconfirm --needed"; DISTRO="arch" elif command -v apk &>/dev/null; then PKG="sudo apk add --no-cache"; DISTRO="alpine" else err "Unsupported distro."; exit 1; fi log "Detected: ${DISTRO}" } detect_pkg_manager if ! command -v ffmpeg &>/dev/null; then info "Installing ffmpeg..."; eval "${PKG} ffmpeg"; log "ffmpeg installed." fi if ! command -v git &>/dev/null; then info "Installing git..."; eval "${PKG} git" fi if ! command -v curl &>/dev/null; then info "Installing curl..."; eval "${PKG} curl" fi if ! command -v jq &>/dev/null; then info "Installing jq..."; eval "${PKG} jq" fi # ── 4. Time sync ── if command -v timedatectl &>/dev/null; then sudo timedatectl set-ntp true 2>/dev/null || true fi log "Time sync: OK | $(date)" # ── 5. Go ── GO_TAR="go${GO_VERSION}.linux-${GO_ARCH}.tar.gz" GO_HOME="$HOME/.local/go" if [ ! -f "${GO_HOME}/bin/go" ]; then info "Installing Go ${GO_VERSION}..." curl -sSL "https://go.dev/dl/${GO_TAR}" -o /tmp/${GO_TAR} mkdir -p "${GO_HOME}" tar -C "$HOME/.local" -xzf /tmp/${GO_TAR} rm /tmp/${GO_TAR} fi export PATH="${GO_HOME}/bin:$PATH" export GOPATH="$HOME/go" log "Go: $(${GO_HOME}/bin/go version)" # ── 6. Clone / pull ── if $GREENFIELD; then sudo mkdir -p "${DEPLOY_DIR}" sudo chown "$(whoami):$(id -gn)" "${DEPLOY_DIR}" git clone "${GIT_URL}" "${DEPLOY_DIR}" log "Repository cloned." else git -C "${DEPLOY_DIR}" pull origin main log "Repository updated." fi cd "${DEPLOY_DIR}" # ── 7. go2rtc ── GO2RTC_BIN="${DEPLOY_DIR}/go2rtc" if [ ! -f "${GO2RTC_BIN}" ]; then info "Downloading go2rtc ${GO2RTC_VER}..." curl -sSL "https://github.com/AlexxIT/go2rtc/releases/download/v${GO2RTC_VER}/go2rtc_linux_${GO_ARCH}" -o "${GO2RTC_BIN}" chmod +x "${GO2RTC_BIN}" log "go2rtc installed." fi # ── 8. Configuration (greenfield only) ── CONFIG_FILE="${DEPLOY_DIR}/config.json" if $GREENFIELD; then # Build config.json from sample with sensible defaults. cp config.json.sample "${CONFIG_FILE}" STORAGE_PATH=$(jq -r .storage.recordings_path "${CONFIG_FILE}") sudo mkdir -p "${STORAGE_PATH}" 2>/dev/null || mkdir -p "${STORAGE_PATH}" sudo chown "$(whoami):$(id -gn)" "${STORAGE_PATH}" 2>/dev/null || true log "Config created: ${CONFIG_FILE}" log "Storage: ${STORAGE_PATH}" else log "Config preserved: ${CONFIG_FILE}" fi # ── 9. Build ── log "Building NextNVR..." go mod tidy 2>/dev/null || true 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: nextnvr v${VERSION} ($(du -h nextnvr | cut -f1))" # ── 10. Systemd service ── SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" sudo tee "${SERVICE_FILE}" > /dev/null </dev/null | awk '{print $1}' || echo 'localhost'):8080" info " Config: ${CONFIG_FILE}" info " Storage: $(jq -r .storage.recordings_path ${CONFIG_FILE} 2>/dev/null || echo 'unknown')" echo "" echo " Service:" echo " sudo systemctl status ${SERVICE_NAME}" echo " sudo systemctl restart ${SERVICE_NAME}" echo " sudo journalctl -u ${SERVICE_NAME} -f" echo "" sudo systemctl status "${SERVICE_NAME}" --no-pager --lines=5 2>/dev/null || true echo ""