feat: prepare sellable source delivery edition

This commit is contained in:
liumangmang
2026-04-16 23:28:26 +08:00
parent f606d20000
commit 37dc4d8216
93 changed files with 7649 additions and 3096 deletions

View File

@@ -0,0 +1,28 @@
@echo off
setlocal
set "ROOT=%~dp0..\.."
for %%I in ("%ROOT%") do set "ROOT=%%~fI"
cd /d "%ROOT%"
docker compose version >nul 2>nul
if errorlevel 1 (
echo [ERROR] 未检测到 docker compose请先安装 Docker Desktop
pause
exit /b 1
)
echo 启动 Docker 版 SSH Manager...
docker compose -f docker/docker-compose.yml up -d --build
if errorlevel 1 (
echo [ERROR] Docker 启动失败
pause
exit /b 1
)
echo.
echo 已启动: http://localhost:48080
echo 查看日志: docker compose -f docker/docker-compose.yml logs -f
pause
endlocal

View File

@@ -0,0 +1,13 @@
@echo off
setlocal
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0start-installed.ps1"
if errorlevel 1 (
echo.
echo [ERROR] SSH Manager 启动失败
echo 日志目录: %LOCALAPPDATA%\SSHManager\logs
pause
exit /b 1
)
endlocal

View File

@@ -0,0 +1,186 @@
$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"

View File

@@ -0,0 +1,3 @@
Set shell = CreateObject("WScript.Shell")
scriptPath = Replace(WScript.ScriptFullName, ".vbs", ".cmd")
shell.Run Chr(34) & scriptPath & Chr(34), 0, False

View File

@@ -0,0 +1,56 @@
@echo off
setlocal enabledelayedexpansion
set "ROOT=%~dp0..\.."
for %%I in ("%ROOT%") do set "ROOT=%%~fI"
set "RUNTIME_DIR=%ROOT%\runtime"
set "DATA_DIR=%ROOT%\data"
set "ENV_FILE=%RUNTIME_DIR%\local-env.bat"
if not exist "%RUNTIME_DIR%" mkdir "%RUNTIME_DIR%"
if not exist "%DATA_DIR%" mkdir "%DATA_DIR%"
where java >nul 2>nul
if errorlevel 1 (
echo [ERROR] 未检测到 Java请先安装 JDK/JRE 8+
pause
exit /b 1
)
if not exist "%ENV_FILE%" (
echo 首次启动,正在生成本地运行密钥...
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"$jwt=[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes(([Guid]::NewGuid().ToString('N')+[Guid]::NewGuid().ToString('N'))));" ^
"$bytes=New-Object byte[] 32; [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes); $enc=[Convert]::ToBase64String($bytes);" ^
"$content=@('set SSHMANAGER_JWT_SECRET='+$jwt,'set SSHMANAGER_ENCRYPTION_KEY='+$enc); Set-Content -Path '%ENV_FILE%' -Value $content -Encoding ASCII"
if errorlevel 1 (
echo [ERROR] 生成本地密钥失败
pause
exit /b 1
)
)
call "%ENV_FILE%"
set "DATA_DIR=%DATA_DIR%"
set "APP_JAR="
for %%F in ("%ROOT%\backend\target\*.jar") do (
set "APP_JAR=%%~fF"
)
if not defined APP_JAR (
echo [ERROR] 未找到 backend\target\*.jar
echo 请先执行发布构建,或将打包后的 jar 放到 backend\target 目录。
pause
exit /b 1
)
echo 启动 SSH Manager...
echo 数据目录: %DATA_DIR%
echo 程序包: %APP_JAR%
echo 访问地址: http://localhost:48080
echo.
java -jar "%APP_JAR%"
endlocal

View File

@@ -0,0 +1,11 @@
@echo off
setlocal
set "ROOT=%~dp0..\.."
for %%I in ("%ROOT%") do set "ROOT=%%~fI"
cd /d "%ROOT%"
docker compose -f docker/docker-compose.yml down
pause
endlocal

View File

@@ -0,0 +1,14 @@
@echo off
setlocal
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0stop-installed.ps1"
if errorlevel 1 (
echo.
echo [ERROR] SSH Manager 停止失败
pause
exit /b 1
)
pause
endlocal

View File

@@ -0,0 +1,50 @@
$ErrorActionPreference = 'Stop'
$runtimeDir = Join-Path (Join-Path $env:LOCALAPPDATA 'SSHManager') 'runtime'
$pidFile = Join-Path $runtimeDir 'app.pid'
if (-not (Test-Path $pidFile)) {
Write-Output 'SSH Manager 当前未运行。'
exit 0
}
$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
Write-Output '已清理无效 PID 文件。'
exit 0
}
$process = Get-Process -Id ([int]$pidInfo.pid) -ErrorAction SilentlyContinue
if (-not $process) {
Remove-Item -Path $pidFile -Force -ErrorAction SilentlyContinue
Write-Output 'SSH Manager 当前未运行。'
exit 0
}
if ($pidInfo.startedAt -and $process.StartTime) {
$processStartedAt = ([DateTimeOffset]$process.StartTime).ToUnixTimeMilliseconds()
if ($processStartedAt -ne [int64]$pidInfo.startedAt) {
Remove-Item -Path $pidFile -Force -ErrorAction SilentlyContinue
Write-Output '已清理过期 PID 文件,未停止任何进程。'
exit 0
}
}
Stop-Process -Id ([int]$pidInfo.pid) -Force
Remove-Item -Path $pidFile -Force -ErrorAction SilentlyContinue
Write-Output 'SSH Manager 已停止。'