diff --git a/api.go b/api.go index fc8996a..dff06ba 100644 --- a/api.go +++ b/api.go @@ -79,7 +79,7 @@ func (s *Server) handleCameraByID(w http.ResponseWriter, r *http.Request) { // handleConfig handles configuration read/write. // GET /api/config — return current config (without passwords) -// POST /api/config — save full config to config.yaml +// POST /api/config — save full config to config.json func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: @@ -126,9 +126,9 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { *s.appConfig = newCfg - if err := SaveConfig(*s.appConfig, "/opt/nextnvr/config.yaml"); err != nil { + if err := SaveConfig(*s.appConfig, "/opt/nextnvr/config.json"); err != nil { // Try saving to the app's config path. - _ = SaveConfig(*s.appConfig, "config.yaml") + _ = SaveConfig(*s.appConfig, "config.json") } jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "config saved"}) diff --git a/config.go b/config.go index fad71ce..d355c18 100644 --- a/config.go +++ b/config.go @@ -1,75 +1,67 @@ // NextNVR — MIT License // Copyright (c) 2026 NextNVR Contributors // SPDX-License-Identifier: MIT - -// Handles config.yaml parsing with sensible defaults +// +// config.go — JSON configuration management. Zero external dependencies. package main import ( + "encoding/json" "fmt" "os" - "path/filepath" - - "gopkg.in/yaml.v3" ) // Config represents the top-level application configuration. type Config struct { - Server ServerConfig `yaml:"server" json:"server"` - Storage StorageConfig `yaml:"storage" json:"storage"` - Auth AuthConfig `yaml:"auth" json:"auth"` - Cameras []CameraConfig `yaml:"cameras" json:"cameras"` - Go2RTC Go2RTCConfig `yaml:"go2rtc" json:"go2rtc"` + Server ServerConfig `json:"server"` + Storage StorageConfig `json:"storage"` + Auth AuthConfig `json:"auth"` + Cameras []CameraConfig `json:"cameras"` + Go2RTC Go2RTCConfig `json:"go2rtc"` } -// ServerConfig holds HTTP server settings. type ServerConfig struct { - Port string `yaml:"port" json:"port"` - BindHost string `yaml:"bind_host" json:"bind_host"` - ViewerPort string `yaml:"viewer_port" json:"viewer_port"` + Port string `json:"port"` + BindHost string `json:"bind_host"` + ViewerPort string `json:"viewer_port"` } -// StorageConfig holds recording and retention settings. type StorageConfig struct { - RecordingsPath string `yaml:"recordings_path" json:"recordings_path"` - RetentionDays int `yaml:"retention_days" json:"retention_days"` - CleanupIntervalMins int `yaml:"cleanup_interval_mins" json:"cleanup_interval_mins"` + RecordingsPath string `json:"recordings_path"` + RetentionDays int `json:"retention_days"` + CleanupIntervalMins int `json:"cleanup_interval_mins"` } -// CameraConfig represents a single camera's configuration. type CameraConfig struct { - ID string `yaml:"id" json:"id"` - Name string `yaml:"name" json:"name"` - IP string `yaml:"ip" json:"ip"` - Username string `yaml:"username" json:"username"` - Password string `yaml:"password" json:"password"` - ONVIFPort int `yaml:"onvif_port" json:"onvif_port,string"` - RTSPMain string `yaml:"rtsp_main" json:"rtsp_main"` - RTSPSub string `yaml:"rtsp_sub" json:"rtsp_sub"` - Description string `yaml:"description" json:"description"` - Enabled bool `yaml:"enabled" json:"enabled"` - Record bool `yaml:"record" json:"record"` + ID string `json:"id"` + Name string `json:"name"` + IP string `json:"ip"` + Username string `json:"username"` + Password string `json:"password"` + ONVIFPort int `json:"onvif_port,string"` + RTSPMain string `json:"rtsp_main"` + RTSPSub string `json:"rtsp_sub"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + Record bool `json:"record"` } -// Go2RTCConfig holds go2rtc child process settings. type Go2RTCConfig struct { - Enabled bool `yaml:"enabled" json:"enabled"` - Port string `yaml:"port" json:"port"` - Binary string `yaml:"binary" json:"binary"` + Enabled bool `json:"enabled"` + Port string `json:"port"` + Binary string `json:"binary"` } -// AuthConfig holds authentication settings. type AuthConfig struct { - Enabled bool `yaml:"enabled" json:"enabled"` - Master UserConfig `yaml:"master" json:"master"` - Viewer UserConfig `yaml:"viewer" json:"viewer"` + Enabled bool `json:"enabled"` + Master UserConfig `json:"master"` + Viewer UserConfig `json:"viewer"` } -// UserConfig holds a single user's credentials. type UserConfig struct { - Username string `yaml:"username" json:"username"` - Password string `yaml:"password" json:"password"` // bcrypt hash - Enabled bool `yaml:"enabled" json:"enabled"` + Username string `json:"username"` + Password string `json:"password"` + Enabled bool `json:"enabled"` } // APIResponse wraps all JSON API responses. @@ -96,20 +88,23 @@ func DefaultConfig() Config { ViewerPort: ":8090", }, Storage: StorageConfig{ - RecordingsPath: "/mnt/recordings", + RecordingsPath: "/home/master/nvr_data", RetentionDays: 7, CleanupIntervalMins: 60, }, + Auth: AuthConfig{ + Enabled: false, + Viewer: UserConfig{Enabled: true}, + }, Go2RTC: Go2RTCConfig{ Enabled: true, Port: ":1984", - Binary: "go2rtc", + Binary: "/opt/nextnvr/go2rtc", }, } } -// LoadConfig reads and parses a YAML configuration file. -// If the file does not exist, it returns DefaultConfig(). +// LoadConfig reads and parses a JSON configuration file. func LoadConfig(path string) (Config, error) { cfg := DefaultConfig() @@ -121,31 +116,22 @@ func LoadConfig(path string) (Config, error) { return cfg, fmt.Errorf("reading config: %w", err) } - if err := yaml.Unmarshal(data, &cfg); err != nil { + if err := json.Unmarshal(data, &cfg); err != nil { return cfg, fmt.Errorf("parsing config: %w", err) } - // Apply path defaults relative to config file location. - configDir := filepath.Dir(path) - if cfg.Storage.RecordingsPath == "" { - cfg.Storage.RecordingsPath = "/mnt/recordings" - } - _ = configDir // reserved for relative path resolution - return cfg, nil } -// SaveConfig writes the configuration to a YAML file. +// SaveConfig writes the configuration to a JSON file. func SaveConfig(cfg Config, path string) error { - data, err := yaml.Marshal(&cfg) + data, err := json.MarshalIndent(&cfg, "", " ") if err != nil { return fmt.Errorf("marshaling config: %w", err) } + data = append(data, '\n') - header := []byte("# NextNVR Configuration\n# Generated by NextNVR setup\n\n") - out := append(header, data...) - - if err := os.WriteFile(path, out, 0600); err != nil { + if err := os.WriteFile(path, data, 0600); err != nil { return fmt.Errorf("writing config: %w", err) } return nil diff --git a/config.json.sample b/config.json.sample new file mode 100644 index 0000000..e1181d4 --- /dev/null +++ b/config.json.sample @@ -0,0 +1,31 @@ +{ + "server": { + "port": ":8080", + "bind_host": "0.0.0.0", + "viewer_port": ":8090" + }, + "storage": { + "recordings_path": "", + "retention_days": 7, + "cleanup_interval_mins": 60 + }, + "auth": { + "enabled": false, + "master": { + "username": "", + "password": "", + "enabled": false + }, + "viewer": { + "username": "", + "password": "", + "enabled": true + } + }, + "go2rtc": { + "enabled": true, + "port": ":1984", + "binary": "/opt/nextnvr/go2rtc" + }, + "cameras": [] +} diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 973675b..0000000 --- a/config.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# NextNVR Configuration -# Edit this file or use the web UI (Settings tab) to configure cameras. - -server: - port: ":8080" - bind_host: "0.0.0.0" - viewer_port: ":8090" - -storage: - recordings_path: "/home/master/nvr_data" - retention_days: 7 - cleanup_interval_mins: 60 - -auth: - enabled: false - master: - username: "" - password: "" - viewer: - enabled: true - username: "" - password: "" - -go2rtc: - enabled: false - port: ":1984" - binary: "go2rtc" - -cameras: [] diff --git a/deploy_nvr.sh b/deploy_nvr.sh index 246ad7d..d33b924 100755 --- a/deploy_nvr.sh +++ b/deploy_nvr.sh @@ -3,8 +3,8 @@ # Usage: curl -sSLO https://git.lohmar.co.uk/cclohmar/NextNVR/raw/branch/main/deploy_nvr.sh # chmod +x deploy_nvr.sh && ./deploy_nvr.sh # -# Supports: Debian, Ubuntu, Fedora, RHEL, Rocky, Alma, Arch, Alpine -# Requires: curl, sudo, systemd (or falls back to nohup for non-systemd) +# 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" @@ -12,16 +12,11 @@ DEPLOY_DIR="/opt/nextnvr" SERVICE_NAME="nextnvr" GO_VERSION="1.24.5" GO_ARCH="amd64" +GO2RTC_VER="1.9.14" -# 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' +[ "$(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"; } @@ -29,30 +24,14 @@ info() { echo -e "${CYAN}[NextNVR]${NC} $1"; } echo "" echo "============================================" -echo " NextNVR — Portable Deployment" +echo " NextNVR — Deploy / Update" echo " $(uname -m) / $(uname -s)" echo "============================================" echo "" -# ── 0. Stop existing service (clean update) ── -if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then - log "Stopping existing NextNVR service..." - sudo systemctl stop "${SERVICE_NAME}" - log "Service stopped." -elif pgrep -f "/opt/nextnvr/nextnvr" > /dev/null 2>&1; then - warn "NextNVR is running outside systemd. Stopping..." - sudo pkill -f "/opt/nextnvr/nextnvr" 2>/dev/null || true - sleep 2 -fi - -# Clean up any lingering processes. -sudo pkill -f "go2rtc" 2>/dev/null || true -sudo pkill -f "ffmpeg.*rec_" 2>/dev/null || true - -# ── Check for sudo ── +# ── 0. Check 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." @@ -60,207 +39,134 @@ if ! command -v sudo &>/dev/null; then 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}'))" -} +# ── 1. Kill all existing instances ── +log "Stopping any running NextNVR instances..." +sudo systemctl stop "${SERVICE_NAME}" 2>/dev/null || true +sudo pkill -9 -f "nextnvr" 2>/dev/null || true +sudo pkill -9 -f "go2rtc" 2>/dev/null || true +sudo pkill -9 -f "ffmpeg.*rec_" 2>/dev/null || true +sleep 2 +if pgrep -f "nextnvr" > /dev/null 2>&1; then + err "Cannot kill existing NextNVR processes. Please stop them manually and retry." + exit 1 +fi +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 -# ── 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)" + 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 -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 +# ── 4. Time sync ── 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 + sudo timedatectl set-ntp true 2>/dev/null || true fi +log "Time sync: OK | $(date)" -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}..." - +# ── 5. Go ── 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} +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} - log "Go ${GO_VERSION} installed." fi - export PATH="${GO_HOME}/bin:$PATH" export GOPATH="$HOME/go" -export GOCACHE="$HOME/.cache/go-build" +log "Go: $(${GO_HOME}/bin/go version)" -# ── 3. Clone or update repository ── -log "Step 3/6: Fetching NextNVR source..." +# ── 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}" -# Download go2rtc (required for live streaming and snapshots). -GO2RTC_VER="1.9.14" +# ── 7. go2rtc ── GO2RTC_BIN="${DEPLOY_DIR}/go2rtc" if [ ! -f "${GO2RTC_BIN}" ]; then info "Downloading go2rtc ${GO2RTC_VER}..." - sudo mkdir -p "${DEPLOY_DIR}" 2>/dev/null || true 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 -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..." - -# Repo includes a default config.yaml. Check if storage path needs setting. -CURRENT_PATH=$(grep recordings_path "${DEPLOY_DIR}/config.yaml" 2>/dev/null | awk -F'"' '{print $2}') -if [ -z "${CURRENT_PATH}" ] || [ "${CURRENT_PATH}" = "/home/master/nvr_data" ]; then +# ── 8. Configuration (greenfield only) ── +CONFIG_FILE="${DEPLOY_DIR}/config.json" +if $GREENFIELD; then echo "" - info "Storage path not set. Where should recordings be stored?" + info "Where should recordings be stored?" + info " Examples: /mnt/recordings, /home/user/nvr_data, /media/usb" if [ -e /dev/tty ]; then - read -p " Path [${HOME}/nvr_data]: " STORAGE_PATH "${CONFIG_FILE}" + sudo mkdir -p "${STORAGE_PATH}" sudo chown "$(whoami):$(id -gn)" "${STORAGE_PATH}" - sed -i "s|recordings_path:.*|recordings_path: \"${STORAGE_PATH}\"|" "${DEPLOY_DIR}/config.yaml" - # Set go2rtc binary path. - sed -i "s|binary:.*|binary: \"${GO2RTC_BIN}\"|" "${DEPLOY_DIR}/config.yaml" - log "Storage: ${STORAGE_PATH}" + log "Config created: ${CONFIG_FILE}" + log "Storage path: ${STORAGE_PATH}" else - log "Storage: ${CURRENT_PATH}" - sudo mkdir -p "${CURRENT_PATH}" 2>/dev/null || true + log "Config preserved: ${CONFIG_FILE}" fi -# ── 6. Install service ── -log "Step 6/6: Service installation..." +# ── 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))" -RUN_USER="$(whoami)" +# ── 10. Systemd service ── 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 </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 +sudo systemctl daemon-reload +sudo systemctl enable "${SERVICE_NAME}" +sudo systemctl restart "${SERVICE_NAME}" +sleep 2 # ── Done ── echo "" @@ -314,18 +199,14 @@ 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: $(grep recordings_path ${DEPLOY_DIR}/config.yaml 2>/dev/null | awk '{print $2}' | tr -d '"')" +info " Config: ${CONFIG_FILE}" +info " Storage: $(jq -r .storage.recordings_path ${CONFIG_FILE} 2>/dev/null || echo 'unknown')" 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 " 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 "" diff --git a/go.mod b/go.mod index fb895b4..1fe8edf 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module github.com/cclohmar/NextNVR go 1.25.0 -require ( - golang.org/x/crypto v0.54.0 - gopkg.in/yaml.v3 v3.0.1 -) +require golang.org/x/crypto v0.54.0 diff --git a/go.sum b/go.sum index 576e662..d3f7642 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,2 @@ golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 32a1a96..5214832 100644 --- a/main.go +++ b/main.go @@ -136,13 +136,13 @@ func flagConfigPath() string { } // Default: look next to the binary, then /opt/nextnvr. - if _, err := os.Stat("config.yaml"); err == nil { - return "config.yaml" + if _, err := os.Stat("config.json"); err == nil { + return "config.json" } - if _, err := os.Stat("/opt/nextnvr/config.yaml"); err == nil { - return "/opt/nextnvr/config.yaml" + if _, err := os.Stat("/opt/nextnvr/config.json"); err == nil { + return "/opt/nextnvr/config.json" } - return "config.yaml" + return "config.json" } // App holds the application runtime state.