diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 10fe327..4870c55 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -16,25 +16,50 @@ Write-Host "" $v = Get-Command v -ErrorAction SilentlyContinue if (-not $v) { Write-Host "V compiler not found. Downloading..." - - # Download V weekly build + $vUrl = "https://github.com/vlang/v/releases/download/weekly.2024.32/v_windows.zip" $vZip = "$env:TEMP\v_windows.zip" $vDir = "$env:LOCALAPPDATA\v" - + Invoke-WebRequest -Uri $vUrl -OutFile $vZip Expand-Archive -Path $vZip -DestinationPath $vDir -Force Remove-Item $vZip - + $env:PATH = "$vDir\v;$env:PATH" Write-Host "V installed to $vDir" } +# Install NSSM (required for Windows service) +$nssmPath = "$env:ProgramFiles\ntfy-bridge\nssm.exe" +if (-not (Test-Path $nssmPath)) { + Write-Host "Downloading NSSM (Non-Sucking Service Manager)..." + $nssmUrl = "https://nssm.cc/release/nssm-2.24.zip" + $nssmZip = "$env:TEMP\nssm.zip" + $nssmDir = "$env:TEMP\nssm_extract" + + Invoke-WebRequest -Uri $nssmUrl -OutFile $nssmZip + Expand-Archive -Path $nssmZip -DestinationPath $nssmDir -Force + + # Find nssm.exe in the extracted folder (it's in a win64 subfolder) + $nssmExe = Get-ChildItem -Path $nssmDir -Recurse -Name "nssm.exe" | Select-Object -First 1 + if (-not $nssmExe) { + Write-Error "Could not find nssm.exe in the downloaded archive." + exit 1 + } + $extractedNssm = Join-Path $nssmDir $nssmExe + + New-Item -ItemType Directory -Force -Path "$env:ProgramFiles\ntfy-bridge" | Out-Null + Copy-Item -Force $extractedNssm $nssmPath + Remove-Item $nssmZip + Remove-Item -Recurse -Force $nssmDir + Write-Host "NSSM installed to $nssmPath" +} + # Build Write-Host "Building ntfy-bridge..." v -prod -o ntfy-bridge.exe . -# Copy to a permanent location +# Copy to install directory $installDir = "$env:ProgramFiles\ntfy-bridge" New-Item -ItemType Directory -Force -Path $installDir | Out-Null Copy-Item -Force ntfy-bridge.exe $installDir\ @@ -47,9 +72,9 @@ if (-not (Test-Path "$installDir\ntfy-bridge.yaml")) { Write-Host " ⚠️ Edit $installDir\ntfy-bridge.yaml to configure your sources!" -ForegroundColor Yellow } -# Install as Windows service +# Install as Windows service (via NSSM) Write-Host "Installing as Windows service..." -& "$installDir\ntfy-bridge.exe" --install-service --config "$installDir\ntfy-bridge.yaml" 2>&1 | Out-Null +& "$installDir\ntfy-bridge.exe" --install-service --config "$installDir\ntfy-bridge.yaml" 2>&1 | Write-Host Write-Host "" Write-Host "Done! ntfy-bridge is installed and running as a Windows service." -ForegroundColor Green @@ -60,6 +85,9 @@ Write-Host " Dashboard: http://localhost:9090/" Write-Host " Health: http://localhost:9090/health" Write-Host "" Write-Host " Service commands:" -Write-Host " sc query ntfy-bridge" -Write-Host " sc stop ntfy-bridge" -Write-Host " sc start ntfy-bridge" +Write-Host " nssm status ntfy-bridge" +Write-Host " nssm stop ntfy-bridge" +Write-Host " nssm start ntfy-bridge" +Write-Host " nssm remove ntfy-bridge confirm (uninstall)" +Write-Host "" +Write-Host " Logs: C:\ProgramData\ntfy-bridge\service.log" diff --git a/service.v b/service.v index 36a11fb..95e3cfb 100644 --- a/service.v +++ b/service.v @@ -9,22 +9,72 @@ pub fn service_install(config_path string, quiet bool) ! { println(' Config: ${abs_config}') $if windows { - args := '--quiet --config "${abs_config}"' name := 'ntfy-bridge' - cmd := 'sc create ${name} binPath= "${exe_path} ${args}" start= auto DisplayName= "ntfy-bridge Notification Hub"' - r := os.execute(cmd) - if r.exit_code != 0 { - eprintln('sc create failed: ${r.output}') + args := '--quiet --config "${abs_config}"' + + // Find nssm + mut nssm_exe := '' + dir := os.dir(exe_path) + candidate := os.join_path(dir, 'nssm.exe') + if os.exists(candidate) { + nssm_exe = candidate + } else { + r := os.execute('where nssm 2>nul') + if r.exit_code == 0 && r.output.contains('nssm') { + nssm_exe = 'nssm' + } + } + + if nssm_exe == '' { + eprintln('nssm.exe not found. Windows requires NSSM to run ntfy-bridge as a service.') + eprintln('') + eprintln('Install NSSM (Non-Sucking Service Manager):') + eprintln(' scoop install nssm') + eprintln(' OR download from https://nssm.cc/download') + eprintln(' OR place nssm.exe next to ntfy-bridge.exe') exit(1) } - println(' Service created.') - os.execute('sc failure ${name} reset= 86400 actions= restart/10000/restart/30000/restart/60000') - os.execute('sc description ${name} "Homelab notification hub"') - r2 := os.execute('sc start ${name}') - if r2.exit_code != 0 { - println(' Warning: sc start ${name} manually.') + println(' Using nssm: ${nssm_exe}') + + // Remove previous install if exists + os.execute('"${nssm_exe}" stop ${name}') + os.execute('"${nssm_exe}" remove ${name} confirm') + + // Install the service + r := os.execute('"${nssm_exe}" install ${name} "${exe_path}" ${args}') + if r.exit_code != 0 { + eprintln('nssm install failed: ${r.output}') + exit(1) } - println(' Service started. sc start|stop|query|delete ${name}') + println(' Service registered.') + + // Configure service + os.execute('"${nssm_exe}" set ${name} AppStdout C:\\ProgramData\\ntfy-bridge\\service.log') + os.execute('"${nssm_exe}" set ${name} AppStderr C:\\ProgramData\\ntfy-bridge\\service.log') + os.execute('"${nssm_exe}" set ${name} AppRotateFiles 1') + os.execute('"${nssm_exe}" set ${name} AppRotateSeconds 86400') + os.execute('"${nssm_exe}" set ${name} AppRotateBytes 1048576') + os.execute('"${nssm_exe}" set ${name} DisplayName "ntfy-bridge Notification Hub"') + os.execute('"${nssm_exe}" set ${name} Description "Homelab notification hub"') + os.execute('"${nssm_exe}" set ${name} Start SERVICE_AUTO_START') + + // Create log directory + os.mkdir_all('C:\\ProgramData\\ntfy-bridge') or {} + + // Start the service + r2 := os.execute('"${nssm_exe}" start ${name}') + if r2.exit_code != 0 { + println(' Warning: nssm start ${name} failed — start manually.') + } else { + println(' Service started successfully.') + } + println('') + println('Service commands:') + println(' nssm status ${name}') + println(' nssm start ${name}') + println(' nssm stop ${name}') + println(' nssm restart ${name}') + println(' nssm remove ${name} confirm (uninstall)') return } $if linux { @@ -102,10 +152,32 @@ pub fn service_uninstall() ! { $if windows { name := 'ntfy-bridge' - os.execute('sc stop ${name}') - r := os.execute('sc delete ${name}') - if r.exit_code != 0 { - return error('sc delete failed: ${r.output}') + // Find nssm + exe_path := os.executable() + mut nssm_exe := '' + dir := os.dir(exe_path) + candidate := os.join_path(dir, 'nssm.exe') + if os.exists(candidate) { + nssm_exe = candidate + } else { + r := os.execute('where nssm 2>nul') + if r.exit_code == 0 && r.output.contains('nssm') { + nssm_exe = 'nssm' + } + } + + if nssm_exe != '' { + os.execute('"${nssm_exe}" stop ${name}') + r := os.execute('"${nssm_exe}" remove ${name} confirm') + if r.exit_code != 0 { + return error('nssm remove failed: ${r.output}') + } + } else { + os.execute('sc stop ${name}') + r := os.execute('sc delete ${name}') + if r.exit_code != 0 { + return error('sc delete failed: ${r.output}') + } } println(' Service uninstalled.') return @@ -142,8 +214,27 @@ pub fn service_uninstall() ! { pub fn service_status() ! { $if windows { - r := os.execute('sc query ntfy-bridge') - if r.exit_code == 0 { println(r.output) } else { println('Not installed.') } + // Find nssm + exe_path := os.executable() + mut nssm_exe := '' + dir := os.dir(exe_path) + candidate := os.join_path(dir, 'nssm.exe') + if os.exists(candidate) { + nssm_exe = candidate + } else { + r := os.execute('where nssm 2>nul') + if r.exit_code == 0 && r.output.contains('nssm') { + nssm_exe = 'nssm' + } + } + + if nssm_exe != '' { + r := os.execute('"${nssm_exe}" status ntfy-bridge') + if r.exit_code == 0 { println(r.output) } else { println('Not installed.') } + } else { + r := os.execute('sc query ntfy-bridge') + if r.exit_code == 0 { println(r.output) } else { println('Not installed.') } + } return } $if linux {