diff --git a/config/platform-infra/wechat-archive.yaml b/config/platform-infra/wechat-archive.yaml index e4ba750f..2d8bc67e 100644 --- a/config/platform-infra/wechat-archive.yaml +++ b/config/platform-infra/wechat-archive.yaml @@ -107,6 +107,11 @@ personalWechatIngress: context: src/components/platform-infra/personal-wechat-collector dockerfile: src/components/platform-infra/personal-wechat-collector/Dockerfile wcferryVersion: 39.5.2.0 + pip: + indexUrl: http://mirrors.aliyun.com/pypi/simple/ + trustedHost: mirrors.aliyun.com + timeoutSeconds: 120 + retries: 8 wcfHost: 172.26.16.1 commandPort: 10086 messagePort: 10087 diff --git a/scripts/src/platform-infra-wechat-archive.ts b/scripts/src/platform-infra-wechat-archive.ts index 90ef4392..09916d05 100644 --- a/scripts/src/platform-infra-wechat-archive.ts +++ b/scripts/src/platform-infra-wechat-archive.ts @@ -97,7 +97,15 @@ interface WechatArchiveConfig { createNamespace: boolean; workload: { kind: string; name: string; serviceAccountName: string; replicas: number; containerName: string }; storage: { kind: string; name: string; mountPath: string; size: string; create: boolean }; - image: { repository: string; tag: string; pullPolicy: string; context: string; dockerfile: string; wcferryVersion: string }; + image: { + repository: string; + tag: string; + pullPolicy: string; + context: string; + dockerfile: string; + wcferryVersion: string; + pip: { indexUrl: string; trustedHost: string; timeoutSeconds: number; retries: number }; + }; wcfHost: string; commandPort: number; messagePort: number; @@ -819,11 +827,17 @@ function windowsScriptSyncAndStartPrepareScript(_archive: WechatArchiveConfig): $StateRoot = ${psSingleQuote(stateRoot)} New-Item -ItemType Directory -Force $StateRoot | Out-Null if (Test-Path ${psSingleQuote(resultFile)}) { Remove-Item -Force ${psSingleQuote(resultFile)} } -$proc = Start-Process -FilePath "powershell.exe" -ArgumentList @("-NoProfile","-ExecutionPolicy","Bypass","-File",${psSingleQuote(prepare)}) -WindowStyle Hidden -PassThru -RedirectStandardOutput ${psSingleQuote(stdout)} -RedirectStandardError ${psSingleQuote(stderr)} +$Wrapper = Join-Path $StateRoot "prepare-runner.cmd" +@" +@echo off +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${prepare}" > "${stdout}" 2> "${stderr}" +"@ | Set-Content -Encoding ASCII $Wrapper +$cmd = '/c start "" /min "' + $Wrapper + '"' +$proc = Start-Process -FilePath "cmd.exe" -ArgumentList $cmd -WindowStyle Hidden -PassThru [pscustomobject]@{ ok = $true started = $true - pid = $proc.Id + launcherPid = $proc.Id opsRoot = $OpsRoot stdout = ${psSingleQuote(stdout)} stderr = ${psSingleQuote(stderr)} @@ -1027,7 +1041,13 @@ import json, sys, time payload = {"ok": False, "status": "running", "startedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "image": "${image}", "logs": {"build": sys.argv[2], "push": sys.argv[3]}} open(sys.argv[1], "w", encoding="utf-8").write(json.dumps(payload, ensure_ascii=False, indent=2)) PY -docker build --build-arg WCFERRY_VERSION=${shQuote(ingress.collector.image.wcferryVersion)} -t ${shQuote(image)} "$job_dir" >"$build_log" 2>&1 +docker build \\ + --build-arg WCFERRY_VERSION=${shQuote(ingress.collector.image.wcferryVersion)} \\ + --build-arg PIP_INDEX_URL=${shQuote(ingress.collector.image.pip.indexUrl)} \\ + --build-arg PIP_TRUSTED_HOST=${shQuote(ingress.collector.image.pip.trustedHost)} \\ + --build-arg PIP_TIMEOUT_SECONDS=${shQuote(String(ingress.collector.image.pip.timeoutSeconds))} \\ + --build-arg PIP_RETRIES=${shQuote(String(ingress.collector.image.pip.retries))} \\ + -t ${shQuote(image)} "$job_dir" >"$build_log" 2>&1 build_rc=$? if [ "$build_rc" -eq 0 ]; then docker push ${shQuote(image)} >"$push_log" 2>&1 @@ -1399,6 +1419,7 @@ function parsePersonalWechatIngress(raw: Record): WechatArchive const workload = recordField(collector, "workload", `${configLabel}.personalWechatIngress.collector`); const storage = recordField(collector, "storage", `${configLabel}.personalWechatIngress.collector`); const image = recordField(collector, "image", `${configLabel}.personalWechatIngress.collector`); + const pip = recordField(image, "pip", `${configLabel}.personalWechatIngress.collector.image`); const readOnly = recordField(collector, "readOnly", `${configLabel}.personalWechatIngress.collector`); const poc = recordField(raw, "poc", `${configLabel}.personalWechatIngress`); return { @@ -1460,6 +1481,12 @@ function parsePersonalWechatIngress(raw: Record): WechatArchive context: stringField(image, "context", `${configLabel}.personalWechatIngress.collector.image`), dockerfile: stringField(image, "dockerfile", `${configLabel}.personalWechatIngress.collector.image`), wcferryVersion: stringField(image, "wcferryVersion", `${configLabel}.personalWechatIngress.collector.image`), + pip: { + indexUrl: stringField(pip, "indexUrl", `${configLabel}.personalWechatIngress.collector.image.pip`), + trustedHost: stringField(pip, "trustedHost", `${configLabel}.personalWechatIngress.collector.image.pip`), + timeoutSeconds: numberField(pip, "timeoutSeconds", `${configLabel}.personalWechatIngress.collector.image.pip`), + retries: numberField(pip, "retries", `${configLabel}.personalWechatIngress.collector.image.pip`), + }, }, wcfHost: stringField(collector, "wcfHost", `${configLabel}.personalWechatIngress.collector`), commandPort: numberField(collector, "commandPort", `${configLabel}.personalWechatIngress.collector`), @@ -1574,6 +1601,12 @@ function personalWechatIngressSummary(config: WechatArchiveConfig["personalWecha context: config.collector.image.context, dockerfile: config.collector.image.dockerfile, wcferryVersion: config.collector.image.wcferryVersion, + pip: { + indexUrl: config.collector.image.pip.indexUrl, + trustedHost: config.collector.image.pip.trustedHost, + timeoutSeconds: config.collector.image.pip.timeoutSeconds, + retries: config.collector.image.pip.retries, + }, }, wcfHost: config.collector.wcfHost, commandPort: config.collector.commandPort, diff --git a/scripts/windows/personal-wechat/prepare.ps1 b/scripts/windows/personal-wechat/prepare.ps1 index 5c1accef..4eb033b3 100644 --- a/scripts/windows/personal-wechat/prepare.ps1 +++ b/scripts/windows/personal-wechat/prepare.ps1 @@ -6,18 +6,44 @@ $WcfRoot = Join-Path $Root "wcf\v39.5.2" $StateRoot = Join-Path $Root "wcf-state" $DownloadRoot = Join-Path $Root "downloads" $PrepareResult = Join-Path $StateRoot "prepare-result.json" +$ProgressLog = Join-Path $StateRoot "prepare-progress.log" $Python = "C:\ProgramData\miniconda3\python.exe" $PipIndex = "http://mirrors.aliyun.com/pypi/simple/" $ReleaseBase = "https://github.com/lich0821/WeChatFerry/releases/download/v39.5.2" New-Item -ItemType Directory -Force $WechatRoot,$WcfRoot,$StateRoot,$DownloadRoot | Out-Null +$StartedAt = (Get-Date).ToUniversalTime().ToString("o") + +function Write-PrepareProgress { + param([string]$Message) + Add-Content -Encoding utf8 -Path $ProgressLog -Value "$((Get-Date).ToUniversalTime().ToString("o")) $Message" +} + +trap { + $message = $_.Exception.Message + Write-PrepareProgress "error $message" + $failed = [ordered]@{ + ok = $false + startedAt = $StartedAt + finishedAt = (Get-Date).ToUniversalTime().ToString("o") + root = $Root + stateRoot = $StateRoot + python = $Python + error = $message + valuesPrinted = $false + } + $failed | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 $PrepareResult + exit 1 +} function Download-IfMissing { param([string]$Url, [string]$Path) if (Test-Path $Path) { return } + Write-PrepareProgress "download-start $Url" $tmp = "$Path.part" Invoke-WebRequest -Uri $Url -OutFile $tmp -UseBasicParsing Move-Item -Force $tmp $Path + Write-PrepareProgress "download-done $Path" } function Find-WeChatExe { @@ -32,7 +58,7 @@ function Find-WeChatExe { return $null } -$startedAt = (Get-Date).ToUniversalTime().ToString("o") +Write-PrepareProgress "prepare-start" $installer = Join-Path $DownloadRoot "WeChatSetup-3.9.12.51.exe" Download-IfMissing "$ReleaseBase/WeChatSetup-3.9.12.51.exe" $installer Download-IfMissing "$ReleaseBase/sdk.dll" (Join-Path $WcfRoot "sdk.dll") @@ -45,8 +71,10 @@ if (!(Test-Path $Python)) { $pipOut = Join-Path $StateRoot "prepare-pip.stdout.log" $pipErr = Join-Path $StateRoot "prepare-pip.stderr.log" +Write-PrepareProgress "pip-install-start" & $Python -m pip install --trusted-host mirrors.aliyun.com --index-url $PipIndex "wcferry==39.5.2.0" > $pipOut 2> $pipErr if ($LASTEXITCODE -ne 0) { throw "pip install wcferry failed" } +Write-PrepareProgress "pip-install-done" Copy-Item -Force "$PSScriptRoot\wcf_host.py" (Join-Path $WcfRoot "wcf_host.py") @@ -70,8 +98,10 @@ $installStderr = Join-Path $StateRoot "prepare-wechat-installer.stderr.log" if (-not $wechatExe) { $installAttempted = $true $args = @("/S", "/D=$WechatRoot") + Write-PrepareProgress "wechat-install-start $installer" $proc = Start-Process -FilePath $installer -ArgumentList $args -Wait -PassThru -RedirectStandardOutput $installStdout -RedirectStandardError $installStderr -WindowStyle Hidden $installExitCode = $proc.ExitCode + Write-PrepareProgress "wechat-install-exit $installExitCode" Start-Sleep -Seconds 5 $wechatExe = Find-WeChatExe } @@ -93,7 +123,7 @@ $pyProbe = $pyProbeJson | ConvertFrom-Json $summary = [ordered]@{ ok = [bool]($pyProbe.ok -and $wechatExe) - startedAt = $startedAt + startedAt = $StartedAt finishedAt = (Get-Date).ToUniversalTime().ToString("o") root = $Root wechatInstaller = $installer @@ -111,5 +141,6 @@ $summary = [ordered]@{ wcferryVersion = $pyProbe.wcferryVersion next = if ($wechatExe) { "Run start.ps1 and scan the WeChat login QR." } else { "WeChat silent install did not produce WeChat.exe; run the installer UI from the interactive Windows session, then re-run prepare.ps1." } } +Write-PrepareProgress "prepare-done ok=$($summary.ok)" $summary | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 $PrepareResult $summary | ConvertTo-Json -Depth 6 diff --git a/scripts/windows/personal-wechat/status.ps1 b/scripts/windows/personal-wechat/status.ps1 index 4a51e746..fcef8c06 100644 --- a/scripts/windows/personal-wechat/status.ps1 +++ b/scripts/windows/personal-wechat/status.ps1 @@ -5,6 +5,9 @@ $StateRoot = Join-Path $Root "wcf-state" $PidFile = Join-Path $StateRoot "wcf-host.pid" $StatusFile = Join-Path $StateRoot "status.json" $PrepareResultFile = Join-Path $StateRoot "prepare-result.json" +$PrepareStdout = Join-Path $StateRoot "prepare.stdout.log" +$PrepareStderr = Join-Path $StateRoot "prepare.stderr.log" +$PrepareProgress = Join-Path $StateRoot "prepare-progress.log" $Stdout = Join-Path $StateRoot "wcf-host.stdout.log" $Stderr = Join-Path $StateRoot "wcf-host.stderr.log" function Read-JsonFile { @@ -31,12 +34,25 @@ if (Test-Path $PidFile) { } $status = Read-JsonFile $StatusFile $prepare = Read-JsonFile $PrepareResultFile +$prepareProc = Get-Process -Name powershell -ErrorAction SilentlyContinue | + Where-Object { $_.Path -like "*powershell*" -or $_.ProcessName -like "*powershell*" } | + Sort-Object StartTime -Descending | + Select-Object -First 5 Id,ProcessName,StartTime [pscustomobject]@{ ok = $true pid = $pidValue running = $running prepared = if ($prepare) { [bool]$prepare.ok } else { $false } prepare = $prepare + prepareRuntime = [ordered]@{ + candidateProcesses = $prepareProc + stdout = $PrepareStdout + stderr = $PrepareStderr + progress = $PrepareProgress + stdoutTail = Tail-Text $PrepareStdout + stderrTail = Tail-Text $PrepareStderr + progressTail = Tail-Text $PrepareProgress + } status = $status ports = Get-NetTCPConnection -LocalPort 10086,10087 -ErrorAction SilentlyContinue | Select-Object LocalAddress,LocalPort,State,OwningProcess logs = [ordered]@{ diff --git a/src/components/platform-infra/personal-wechat-collector/Dockerfile b/src/components/platform-infra/personal-wechat-collector/Dockerfile index 024c8317..1f0639c3 100644 --- a/src/components/platform-infra/personal-wechat-collector/Dockerfile +++ b/src/components/platform-infra/personal-wechat-collector/Dockerfile @@ -1,10 +1,17 @@ FROM python:3.12-alpine ARG WCFERRY_VERSION=39.5.2.0 +ARG PIP_INDEX_URL=http://mirrors.aliyun.com/pypi/simple/ +ARG PIP_TRUSTED_HOST=mirrors.aliyun.com +ARG PIP_TIMEOUT_SECONDS=120 +ARG PIP_RETRIES=8 RUN apk add --no-cache ca-certificates tzdata \ - && python -m pip install --no-cache-dir --trusted-host mirrors.aliyun.com \ - --index-url http://mirrors.aliyun.com/pypi/simple/ \ + && python -m pip install --no-cache-dir \ + --trusted-host "${PIP_TRUSTED_HOST}" \ + --index-url "${PIP_INDEX_URL}" \ + --timeout "${PIP_TIMEOUT_SECONDS}" \ + --retries "${PIP_RETRIES}" \ "wcferry==${WCFERRY_VERSION}" \ && python - <<'PY' import importlib.metadata