Merge pull request #735 from kekingcn/codex/ci-auto-deploy-fix

fix: harden master auto deploy artifact delivery
This commit is contained in:
kl
2026-04-11 17:16:26 +08:00
committed by GitHub
4 changed files with 85 additions and 48 deletions

View File

@@ -31,10 +31,7 @@ def main() -> int:
password = require_env("KK_DEPLOY_PASSWORD") password = require_env("KK_DEPLOY_PASSWORD")
deploy_root = optional_env("KK_DEPLOY_ROOT", r"C:\kkFileView-5.0") deploy_root = optional_env("KK_DEPLOY_ROOT", r"C:\kkFileView-5.0")
health_url = optional_env("KK_DEPLOY_HEALTH_URL", "http://127.0.0.1:8012/") health_url = optional_env("KK_DEPLOY_HEALTH_URL", "http://127.0.0.1:8012/")
artifact_name = optional_env("KK_DEPLOY_ARTIFACT_NAME", "kkfileview-server-jar") artifact_url = require_env("KK_DEPLOY_ARTIFACT_URL")
repository = require_env("GITHUB_REPOSITORY_NAME")
run_id = require_env("GITHUB_RUN_ID_VALUE")
artifact_token = require_env("KK_DEPLOY_ARTIFACT_TOKEN")
dry_run = optional_env("KK_DEPLOY_DRY_RUN", "false").lower() dry_run = optional_env("KK_DEPLOY_DRY_RUN", "false").lower()
script_path = pathlib.Path(__file__).with_name("remote_windows_deploy.ps1") script_path = pathlib.Path(__file__).with_name("remote_windows_deploy.ps1")
@@ -77,16 +74,17 @@ $ErrorActionPreference = 'Stop'
$raw = Get-Content -LiteralPath '{ps_quote(remote_b64_path)}' -Raw $raw = Get-Content -LiteralPath '{ps_quote(remote_b64_path)}' -Raw
[System.IO.File]::WriteAllBytes('{ps_quote(remote_ps1_path)}', [Convert]::FromBase64String($raw)) [System.IO.File]::WriteAllBytes('{ps_quote(remote_ps1_path)}', [Convert]::FromBase64String($raw))
try {{ try {{
$env:KK_DEPLOY_ARTIFACT_URL = '{ps_quote(artifact_url)}'
$env:KK_DEPLOY_ROOT = '{ps_quote(deploy_root)}'
$env:KK_DEPLOY_HEALTH_URL = '{ps_quote(health_url)}'
$env:KK_DEPLOY_DRY_RUN = '{ps_quote(dry_run)}'
powershell -NoProfile -ExecutionPolicy Bypass -File '{ps_quote(remote_ps1_path)}' ` 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 $code = $LASTEXITCODE
}} finally {{ }} finally {{
Remove-Item Env:KK_DEPLOY_ARTIFACT_URL -ErrorAction SilentlyContinue
Remove-Item Env:KK_DEPLOY_ROOT -ErrorAction SilentlyContinue
Remove-Item Env:KK_DEPLOY_HEALTH_URL -ErrorAction SilentlyContinue
Remove-Item Env:KK_DEPLOY_DRY_RUN -ErrorAction SilentlyContinue
Remove-Item '{ps_quote(remote_b64_path)}' -Force -ErrorAction SilentlyContinue Remove-Item '{ps_quote(remote_b64_path)}' -Force -ErrorAction SilentlyContinue
Remove-Item '{ps_quote(remote_ps1_path)}' -Force -ErrorAction SilentlyContinue Remove-Item '{ps_quote(remote_ps1_path)}' -Force -ErrorAction SilentlyContinue
}} }}

View File

@@ -1,13 +1,3 @@
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' $ErrorActionPreference = 'Stop'
function Write-Step { function Write-Step {
@@ -15,6 +5,36 @@ function Write-Step {
Write-Host "==> $Message" Write-Host "==> $Message"
} }
function Get-RequiredEnv {
param([string]$Name)
$Value = [Environment]::GetEnvironmentVariable($Name)
if ([string]::IsNullOrWhiteSpace($Value)) {
throw "Missing required environment variable: $Name"
}
return $Value
}
function Get-OptionalEnv {
param(
[string]$Name,
[string]$DefaultValue
)
$Value = [Environment]::GetEnvironmentVariable($Name)
if ([string]::IsNullOrWhiteSpace($Value)) {
return $DefaultValue
}
return $Value
}
$ArtifactDownloadUrl = Get-RequiredEnv 'KK_DEPLOY_ARTIFACT_URL'
$DeployRoot = Get-OptionalEnv 'KK_DEPLOY_ROOT' 'C:\kkFileView-5.0'
$HealthUrl = Get-OptionalEnv 'KK_DEPLOY_HEALTH_URL' 'http://127.0.0.1:8012/'
$DryRun = Get-OptionalEnv 'KK_DEPLOY_DRY_RUN' 'false'
$BinDir = Join-Path $DeployRoot 'bin' $BinDir = Join-Path $DeployRoot 'bin'
$StartupScript = Join-Path $BinDir 'startup.bat' $StartupScript = Join-Path $BinDir 'startup.bat'
$ReleaseDir = Join-Path $DeployRoot 'releases' $ReleaseDir = Join-Path $DeployRoot 'releases'
@@ -63,33 +83,33 @@ if (Test-Path $ExtractDir) {
Remove-Item $ExtractDir -Recurse -Force Remove-Item $ExtractDir -Recurse -Force
} }
$Headers = @{ Write-Step 'Downloading workflow artifact via signed URL'
Authorization = "Bearer $GitHubToken" $PreviousProgressPreference = $ProgressPreference
Accept = "application/vnd.github+json" $ProgressPreference = 'SilentlyContinue'
"X-GitHub-Api-Version" = "2022-11-28" try {
"User-Agent" = "kkFileView-auto-deploy" Invoke-WebRequest -Uri $ArtifactDownloadUrl -OutFile $ArtifactZip -UseBasicParsing -TimeoutSec 120
} finally {
$ProgressPreference = $PreviousProgressPreference
} }
$ArtifactsApi = "https://api.github.com/repos/$Repository/actions/runs/$RunId/artifacts" if (-not (Test-Path $ArtifactZip)) {
Write-Step "Resolving workflow artifact: $ArtifactName" throw "Artifact zip was not created: $ArtifactZip"
$ArtifactsResponse = Invoke-RestMethod -Headers $Headers -Uri $ArtifactsApi -Method Get }
$Artifact = $ArtifactsResponse.artifacts | Where-Object { $_.name -eq $ArtifactName } | Select-Object -First 1
$ArtifactZipInfo = Get-Item $ArtifactZip
if (-not $Artifact) { if ($ArtifactZipInfo.Length -le 0) {
throw "Artifact '$ArtifactName' not found for workflow run $RunId" throw "Downloaded artifact zip is empty: $ArtifactZip"
} }
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 Expand-Archive -LiteralPath $ArtifactZip -DestinationPath $ExtractDir -Force
$DownloadedJars = Get-ChildItem $ExtractDir -Filter 'kkFileView-*.jar' -Recurse $DownloadedJars = Get-ChildItem $ExtractDir -Filter 'kkFileView-*.jar' -Recurse
if (-not $DownloadedJars) { if (-not $DownloadedJars) {
throw "No kkFileView jar found inside artifact '$ArtifactName'" throw 'No kkFileView jar found inside downloaded workflow artifact'
} }
if ($DownloadedJars.Count -ne 1) { if ($DownloadedJars.Count -ne 1) {
throw "Expected exactly one kkFileView jar inside artifact '$ArtifactName', found $($DownloadedJars.Count)" throw "Expected exactly one kkFileView jar inside downloaded workflow artifact, found $($DownloadedJars.Count)"
} }
$DownloadedJar = $DownloadedJars[0] $DownloadedJar = $DownloadedJars[0]

View File

@@ -44,7 +44,6 @@ jobs:
env: env:
GITHUB_REPOSITORY_NAME: ${{ github.repository }} GITHUB_REPOSITORY_NAME: ${{ github.repository }}
GITHUB_RUN_ID_VALUE: ${{ github.run_id }} GITHUB_RUN_ID_VALUE: ${{ github.run_id }}
KK_DEPLOY_ARTIFACT_TOKEN: ${{ secrets.KK_DEPLOY_ARTIFACT_TOKEN }}
KK_DEPLOY_HOST: ${{ secrets.KK_DEPLOY_HOST }} KK_DEPLOY_HOST: ${{ secrets.KK_DEPLOY_HOST }}
KK_DEPLOY_PORT: ${{ secrets.KK_DEPLOY_PORT }} KK_DEPLOY_PORT: ${{ secrets.KK_DEPLOY_PORT }}
KK_DEPLOY_USERNAME: ${{ secrets.KK_DEPLOY_USERNAME }} KK_DEPLOY_USERNAME: ${{ secrets.KK_DEPLOY_USERNAME }}
@@ -67,10 +66,30 @@ jobs:
- name: Validate deploy secrets - name: Validate deploy secrets
run: | 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_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_USERNAME" || (echo "Missing secret: KK_DEPLOY_USERNAME" && exit 1)
test -n "$KK_DEPLOY_PASSWORD" || (echo "Missing secret: KK_DEPLOY_PASSWORD" && exit 1) test -n "$KK_DEPLOY_PASSWORD" || (echo "Missing secret: KK_DEPLOY_PASSWORD" && exit 1)
- name: Resolve artifact download URL
env:
GH_TOKEN: ${{ github.token }}
run: |
artifact_json=$(curl -fsSL \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/$GITHUB_REPOSITORY_NAME/actions/runs/$GITHUB_RUN_ID_VALUE/artifacts")
artifact_id=$(ARTIFACT_JSON="$artifact_json" ARTIFACT_NAME="$KK_DEPLOY_ARTIFACT_NAME" python -c "import json, os; payload=json.loads(os.environ['ARTIFACT_JSON']); name=os.environ['ARTIFACT_NAME']; matches=[artifact for artifact in payload.get('artifacts', []) if artifact.get('name') == name]; matches or (_ for _ in ()).throw(SystemExit(f\"Artifact '{name}' not found for run\")); len(matches) == 1 or (_ for _ in ()).throw(SystemExit(f\"Expected one artifact named '{name}', found {len(matches)}\")); print(matches[0]['id'])")
headers_file=$(mktemp)
curl -fsS -D "$headers_file" -o /dev/null \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/$GITHUB_REPOSITORY_NAME/actions/artifacts/$artifact_id/zip"
artifact_url=$(awk 'BEGIN{IGNORECASE=1} /^location:/ { sub(/\r$/, "", $0); print substr($0, index($0, ":") + 2); exit }' "$headers_file")
test -n "$artifact_url" || (echo "Failed to resolve artifact download redirect URL" && exit 1)
rm -f "$headers_file"
echo "KK_DEPLOY_ARTIFACT_URL=$artifact_url" >> "$GITHUB_ENV"
- name: Deploy to Windows server - name: Deploy to Windows server
run: python .github/scripts/deploy_windows_winrm.py run: python .github/scripts/deploy_windows_winrm.py

View File

@@ -11,16 +11,18 @@
服务器当前没有安装 `git` `mvn`因此自动部署链路采用 服务器当前没有安装 `git` `mvn`因此自动部署链路采用
1. GitHub Actions `master` 合并后构建 `kkFileView-*.jar` 1. GitHub Actions `master` 合并后构建 `kkFileView-*.jar`
2. 通过 WinRM 连接 Windows 服务器 2. GitHub Actions runner 解析当前 workflow artifact 的临时下载地址
3. 由服务器从当前 workflow run 下载 jar artifact 3. 通过 WinRM 连接 Windows 服务器
4. 备份线上 jar替换为新版本 4. 由服务器通过临时下载地址拉取 jar artifact
5. 使用现有 `startup.bat` 重启并做健康检查 5. 备份线上 jar替换为新版本
6. 如果健康检查失败则自动回滚旧 jar 并重新拉起 6. 使用现有 `startup.bat` 重启并做健康检查
7. 如果健康检查失败则自动回滚旧 jar 并重新拉起
这样做的目的是不把 GitHub token 下发到生产服务器服务器只接触一次性 artifact 下载链接
## 需要配置的 GitHub Secrets ## 需要配置的 GitHub Secrets
- `KK_DEPLOY_HOST` - `KK_DEPLOY_HOST`
- `KK_DEPLOY_ARTIFACT_TOKEN`
- `KK_DEPLOY_USERNAME` - `KK_DEPLOY_USERNAME`
- `KK_DEPLOY_PASSWORD` - `KK_DEPLOY_PASSWORD`
@@ -30,12 +32,10 @@
- `KK_DEPLOY_ROOT=C:\kkFileView-5.0` - `KK_DEPLOY_ROOT=C:\kkFileView-5.0`
- `KK_DEPLOY_HEALTH_URL=http://127.0.0.1:8012/` - `KK_DEPLOY_HEALTH_URL=http://127.0.0.1:8012/`
其中 `KK_DEPLOY_ARTIFACT_TOKEN` 建议使用单独的细粒度 token只授予当前仓库所需的最小读取权限不要复用默认 `GITHUB_TOKEN` 到生产服务器
## Workflow ## Workflow
新增 workflow`.github/workflows/master-auto-deploy.yml` 新增 workflow`.github/workflows/master-auto-deploy.yml`
- 触发条件`push` `master`或手动 `workflow_dispatch` - 触发条件`push` `master`或手动 `workflow_dispatch`
- 构建产物`kkfileview-server-jar` - 构建产物`kkfileview-server-jar`
- 部署方式WinRM + GitHub Actions artifact 下载 - 部署方式WinRM + runner 侧解析 artifact 临时下载地址 + Windows 服务器拉取 artifact