diff --git a/README.md b/README.md index 2e3d76c6..60858b04 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 29c6be8b..8339279e 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 5d31c161..0496758d 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 4085e230..60050a70 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 5e019790..b6809440 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 00000000..bfb7a4c1 --- /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 00000000..47b3e52f --- /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