G7 증명과 G8 clean-head 승격 준비
This commit is contained in:
parent
94c681d450
commit
5221f79e3f
52 changed files with 6876 additions and 506 deletions
|
|
@ -1,134 +1,233 @@
|
|||
# Public Runtime Watchdog
|
||||
|
||||
This runbook keeps the public Vignette runtime recoverable after Windows
|
||||
reboot, update, or process crash. It covers:
|
||||
이 runbook은 Windows 재부팅, 업데이트, 프로세스 장애 뒤 Vignette 공개 런타임을 복구한다.
|
||||
|
||||
- engine gateway: `http://127.0.0.1:9099`
|
||||
- prod API: `http://127.0.0.1:8001`
|
||||
- Cloudflare tunnel for `https://api-vignette.chanpaca.net`
|
||||
- engine gateway: http://127.0.0.1:9099
|
||||
- prod API: http://127.0.0.1:8001
|
||||
- web preview: http://127.0.0.1:5174
|
||||
- Cloudflare tunnel: https://api-vignette.chanpaca.net
|
||||
|
||||
## Secret Handling
|
||||
## 복구 소스 신뢰 계약
|
||||
|
||||
Do not put secrets in scheduled task arguments.
|
||||
운영 task는 개발 중인 shared branch worktree를 실행하지 않는다. 아래 조건을 모두 만족하는 별도 release
|
||||
worktree만 허용한다.
|
||||
|
||||
The scripts use the existing runtime locations:
|
||||
- 승인된 commit을 가리키는 detached HEAD
|
||||
- tracked/untracked non-ignored 변경 0
|
||||
- 설치 시 기록한 Git commit SHA와 tree SHA 일치
|
||||
- watchdog 또는 boot script SHA-256과 start-public-runtime.ps1 SHA-256 일치
|
||||
- task action의 working directory와 실행 script가 동일 release root
|
||||
|
||||
- API secrets stay in `apps/api/.env`.
|
||||
- Cloudflared credentials stay under the current user's `.cloudflared` config.
|
||||
- Claude CLI OAuth/config stays in the current Windows user profile.
|
||||
watchdog은 이 증거를 health probe와 failcount 기록보다 먼저 다시 확인한다. boot recovery는 Docker, DB,
|
||||
프로세스 mutation보다 먼저 확인한다. 하나라도 달라지면 현재 운영 프로세스를 유지하고 nonzero로 종료한다.
|
||||
|
||||
The installer creates a per-user interactive scheduled task. It starts at user
|
||||
logon and repeats as a watchdog. Fully unattended boot before any user logs in
|
||||
requires an operator-managed service account or Task Scheduler credential; do
|
||||
that in Windows, not by adding secrets to these scripts.
|
||||
API secret은 release root의 apps/api/.env에 두되 task 인자에는 넣지 않는다. 이 파일과 web node_modules,
|
||||
runtime log는 Git ignore 대상이다. Cloudflared와 Claude CLI credential은 현재 Windows 사용자 profile에 둔다.
|
||||
|
||||
## Install Or Update
|
||||
## Stable Release 준비
|
||||
|
||||
From the repo root:
|
||||
아래 작업은 승인된 clean commit이 생긴 뒤 단일 public mutation owner가 수행한다. 기존 release root를
|
||||
덮어쓰지 않는다.
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-public-runtime-task.ps1 -RunNow
|
||||
```
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = 'D:\workspace\vignette'
|
||||
$commit = (& git.exe -C $repoRoot rev-parse --verify HEAD).Trim()
|
||||
if ($LASTEXITCODE -ne 0) { throw 'HEAD 조회 실패' }
|
||||
$releaseRoot = "D:\workspace\vignette-public-runtime-$($commit.Substring(0, 12))"
|
||||
if (Test-Path -LiteralPath $releaseRoot) { throw "release root already exists: $releaseRoot" }
|
||||
|
||||
The installer is idempotent. Re-running it updates the same task:
|
||||
& git.exe -C $repoRoot worktree add --detach $releaseRoot $commit
|
||||
if ($LASTEXITCODE -ne 0) { throw 'detached release worktree 생성 실패' }
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-public-runtime-task.ps1 -IntervalMinutes 5
|
||||
```
|
||||
Copy-Item -LiteralPath (Join-Path $repoRoot 'apps\api\.env') `
|
||||
-Destination (Join-Path $releaseRoot 'apps\api\.env')
|
||||
Push-Location (Join-Path $releaseRoot 'apps\web')
|
||||
& npm.cmd ci
|
||||
if ($LASTEXITCODE -ne 0) { throw 'release web npm ci 실패' }
|
||||
Pop-Location
|
||||
|
||||
Do not add future domains to the default watchdog until DNS and Cloudflare
|
||||
routing are live. For example, `api-vnet.18ka.net` is intentionally excluded
|
||||
from the default checks while that domain has no DNS. After a public domain is
|
||||
actually reachable, add it explicitly:
|
||||
$dirty = @(& git.exe -C $releaseRoot status --porcelain=v1 --untracked-files=normal)
|
||||
if ($LASTEXITCODE -ne 0 -or $dirty.Count -ne 0) {
|
||||
throw "release source가 clean하지 않음: $($dirty -join '; ')"
|
||||
}
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-public-runtime-task.ps1 `
|
||||
-AdditionalPublicHealthUrls https://api-vnet.18ka.net/health
|
||||
```
|
||||
apps/api/.env의 내용을 console이나 evidence에 출력하지 않는다. 새 root에 node_modules와 .env를 준비한 뒤에도
|
||||
위 Git status 결과는 빈 값이어야 한다.
|
||||
|
||||
Task name:
|
||||
## Task 설치 또는 승격
|
||||
|
||||
```powershell
|
||||
VignettePublicRuntimeWatchdog
|
||||
```
|
||||
두 registrar 자체도 동일 stable release root에서 실행해야 한다. 다른 worktree의 registrar로 target만
|
||||
바꾸는 호출은 거부된다.
|
||||
|
||||
## Manual Start
|
||||
$bootRegistrar = Join-Path $releaseRoot 'scripts\register-boot-task.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $bootRegistrar `
|
||||
-StableSourceRoot $releaseRoot
|
||||
if ($LASTEXITCODE -ne 0) { throw 'boot task 등록 실패' }
|
||||
|
||||
Use this when you want to force a runtime restore immediately:
|
||||
$watchdogInstaller = Join-Path $releaseRoot 'scripts\install-public-runtime-task.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $watchdogInstaller `
|
||||
-StableSourceRoot $releaseRoot `
|
||||
-IntervalMinutes 5
|
||||
if ($LASTEXITCODE -ne 0) { throw 'watchdog task 등록 실패' }
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1
|
||||
```
|
||||
task 이름과 역할:
|
||||
|
||||
`start-public-runtime.ps1`는 관리자·인증 제어면과 엔진을 분리한다. 이미
|
||||
`environment=prod`, `db=true`인 API와 정상 웹·터널은 유지하고, 엔진만 실패한 경우
|
||||
엔진만 복구한다. 엔진이 늦게 준비돼도 관리자·인증 API 기동을 막지 않는다.
|
||||
- VignettePublicRuntime: 사용자 로그온 시 Docker, PostgreSQL, runtime 복구
|
||||
- VignettePublicRuntimeWatchdog: 사용자 로그온 및 5분 반복 health/recovery
|
||||
|
||||
API 코드 변경을 운영 프로세스에 반영할 때는 다른 표면을 유지한 채 API만 명시적으로 교체한다.
|
||||
둘 다 현재 사용자의 Interactive/Limited task다. 사용자 로그인 전 headless boot가 필요하면 별도
|
||||
operator-managed service account가 필요하며 credential을 script나 task arguments에 넣지 않는다.
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1 `
|
||||
-ForceApiRestart `
|
||||
-SkipEngineRestart `
|
||||
-SkipWebRestart `
|
||||
-SkipCloudflaredRestart
|
||||
```
|
||||
### 등록 직후 source pin 검증
|
||||
|
||||
## Health Checks
|
||||
RunNow 전에 action을 읽어 두 task가 같은 release root와 commit을 가리키는지 확인한다.
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\watch-public-runtime.ps1 -CheckOnly
|
||||
Invoke-RestMethod http://127.0.0.1:9099/health
|
||||
Invoke-RestMethod http://127.0.0.1:8001/health
|
||||
Invoke-RestMethod https://api-vignette.chanpaca.net/health
|
||||
```
|
||||
$requirements = @{
|
||||
VignettePublicRuntime = @(
|
||||
'-StableSourceRoot',
|
||||
'-ExpectedSourceCommit',
|
||||
'-ExpectedSourceTree',
|
||||
'-ExpectedBootScriptSha256',
|
||||
'-ExpectedStartScriptSha256'
|
||||
)
|
||||
VignettePublicRuntimeWatchdog = @(
|
||||
'-StableSourceRoot',
|
||||
'-ExpectedSourceCommit',
|
||||
'-ExpectedSourceTree',
|
||||
'-ExpectedWatchdogSha256',
|
||||
'-ExpectedStartScriptSha256'
|
||||
)
|
||||
}
|
||||
|
||||
Optional extra public host check, only after the host resolves:
|
||||
foreach ($taskName in $requirements.Keys) {
|
||||
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction Stop
|
||||
$action = @($task.Actions)[0]
|
||||
if ($action.WorkingDirectory -ne $releaseRoot) {
|
||||
throw "$taskName working directory drift: $($action.WorkingDirectory)"
|
||||
}
|
||||
if ($action.Arguments.IndexOf($releaseRoot, [StringComparison]::OrdinalIgnoreCase) -lt 0) {
|
||||
throw "$taskName release root pin 누락"
|
||||
}
|
||||
if ($action.Arguments.IndexOf($commit, [StringComparison]::OrdinalIgnoreCase) -lt 0) {
|
||||
throw "$taskName commit pin 누락"
|
||||
}
|
||||
foreach ($marker in $requirements[$taskName]) {
|
||||
if ($action.Arguments.IndexOf($marker, [StringComparison]::Ordinal) -lt 0) {
|
||||
throw "$taskName action pin 누락: $marker"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\watch-public-runtime.ps1 `
|
||||
-CheckOnly `
|
||||
-AdditionalPublicHealthUrls https://api-vnet.18ka.net/health
|
||||
```
|
||||
검증 뒤 watchdog만 명시적으로 실행하고 완료 상태를 확인한다.
|
||||
|
||||
Optional real Claude CLI readiness smoke:
|
||||
Start-ScheduledTask -TaskName VignettePublicRuntimeWatchdog
|
||||
Get-ScheduledTaskInfo -TaskName VignettePublicRuntimeWatchdog
|
||||
|
||||
```powershell
|
||||
Invoke-RestMethod http://127.0.0.1:9099/ready
|
||||
```
|
||||
LastTaskResult=0과 stable release root의 public-runtime-watchdog.failcount=0을 확인한다. health만 정상이라고
|
||||
새 source 배포가 완료된 것은 아니다. 공개 API process cwd, Git commit, OpenAPI, auth, voice provider/model,
|
||||
실제 session smoke까지 별도 배포 gate에서 확인한다.
|
||||
|
||||
`/ready` can consume a small Claude budget because it performs a real generation.
|
||||
## 숨김 수동 Trigger
|
||||
|
||||
## Logs And Task State
|
||||
watch-public-runtime-hidden.vbs는 source script를 직접 실행하지 않는다. 등록된 watchdog task action에
|
||||
StableSourceRoot, commit, tree, watchdog SHA, start SHA marker가 모두 있고 legacy Workspace action이 아님을
|
||||
검사한 뒤 Start-ScheduledTask만 호출한다.
|
||||
|
||||
```powershell
|
||||
Get-ScheduledTask -TaskName VignettePublicRuntimeWatchdog
|
||||
Get-ScheduledTaskInfo -TaskName VignettePublicRuntimeWatchdog
|
||||
Get-Content .\public-runtime-watchdog.log -Tail 50
|
||||
Get-Content .\apps\api\engine.public.err.log -Tail 50
|
||||
Get-Content .\apps\api\api.public.err.log -Tail 50
|
||||
Get-Content .\cloudflared.public.err.log -Tail 50
|
||||
```
|
||||
cscript.exe //nologo scripts\watch-public-runtime-hidden.vbs
|
||||
|
||||
## Remove
|
||||
task가 아직 legacy shared-worktree action이면 VBS도 fail-closed한다.
|
||||
|
||||
```powershell
|
||||
Unregister-ScheduledTask -TaskName VignettePublicRuntimeWatchdog -Confirm:$false
|
||||
```
|
||||
## Manual Source Recovery
|
||||
|
||||
## Recovery Notes
|
||||
운영 code를 강제로 교체해야 할 때도 shared worktree의 start-public-runtime.ps1을 실행하지 않는다.
|
||||
위 pin 검증을 끝낸 release root의 script만 사용한다.
|
||||
|
||||
If local health is good but public health fails, inspect the cloudflared process
|
||||
and `C:\Users\<user>\.cloudflared\vignette-config.yml`.
|
||||
$startScript = Join-Path $releaseRoot 'scripts\start-public-runtime.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $startScript `
|
||||
-Workspace $releaseRoot `
|
||||
-ForceApiRestart `
|
||||
-SkipEngineRestart `
|
||||
-SkipWebRestart `
|
||||
-SkipCloudflaredRestart
|
||||
if ($LASTEXITCODE -ne 0) { throw 'public API 교체 실패' }
|
||||
|
||||
If engine health fails, verify that `claude` runs for the same Windows user that
|
||||
owns the scheduled task and that the user has completed Claude CLI login.
|
||||
start-public-runtime.ps1은 engine, API, web, tunnel을 분리해 이미 healthy인 표면을 유지한다. local Whisper와
|
||||
MeloTTS exact readiness가 닫히기 전에는 API restart gate를 통과하지 않는다.
|
||||
|
||||
엔진 장애 중에도 `http://127.0.0.1:8001/health`의 `environment=prod`, `db=true`가
|
||||
유지되면 관리자·인증 제어면은 정상이다. 이때 watchdog은 API·웹·터널을 재시작하지
|
||||
않는다. 예약 작업 확인 기준은 `VignettePublicRuntime`의 `LastTaskResult=0`과
|
||||
`public-runtime-watchdog.failcount=0`이다.
|
||||
위 명령은 routine API-only 복구라 cloudflared를 유지하며 **G7 fresh topology 증거를 만들지 않는다**. G7 공개
|
||||
승격에서는 기존 API/cloudflared PID를 재사용하지 않고 아래 opt-in 계약을 사용한다. 실행 전에 detached-clean
|
||||
commit/tree와 Python/cloudflared/config SHA를 read-only로 고정하고, config ingress가 이미 exact public topology인지
|
||||
확인한다. 이 모드는 `-ForceApiRestart`가 필수이고 `-SkipCloudflaredRestart`를 허용하지 않는다.
|
||||
|
||||
If API health fails with `environment`, `db`, or auth configuration errors,
|
||||
inspect `apps/api/.env`; do not copy secret values into scripts or task
|
||||
arguments.
|
||||
$python = 'C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe'
|
||||
$cloudflared = 'C:\Users\encep\AppData\Local\Microsoft\WinGet\Links\cloudflared.exe'
|
||||
$cloudflaredConfig = 'C:\Users\encep\.cloudflared\vignette-config.yml'
|
||||
$commit = (& git.exe -C $releaseRoot rev-parse --verify HEAD).Trim()
|
||||
$tree = (& git.exe -C $releaseRoot rev-parse --verify 'HEAD^{tree}').Trim()
|
||||
$pythonSha = (Get-FileHash -LiteralPath $python -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$cloudflaredSha = (Get-FileHash -LiteralPath $cloudflared -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$configSha = (Get-FileHash -LiteralPath $cloudflaredConfig -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$receipt = Join-Path (Join-Path 'D:\workspace\vignette-runtime-evidence' $commit) 'public-runtime-launch-provenance.json'
|
||||
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $startScript `
|
||||
-Workspace $releaseRoot `
|
||||
-ForceApiRestart `
|
||||
-SkipEngineRestart `
|
||||
-SkipWebRestart `
|
||||
-RequireFreshPublicProvenance `
|
||||
-ExpectedSourceCommit $commit `
|
||||
-ExpectedSourceTree $tree `
|
||||
-ExpectedPythonSha256 $pythonSha `
|
||||
-ExpectedCloudflaredSha256 $cloudflaredSha `
|
||||
-ExpectedCloudflaredConfigSha256 $configSha `
|
||||
-RuntimeProvenancePath $receipt
|
||||
if ($LASTEXITCODE -ne 0) { throw 'fresh public provenance 승격 실패' }
|
||||
|
||||
receipt에는 raw command line·config contents를 넣지 않고 PID/start/executable·command SHA/실제 cwd와 topology 입력만
|
||||
남긴다. 이 receipt의 PID와 pin을 `run-g7-external-proof-window.py --topology-mode windows-host`에 그대로 전달하고,
|
||||
공개 health·auth·OpenAPI·local provider ready를 확인하기 전에는 task action을 새 root로 재등록하지 않는다.
|
||||
|
||||
## Read-only CheckOnly
|
||||
|
||||
watchdog script를 직접 CheckOnly로 실행할 때도 task와 같은 pin을 모두 전달해야 한다.
|
||||
|
||||
$watchScript = Join-Path $releaseRoot 'scripts\watch-public-runtime.ps1'
|
||||
$startScript = Join-Path $releaseRoot 'scripts\start-public-runtime.ps1'
|
||||
$commit = (& git.exe -C $releaseRoot rev-parse --verify HEAD).Trim()
|
||||
$tree = (& git.exe -C $releaseRoot rev-parse --verify 'HEAD^{tree}').Trim()
|
||||
$watchSha = (Get-FileHash -LiteralPath $watchScript -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$startSha = (Get-FileHash -LiteralPath $startScript -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $watchScript `
|
||||
-StableSourceRoot $releaseRoot `
|
||||
-ExpectedSourceCommit $commit `
|
||||
-ExpectedSourceTree $tree `
|
||||
-ExpectedWatchdogSha256 $watchSha `
|
||||
-ExpectedStartScriptSha256 $startSha `
|
||||
-CheckOnly
|
||||
|
||||
추가 public host는 DNS와 routing이 실제로 열린 뒤 installer의
|
||||
AdditionalPublicHealthUrls에 명시한다. 아직 열리지 않은 future host를 기본 probe에 넣어 restart loop를 만들지 않는다.
|
||||
|
||||
## Health와 로그
|
||||
|
||||
Invoke-RestMethod http://127.0.0.1:9099/health
|
||||
Invoke-RestMethod http://127.0.0.1:8001/health
|
||||
Invoke-RestMethod https://api-vignette.chanpaca.net/health
|
||||
Get-ScheduledTaskInfo -TaskName VignettePublicRuntimeWatchdog
|
||||
Get-Content -LiteralPath (Join-Path $releaseRoot 'public-runtime-watchdog.log') -Tail 50
|
||||
Get-Content -LiteralPath (Join-Path $releaseRoot 'apps\api\engine.public.err.log') -Tail 50
|
||||
Get-Content -LiteralPath (Join-Path $releaseRoot 'apps\api\api.public.err.log') -Tail 50
|
||||
|
||||
engine /ready는 실제 Claude generation을 수행할 수 있어 소량의 budget을 사용한다. shared secret으로 기동한
|
||||
gateway는 token header가 필요하다.
|
||||
|
||||
엔진 장애 중에도 API health가 environment=prod, db=true이면 관리자와 인증 제어면은 유지한다. source
|
||||
provenance failure는 재시작으로 우회하지 말고 task action과 stable release를 다시 승격한다.
|
||||
|
||||
## Task 제거
|
||||
|
||||
제거는 명시적 운영 결정으로만 수행한다.
|
||||
|
||||
Unregister-ScheduledTask -TaskName VignettePublicRuntimeWatchdog -Confirm:$false
|
||||
Unregister-ScheduledTask -TaskName VignettePublicRuntime -Confirm:$false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue