187 lines
4.4 KiB
PowerShell
187 lines
4.4 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
|
|
param(
|
|
[switch]$NoBrowser
|
|
)
|
|
|
|
$appRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$productName = 'SSH Manager'
|
|
$baseUrl = 'http://127.0.0.1:48080'
|
|
$healthUrl = "$baseUrl/api/auth/health"
|
|
$localRoot = Join-Path $env:LOCALAPPDATA 'SSHManager'
|
|
$dataDir = Join-Path $localRoot 'data'
|
|
$runtimeDir = Join-Path $localRoot 'runtime'
|
|
$logsDir = Join-Path $localRoot 'logs'
|
|
$stateFile = Join-Path $runtimeDir 'launcher-state.json'
|
|
$pidFile = Join-Path $runtimeDir 'app.pid'
|
|
$logFile = Join-Path $logsDir 'backend.log'
|
|
$jarPath = Join-Path $appRoot 'ssh-manager.jar'
|
|
$bundledJavaw = Join-Path $appRoot 'jre\bin\javaw.exe'
|
|
$bundledJava = Join-Path $appRoot 'jre\bin\java.exe'
|
|
|
|
function Ensure-Directory([string]$path) {
|
|
if (-not (Test-Path $path)) {
|
|
New-Item -ItemType Directory -Path $path | Out-Null
|
|
}
|
|
}
|
|
|
|
function Test-Healthy {
|
|
try {
|
|
$response = Invoke-RestMethod -Uri $healthUrl -Method Get -TimeoutSec 2
|
|
return $response.app -eq 'ssh-manager' -and $response.status -eq 'ok'
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Get-JavaCommand {
|
|
if (Test-Path $bundledJavaw) {
|
|
return $bundledJavaw
|
|
}
|
|
if (Test-Path $bundledJava) {
|
|
return $bundledJava
|
|
}
|
|
|
|
$javaw = Get-Command javaw.exe -ErrorAction SilentlyContinue
|
|
if ($javaw) {
|
|
return $javaw.Source
|
|
}
|
|
|
|
$java = Get-Command java.exe -ErrorAction SilentlyContinue
|
|
if ($java) {
|
|
return $java.Source
|
|
}
|
|
|
|
throw '未找到可用的 Java 运行时,请重新安装带内置 JRE 的版本。'
|
|
}
|
|
|
|
function New-State {
|
|
$jwtBytes = New-Object byte[] 48
|
|
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($jwtBytes)
|
|
|
|
$encBytes = New-Object byte[] 32
|
|
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($encBytes)
|
|
|
|
$state = @{
|
|
jwtSecret = [Convert]::ToBase64String($jwtBytes)
|
|
encryptionKey = [Convert]::ToBase64String($encBytes)
|
|
createdAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
|
|
}
|
|
|
|
$state | ConvertTo-Json | Set-Content -Path $stateFile -Encoding ASCII
|
|
return $state
|
|
}
|
|
|
|
function Get-State {
|
|
if (-not (Test-Path $stateFile)) {
|
|
return New-State
|
|
}
|
|
|
|
$state = Get-Content -Path $stateFile -Raw | ConvertFrom-Json
|
|
if (-not $state.jwtSecret -or -not $state.encryptionKey) {
|
|
return New-State
|
|
}
|
|
|
|
return $state
|
|
}
|
|
|
|
function Clear-StalePid {
|
|
if (-not (Test-Path $pidFile)) {
|
|
return
|
|
}
|
|
|
|
$rawPid = Get-Content -Path $pidFile -Raw
|
|
$pidInfo = $null
|
|
|
|
try {
|
|
$pidInfo = $rawPid | ConvertFrom-Json
|
|
} catch {
|
|
$legacyPid = 0
|
|
if ([int]::TryParse($rawPid, [ref]$legacyPid)) {
|
|
$pidInfo = @{
|
|
pid = $legacyPid
|
|
startedAt = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
if (-not $pidInfo) {
|
|
Remove-Item -Path $pidFile -Force -ErrorAction SilentlyContinue
|
|
return
|
|
}
|
|
|
|
$process = Get-Process -Id ([int]$pidInfo.pid) -ErrorAction SilentlyContinue
|
|
if (-not $process) {
|
|
Remove-Item -Path $pidFile -Force -ErrorAction SilentlyContinue
|
|
return
|
|
}
|
|
|
|
if ($pidInfo.startedAt -and $process.StartTime) {
|
|
$processStartedAt = [DateTimeOffset]$process.StartTime
|
|
if ($processStartedAt.ToUnixTimeMilliseconds() -ne [int64]$pidInfo.startedAt) {
|
|
Remove-Item -Path $pidFile -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
|
|
Ensure-Directory $localRoot
|
|
Ensure-Directory $dataDir
|
|
Ensure-Directory $runtimeDir
|
|
Ensure-Directory $logsDir
|
|
|
|
if (-not (Test-Path $jarPath)) {
|
|
throw "未找到程序包:$jarPath"
|
|
}
|
|
|
|
Clear-StalePid
|
|
|
|
if (Test-Healthy) {
|
|
if (-not $NoBrowser) {
|
|
Start-Process $baseUrl | Out-Null
|
|
}
|
|
exit 0
|
|
}
|
|
|
|
$state = Get-State
|
|
$javaCommand = Get-JavaCommand
|
|
|
|
$arguments = @(
|
|
'-Dfile.encoding=UTF-8'
|
|
'-Dserver.address=127.0.0.1'
|
|
"-Dlogging.file.name=$logFile"
|
|
"-DDATA_DIR=$dataDir"
|
|
"-Dsshmanager.jwt-secret=$($state.jwtSecret)"
|
|
"-Dsshmanager.encryption-key=$($state.encryptionKey)"
|
|
'-jar'
|
|
$jarPath
|
|
)
|
|
|
|
$process = Start-Process -FilePath $javaCommand -ArgumentList $arguments -WorkingDirectory $appRoot -PassThru
|
|
$pidInfo = @{
|
|
pid = $process.Id
|
|
startedAt = ([DateTimeOffset]$process.StartTime).ToUnixTimeMilliseconds()
|
|
}
|
|
$pidInfo | ConvertTo-Json | Set-Content -Path $pidFile -Encoding ASCII
|
|
|
|
for ($i = 0; $i -lt 45; $i++) {
|
|
Start-Sleep -Seconds 1
|
|
|
|
if (Test-Healthy) {
|
|
if (-not $NoBrowser) {
|
|
Start-Process $baseUrl | Out-Null
|
|
}
|
|
exit 0
|
|
}
|
|
|
|
$process.Refresh()
|
|
if ($process.HasExited) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if (-not $process.HasExited) {
|
|
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
throw "SSH Manager 启动失败,请检查日志:$logFile"
|