From 7d8c2f5c8ae88fbffa40c35606907e5c6741e364 Mon Sep 17 00:00:00 2001 From: TJ da Tuna Date: Sun, 15 Feb 2026 14:57:54 -0600 Subject: [PATCH] Add password reset helper scripts for all platforms Scripts automate the full password reset flow (stop service, clear DB password, restart, extract temp password from logs) for Windows (PowerShell), Docker, macOS native, and Linux native deployments. Update all deployment docs with script usage and manual fallbacks. --- README.md | 17 ++ docker/DEPLOYMENT.md | 23 +- docker/NATIVE-DEPLOYMENT.md | 28 +++ docs/MACOS-INSTALLATION.md | 29 +++ scripts/proxmox/README.md | 21 ++ scripts/reset-password.ps1 | 252 +++++++++++++++++++ scripts/reset-password.sh | 478 ++++++++++++++++++++++++++++++++++++ 7 files changed, 844 insertions(+), 4 deletions(-) create mode 100644 scripts/reset-password.ps1 create mode 100755 scripts/reset-password.sh diff --git a/README.md b/README.md index 2e3d76c6d..60858b041 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,23 @@ All core functionality is working and tested on many combinations of UniFi Gatew In progress: Time-series metrics, cable modem monitoring, WiFi analysis, multi-site support. +## Password Reset + +If you forget the admin password, use the reset script for your platform: + +**Windows (PowerShell as Administrator):** +```powershell +irm https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.ps1 -OutFile reset-password.ps1 +.\reset-password.ps1 +``` + +**Docker / macOS / Linux:** +```bash +curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash +``` + +The script stops the service, clears the password, restarts, and shows you the new temporary password. + ## Contributing If you find issues, report them via GitHub Issues. Include your UniFi device models and controller version. Sanitize credentials and IPs before attaching logs. diff --git a/docker/DEPLOYMENT.md b/docker/DEPLOYMENT.md index 29c6be8b0..8339279e2 100644 --- a/docker/DEPLOYMENT.md +++ b/docker/DEPLOYMENT.md @@ -648,15 +648,30 @@ docker compose pull && docker compose up -d ### Reset Admin Password -If you've forgotten your password or need to reset it: +If you've forgotten your password or need to reset it, use the reset script: + +```bash +curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash +``` + +Or download and run it (useful inside Proxmox LXC or restricted environments): + +```bash +curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh -o reset-password.sh +bash reset-password.sh +``` + +The script auto-detects your Docker container, clears the password, restarts, and displays the new temporary password. + +**Manual fallback** (if you prefer not to use the script): ```bash # Clear the password from the database -docker exec network-optimizer sqlite3 /app/data/network_optimizer.db "UPDATE AdminSettings SET Password = NULL;" +docker exec network-optimizer sqlite3 /app/data/network_optimizer.db \ + "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" # Restart to trigger auto-generated password -cd /path/to/network-optimizer/docker -docker compose up -d +docker restart network-optimizer # View the new auto-generated password docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED" diff --git a/docker/NATIVE-DEPLOYMENT.md b/docker/NATIVE-DEPLOYMENT.md index 5d31c1612..0496758d5 100644 --- a/docker/NATIVE-DEPLOYMENT.md +++ b/docker/NATIVE-DEPLOYMENT.md @@ -611,6 +611,34 @@ journalctl -u network-optimizer -f type C:\NetworkOptimizer\logs\stdout.log ``` +### Reset Admin Password + +If you forget the admin password, use the reset script: + +```bash +curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash +``` + +The script auto-detects macOS or Linux native installations, clears the password, restarts the service, and displays the new temporary password. Use `--macos` or `--linux` to force a specific mode. + +**Manual fallback:** + +```bash +# macOS +launchctl unload ~/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist +sqlite3 ~/Library/Application\ Support/NetworkOptimizer/network_optimizer.db \ + "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" +launchctl load ~/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist +grep "Password:" ~/network-optimizer/logs/stdout.log | tail -1 + +# Linux +sudo systemctl stop network-optimizer +sqlite3 /opt/network-optimizer/data/network_optimizer.db \ + "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" +sudo systemctl start network-optimizer +journalctl -u network-optimizer --since "2 minutes ago" | grep "Password:" +``` + --- ## Support diff --git a/docs/MACOS-INSTALLATION.md b/docs/MACOS-INSTALLATION.md index 4085e230a..60050a703 100644 --- a/docs/MACOS-INSTALLATION.md +++ b/docs/MACOS-INSTALLATION.md @@ -80,6 +80,35 @@ git pull The install script preserves your database, encryption keys, and `start.sh` configuration by backing them up before reinstalling. +## Troubleshooting + +### Reset Admin Password + +If you forget the admin password, use the reset script: + +```bash +curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash +``` + +The script auto-detects the macOS native installation, clears the password, restarts the service, and displays the new temporary password. + +**Manual fallback:** + +```bash +# Stop the service +launchctl unload ~/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist + +# Clear the password +sqlite3 ~/Library/Application\ Support/NetworkOptimizer/network_optimizer.db \ + "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" + +# Restart +launchctl load ~/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist + +# View the new password +grep "Password:" ~/network-optimizer/logs/stdout.log | tail -1 +``` + ## Uninstalling ```bash diff --git a/scripts/proxmox/README.md b/scripts/proxmox/README.md index 5e019790a..b68094406 100644 --- a/scripts/proxmox/README.md +++ b/scripts/proxmox/README.md @@ -297,6 +297,27 @@ pct exec -- ls -la /opt/network-optimizer/ pct exec -- chown -R 1000:1000 /opt/network-optimizer/data ``` +### Reset Admin Password + +If you forget the admin password: + +```bash +pct exec -- bash -c "curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash -s -- --force" +``` + +**Manual fallback:** + +```bash +# Clear the password +pct exec -- docker exec network-optimizer sqlite3 /app/data/network_optimizer.db \ + "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" + +# Restart and view the new password +pct exec -- docker restart network-optimizer +sleep 10 +pct exec -- docker logs network-optimizer 2>&1 | grep -A5 "AUTO-GENERATED" +``` + ## Uninstall To completely remove Network Optimizer: diff --git a/scripts/reset-password.ps1 b/scripts/reset-password.ps1 new file mode 100644 index 000000000..bfb7a4c17 --- /dev/null +++ b/scripts/reset-password.ps1 @@ -0,0 +1,252 @@ +<# +.SYNOPSIS + Resets the Network Optimizer admin password on Windows. + +.DESCRIPTION + Stops the NetworkOptimizer service, clears the admin password from the + SQLite database, restarts the service, and extracts the auto-generated + temporary password from the log file. + +.PARAMETER InstallDir + Override the install directory. By default, auto-detected from the + Windows service registration or defaults to + "C:\Program Files\Ozark Connect\Network Optimizer". + +.PARAMETER Force + Skip the confirmation prompt. + +.PARAMETER TimeoutSeconds + How long to wait for the service to become healthy (default: 60). + +.EXAMPLE + .\reset-password.ps1 + .\reset-password.ps1 -Force + .\reset-password.ps1 -InstallDir "D:\NetworkOptimizer" +#> + +[CmdletBinding()] +param( + [string]$InstallDir, + [switch]$Force, + [int]$TimeoutSeconds = 60 +) + +$ErrorActionPreference = 'Stop' + +# ============================================================================= +# Require Administrator +# ============================================================================= +$isAdmin = ([Security.Principal.WindowsPrincipal] ` + [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( + [Security.Principal.WindowsBuiltInRole]::Administrator) + +if (-not $isAdmin) { + Write-Host "ERROR: This script must be run as Administrator." -ForegroundColor Red + Write-Host "Right-click PowerShell and select 'Run as administrator', then try again." + exit 1 +} + +# ============================================================================= +# Constants +# ============================================================================= +$ServiceName = "NetworkOptimizer" +$DbFileName = "network_optimizer.db" +$HealthUrl = "http://localhost:8042/api/health" + +# ============================================================================= +# Auto-detect Install Directory +# ============================================================================= +if (-not $InstallDir) { + # Try 1: Get path from Windows service + $svc = Get-CimInstance Win32_Service -Filter "Name='$ServiceName'" -ErrorAction SilentlyContinue + if ($svc -and $svc.PathName) { + $exePath = $svc.PathName -replace '"', '' + $InstallDir = Split-Path $exePath -Parent + } + + # Try 2: Registry (WiX installer writes InstallFolder) + if (-not $InstallDir) { + $regPaths = @( + "HKLM:\SOFTWARE\Ozark Connect\Network Optimizer", + "HKLM:\SOFTWARE\WOW6432Node\Ozark Connect\Network Optimizer" + ) + foreach ($rp in $regPaths) { + if (Test-Path $rp) { + $regVal = Get-ItemProperty $rp -Name "InstallFolder" -ErrorAction SilentlyContinue + if ($regVal) { $InstallDir = $regVal.InstallFolder; break } + } + } + } + + # Try 3: Default path + if (-not $InstallDir) { + $InstallDir = "C:\Program Files\Ozark Connect\Network Optimizer" + } +} + +Write-Host "" +Write-Host "Network Optimizer - Password Reset" -ForegroundColor Cyan +Write-Host "===================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "Install directory: $InstallDir" + +# ============================================================================= +# Verify database exists +# ============================================================================= +$dbPath = Join-Path $InstallDir "data\$DbFileName" +if (-not (Test-Path $dbPath)) { + Write-Host "ERROR: Database not found at $dbPath" -ForegroundColor Red + Write-Host "Use -InstallDir to specify the correct installation directory." + exit 1 +} + +Write-Host "Database found: $dbPath" -ForegroundColor Green + +# ============================================================================= +# Check for sqlite3 +# ============================================================================= +$sqlite3 = Get-Command sqlite3 -ErrorAction SilentlyContinue +if (-not $sqlite3) { + # Check in the install directory (bundled with WiX installer) + $bundled = Join-Path $InstallDir "sqlite3.exe" + if (Test-Path $bundled) { + $sqlite3Path = $bundled + } else { + Write-Host "" + Write-Host "ERROR: sqlite3 not found in PATH or install directory." -ForegroundColor Red + Write-Host "" + Write-Host "Install it with: winget install SQLite.SQLite" -ForegroundColor Yellow + Write-Host "Then restart this terminal and try again." + exit 1 + } +} else { + $sqlite3Path = $sqlite3.Source +} + +Write-Host "sqlite3: $sqlite3Path" -ForegroundColor Green +Write-Host "" + +# ============================================================================= +# Confirm with user +# ============================================================================= +if (-not $Force) { + Write-Host "This will:" -ForegroundColor Yellow + Write-Host " 1. Stop the NetworkOptimizer service" + Write-Host " 2. Clear the admin password from the database" + Write-Host " 3. Restart the service" + Write-Host " 4. Display the new auto-generated temporary password" + Write-Host "" + $confirm = Read-Host "Continue? (y/N)" + if ($confirm -notmatch '^[Yy]') { + Write-Host "Cancelled." + exit 0 + } + Write-Host "" +} + +# ============================================================================= +# Stop the service +# ============================================================================= +$svcObj = Get-Service $ServiceName -ErrorAction SilentlyContinue +if (-not $svcObj) { + Write-Host "ERROR: Service '$ServiceName' not found." -ForegroundColor Red + Write-Host "Is Network Optimizer installed as a Windows service?" + exit 1 +} + +if ($svcObj.Status -eq 'Running') { + Write-Host "Stopping service..." -NoNewline + Stop-Service $ServiceName -Force + $svcObj.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(30)) + Write-Host " done." -ForegroundColor Green +} else { + Write-Host "Service is already stopped." -ForegroundColor Yellow +} + +# ============================================================================= +# Clear admin password +# ============================================================================= +Write-Host "Clearing admin password..." -NoNewline +& $sqlite3Path $dbPath "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" +if ($LASTEXITCODE -ne 0) { + Write-Host " FAILED." -ForegroundColor Red + Write-Host "sqlite3 returned exit code $LASTEXITCODE" + exit 1 +} +Write-Host " done." -ForegroundColor Green + +# ============================================================================= +# Start the service +# ============================================================================= +Write-Host "Starting service..." -NoNewline +Start-Service $ServiceName +Write-Host " done." -ForegroundColor Green + +# ============================================================================= +# Wait for health endpoint +# ============================================================================= +Write-Host "Waiting for application to start..." -NoNewline +$deadline = (Get-Date).AddSeconds($TimeoutSeconds) +$healthy = $false + +while ((Get-Date) -lt $deadline) { + try { + $resp = Invoke-WebRequest -Uri $HealthUrl -UseBasicParsing -TimeoutSec 3 -ErrorAction SilentlyContinue + if ($resp.StatusCode -eq 200) { + $healthy = $true + break + } + } catch { + # Not ready yet + } + Start-Sleep -Seconds 2 + Write-Host "." -NoNewline +} + +if ($healthy) { + Write-Host " ready!" -ForegroundColor Green +} else { + Write-Host " timed out." -ForegroundColor Yellow + Write-Host "The service may still be starting. Check the logs manually." +} + +# ============================================================================= +# Extract password from log +# ============================================================================= +Write-Host "" +$logDir = Join-Path $InstallDir "logs" +$today = (Get-Date).ToString("yyyyMMdd") +$logFile = Join-Path $logDir "networkoptimizer-$today.log" + +$password = $null +if (Test-Path $logFile) { + # Find the last occurrence of the password line after AUTO-GENERATED banner + $logContent = Get-Content $logFile -Tail 100 + for ($i = $logContent.Count - 1; $i -ge 0; $i--) { + if ($logContent[$i] -match 'Password:\s+(\S+)') { + $password = $Matches[1] + break + } + } +} + +if ($password) { + Write-Host "===================================" -ForegroundColor Green + Write-Host " Password reset successful!" -ForegroundColor Green + Write-Host "===================================" -ForegroundColor Green + Write-Host "" + Write-Host " Temporary password: $password" -ForegroundColor Cyan + Write-Host "" + Write-Host " Open http://localhost:8042 and log in with this password." + Write-Host " Go to Settings to set a permanent password." + Write-Host "" +} else { + Write-Host "Password reset completed, but could not extract the new password from logs." -ForegroundColor Yellow + Write-Host "" + Write-Host "Check the log file manually:" + Write-Host " $logFile" -ForegroundColor Cyan + Write-Host "" + Write-Host "Or look for the password in the Windows Event Viewer under Application logs." + Write-Host "Search for 'AUTO-GENERATED' in the log output." + Write-Host "" +} diff --git a/scripts/reset-password.sh b/scripts/reset-password.sh new file mode 100755 index 000000000..47b3e52f9 --- /dev/null +++ b/scripts/reset-password.sh @@ -0,0 +1,478 @@ +#!/usr/bin/env bash + +# Network Optimizer - Password Reset Script +# https://github.com/Ozark-Connect/NetworkOptimizer +# +# Resets the admin password by clearing it from the database and restarting +# the service. Works with Docker, macOS native, and Linux native deployments. +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/Ozark-Connect/NetworkOptimizer/main/scripts/reset-password.sh | bash +# bash reset-password.sh [--docker|--macos|--linux] [--container NAME] [--data-dir PATH] [--force] + +set -euo pipefail + +# ============================================================================= +# Colors and Formatting (matches proxmox/install.sh) +# ============================================================================= +if [[ -t 1 ]]; then + readonly RD='\033[0;31m' + readonly GN='\033[0;32m' + readonly YW='\033[0;33m' + readonly BL='\033[0;34m' + readonly CY='\033[0;36m' + readonly BLD='\033[1m' + readonly CL='\033[0m' +else + readonly RD='' GN='' YW='' BL='' CY='' BLD='' CL='' +fi + +msg_info() { echo -e "${BL}[INFO]${CL} $1"; } +msg_ok() { echo -e "${GN}[OK]${CL} $1"; } +msg_warn() { echo -e "${YW}[WARN]${CL} $1"; } +msg_error() { echo -e "${RD}[ERROR]${CL} $1"; } + +header() { + echo "" + echo -e "${BLD}${CY}Network Optimizer - Password Reset${CL}" + echo -e "${BLD}${CY}===================================${CL}" + echo "" +} + +# ============================================================================= +# Defaults +# ============================================================================= +MODE="" # docker, macos, linux (auto-detected if empty) +CONTAINER="network-optimizer" +DATA_DIR="" +FORCE=false +TIMEOUT=60 +HEALTH_URL="http://localhost:8042/api/health" + +# ============================================================================= +# Parse Arguments +# ============================================================================= +while [[ $# -gt 0 ]]; do + case "$1" in + --docker) MODE="docker"; shift ;; + --macos) MODE="macos"; shift ;; + --linux) MODE="linux"; shift ;; + --container) CONTAINER="$2"; shift 2 ;; + --data-dir) DATA_DIR="$2"; shift 2 ;; + --force) FORCE=true; shift ;; + --timeout) TIMEOUT="$2"; shift 2 ;; + -h|--help) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --docker Force Docker mode" + echo " --macos Force macOS native mode" + echo " --linux Force Linux native mode" + echo " --container NAME Docker container name (default: network-optimizer)" + echo " --data-dir PATH Override database directory path" + echo " --force Skip confirmation prompt" + echo " --timeout SECS Health check timeout (default: 60)" + echo " -h, --help Show this help" + exit 0 + ;; + *) + msg_error "Unknown option: $1" + echo "Use --help for usage information." + exit 1 + ;; + esac +done + +# Auto-force when stdin is not a terminal (e.g., curl | bash) +if [[ ! -t 0 ]]; then + FORCE=true +fi + +# ============================================================================= +# Auto-detect Mode +# ============================================================================= +detect_mode() { + if [[ -n "$MODE" ]]; then + return + fi + + # Check for Docker container + if command -v docker &>/dev/null; then + if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then + MODE="docker" + msg_info "Detected Docker container: $CONTAINER" + return + fi + fi + + # Check for macOS native install + if [[ "$(uname)" == "Darwin" ]]; then + if [[ -d "$HOME/network-optimizer" ]] || \ + [[ -f "$HOME/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist" ]]; then + MODE="macos" + msg_info "Detected macOS native installation" + return + fi + fi + + # Check for Linux native install + if [[ "$(uname)" == "Linux" ]]; then + if systemctl list-unit-files 2>/dev/null | grep -qi "networkoptimizer\|network-optimizer"; then + MODE="linux" + msg_info "Detected Linux native installation (systemd)" + return + fi + if pgrep -f "NetworkOptimizer.Web" &>/dev/null; then + MODE="linux" + msg_info "Detected running NetworkOptimizer process" + return + fi + if [[ -d "/opt/network-optimizer" ]]; then + MODE="linux" + msg_info "Detected Linux installation at /opt/network-optimizer" + return + fi + fi + + msg_error "Could not auto-detect installation type." + echo "" + echo "Please specify one of:" + echo " --docker Docker container" + echo " --macos macOS native install" + echo " --linux Linux native install" + exit 1 +} + +# ============================================================================= +# Check for sqlite3 +# ============================================================================= +check_sqlite3() { + if command -v sqlite3 &>/dev/null; then + return 0 + fi + + msg_error "sqlite3 is not installed." + echo "" + if [[ "$(uname)" == "Darwin" ]]; then + echo "sqlite3 should be included with macOS. Try:" + echo " brew install sqlite3" + elif command -v apt-get &>/dev/null; then + echo "Install with: sudo apt-get install -y sqlite3" + elif command -v dnf &>/dev/null; then + echo "Install with: sudo dnf install -y sqlite" + elif command -v pacman &>/dev/null; then + echo "Install with: sudo pacman -S sqlite" + else + echo "Install sqlite3 using your package manager." + fi + exit 1 +} + +# ============================================================================= +# Wait for health endpoint +# ============================================================================= +wait_for_health() { + msg_info "Waiting for application to start..." + local deadline=$((SECONDS + TIMEOUT)) + + while [[ $SECONDS -lt $deadline ]]; do + if curl -sf "$HEALTH_URL" -o /dev/null --max-time 3 2>/dev/null; then + msg_ok "Application is ready" + return 0 + fi + sleep 2 + done + + msg_warn "Health check timed out after ${TIMEOUT}s. The service may still be starting." + return 1 +} + +# ============================================================================= +# Confirm with user +# ============================================================================= +confirm() { + if [[ "$FORCE" == true ]]; then + return 0 + fi + + echo "This will:" + echo " 1. Stop the Network Optimizer service" + echo " 2. Clear the admin password from the database" + echo " 3. Restart the service" + echo " 4. Display the new auto-generated temporary password" + echo "" + read -rp "Continue? (y/N) " answer + if [[ ! "$answer" =~ ^[Yy] ]]; then + echo "Cancelled." + exit 0 + fi + echo "" +} + +# ============================================================================= +# Docker Mode +# ============================================================================= +reset_docker() { + msg_info "Mode: Docker (container: $CONTAINER)" + echo "" + + # Check container exists + if ! docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then + msg_error "Container '$CONTAINER' not found." + echo "Use --container NAME to specify a different container name." + exit 1 + fi + + # If container is stopped, start it temporarily for docker exec + if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then + msg_warn "Container is stopped. Starting it temporarily..." + docker start "$CONTAINER" >/dev/null + sleep 3 + fi + + confirm + + # Clear password via docker exec + msg_info "Clearing admin password..." + docker exec "$CONTAINER" sqlite3 /app/data/network_optimizer.db \ + "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" + msg_ok "Password cleared" + + # Restart container + msg_info "Restarting container..." + docker restart "$CONTAINER" >/dev/null + msg_ok "Container restarted" + + # Wait for health + wait_for_health || true + + # Extract password from docker logs + echo "" + local password + password=$(docker logs --since 2m "$CONTAINER" 2>&1 \ + | grep "Password:" | tail -1 \ + | sed -E 's/.*Password:\s+//' | tr -d '[:space:]') + + show_result "$password" +} + +# ============================================================================= +# macOS Native Mode +# ============================================================================= +reset_macos() { + msg_info "Mode: macOS native" + echo "" + + local plist="$HOME/Library/LaunchAgents/net.ozarkconnect.networkoptimizer.plist" + local db_dir="${DATA_DIR:-$HOME/Library/Application Support/NetworkOptimizer}" + local db_path="$db_dir/network_optimizer.db" + local log_file="$HOME/network-optimizer/logs/stdout.log" + + # Verify database + if [[ ! -f "$db_path" ]]; then + msg_error "Database not found at: $db_path" + echo "Use --data-dir to specify the correct data directory." + exit 1 + fi + msg_ok "Database found: $db_path" + + check_sqlite3 + confirm + + # Stop service + if [[ -f "$plist" ]]; then + msg_info "Stopping service..." + launchctl unload "$plist" 2>/dev/null || true + msg_ok "Service stopped" + else + msg_warn "LaunchAgent plist not found at $plist" + msg_warn "You may need to stop the service manually." + fi + + # Clear password + msg_info "Clearing admin password..." + sqlite3 "$db_path" "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" + msg_ok "Password cleared" + + # Start service + if [[ -f "$plist" ]]; then + msg_info "Starting service..." + launchctl load "$plist" + msg_ok "Service started" + else + msg_warn "Cannot auto-start - start the service manually." + fi + + # Wait for health + wait_for_health || true + + # Extract password from log + echo "" + local password="" + if [[ -f "$log_file" ]]; then + password=$(tail -100 "$log_file" \ + | grep "Password:" | tail -1 \ + | sed -E 's/.*Password:\s+//' | tr -d '[:space:]') + fi + + show_result "$password" +} + +# ============================================================================= +# Linux Native Mode +# ============================================================================= +reset_linux() { + msg_info "Mode: Linux native" + echo "" + + # Find the systemd service name + local service_name="" + for name in networkoptimizer NetworkOptimizer network-optimizer; do + if systemctl list-unit-files "${name}.service" &>/dev/null 2>&1; then + if systemctl list-unit-files "${name}.service" 2>/dev/null | grep -q "$name"; then + service_name="$name" + break + fi + fi + done + + # Find database + local db_path="" + if [[ -n "$DATA_DIR" ]]; then + db_path="$DATA_DIR/network_optimizer.db" + else + for candidate in \ + "/opt/network-optimizer/data/network_optimizer.db" \ + "$HOME/.local/share/NetworkOptimizer/network_optimizer.db" \ + "/var/lib/network-optimizer/network_optimizer.db"; do + if [[ -f "$candidate" ]]; then + db_path="$candidate" + break + fi + done + fi + + if [[ -z "$db_path" ]] || [[ ! -f "$db_path" ]]; then + msg_error "Database not found." + echo "Searched:" + echo " /opt/network-optimizer/data/network_optimizer.db" + echo " ~/.local/share/NetworkOptimizer/network_optimizer.db" + echo " /var/lib/network-optimizer/network_optimizer.db" + echo "" + echo "Use --data-dir to specify the correct data directory." + exit 1 + fi + msg_ok "Database found: $db_path" + + check_sqlite3 + confirm + + # Stop service + if [[ -n "$service_name" ]]; then + msg_info "Stopping service ($service_name)..." + sudo systemctl stop "$service_name" + msg_ok "Service stopped" + else + msg_warn "No systemd service found. Attempting to kill the process..." + if pkill -f "NetworkOptimizer.Web" 2>/dev/null; then + msg_ok "Process stopped" + else + msg_warn "Could not stop process. It may not be running." + fi + fi + + # Clear password + msg_info "Clearing admin password..." + sqlite3 "$db_path" "UPDATE AdminSettings SET Password = NULL, Enabled = 0;" + msg_ok "Password cleared" + + # Start service + if [[ -n "$service_name" ]]; then + msg_info "Starting service ($service_name)..." + sudo systemctl start "$service_name" + msg_ok "Service started" + else + msg_warn "No systemd service found. Start the application manually." + msg_warn "The new password will appear in the application logs." + echo "" + return + fi + + # Wait for health + wait_for_health || true + + # Extract password from journalctl or log file + echo "" + local password="" + if [[ -n "$service_name" ]]; then + password=$(journalctl -u "$service_name" --since "2 minutes ago" --no-pager 2>/dev/null \ + | grep "Password:" | tail -1 \ + | sed -E 's/.*Password:\s+//' | tr -d '[:space:]') + fi + + # Fallback: check log files + if [[ -z "$password" ]]; then + for log_file in \ + "/opt/network-optimizer/logs/stdout.log" \ + "$HOME/.local/share/NetworkOptimizer/logs/stdout.log"; do + if [[ -f "$log_file" ]]; then + password=$(tail -100 "$log_file" \ + | grep "Password:" | tail -1 \ + | sed -E 's/.*Password:\s+//' | tr -d '[:space:]') + if [[ -n "$password" ]]; then break; fi + fi + done + fi + + show_result "$password" +} + +# ============================================================================= +# Display Result +# ============================================================================= +show_result() { + local password="$1" + + if [[ -n "$password" ]]; then + echo -e "${GN}===================================${CL}" + echo -e "${GN} Password reset successful!${CL}" + echo -e "${GN}===================================${CL}" + echo "" + echo -e " Temporary password: ${CY}${BLD}${password}${CL}" + echo "" + echo " Open http://localhost:8042 and log in with this password." + echo " Go to Settings to set a permanent password." + echo "" + else + msg_warn "Password reset completed, but could not extract the new password from logs." + echo "" + echo "Check the logs manually:" + if [[ "$MODE" == "docker" ]]; then + echo " docker logs $CONTAINER 2>&1 | grep -A5 'AUTO-GENERATED'" + elif [[ "$MODE" == "macos" ]]; then + echo " grep 'Password:' ~/network-optimizer/logs/stdout.log | tail -1" + else + echo " journalctl -u networkoptimizer --since '5 minutes ago' | grep 'Password:'" + fi + echo "" + echo "Look for the line containing 'AUTO-GENERATED ADMIN PASSWORD'." + echo "" + fi +} + +# ============================================================================= +# Main +# ============================================================================= +header +detect_mode +echo "" + +case "$MODE" in + docker) reset_docker ;; + macos) reset_macos ;; + linux) reset_linux ;; + *) + msg_error "Unknown mode: $MODE" + exit 1 + ;; +esac