feat: prepare sellable source delivery edition
This commit is contained in:
BIN
scripts/installer/assets/ssh-manager.ico
Normal file
BIN
scripts/installer/assets/ssh-manager.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
72
scripts/installer/ssh-manager.iss
Normal file
72
scripts/installer/ssh-manager.iss
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef MyAppVersion
|
||||
#define MyAppVersion "1.0.0"
|
||||
#endif
|
||||
|
||||
#ifndef StageDir
|
||||
#define StageDir "..\..\release\windows-app"
|
||||
#endif
|
||||
|
||||
#ifndef OutputDir
|
||||
#define OutputDir "..\..\release\windows-installer"
|
||||
#endif
|
||||
|
||||
#ifndef IconFile
|
||||
#define IconFile "assets\ssh-manager.ico"
|
||||
#endif
|
||||
|
||||
#define MyAppName "SSH Manager"
|
||||
#define MyAppPublisher "SSH Manager"
|
||||
#define MyAppURL "http://127.0.0.1:48080"
|
||||
|
||||
[Setup]
|
||||
AppId={{7EFC73C3-79F8-4CC2-92A1-8C3A56E4E8B1}
|
||||
AppName={#MyAppName}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
UninstallDisplayName={#MyAppName}
|
||||
UninstallDisplayIcon={app}\ssh-manager.ico
|
||||
SetupIconFile={#IconFile}
|
||||
DefaultDirName={autopf}\SSH Manager
|
||||
DefaultGroupName=SSH Manager
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
PrivilegesRequired=admin
|
||||
AppMutex=SSHManagerLocalApp
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
SetupLogging=yes
|
||||
OutputDir={#OutputDir}
|
||||
OutputBaseFilename=SSHManager-Setup-{#MyAppVersion}
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
VersionInfoCompany={#MyAppPublisher}
|
||||
VersionInfoDescription=SSH Manager Windows 本地管理工具
|
||||
VersionInfoProductName={#MyAppName}
|
||||
VersionInfoProductVersion={#MyAppVersion}
|
||||
VersionInfoCopyright=SSH Manager
|
||||
|
||||
[Languages]
|
||||
Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "附加任务:"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "{#StageDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\SSH Manager"; Filename: "{app}\start-installed.vbs"; WorkingDir: "{app}"; IconFilename: "{app}\ssh-manager.ico"
|
||||
Name: "{group}\停止 SSH Manager"; Filename: "{app}\stop-installed.cmd"; WorkingDir: "{app}"; IconFilename: "{app}\ssh-manager.ico"
|
||||
Name: "{group}\买家使用说明"; Filename: "{app}\BUYER-GUIDE.txt"; WorkingDir: "{app}"; IconFilename: "{app}\ssh-manager.ico"
|
||||
Name: "{group}\售后排查 FAQ"; Filename: "{app}\AFTER-SALES-FAQ.txt"; WorkingDir: "{app}"; IconFilename: "{app}\ssh-manager.ico"
|
||||
Name: "{group}\打开安装目录"; Filename: "{app}"
|
||||
Name: "{autodesktop}\SSH Manager"; Filename: "{app}\start-installed.vbs"; WorkingDir: "{app}"; Tasks: desktopicon; IconFilename: "{app}\ssh-manager.ico"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\start-installed.vbs"; Description: "立即启动 SSH Manager"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[UninstallDelete]
|
||||
Type: files; Name: "{localappdata}\SSHManager\runtime\app.pid"
|
||||
64
scripts/release/build-local-package.bat
Normal file
64
scripts/release/build-local-package.bat
Normal file
@@ -0,0 +1,64 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "ROOT=%~dp0..\.."
|
||||
for %%I in ("%ROOT%") do set "ROOT=%%~fI"
|
||||
set "OUT_DIR=%ROOT%\release\local-package"
|
||||
|
||||
cd /d "%ROOT%"
|
||||
|
||||
where npm >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 npm,请先安装 Node.js 18+
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
where mvn >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 Maven,请先安装 Maven 3.6+
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if exist "%OUT_DIR%" rmdir /s /q "%OUT_DIR%"
|
||||
mkdir "%OUT_DIR%"
|
||||
|
||||
echo [1/3] 构建前端...
|
||||
cd /d "%ROOT%\frontend"
|
||||
call npm run build
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 前端构建失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [2/3] 打包后端...
|
||||
cd /d "%ROOT%\backend"
|
||||
call mvn -Pembed-frontend-dist -DskipTests package
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 后端打包失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [3/3] 组装本地版交付包...
|
||||
copy /Y "%ROOT%\backend\target\*.jar" "%OUT_DIR%\" >nul
|
||||
copy /Y "%ROOT%\scripts\windows\start-local.bat" "%OUT_DIR%\" >nul
|
||||
|
||||
(
|
||||
echo SSH Manager 本地版
|
||||
echo.
|
||||
echo 1. 安装 Java 8+
|
||||
echo 2. 双击 start-local.bat
|
||||
echo 3. 浏览器访问 http://localhost:48080
|
||||
echo.
|
||||
echo 首次启动会自动在 runtime 目录生成本地密钥,在 data 目录保存数据库数据。
|
||||
) > "%OUT_DIR%\README.txt"
|
||||
|
||||
echo.
|
||||
echo 本地版交付包已生成:
|
||||
echo %OUT_DIR%
|
||||
pause
|
||||
|
||||
endlocal
|
||||
29
scripts/release/build-local-package.sh
Normal file
29
scripts/release/build-local-package.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
OUT_DIR="$ROOT/release/local-package"
|
||||
|
||||
rm -rf "$OUT_DIR"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
cd "$ROOT/frontend"
|
||||
npm run build
|
||||
|
||||
cd "$ROOT/backend"
|
||||
mvn -Pembed-frontend-dist -DskipTests package
|
||||
|
||||
cp target/*.jar "$OUT_DIR/"
|
||||
cp "$ROOT/scripts/windows/start-local.bat" "$OUT_DIR/"
|
||||
|
||||
cat > "$OUT_DIR/README.txt" <<'EOF'
|
||||
SSH Manager 本地版
|
||||
|
||||
1. 安装 Java 8+
|
||||
2. 双击 start-local.bat
|
||||
3. 浏览器访问 http://localhost:48080
|
||||
|
||||
首次启动会自动在 runtime 目录生成本地密钥,在 data 目录保存数据库数据。
|
||||
EOF
|
||||
|
||||
echo "Local package created at: $OUT_DIR"
|
||||
146
scripts/release/build-windows-installer.bat
Normal file
146
scripts/release/build-windows-installer.bat
Normal file
@@ -0,0 +1,146 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set "ROOT=%~dp0..\.."
|
||||
for %%I in ("%ROOT%") do set "ROOT=%%~fI"
|
||||
set "STAGE_DIR=%ROOT%\release\windows-app"
|
||||
set "OUT_DIR=%ROOT%\release\windows-installer"
|
||||
set "JRE_DIR=%SSH_MANAGER_WINDOWS_JRE_DIR%"
|
||||
set "VERSION_SCRIPT=%ROOT%\scripts\release\get-app-version.ps1"
|
||||
|
||||
cd /d "%ROOT%"
|
||||
|
||||
where powershell >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 PowerShell,无法解析版本号
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%V in (`powershell -NoProfile -ExecutionPolicy Bypass -File "%VERSION_SCRIPT%"`) do set "APP_VERSION=%%V"
|
||||
if not defined APP_VERSION (
|
||||
echo [ERROR] 无法从 backend\pom.xml 读取版本号
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [INFO] 当前安装包版本: %APP_VERSION%
|
||||
|
||||
where npm >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 npm,请先安装 Node.js 18+
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
where mvn >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 Maven,请先安装 Maven 3.6+
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not defined JRE_DIR (
|
||||
echo [ERROR] 请先设置环境变量 SSH_MANAGER_WINDOWS_JRE_DIR,指向已解压的 Windows JRE 目录
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%JRE_DIR%\bin\javaw.exe" (
|
||||
echo [ERROR] %JRE_DIR% 不是有效的 Windows JRE 目录,缺少 bin\javaw.exe
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if exist "%STAGE_DIR%" rmdir /s /q "%STAGE_DIR%"
|
||||
if exist "%OUT_DIR%" rmdir /s /q "%OUT_DIR%"
|
||||
mkdir "%STAGE_DIR%"
|
||||
mkdir "%OUT_DIR%"
|
||||
|
||||
echo [1/5] 构建前端...
|
||||
cd /d "%ROOT%\frontend"
|
||||
call npm run build
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 前端构建失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [2/5] 打包后端(内嵌前端静态资源)...
|
||||
cd /d "%ROOT%\backend"
|
||||
call mvn -Pembed-frontend-dist -DskipTests package
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 后端打包失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [3/5] 组装 Windows 应用目录...
|
||||
copy /Y "%ROOT%\backend\target\*.jar" "%STAGE_DIR%\ssh-manager.jar" >nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 复制后端 jar 失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
xcopy "%JRE_DIR%" "%STAGE_DIR%\jre\" /E /I /Y >nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 复制 JRE 失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
copy /Y "%ROOT%\scripts\windows\start-installed.ps1" "%STAGE_DIR%\" >nul
|
||||
copy /Y "%ROOT%\scripts\windows\start-installed.cmd" "%STAGE_DIR%\" >nul
|
||||
copy /Y "%ROOT%\scripts\windows\start-installed.vbs" "%STAGE_DIR%\" >nul
|
||||
copy /Y "%ROOT%\scripts\windows\stop-installed.ps1" "%STAGE_DIR%\" >nul
|
||||
copy /Y "%ROOT%\scripts\windows\stop-installed.cmd" "%STAGE_DIR%\" >nul
|
||||
copy /Y "%ROOT%\scripts\installer\assets\ssh-manager.ico" "%STAGE_DIR%\" >nul
|
||||
copy /Y "%ROOT%\docs\windows-buyer-guide.md" "%STAGE_DIR%\BUYER-GUIDE.txt" >nul
|
||||
copy /Y "%ROOT%\docs\windows-after-sales-faq.md" "%STAGE_DIR%\AFTER-SALES-FAQ.txt" >nul
|
||||
|
||||
(
|
||||
echo SSH Manager Windows 成品版
|
||||
echo.
|
||||
echo 1. 运行安装包或直接双击 start-installed.vbs
|
||||
echo 2. 程序会自动启动本地服务并打开浏览器
|
||||
echo 3. 默认访问地址: http://127.0.0.1:48080
|
||||
echo.
|
||||
echo 数据目录:
|
||||
echo %%LOCALAPPDATA%%\SSHManager\data
|
||||
echo 日志目录:
|
||||
echo %%LOCALAPPDATA%%\SSHManager\logs
|
||||
echo.
|
||||
echo 买家使用说明:
|
||||
echo BUYER-GUIDE.txt
|
||||
echo 售后排查 FAQ:
|
||||
echo AFTER-SALES-FAQ.txt
|
||||
echo.
|
||||
echo 如需停止服务,可执行 stop-installed.cmd
|
||||
) > "%STAGE_DIR%\README.txt"
|
||||
|
||||
echo [4/5] 检测 Inno Setup...
|
||||
where ISCC.exe >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [WARN] 未检测到 ISCC.exe,已生成 Windows 应用目录:
|
||||
echo %STAGE_DIR%
|
||||
echo [WARN] 安装 Inno Setup 后可手动执行:
|
||||
echo ISCC.exe /DStageDir="%STAGE_DIR%" /DOutputDir="%OUT_DIR%" /DMyAppVersion="%APP_VERSION%" "%ROOT%\scripts\installer\ssh-manager.iss"
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo [5/5] 生成安装包...
|
||||
ISCC.exe /DStageDir="%STAGE_DIR%" /DOutputDir="%OUT_DIR%" /DMyAppVersion="%APP_VERSION%" "%ROOT%\scripts\installer\ssh-manager.iss"
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] Inno Setup 打包失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Windows 安装包已生成:
|
||||
echo %OUT_DIR%
|
||||
pause
|
||||
|
||||
endlocal
|
||||
149
scripts/release/check-windows-release.bat
Normal file
149
scripts/release/check-windows-release.bat
Normal file
@@ -0,0 +1,149 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set "ROOT=%~dp0..\.."
|
||||
for %%I in ("%ROOT%") do set "ROOT=%%~fI"
|
||||
set "VERSION_SCRIPT=%ROOT%\scripts\release\get-app-version.ps1"
|
||||
set "JRE_DIR=%SSH_MANAGER_WINDOWS_JRE_DIR%"
|
||||
|
||||
cd /d "%ROOT%"
|
||||
|
||||
where powershell >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 PowerShell,无法解析版本号
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%V in (`powershell -NoProfile -ExecutionPolicy Bypass -File "%VERSION_SCRIPT%"`) do set "APP_VERSION=%%V"
|
||||
if not defined APP_VERSION (
|
||||
echo [ERROR] 无法从 backend\pom.xml 读取版本号
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [INFO] 当前发布版本: %APP_VERSION%
|
||||
|
||||
where npm >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 npm,请先安装 Node.js 18+
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
where mvn >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 未检测到 Maven,请先安装 Maven 3.6+
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not defined JRE_DIR (
|
||||
echo [ERROR] 请先设置环境变量 SSH_MANAGER_WINDOWS_JRE_DIR,指向已解压的 Windows JRE 目录
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%JRE_DIR%\bin\javaw.exe" (
|
||||
echo [ERROR] %JRE_DIR% 不是有效的 Windows JRE 目录,缺少 bin\javaw.exe
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%ROOT%\scripts\installer\ssh-manager.iss" (
|
||||
echo [ERROR] 缺少安装器脚本 scripts\installer\ssh-manager.iss
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%ROOT%\scripts\installer\assets\ssh-manager.ico" (
|
||||
echo [ERROR] 缺少安装器图标 scripts\installer\assets\ssh-manager.ico
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%ROOT%\docs\windows-buyer-guide.md" (
|
||||
echo [ERROR] 缺少 docs\windows-buyer-guide.md
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%ROOT%\docs\windows-after-sales-faq.md" (
|
||||
echo [ERROR] 缺少 docs\windows-after-sales-faq.md
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
where ISCC.exe >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [WARN] 未检测到 ISCC.exe,本次只能验证构建链路,暂时无法生成 Setup.exe
|
||||
) else (
|
||||
echo [INFO] 已检测到 Inno Setup 编译器 ISCC.exe
|
||||
)
|
||||
|
||||
echo [1/4] 构建前端...
|
||||
cd /d "%ROOT%\frontend"
|
||||
call npm run build
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 前端构建失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%ROOT%\frontend\dist\index.html" (
|
||||
echo [ERROR] 前端 dist 产物不完整,缺少 frontend\dist\index.html
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [2/4] 打包后端(内嵌前端静态资源)...
|
||||
cd /d "%ROOT%\backend"
|
||||
call mvn -Pembed-frontend-dist -DskipTests package
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 后端打包失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
dir /b "%ROOT%\backend\target\*.jar" >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo [ERROR] 后端产物不完整,未找到 target\*.jar
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [3/4] 检查安装器输入文件...
|
||||
if not exist "%ROOT%\scripts\windows\start-installed.ps1" (
|
||||
echo [ERROR] 缺少 scripts\windows\start-installed.ps1
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%ROOT%\scripts\windows\start-installed.cmd" (
|
||||
echo [ERROR] 缺少 scripts\windows\start-installed.cmd
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%ROOT%\scripts\windows\start-installed.vbs" (
|
||||
echo [ERROR] 缺少 scripts\windows\start-installed.vbs
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%ROOT%\scripts\windows\stop-installed.ps1" (
|
||||
echo [ERROR] 缺少 scripts\windows\stop-installed.ps1
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
if not exist "%ROOT%\scripts\windows\stop-installed.cmd" (
|
||||
echo [ERROR] 缺少 scripts\windows\stop-installed.cmd
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [4/4] 发布预检完成
|
||||
echo [OK] Windows 发布预检通过
|
||||
echo [OK] 版本号: %APP_VERSION%
|
||||
echo [OK] 下一步执行:
|
||||
echo scripts\release\build-windows-installer.bat
|
||||
pause
|
||||
|
||||
endlocal
|
||||
14
scripts/release/get-app-version.ps1
Normal file
14
scripts/release/get-app-version.ps1
Normal file
@@ -0,0 +1,14 @@
|
||||
param(
|
||||
[string]$PomPath = (Join-Path $PSScriptRoot '..\..\backend\pom.xml')
|
||||
)
|
||||
|
||||
$resolvedPath = Resolve-Path -LiteralPath $PomPath -ErrorAction Stop
|
||||
[xml]$pom = Get-Content -LiteralPath $resolvedPath -Raw -Encoding UTF8
|
||||
$version = $pom.project.version
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||
Write-Error "Failed to read <project><version> from $resolvedPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output $version.Trim()
|
||||
28
scripts/windows/start-docker.bat
Normal file
28
scripts/windows/start-docker.bat
Normal 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
|
||||
13
scripts/windows/start-installed.cmd
Normal file
13
scripts/windows/start-installed.cmd
Normal 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
|
||||
186
scripts/windows/start-installed.ps1
Normal file
186
scripts/windows/start-installed.ps1
Normal 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"
|
||||
3
scripts/windows/start-installed.vbs
Normal file
3
scripts/windows/start-installed.vbs
Normal 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
|
||||
56
scripts/windows/start-local.bat
Normal file
56
scripts/windows/start-local.bat
Normal 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
|
||||
11
scripts/windows/stop-docker.bat
Normal file
11
scripts/windows/stop-docker.bat
Normal 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
|
||||
14
scripts/windows/stop-installed.cmd
Normal file
14
scripts/windows/stop-installed.cmd
Normal 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
|
||||
50
scripts/windows/stop-installed.ps1
Normal file
50
scripts/windows/stop-installed.ps1
Normal 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 已停止。'
|
||||
Reference in New Issue
Block a user