diff --git a/.github/scripts/deploy_windows_winrm.py b/.github/scripts/deploy_windows_winrm.py index e24f46e5..f9b7cad8 100644 --- a/.github/scripts/deploy_windows_winrm.py +++ b/.github/scripts/deploy_windows_winrm.py @@ -3,6 +3,7 @@ import base64 import os import pathlib import sys +import uuid import winrm @@ -33,30 +34,20 @@ def main() -> int: artifact_name = optional_env("KK_DEPLOY_ARTIFACT_NAME", "kkfileview-server-jar") repository = require_env("GITHUB_REPOSITORY_NAME") run_id = require_env("GITHUB_RUN_ID_VALUE") - github_token = require_env("GITHUB_TOKEN_VALUE") + artifact_token = require_env("KK_DEPLOY_ARTIFACT_TOKEN") dry_run = optional_env("KK_DEPLOY_DRY_RUN", "false").lower() script_path = pathlib.Path(__file__).with_name("remote_windows_deploy.ps1") script_body = script_path.read_text(encoding="utf-8") - - bootstrap = f""" -$Repository = '{ps_quote(repository)}' -$RunId = '{ps_quote(run_id)}' -$ArtifactName = '{ps_quote(artifact_name)}' -$GitHubToken = '{ps_quote(github_token)}' -$DeployRoot = '{ps_quote(deploy_root)}' -$HealthUrl = '{ps_quote(health_url)}' -$DryRun = '{ps_quote(dry_run)}' -""" - - payload = (bootstrap + "\n" + script_body).encode("utf-8-sig") + payload = script_body.encode("utf-8-sig") payload_b64 = base64.b64encode(payload).decode("ascii") endpoint = f"http://{host}:{port}/wsman" session = winrm.Session(endpoint, auth=(username, password), transport="ntlm") - remote_b64_path = r"C:\Windows\Temp\kkfileview_deploy.b64" - remote_ps1_path = r"C:\Windows\Temp\kkfileview_deploy.ps1" + suffix = uuid.uuid4().hex + remote_b64_path = fr"C:\Windows\Temp\kkfileview_deploy_{suffix}.b64" + remote_ps1_path = fr"C:\Windows\Temp\kkfileview_deploy_{suffix}.ps1" prep = session.run_ps( f""" @@ -85,10 +76,20 @@ New-Item -ItemType File -Path '{ps_quote(remote_b64_path)}' -Force | Out-Null $ErrorActionPreference = 'Stop' $raw = Get-Content -LiteralPath '{ps_quote(remote_b64_path)}' -Raw [System.IO.File]::WriteAllBytes('{ps_quote(remote_ps1_path)}', [Convert]::FromBase64String($raw)) -powershell -NoProfile -ExecutionPolicy Bypass -File '{ps_quote(remote_ps1_path)}' -$code = $LASTEXITCODE -Remove-Item '{ps_quote(remote_b64_path)}' -Force -ErrorAction SilentlyContinue -Remove-Item '{ps_quote(remote_ps1_path)}' -Force -ErrorAction SilentlyContinue +try {{ + powershell -NoProfile -ExecutionPolicy Bypass -File '{ps_quote(remote_ps1_path)}' ` + -Repository '{ps_quote(repository)}' ` + -RunId '{ps_quote(run_id)}' ` + -ArtifactName '{ps_quote(artifact_name)}' ` + -GitHubToken '{ps_quote(artifact_token)}' ` + -DeployRoot '{ps_quote(deploy_root)}' ` + -HealthUrl '{ps_quote(health_url)}' ` + -DryRun '{ps_quote(dry_run)}' + $code = $LASTEXITCODE +}} finally {{ + Remove-Item '{ps_quote(remote_b64_path)}' -Force -ErrorAction SilentlyContinue + Remove-Item '{ps_quote(remote_ps1_path)}' -Force -ErrorAction SilentlyContinue +}} exit $code """ ) diff --git a/.github/scripts/remote_windows_deploy.ps1 b/.github/scripts/remote_windows_deploy.ps1 index d1f7f954..149378ba 100644 --- a/.github/scripts/remote_windows_deploy.ps1 +++ b/.github/scripts/remote_windows_deploy.ps1 @@ -1,3 +1,13 @@ +param( + [Parameter(Mandatory = $true)][string]$Repository, + [Parameter(Mandatory = $true)][string]$RunId, + [Parameter(Mandatory = $true)][string]$ArtifactName, + [Parameter(Mandatory = $true)][string]$GitHubToken, + [Parameter(Mandatory = $true)][string]$DeployRoot, + [Parameter(Mandatory = $true)][string]$HealthUrl, + [string]$DryRun = 'false' +) + $ErrorActionPreference = 'Stop' function Write-Step { @@ -73,17 +83,24 @@ Write-Step "Downloading artifact from GitHub Actions" Invoke-WebRequest -Headers $Headers -Uri $Artifact.archive_download_url -OutFile $ArtifactZip Expand-Archive -LiteralPath $ArtifactZip -DestinationPath $ExtractDir -Force -$DownloadedJar = Get-ChildItem $ExtractDir -Filter 'kkFileView-*.jar' -Recurse | Select-Object -First 1 -if (-not $DownloadedJar) { +$DownloadedJars = Get-ChildItem $ExtractDir -Filter 'kkFileView-*.jar' -Recurse +if (-not $DownloadedJars) { throw "No kkFileView jar found inside artifact '$ArtifactName'" } +if ($DownloadedJars.Count -ne 1) { + throw "Expected exactly one kkFileView jar inside artifact '$ArtifactName', found $($DownloadedJars.Count)" +} + +$DownloadedJar = $DownloadedJars[0] + $Timestamp = Get-Date -Format 'yyyyMMddHHmmss' $BackupJar = Join-Path $ReleaseDir ("{0}.{1}.bak" -f $JarName, $Timestamp) function Stop-KkFileView { + $JarPattern = [regex]::Escape($JarName) $Processes = Get-CimInstance Win32_Process | Where-Object { - $_.Name -match '^java(\.exe)?$' -and $_.CommandLine -like "*-jar $JarName*" + $_.Name -match '^java(\.exe)?$' -and $_.CommandLine -and $_.CommandLine -match $JarPattern } foreach ($Process in $Processes) { @@ -92,6 +109,25 @@ function Stop-KkFileView { } } +function Wait-KkFileViewStopped { + param([int]$TimeoutSeconds = 30) + + $JarPattern = [regex]::Escape($JarName) + for ($i = 0; $i -lt $TimeoutSeconds; $i++) { + $Processes = Get-CimInstance Win32_Process | Where-Object { + $_.Name -match '^java(\.exe)?$' -and $_.CommandLine -and $_.CommandLine -match $JarPattern + } + + if (-not $Processes) { + return $true + } + + Start-Sleep -Seconds 1 + } + + return $false +} + function Start-KkFileView { Write-Step "Starting kkFileView" Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', "`"$StartupScript`"" -WorkingDirectory $BinDir -WindowStyle Hidden @@ -119,7 +155,9 @@ Write-Step "Backing up current jar to $BackupJar" Copy-Item $JarPath $BackupJar -Force Stop-KkFileView -Start-Sleep -Seconds 3 +if (-not (Wait-KkFileViewStopped)) { + throw "Timed out waiting for the previous kkFileView process to exit" +} Write-Step "Replacing jar with artifact output" Copy-Item $DownloadedJar.FullName $JarPath -Force @@ -129,7 +167,9 @@ Start-KkFileView if (-not (Wait-Health -Url $HealthUrl)) { Write-Step "Health check failed, rolling back" Stop-KkFileView - Start-Sleep -Seconds 2 + if (-not (Wait-KkFileViewStopped)) { + throw "Timed out waiting for the failed kkFileView process to exit during rollback" + } Copy-Item $BackupJar $JarPath -Force Start-KkFileView diff --git a/.github/workflows/master-auto-deploy.yml b/.github/workflows/master-auto-deploy.yml index 7dcb0e64..6e3a1e95 100644 --- a/.github/workflows/master-auto-deploy.yml +++ b/.github/workflows/master-auto-deploy.yml @@ -5,6 +5,10 @@ on: branches: [ master ] workflow_dispatch: +concurrency: + group: master-auto-deploy-production + cancel-in-progress: false + permissions: contents: read actions: read @@ -40,7 +44,7 @@ jobs: env: GITHUB_REPOSITORY_NAME: ${{ github.repository }} GITHUB_RUN_ID_VALUE: ${{ github.run_id }} - GITHUB_TOKEN_VALUE: ${{ github.token }} + KK_DEPLOY_ARTIFACT_TOKEN: ${{ secrets.KK_DEPLOY_ARTIFACT_TOKEN }} KK_DEPLOY_HOST: ${{ secrets.KK_DEPLOY_HOST }} KK_DEPLOY_PORT: ${{ secrets.KK_DEPLOY_PORT }} KK_DEPLOY_USERNAME: ${{ secrets.KK_DEPLOY_USERNAME }} @@ -63,6 +67,7 @@ jobs: - name: Validate deploy secrets run: | + test -n "$KK_DEPLOY_ARTIFACT_TOKEN" || (echo "Missing secret: KK_DEPLOY_ARTIFACT_TOKEN" && exit 1) test -n "$KK_DEPLOY_HOST" || (echo "Missing secret: KK_DEPLOY_HOST" && exit 1) test -n "$KK_DEPLOY_USERNAME" || (echo "Missing secret: KK_DEPLOY_USERNAME" && exit 1) test -n "$KK_DEPLOY_PASSWORD" || (echo "Missing secret: KK_DEPLOY_PASSWORD" && exit 1) diff --git a/doc/ci-auto-deploy.md b/doc/ci-auto-deploy.md index a9d54357..991b9326 100644 --- a/doc/ci-auto-deploy.md +++ b/doc/ci-auto-deploy.md @@ -20,18 +20,18 @@ ## 需要配置的 GitHub Secrets - `KK_DEPLOY_HOST` -- `KK_DEPLOY_PORT` +- `KK_DEPLOY_ARTIFACT_TOKEN` - `KK_DEPLOY_USERNAME` - `KK_DEPLOY_PASSWORD` -- `KK_DEPLOY_ROOT` -- `KK_DEPLOY_HEALTH_URL` -推荐值: +下面这些可以不配,未配置时会使用默认值: - `KK_DEPLOY_PORT=5985` - `KK_DEPLOY_ROOT=C:\kkFileView-5.0` - `KK_DEPLOY_HEALTH_URL=http://127.0.0.1:8012/` +其中 `KK_DEPLOY_ARTIFACT_TOKEN` 建议使用单独的细粒度 token,只授予当前仓库所需的最小读取权限,不要复用默认 `GITHUB_TOKEN` 到生产服务器。 + ## Workflow 新增 workflow:`.github/workflows/master-auto-deploy.yml`