diff --git a/README.md b/README.md index ee52a93..c0a4032 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,23 @@ curl -sSL https://git.lohmar.co.uk/cclohmar/NextNVR/raw/branch/main/setup.sh | b That's it. The script installs everything needed and starts NextNVR automatically. -Once done, open **[http://192.168.1.10:8080](http://192.168.1.10:8080)** in your browser. +Once done, open the web interface in your browser (the script prints the URL). + +--- + +## Supported Systems + +| Distro | Package Manager | Status | +|---|---|---| +| Debian 12+ / Ubuntu 22.04+ | `apt` | ✅ Tested | +| Fedora 40+ / RHEL 9+ | `dnf` / `yum` | ✅ | +| Arch Linux | `pacman` | ✅ | +| Alpine Linux | `apk` | ✅ | +| Other (systemd) | auto-detect | ✅ | +| Other (no systemd) | fallback to `nohup` | ✅ | + +- **ARM64** (Raspberry Pi, etc.) — auto-detected +- **Root or sudo** — handles both --- @@ -35,10 +51,10 @@ Once done, open **[http://192.168.1.10:8080](http://192.168.1.10:8080)** in your | What | Detail | |---|---| -| Address | `http://192.168.1.10:8080` | +| Web interface | `http://:8080` | | Config file | `/opt/nextnvr/config.yaml` | | Recordings | `/mnt/recordings/` | -| Logs | `sudo journalctl -u nextnvr -f` | +| Logs | `sudo journalctl -u nextnvr -f` (systemd) or `tail -f /opt/nextnvr/nextnvr.log` (nohup) | ### Useful Commands @@ -71,7 +87,7 @@ IP Cameras NextNVR Your Browser ## Adding or Changing Cameras -1. Open **http://192.168.1.10:8080** +1. Open the web interface in your browser 2. Click the **Settings** tab 3. Click **🔍 Scan Network** to find your cameras 4. Give each camera a name and description diff --git a/setup.sh b/setup.sh index a366dcf..6846280 100755 --- a/setup.sh +++ b/setup.sh @@ -1,20 +1,25 @@ #!/bin/bash -# NextNVR — One-Line Deployment Script +# 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_OS="linux" 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' # No Color +NC='\033[0m' log() { echo -e "${GREEN}[NextNVR]${NC} $1"; } warn() { echo -e "${YELLOW}[NextNVR]${NC} $1"; } @@ -23,79 +28,132 @@ info() { echo -e "${CYAN}[NextNVR]${NC} $1"; } echo "" echo "============================================" -echo " NextNVR — IP Camera Recorder" -echo " One-Line Deployment" +echo " NextNVR — Portable Deployment" +echo " $(uname -m) / $(uname -s)" echo "============================================" echo "" # ── Check for sudo ── if ! command -v sudo &>/dev/null; then - err "sudo is required but not installed. Please install sudo first." - exit 1 + 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..." - sudo apt-get update -qq - sudo apt-get install -y -qq ffmpeg + 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 -if ! command -v git &>/dev/null; then - info "Installing git..." - sudo apt-get install -y -qq git - log "git installed." -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 -if ! command -v curl &>/dev/null; then - sudo apt-get install -y -qq curl -fi - -# ── 1b. Verify time synchronization (critical for NVR timestamps) ── +# ── 1b. Time synchronization ── log "Checking time sync..." -if systemctl is-active --quiet systemd-timesyncd 2>/dev/null; then - log "systemd-timesyncd is active." -elif ! timedatectl status 2>/dev/null | grep -q "System clock synchronized: yes"; then - warn "Time sync is NOT active. Enabling systemd-timesyncd..." - sudo systemctl enable --now systemd-timesyncd 2>/dev/null || true - sudo timedatectl set-ntp true 2>/dev/null || true - log "Time sync enabled." + +# 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 - log "Time sync is active." + 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 -# Verify timezone is sensible. -TZ=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "unknown") -log "Timezone: ${TZ}" -log "Local time: $(date)" +if $TIME_SYNC_OK; then + log "Time sync: OK" +else + warn "Time sync could not be verified. Timestamps may drift." +fi -# ── 2. Install Go (user-local, no sudo needed after download) ── +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}.${GO_OS}-${GO_ARCH}.tar.gz" +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 - CURRENT_GO=$(${GO_HOME}/bin/go version 2>/dev/null | grep -oP 'go\K[0-9.]+' || echo "0") - if [ "$CURRENT_GO" = "$GO_VERSION" ]; then - log "Go ${GO_VERSION} already installed." - else - warn "Go ${CURRENT_GO} found, upgrading to ${GO_VERSION}..." - rm -rf "${GO_HOME}" - 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 + log "Go already installed: $(${GO_HOME}/bin/go version)" else - info "Downloading Go ${GO_VERSION}..." + 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} @@ -105,31 +163,31 @@ 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..." - cd "${DEPLOY_DIR}" - git pull origin main + git -C "${DEPLOY_DIR}" pull origin main log "Source updated." else info "Cloning repository..." sudo mkdir -p "${DEPLOY_DIR}" - sudo chown "$(whoami):$(whoami)" "${DEPLOY_DIR}" + sudo chown "$(whoami):$(id -gn)" "${DEPLOY_DIR}" git clone "${GIT_URL}" "${DEPLOY_DIR}" - cd "${DEPLOY_DIR}" log "Source cloned." fi +cd "${DEPLOY_DIR}" + # ── 4. Build ── log "Step 4/6: Building NextNVR binary..." -cd "${DEPLOY_DIR}" go mod tidy -VERSION=$(cat VERSION 2>/dev/null || echo "unknown") +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 \ @@ -139,26 +197,32 @@ CGO_ENABLED=0 go build \ chmod +x nextnvr log "Binary built: nextnvr v${VERSION} ($(du -h nextnvr | cut -f1))" -# ── 5. Create default config if missing ── -log "Step 5/6: Checking configuration..." +# ── 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. Edit it to add your cameras:" - info " sudo nano ${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 -# Ensure recordings directory exists. -sudo mkdir -p /mnt/recordings -sudo chown "$(whoami):$(whoami)" /mnt/recordings 2>/dev/null || true +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 systemd service ── -log "Step 6/6: Installing systemd service..." +# ── 6. Install service ── +log "Step 6/6: Service installation..." +RUN_USER="$(whoami)" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" -sudo tee "${SERVICE_FILE}" > /dev/null </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 "" @@ -191,16 +275,19 @@ echo "============================================" echo -e " ${GREEN}NextNVR deployment complete!${NC}" echo "============================================" echo "" -info " Web UI: http://192.168.1.10:8080" -info " Config: ${DEPLOY_DIR}/config.yaml" -info " Storage: /mnt/recordings/" +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 "" -echo " Service commands:" -echo " sudo systemctl status ${SERVICE_NAME}" -echo " sudo systemctl restart ${SERVICE_NAME}" -echo " sudo journalctl -u ${SERVICE_NAME} -f" -echo "" - -# Show service status. -sudo systemctl status "${SERVICE_NAME}" --no-pager --lines=5 2>/dev/null || true +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 ""