G0~G8 성과·동맹 측정 OS 작업 일괄 고정
8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본 폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을 git 이력으로 고정하는 것이 목적이다. - contracts/routes/services: measurement, outcome_trajectory, rupture_repair, deliberate_practice, calibration_transfer, supervision_research, multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트 - infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행) - apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가 - docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG - scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트 engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
This commit is contained in:
parent
93dd8f82d7
commit
16e791e044
390 changed files with 243188 additions and 499 deletions
128
scripts/backup-vignette-db.ps1
Normal file
128
scripts/backup-vignette-db.ps1
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
param(
|
||||
[string]$ContainerName = 'vignette-dev-db',
|
||||
[string]$BackupDirectory = '',
|
||||
[string]$DatabaseName = '',
|
||||
[string]$DatabaseUser = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
||||
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
||||
|
||||
$repo = Split-Path -Parent $PSScriptRoot
|
||||
if ([string]::IsNullOrWhiteSpace($BackupDirectory)) {
|
||||
$workspace = Split-Path -Parent $repo
|
||||
$BackupDirectory = Join-Path $workspace 'vignette-backups'
|
||||
}
|
||||
|
||||
function Invoke-DockerChecked([string[]]$Arguments, [string]$FailureMessage) {
|
||||
& docker @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw ("{0} (exit={1})" -f $FailureMessage, $LASTEXITCODE)
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ContainerEnvValue([string]$Key, [string]$Fallback) {
|
||||
$envLines = @(& docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' $ContainerName 2>$null)
|
||||
if ($LASTEXITCODE -ne 0) { return $Fallback }
|
||||
$prefix = "${Key}="
|
||||
foreach ($line in $envLines) {
|
||||
$text = "$line"
|
||||
if ($text.StartsWith($prefix, [StringComparison]::Ordinal)) {
|
||||
return $text.Substring($prefix.Length)
|
||||
}
|
||||
}
|
||||
return $Fallback
|
||||
}
|
||||
|
||||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
|
||||
throw 'docker CLI를 찾을 수 없어 DB 백업을 시작하지 않았다.'
|
||||
}
|
||||
|
||||
$containerState = & docker inspect -f '{{.State.Status}}' $ContainerName 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or "$containerState".Trim() -ne 'running') {
|
||||
throw "DB container '$ContainerName'이 running 상태가 아니어서 백업을 시작하지 않았다."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($DatabaseName)) {
|
||||
$DatabaseName = Get-ContainerEnvValue 'POSTGRES_DB' 'vignette'
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($DatabaseUser)) {
|
||||
$DatabaseUser = Get-ContainerEnvValue 'POSTGRES_USER' 'vignette_owner'
|
||||
}
|
||||
|
||||
Invoke-DockerChecked @('exec', $ContainerName, 'pg_isready', '-U', $DatabaseUser, '-d', $DatabaseName) `
|
||||
'PostgreSQL readiness 확인 실패. 불완전한 백업을 만들지 않았다.' | Out-Null
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $BackupDirectory | Out-Null
|
||||
$resolvedBackupDirectory = (Resolve-Path -LiteralPath $BackupDirectory).Path
|
||||
$timestamp = (Get-Date).ToUniversalTime().ToString('yyyyMMdd-HHmmss')
|
||||
$safeContainer = $ContainerName -replace '[^A-Za-z0-9_.-]', '_'
|
||||
$fileName = "${safeContainer}-${DatabaseName}-${timestamp}Z.dump"
|
||||
$finalPath = Join-Path $resolvedBackupDirectory $fileName
|
||||
$partialPath = "${finalPath}.partial"
|
||||
$manifestPath = "${finalPath}.json"
|
||||
$manifestPartialPath = "${manifestPath}.partial"
|
||||
$containerTempPath = "/tmp/${safeContainer}-${timestamp}-$PID.dump"
|
||||
|
||||
if ((Test-Path -LiteralPath $finalPath) -or (Test-Path -LiteralPath $manifestPath)) {
|
||||
throw "동일 이름의 백업이 이미 존재한다: $finalPath"
|
||||
}
|
||||
|
||||
$backupSucceeded = $false
|
||||
try {
|
||||
Invoke-DockerChecked @(
|
||||
'exec', $ContainerName,
|
||||
'pg_dump', '-U', $DatabaseUser, '-d', $DatabaseName,
|
||||
'--format=custom', '--no-owner', '--no-privileges',
|
||||
'--file', $containerTempPath
|
||||
) 'pg_dump 실패. 기존 DB에는 변경을 가하지 않았다.' | Out-Null
|
||||
|
||||
Invoke-DockerChecked @('exec', $ContainerName, 'pg_restore', '--list', $containerTempPath) `
|
||||
'pg_restore TOC 검증 실패. 백업을 게시하지 않았다.' | Out-Null
|
||||
|
||||
Invoke-DockerChecked @('cp', "${ContainerName}:${containerTempPath}", $partialPath) `
|
||||
'검증된 dump를 로컬 임시 파일로 복사하지 못했다.' | Out-Null
|
||||
|
||||
$file = Get-Item -LiteralPath $partialPath
|
||||
if ($file.Length -le 0) {
|
||||
throw '로컬 dump 크기가 0이어서 백업을 게시하지 않았다.'
|
||||
}
|
||||
$sha256 = (Get-FileHash -LiteralPath $partialPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
Move-Item -LiteralPath $partialPath -Destination $finalPath
|
||||
|
||||
$containerId = (& docker inspect -f '{{.Id}}' $ContainerName).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($containerId)) {
|
||||
throw '백업 후 container identity를 고정하지 못했다.'
|
||||
}
|
||||
$manifest = [ordered]@{
|
||||
schema_version = 1
|
||||
created_at_utc = (Get-Date).ToUniversalTime().ToString('o')
|
||||
container_name = $ContainerName
|
||||
container_id = $containerId
|
||||
database_name = $DatabaseName
|
||||
format = 'postgres-custom'
|
||||
dump_file = $fileName
|
||||
size_bytes = (Get-Item -LiteralPath $finalPath).Length
|
||||
sha256 = $sha256
|
||||
verified_with = 'pg_restore --list'
|
||||
}
|
||||
$json = $manifest | ConvertTo-Json -Depth 3
|
||||
[IO.File]::WriteAllText($manifestPartialPath, $json + [Environment]::NewLine, [Text.UTF8Encoding]::new($false))
|
||||
Move-Item -LiteralPath $manifestPartialPath -Destination $manifestPath
|
||||
$backupSucceeded = $true
|
||||
|
||||
Write-Output ("Backup : {0}" -f $finalPath)
|
||||
Write-Output ("SHA-256: {0}" -f $sha256)
|
||||
Write-Output ("Manifest: {0}" -f $manifestPath)
|
||||
} finally {
|
||||
& docker exec $ContainerName rm -f $containerTempPath 1>$null 2>$null
|
||||
if (-not $backupSucceeded) {
|
||||
if (Test-Path -LiteralPath $partialPath) {
|
||||
Remove-Item -LiteralPath $partialPath -Force
|
||||
}
|
||||
if (Test-Path -LiteralPath $manifestPartialPath) {
|
||||
Remove-Item -LiteralPath $manifestPartialPath -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue