EENE Dashboard upload to Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
153
scripts/db-local.ps1
Normal file
153
scripts/db-local.ps1
Normal file
@@ -0,0 +1,153 @@
|
||||
# Shared config — project DB always lives in data/postgres on port 54320
|
||||
$script:LocalDbPort = 54320
|
||||
$script:LocalDbUser = 'eee_admin'
|
||||
$script:LocalDbPassword = 'eee_password'
|
||||
$script:LocalDbName = 'eee_dashboard'
|
||||
|
||||
function Get-ProjectRoot {
|
||||
param([string]$FromScript = $PSScriptRoot)
|
||||
return (Resolve-Path (Join-Path $FromScript '..')).Path
|
||||
}
|
||||
|
||||
function Get-PgDataDir {
|
||||
param([string]$Root)
|
||||
return Join-Path $Root 'data\postgres'
|
||||
}
|
||||
|
||||
function Get-PgCtlLogPath {
|
||||
param([string]$Root)
|
||||
return Join-Path $Root 'data\pg_ctl.log'
|
||||
}
|
||||
|
||||
function Find-PgBin {
|
||||
$cmd = Get-Command initdb -ErrorAction SilentlyContinue
|
||||
if ($cmd) { return (Split-Path $cmd.Path -Parent) }
|
||||
|
||||
$candidates = @(
|
||||
'C:\Program Files\PostgreSQL\16\bin',
|
||||
'C:\Program Files\PostgreSQL\15\bin',
|
||||
'C:\Program Files\PostgreSQL\14\bin'
|
||||
)
|
||||
foreach ($dir in $candidates) {
|
||||
if (Test-Path (Join-Path $dir 'initdb.exe')) { return $dir }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Test-DbPortOpen {
|
||||
param([int]$Port = $script:LocalDbPort)
|
||||
$client = $null
|
||||
try {
|
||||
$client = New-Object System.Net.Sockets.TcpClient
|
||||
$connect = $client.BeginConnect('127.0.0.1', $Port, $null, $null)
|
||||
if (-not $connect.AsyncWaitHandle.WaitOne(500, $false)) { return $false }
|
||||
$client.EndConnect($connect)
|
||||
return $client.Connected
|
||||
} catch {
|
||||
return $false
|
||||
} finally {
|
||||
if ($client) { $client.Dispose() }
|
||||
}
|
||||
}
|
||||
|
||||
function Clear-StalePostmasterLock {
|
||||
param([string]$PgData)
|
||||
$pidFile = Join-Path $PgData 'postmaster.pid'
|
||||
if (-not (Test-Path $pidFile)) { return }
|
||||
|
||||
$firstLine = Get-Content $pidFile -TotalCount 1 -ErrorAction SilentlyContinue
|
||||
$oldPid = 0
|
||||
[void][int]::TryParse($firstLine, [ref]$oldPid)
|
||||
|
||||
$alive = $false
|
||||
if ($oldPid -gt 0) {
|
||||
$proc = Get-Process -Id $oldPid -ErrorAction SilentlyContinue
|
||||
if ($proc -and $proc.ProcessName -match 'postgres') { $alive = $true }
|
||||
}
|
||||
|
||||
if (-not $alive) {
|
||||
Remove-Item $pidFile -Force -ErrorAction SilentlyContinue
|
||||
Write-Host ' Cleared stale postmaster.pid (folder moved or crash)'
|
||||
}
|
||||
}
|
||||
|
||||
function Get-DatabaseUrl {
|
||||
return "postgresql://$($script:LocalDbUser):$($script:LocalDbPassword)@localhost:$($script:LocalDbPort)/$($script:LocalDbName)"
|
||||
}
|
||||
|
||||
function Set-EnvLine {
|
||||
param(
|
||||
[string]$Content,
|
||||
[string]$Key,
|
||||
[string]$Value
|
||||
)
|
||||
$pattern = "(?m)^$Key="
|
||||
$line = "$Key=`"$Value`""
|
||||
if ($Content -match $pattern) {
|
||||
return ($Content -replace "(?m)^$Key=.*$", $line)
|
||||
}
|
||||
return "$line`r`n$Content"
|
||||
}
|
||||
|
||||
function Ensure-BackendEnv {
|
||||
param([string]$Root)
|
||||
|
||||
$envPath = Join-Path $Root 'backend\.env'
|
||||
$examplePath = Join-Path $Root 'backend\.env.example'
|
||||
if (-not (Test-Path $envPath)) {
|
||||
Copy-Item $examplePath $envPath
|
||||
}
|
||||
|
||||
$url = Get-DatabaseUrl
|
||||
$content = Get-Content $envPath -Raw -Encoding UTF8
|
||||
$content = Set-EnvLine $content 'DATABASE_URL' $url
|
||||
$content = Set-EnvLine $content 'HR_DATA_PATH' '../data/seed/hr-data.json'
|
||||
$content = Set-EnvLine $content 'UPLOAD_DIR' '../uploads'
|
||||
Set-Content -Path $envPath -Value $content.TrimEnd() -Encoding UTF8 -NoNewline
|
||||
Add-Content -Path $envPath -Value '' -Encoding UTF8
|
||||
}
|
||||
|
||||
function Set-PostgresPort {
|
||||
param(
|
||||
[string]$PgData,
|
||||
[int]$Port = $script:LocalDbPort
|
||||
)
|
||||
$auto = Join-Path $PgData 'postgresql.auto.conf'
|
||||
$wanted = @{
|
||||
'port' = "port = $Port"
|
||||
'logging_collector' = 'logging_collector = off'
|
||||
}
|
||||
$existing = @()
|
||||
if (Test-Path $auto) {
|
||||
$existing = @(Get-Content $auto -Encoding UTF8 -ErrorAction SilentlyContinue)
|
||||
}
|
||||
foreach ($key in $wanted.Keys) {
|
||||
$line = $wanted[$key]
|
||||
$found = $false
|
||||
$existing = @($existing | ForEach-Object {
|
||||
if ($_ -match "^\s*$key\s*=") { $found = $true; $line } else { $_ }
|
||||
})
|
||||
if (-not $found) { $existing += $line }
|
||||
}
|
||||
$utf8 = New-Object System.Text.UTF8Encoding $false
|
||||
[System.IO.File]::WriteAllLines($auto, $existing, $utf8)
|
||||
}
|
||||
|
||||
function Test-DockerReady {
|
||||
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { return $false }
|
||||
docker info 2>$null | Out-Null
|
||||
return ($LASTEXITCODE -eq 0)
|
||||
}
|
||||
|
||||
function Invoke-Psql {
|
||||
param(
|
||||
[string]$PgBin,
|
||||
[int]$Port = $script:LocalDbPort,
|
||||
[string]$User = $script:LocalDbUser,
|
||||
[string]$Database = 'postgres',
|
||||
[string]$Sql
|
||||
)
|
||||
$psql = Join-Path $PgBin 'psql.exe'
|
||||
& $psql -h 127.0.0.1 -p $Port -U $User -d $Database -v ON_ERROR_STOP=1 -c $Sql 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "psql failed ($Sql)" }
|
||||
}
|
||||
@@ -1,4 +1,49 @@
|
||||
$ip = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.IPAddress -match '^(172\.|192\.168\.|10\.)' } |
|
||||
# 실제 LAN IP (Docker/Hyper-V 172.17.x 등 가상 어댑터 제외)
|
||||
param(
|
||||
[string]$PreferredPrefix = '172.16.'
|
||||
)
|
||||
|
||||
$override = $env:EENE_LAN_IP
|
||||
if ($override) {
|
||||
Write-Output $override.Trim()
|
||||
exit 0
|
||||
}
|
||||
|
||||
function Test-PhysicalAdapter {
|
||||
param([int]$IfIndex)
|
||||
$adapter = Get-NetAdapter -InterfaceIndex $IfIndex -ErrorAction SilentlyContinue
|
||||
if (-not $adapter -or $adapter.Status -ne 'Up') { return $false }
|
||||
$desc = $adapter.InterfaceDescription
|
||||
if ($desc -match 'Virtual|Hyper-V|Docker|WSL|vEthernet|VMware|Loopback|TAP|TUN|VPN') { return $false }
|
||||
return $true
|
||||
}
|
||||
|
||||
$candidates = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.IPAddress -match '^(172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|10\.)' -and
|
||||
$_.IPAddress -notmatch '^169\.254\.' -and
|
||||
$_.IPAddress -notmatch '^172\.17\.' -and
|
||||
(Test-PhysicalAdapter -IfIndex $_.InterfaceIndex)
|
||||
}
|
||||
|
||||
if (-not $candidates) {
|
||||
$candidates = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.IPAddress -match '^(172\.|192\.168\.|10\.)' -and
|
||||
$_.IPAddress -notmatch '^169\.254\.' -and
|
||||
$_.IPAddress -notmatch '^172\.17\.'
|
||||
}
|
||||
}
|
||||
|
||||
$ip = $candidates |
|
||||
Sort-Object @{
|
||||
Expression = {
|
||||
if ($_.IPAddress -like "$PreferredPrefix*") { 0 }
|
||||
elseif ($_.IPAddress -match '^192\.168\.') { 1 }
|
||||
elseif ($_.IPAddress -match '^10\.') { 2 }
|
||||
else { 3 }
|
||||
}
|
||||
}, @{ Expression = { $_.PrefixLength } } |
|
||||
Select-Object -First 1 -ExpandProperty IPAddress
|
||||
|
||||
if ($ip) { Write-Output $ip }
|
||||
|
||||
88
scripts/gitea-upload.ps1
Normal file
88
scripts/gitea-upload.ps1
Normal file
@@ -0,0 +1,88 @@
|
||||
# EENE Dashboard -> Gitea upload
|
||||
# Run: Gitea업로드.bat or powershell -ExecutionPolicy Bypass -File scripts\gitea-upload.ps1
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$projectRoot = Split-Path $PSScriptRoot -Parent
|
||||
$pubKeyPath = Join-Path $env:USERPROFILE '.ssh\id_ed25519_eene_dashboard.pub'
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '=== EENE Dashboard Gitea upload ===' -ForegroundColor Cyan
|
||||
Write-Host ''
|
||||
|
||||
if (-not (Test-Path $pubKeyPath)) {
|
||||
Write-Host '[ERROR] Deploy key not found. Run:' -ForegroundColor Red
|
||||
Write-Host ' ssh-keygen -t ed25519 -C eene_dashboard-deploy -f $env:USERPROFILE\.ssh\id_ed25519_eene_dashboard -N ""'
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host '[1/4] Deploy key (paste this line in Gitea -> Settings -> Deploy Keys):' -ForegroundColor Yellow
|
||||
Write-Host ''
|
||||
Get-Content $pubKeyPath
|
||||
Write-Host ''
|
||||
$ok = Read-Host 'Registered deploy key on Gitea? (y/n)'
|
||||
if ($ok -ne 'y' -and $ok -ne 'Y') {
|
||||
Write-Host 'Register the key on Gitea, then run again.' -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[2/4] SSH test...' -ForegroundColor Yellow
|
||||
$keyPath = Join-Path $env:USERPROFILE '.ssh\id_ed25519_eene_dashboard'
|
||||
$fp = ssh-keygen -lf $pubKeyPath 2>$null
|
||||
if ($fp) { Write-Host " Key fingerprint: $fp" -ForegroundColor DarkGray }
|
||||
$giteaPort = 222
|
||||
$test = ssh -i $keyPath -T git@172.16.10.175 -p $giteaPort -o IdentitiesOnly=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new 2>&1 | Out-String
|
||||
Write-Host $test.TrimEnd()
|
||||
if ($test -match 'Permission denied') {
|
||||
Write-Host ''
|
||||
Write-Host '[ERROR] Gitea rejected this key.' -ForegroundColor Red
|
||||
Write-Host " SSH port $giteaPort failed. Gitea SSH is often NOT port 22." -ForegroundColor Yellow
|
||||
Write-Host ' Check Gitea web -> SSH clone URL for the correct port.' -ForegroundColor Yellow
|
||||
Write-Host ' Fix on Gitea web:' -ForegroundColor Yellow
|
||||
Write-Host ' 1. eene_dashboard -> Settings -> Deploy Keys' -ForegroundColor Yellow
|
||||
Write-Host ' 2. Delete old keys' -ForegroundColor Yellow
|
||||
Write-Host ' 3. Add key: paste the ssh-ed25519 line from step [1/4] EXACTLY' -ForegroundColor Yellow
|
||||
Write-Host ' 4. Enable write access, then save' -ForegroundColor Yellow
|
||||
Write-Host ' Or: Gitea user Settings -> SSH Keys (same public key line)' -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
if ($test -notmatch 'successfully authenticated') {
|
||||
Write-Host ''
|
||||
Write-Host '[ERROR] Unexpected SSH response. Check port and deploy key.' -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host ' SSH OK' -ForegroundColor Green
|
||||
|
||||
Set-Location $projectRoot
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[3/4] git remote gitea...' -ForegroundColor Yellow
|
||||
$remotes = @(git remote 2>$null)
|
||||
if ($remotes -contains 'gitea') {
|
||||
git remote set-url gitea 'git@gitea-eene:RyuWonJun/eene_dashboard.git'
|
||||
} else {
|
||||
git remote add gitea 'git@gitea-eene:RyuWonJun/eene_dashboard.git'
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[4/4] commit and push...' -ForegroundColor Yellow
|
||||
|
||||
git add -A
|
||||
$status = git status --porcelain
|
||||
if ($status) {
|
||||
git commit -m 'EENE Dashboard upload to Gitea'
|
||||
} else {
|
||||
Write-Host ' No changes to commit, push only.' -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
git push -u gitea main
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host ''
|
||||
Write-Host '[ERROR] push failed. Check branch: git branch' -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '=== Done ===' -ForegroundColor Green
|
||||
Write-Host 'Note: uploads/ and data/postgres/ may not be in Git (.gitignore).' -ForegroundColor Yellow
|
||||
Write-Host ''
|
||||
@@ -3,9 +3,9 @@ $killed = @()
|
||||
Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
|
||||
Where-Object { $ports -contains $_.LocalPort } |
|
||||
ForEach-Object {
|
||||
$pid = $_.OwningProcess
|
||||
if ($killed -notcontains $pid) {
|
||||
Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
|
||||
$killed += $pid
|
||||
$procId = $_.OwningProcess
|
||||
if ($killed -notcontains $procId) {
|
||||
Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue
|
||||
$killed += $procId
|
||||
}
|
||||
}
|
||||
|
||||
111
scripts/migrate-legacy-db.ps1
Normal file
111
scripts/migrate-legacy-db.ps1
Normal file
@@ -0,0 +1,111 @@
|
||||
# One-time: copy data from old Windows PostgreSQL (port 5432) into data/postgres
|
||||
param(
|
||||
[string]$Root = '',
|
||||
[string]$PgBin,
|
||||
[string]$PgData,
|
||||
[int]$Port = 0
|
||||
)
|
||||
|
||||
. "$PSScriptRoot\db-local.ps1"
|
||||
|
||||
if (-not $Root) { $Root = Get-ProjectRoot -FromScript $PSScriptRoot }
|
||||
if (-not $Port) { $Port = $LocalDbPort }
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Parse-DatabaseUrl {
|
||||
param([string]$Url)
|
||||
if ($Url -match '^postgresql://([^:]+):([^@]+)@([^:/]+):(\d+)/(.+)$') {
|
||||
return @{
|
||||
User = $Matches[1]
|
||||
Password = $Matches[2]
|
||||
Host = $Matches[3]
|
||||
Port = [int]$Matches[4]
|
||||
Database = $Matches[5]
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Test-LegacyDatabase {
|
||||
param($Legacy)
|
||||
if (-not $Legacy) { return $false }
|
||||
if ($Legacy.Port -eq $Port) { return $false }
|
||||
if (-not (Test-DbPortOpen -Port $Legacy.Port)) { return $false }
|
||||
|
||||
$env:PGPASSWORD = $Legacy.Password
|
||||
try {
|
||||
$psql = Join-Path $PgBin 'psql.exe'
|
||||
$count = & $psql -h $Legacy.Host -p $Legacy.Port -U $Legacy.User -d $Legacy.Database -tAc 'SELECT COUNT(*) FROM tasks' 2>$null
|
||||
return ($LASTEXITCODE -eq 0 -and [int]$count -gt 0)
|
||||
} catch {
|
||||
return $false
|
||||
} finally {
|
||||
Remove-Item Env:PGPASSWORD -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$envPath = Join-Path $Root 'backend\.env'
|
||||
$legacy = $null
|
||||
if (Test-Path $envPath) {
|
||||
$line = Get-Content $envPath -Encoding UTF8 | Where-Object { $_ -match '^DATABASE_URL=' } | Select-Object -First 1
|
||||
if ($line -match '^DATABASE_URL="(.+)"') {
|
||||
$legacy = Parse-DatabaseUrl $Matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
# Also probe common Windows install (5432 / postgres) when folder is still empty
|
||||
if (-not (Test-LegacyDatabase -Legacy $legacy)) {
|
||||
$legacy = @{
|
||||
User = 'postgres'
|
||||
Password = $LocalDbPassword
|
||||
Host = 'localhost'
|
||||
Port = 5432
|
||||
Database = $LocalDbName
|
||||
}
|
||||
if (-not (Test-LegacyDatabase -Legacy $legacy)) {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ' [이전] Windows PostgreSQL 데이터를 data\postgres 로 옮기는 중...'
|
||||
|
||||
$dumpPath = Join-Path $Root 'data\_legacy_migrate.dump'
|
||||
$initdb = Join-Path $PgBin 'initdb.exe'
|
||||
$pgCtl = Join-Path $PgBin 'pg_ctl.exe'
|
||||
$pgRestore = Join-Path $PgBin 'pg_restore.exe'
|
||||
$pgDump = Join-Path $PgBin 'pg_dump.exe'
|
||||
|
||||
$env:PGPASSWORD = $legacy.Password
|
||||
& $pgDump -h $legacy.Host -p $legacy.Port -U $legacy.User -d $legacy.Database -F c -f $dumpPath --no-owner --no-acl
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Remove-Item Env:PGPASSWORD -ErrorAction SilentlyContinue
|
||||
Remove-Item $dumpPath -ErrorAction SilentlyContinue
|
||||
throw 'legacy pg_dump failed'
|
||||
}
|
||||
Remove-Item Env:PGPASSWORD -ErrorAction SilentlyContinue
|
||||
|
||||
& $initdb -D $PgData -U $LocalDbUser -E UTF8 --locale=C -A trust
|
||||
if ($LASTEXITCODE -ne 0) { throw 'initdb failed' }
|
||||
|
||||
Set-PostgresPort -PgData $PgData -Port $Port
|
||||
$logFile = Get-PgCtlLogPath -Root $Root
|
||||
& $pgCtl -D $PgData -l $logFile -o "-p $Port" start -w -t 60
|
||||
if ($LASTEXITCODE -ne 0) { throw 'pg_ctl start failed (migrate)' }
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
Invoke-Psql -PgBin $PgBin -Port $Port -Sql "ALTER USER $LocalDbUser WITH PASSWORD '$LocalDbPassword';"
|
||||
Invoke-Psql -PgBin $PgBin -Port $Port -Sql "CREATE DATABASE $LocalDbName OWNER $LocalDbUser;"
|
||||
|
||||
$env:PGPASSWORD = $LocalDbPassword
|
||||
& $pgRestore -h 127.0.0.1 -p $Port -U $LocalDbUser -d $LocalDbName --no-owner --no-acl $dumpPath 2>&1 | Out-Null
|
||||
Remove-Item Env:PGPASSWORD -ErrorAction SilentlyContinue
|
||||
if ($LASTEXITCODE -ge 2) {
|
||||
& $pgCtl -D $PgData stop -m fast
|
||||
throw 'pg_restore failed'
|
||||
}
|
||||
|
||||
Remove-Item $dumpPath -ErrorAction SilentlyContinue
|
||||
Ensure-BackendEnv -Root $Root
|
||||
Write-Host ' [이전] 완료 — 이제 DB는 data\postgres 에 저장됩니다.'
|
||||
return $true
|
||||
50
scripts/rebuild-local-data.ps1
Normal file
50
scripts/rebuild-local-data.ps1
Normal file
@@ -0,0 +1,50 @@
|
||||
param(
|
||||
[switch]$SkipStop
|
||||
)
|
||||
|
||||
$Root = Split-Path $PSScriptRoot -Parent
|
||||
Set-Location $Root
|
||||
|
||||
Write-Host '================================'
|
||||
Write-Host ' EENE Dashboard - Data Rebuild'
|
||||
Write-Host ' Source: data\seed\hr-data.json'
|
||||
Write-Host '================================'
|
||||
Write-Host ''
|
||||
Write-Host ' [WARN] 모든 업무·팀원·첨부 DB 레코드를 지우고'
|
||||
Write-Host ' hr-data.json 기준으로 다시 만듭니다.'
|
||||
Write-Host ' (평소 UI 수정은 자동 저장 — 이 파일은 초기화용)'
|
||||
Write-Host ' uploads\ 파일은 유지됩니다.'
|
||||
Write-Host ''
|
||||
|
||||
$confirm = Read-Host '계속하려면 Y 입력'
|
||||
if ($confirm -notmatch '^[Yy]') {
|
||||
Write-Host '취소됨.'
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not $SkipStop) {
|
||||
Write-Host '[1/3] Stopping API/WEB ...'
|
||||
& "$PSScriptRoot\stop-server.ps1" | Out-Null
|
||||
Write-Host ' Done.'
|
||||
} else {
|
||||
Write-Host '[1/3] Skip stop (-SkipStop)'
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[2/3] DB sync + rebuild seed ...'
|
||||
Set-Location (Join-Path $Root 'backend')
|
||||
cmd /c 'npm run db:sync'
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
cmd /c 'npm run db:seed'
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
Set-Location $Root
|
||||
Write-Host ' Done.'
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[3/3] Next steps'
|
||||
Write-Host ' 1) 서버시작.bat 실행'
|
||||
Write-Host ' 2) 브라우저 Ctrl+F5'
|
||||
Write-Host ' 3) (선택) DevTools > Application > Local Storage 에서'
|
||||
Write-Host ' eene-quarter-hub-config-v1, eene-board-slot-headers-v1 삭제'
|
||||
Write-Host ''
|
||||
Write-Host ' Done.'
|
||||
33
scripts/replay-css-transcript.mjs
Normal file
33
scripts/replay-css-transcript.mjs
Normal file
@@ -0,0 +1,33 @@
|
||||
import fs from 'fs';
|
||||
|
||||
const jsonl =
|
||||
'C:/Users/한맥/.cursor/projects/d-EENE-Dashboard-0608/agent-transcripts/a31af370-8cc7-4941-8299-96fbb85f1ad8/a31af370-8cc7-4941-8299-96fbb85f1ad8.jsonl';
|
||||
const cssPath = 'd:/EENE_Dashboard_0608/frontend/src/styles/quarter-board.css';
|
||||
|
||||
let content = fs.readFileSync(cssPath, 'utf8');
|
||||
let applied = 0;
|
||||
let missed = 0;
|
||||
|
||||
for (const line of fs.readFileSync(jsonl, 'utf8').split('\n')) {
|
||||
if (!line.trim()) continue;
|
||||
try {
|
||||
const o = JSON.parse(line);
|
||||
for (const p of o.message?.content ?? []) {
|
||||
if (p.type !== 'tool_use') continue;
|
||||
const inp = p.input ?? {};
|
||||
if (!inp.path?.includes('quarter-board.css')) continue;
|
||||
if (!inp.old_string || !inp.new_string) continue;
|
||||
if (content.includes(inp.old_string)) {
|
||||
content = content.replace(inp.old_string, inp.new_string);
|
||||
applied++;
|
||||
} else {
|
||||
missed++;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(cssPath, content, 'utf8');
|
||||
console.log({ applied, missed, length: content.length });
|
||||
49
scripts/restore-from-transcript.mjs
Normal file
49
scripts/restore-from-transcript.mjs
Normal file
@@ -0,0 +1,49 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const jsonl =
|
||||
'C:/Users/한맥/.cursor/projects/d-EENE-Dashboard-0608/agent-transcripts/a31af370-8cc7-4941-8299-96fbb85f1ad8/a31af370-8cc7-4941-8299-96fbb85f1ad8.jsonl';
|
||||
const root = 'd:/EENE_Dashboard_0608';
|
||||
|
||||
const files = new Map();
|
||||
const replaces = [];
|
||||
|
||||
for (const line of fs.readFileSync(jsonl, 'utf8').split('\n')) {
|
||||
if (!line.trim()) continue;
|
||||
try {
|
||||
const o = JSON.parse(line);
|
||||
for (const p of o.message?.content ?? []) {
|
||||
if (p.type !== 'tool_use') continue;
|
||||
const inp = p.input ?? {};
|
||||
const fp = inp.path;
|
||||
if (!fp?.includes('EENE_Dashboard_0608')) continue;
|
||||
if (inp.contents) files.set(fp, inp.contents);
|
||||
if (inp.old_string && inp.new_string && fp.includes('frontend')) {
|
||||
replaces.push({ fp, old: inp.old_string, new: inp.new_string });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
}
|
||||
|
||||
function toRel(fp) {
|
||||
return fp
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/^d:\/EENE_Dashboard_0608\//i, '')
|
||||
.replace(/^D:\/EENE_Dashboard_0608\//, '');
|
||||
}
|
||||
|
||||
for (const [fp, initial] of files) {
|
||||
let content = initial;
|
||||
for (const r of replaces) {
|
||||
if (r.fp !== fp) continue;
|
||||
if (content.includes(r.old)) content = content.replace(r.old, r.new);
|
||||
}
|
||||
const rel = toRel(fp);
|
||||
if (!rel.startsWith('frontend/') && !rel.startsWith('backend/')) continue;
|
||||
const out = path.join(root, rel);
|
||||
fs.mkdirSync(path.dirname(out), { recursive: true });
|
||||
fs.writeFileSync(out, content, 'utf8');
|
||||
console.log('restored', rel, content.length);
|
||||
}
|
||||
129
scripts/start-local-db.ps1
Normal file
129
scripts/start-local-db.ps1
Normal file
@@ -0,0 +1,129 @@
|
||||
# Start PostgreSQL with data directory = project/data/postgres (no Docker required)
|
||||
param([string]$Root = '')
|
||||
|
||||
. "$PSScriptRoot\db-local.ps1"
|
||||
|
||||
function Start-PostgresWithWait {
|
||||
param(
|
||||
[string]$PgBin,
|
||||
[string]$PgData,
|
||||
[int]$Port,
|
||||
[int]$TimeoutSec = 120
|
||||
)
|
||||
|
||||
$postgres = Join-Path $PgBin 'postgres.exe'
|
||||
if (-not (Test-Path $postgres)) {
|
||||
Write-Host ' [ERROR] postgres.exe not found'
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Host ' Launching postgres...'
|
||||
[Console]::Out.Flush()
|
||||
|
||||
# pg_ctl stdout/stderr를 PowerShell 파이프·캡처하면 Windows에서 영구 블로킹될 수 있음 → postgres 직접 기동
|
||||
Start-Process -FilePath $postgres -ArgumentList @('-D', $PgData, '-p', "$Port") -WindowStyle Hidden | Out-Null
|
||||
|
||||
for ($i = 1; $i -le $TimeoutSec; $i++) {
|
||||
if (Test-DbPortOpen -Port $Port) {
|
||||
Write-Host " PostgreSQL ready (${i}s)"
|
||||
return $true
|
||||
}
|
||||
if ($i -eq 1 -or $i % 5 -eq 0) {
|
||||
Write-Host " ... waiting for port $Port ($i/${TimeoutSec}s)"
|
||||
[Console]::Out.Flush()
|
||||
}
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
if (-not $Root) { $Root = Get-ProjectRoot -FromScript $PSScriptRoot }
|
||||
|
||||
$pgBin = Find-PgBin
|
||||
if (-not $pgBin) {
|
||||
Write-Host '[ERROR] PostgreSQL not found. Install PostgreSQL 16 or Docker Desktop.'
|
||||
return 1
|
||||
}
|
||||
|
||||
$pgData = Get-PgDataDir -Root $Root
|
||||
New-Item -ItemType Directory -Force -Path $pgData | Out-Null
|
||||
|
||||
$initdb = Join-Path $pgBin 'initdb.exe'
|
||||
$pgCtl = Join-Path $pgBin 'pg_ctl.exe'
|
||||
$logFile = Get-PgCtlLogPath -Root $Root
|
||||
$hasData = Test-Path (Join-Path $pgData 'PG_VERSION')
|
||||
|
||||
if ($hasData) {
|
||||
Write-Host ' Existing DB found: data\postgres (keeping your data)'
|
||||
} else {
|
||||
Write-Host ' No DB yet - will create data\postgres'
|
||||
}
|
||||
|
||||
Ensure-BackendEnv -Root $Root
|
||||
Set-PostgresPort -PgData $pgData -Port $LocalDbPort
|
||||
Clear-StalePostmasterLock -PgData $pgData
|
||||
|
||||
if (Test-DbPortOpen -Port $LocalDbPort) {
|
||||
Write-Host " PostgreSQL already running on port $LocalDbPort"
|
||||
return 0
|
||||
}
|
||||
|
||||
# pid file only, process dead -> stop cleanly before restart
|
||||
if (Test-Path (Join-Path $pgData 'postmaster.pid')) {
|
||||
Write-Host ' Cleaning up previous postgres instance...'
|
||||
[Console]::Out.Flush()
|
||||
$stopArgs = @('-D', $pgData, 'stop', '-m', 'fast', '-W', '-t', '5')
|
||||
$null = Start-Process -FilePath $pgCtl -ArgumentList $stopArgs -WindowStyle Hidden -Wait
|
||||
Start-Sleep -Seconds 1
|
||||
Clear-StalePostmasterLock -PgData $pgData
|
||||
}
|
||||
|
||||
if (-not $hasData) {
|
||||
Write-Host ' Local DB init (data\postgres)...'
|
||||
$migrated = $false
|
||||
try {
|
||||
$migrated = & "$PSScriptRoot\migrate-legacy-db.ps1" -Root $Root -PgBin $pgBin -PgData $pgData -Port $LocalDbPort
|
||||
} catch {
|
||||
Write-Warning $_.Exception.Message
|
||||
if (Test-Path $pgData) {
|
||||
Get-ChildItem $pgData -Force -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $pgData | Out-Null
|
||||
}
|
||||
|
||||
if (-not $migrated) {
|
||||
& $initdb -D $pgData -U $LocalDbUser -E UTF8 --locale=C -A trust
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host '[ERROR] initdb failed'
|
||||
return 1
|
||||
}
|
||||
Set-PostgresPort -PgData $pgData -Port $LocalDbPort
|
||||
Clear-StalePostmasterLock -PgData $pgData
|
||||
|
||||
Write-Host ' Starting PostgreSQL (new database)...'
|
||||
if (-not (Start-PostgresWithWait -PgBin $pgBin -PgData $pgData -Port $LocalDbPort)) {
|
||||
Write-Host '[ERROR] PostgreSQL did not start — see data\pg_ctl.log'
|
||||
return 1
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-Psql -PgBin $pgBin -Port $LocalDbPort -Sql "ALTER USER $LocalDbUser WITH PASSWORD '$LocalDbPassword';"
|
||||
Invoke-Psql -PgBin $pgBin -Port $LocalDbPort -Sql "CREATE DATABASE $LocalDbName OWNER $LocalDbUser;"
|
||||
} catch {
|
||||
Write-Host "[ERROR] $($_.Exception.Message)"
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ' Starting PostgreSQL (recovery after move may take 1-2 min)...'
|
||||
if (-not (Start-PostgresWithWait -PgBin $pgBin -PgData $pgData -Port $LocalDbPort)) {
|
||||
Write-Host '[ERROR] PostgreSQL did not start — see data\pg_ctl.log'
|
||||
Write-Host ' 1) 서버종료.bat db 2) 5초 대기 3) 서버시작.bat'
|
||||
Write-Host ' PC 종료 전 서버종료.bat db 로 DB 정상 종료 권장'
|
||||
Write-Host ' OneDrive/백신에서 data\postgres 제외'
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
149
scripts/start-server.ps1
Normal file
149
scripts/start-server.ps1
Normal file
@@ -0,0 +1,149 @@
|
||||
$Root = Split-Path $PSScriptRoot -Parent
|
||||
Set-Location $Root
|
||||
|
||||
. "$PSScriptRoot\db-local.ps1"
|
||||
|
||||
Write-Host '================================'
|
||||
Write-Host ' EENE Dashboard - Start'
|
||||
Write-Host ' Data: data/postgres, uploads/'
|
||||
Write-Host '================================'
|
||||
Write-Host ''
|
||||
|
||||
foreach ($dir in @('data', 'data\postgres', 'data\seed', 'uploads\team')) {
|
||||
$p = Join-Path $Root $dir
|
||||
if (-not (Test-Path $p)) { New-Item -ItemType Directory -Force -Path $p | Out-Null }
|
||||
}
|
||||
|
||||
$envPath = Join-Path $Root 'backend\.env'
|
||||
$envExample = Join-Path $Root 'backend\.env.example'
|
||||
if (-not (Test-Path $envPath)) {
|
||||
Write-Host '[Setup] Creating backend\.env ...'
|
||||
Copy-Item $envExample $envPath
|
||||
Write-Host ' Done.'
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
Ensure-BackendEnv -Root $Root
|
||||
|
||||
Write-Host '[1/4] Stopping existing API/WEB ...'
|
||||
& "$PSScriptRoot\kill-ports.ps1" | Out-Null
|
||||
Start-Sleep -Seconds 1
|
||||
Write-Host ' Done.'
|
||||
Write-Host ''
|
||||
|
||||
Write-Host '[2/4] Starting database (data\postgres) ...'
|
||||
$dbMode = 'unknown'
|
||||
|
||||
if (Test-DockerReady) {
|
||||
if (Test-Path (Join-Path $Root 'docker-compose.yml')) {
|
||||
docker compose up -d 2>$null
|
||||
if ($LASTEXITCODE -eq 0) { $dbMode = 'docker' }
|
||||
}
|
||||
}
|
||||
|
||||
if ($dbMode -eq 'unknown') {
|
||||
$dbRc = & "$PSScriptRoot\start-local-db.ps1"
|
||||
if ($dbRc -eq 0) { $dbMode = 'local' }
|
||||
}
|
||||
|
||||
if ($dbMode -eq 'unknown') {
|
||||
Write-Host ''
|
||||
Write-Host ' [ERROR] Cannot start database.'
|
||||
Write-Host ' - Install PostgreSQL 16 (initdb/pg_ctl in PATH), or'
|
||||
Write-Host ' - Install Docker Desktop and retry'
|
||||
Write-Host ''
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " DB: $dbMode (port 54320, data\postgres)"
|
||||
|
||||
if ($dbMode -eq 'docker') {
|
||||
$waitRc = & "$PSScriptRoot\wait-db.ps1" -Port 54320
|
||||
if ($waitRc -ne 0) { exit 1 }
|
||||
}
|
||||
Write-Host ''
|
||||
|
||||
Write-Host '[3/4] DB schema + seed if empty ...'
|
||||
Set-Location (Join-Path $Root 'backend')
|
||||
if (-not (Test-Path 'node_modules')) {
|
||||
Write-Host ' npm install (backend) ...'
|
||||
cmd /c 'npm install'
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
}
|
||||
cmd /c 'npm run db:sync'
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host '[ERROR] npm run db:sync failed'
|
||||
exit 1
|
||||
}
|
||||
cmd /c 'npm run db:seed-if-empty'
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host '[ERROR] npm run db:seed-if-empty failed'
|
||||
exit 1
|
||||
}
|
||||
cmd /c 'npm run db:seed-team-if-empty'
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host '[ERROR] npm run db:seed-team-if-empty failed'
|
||||
exit 1
|
||||
}
|
||||
cmd /c 'npx prisma generate'
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host '[ERROR] prisma generate failed (stop other servers and retry)'
|
||||
exit 1
|
||||
}
|
||||
Set-Location $Root
|
||||
Write-Host ' Done.'
|
||||
Write-Host ''
|
||||
|
||||
Write-Host '[4/4] Starting API + WEB ...'
|
||||
if (-not (Test-Path (Join-Path $Root 'frontend\node_modules'))) {
|
||||
Write-Host ' npm install (frontend) ...'
|
||||
Set-Location (Join-Path $Root 'frontend')
|
||||
cmd /c 'npm install'
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
Set-Location $Root
|
||||
}
|
||||
|
||||
$lanIp = $null
|
||||
try {
|
||||
$ip = & "$PSScriptRoot\get-lan-ip.ps1" 2>$null
|
||||
if ($ip) { $lanIp = $ip.Trim() }
|
||||
} catch { }
|
||||
|
||||
if (-not $lanIp) {
|
||||
Write-Host ' [WARN] LAN IP not detected — HTTPS for team may not start.'
|
||||
Write-Host ' Set EENE_LAN_IP=172.16.8.248 if needed.'
|
||||
}
|
||||
|
||||
$backend = Join-Path $Root 'backend'
|
||||
$frontend = Join-Path $Root 'frontend'
|
||||
if ($lanIp) {
|
||||
Set-Content -Path (Join-Path $frontend '.lan-ip') -Value $lanIp -NoNewline -Encoding ascii
|
||||
} elseif (Test-Path (Join-Path $frontend '.lan-ip')) {
|
||||
Remove-Item (Join-Path $frontend '.lan-ip') -Force
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host ' Local : http://localhost:3000 (듀얼 모니터, 인증서 없음)'
|
||||
Write-Host ' Admin : http://localhost:3000/admin'
|
||||
if ($lanIp) {
|
||||
Write-Host " LAN : https://${lanIp}:3000 (인증서 경고 → 고급 → 계속 → 허용)"
|
||||
} else {
|
||||
Write-Host ' LAN : (IP 미감지 — EENE_LAN_IP 환경변수 설정 후 재시작)'
|
||||
}
|
||||
Write-Host ' Stop : 서버종료.bat'
|
||||
Write-Host '================================'
|
||||
Write-Host ''
|
||||
|
||||
$webCmds = @(
|
||||
"cd /d `"$backend`" && npm run dev:serve",
|
||||
"cd /d `"$frontend`" && npx --yes wait-on -t 90000 http://127.0.0.1:4000/health && npx vite --mode http-local --host localhost"
|
||||
)
|
||||
if ($lanIp) {
|
||||
$webCmds += "cd /d `"$frontend`" && npx --yes wait-on -t 90000 http://127.0.0.1:4000/health && npx vite --mode https-lan --host $lanIp"
|
||||
}
|
||||
$names = if ($lanIp) { 'API,WEB-HTTP,WEB-HTTPS' } else { 'API,WEB-HTTP' }
|
||||
$colors = if ($lanIp) { 'cyan,green,yellow' } else { 'cyan,green' }
|
||||
npx --yes concurrently -k -n $names -c $colors $webCmds
|
||||
|
||||
Write-Host ''
|
||||
Write-Host ' Server stopped.'
|
||||
15
scripts/stop-local-db.ps1
Normal file
15
scripts/stop-local-db.ps1
Normal file
@@ -0,0 +1,15 @@
|
||||
param([string]$Root = '')
|
||||
|
||||
. "$PSScriptRoot\db-local.ps1"
|
||||
|
||||
if (-not $Root) { $Root = Get-ProjectRoot -FromScript $PSScriptRoot }
|
||||
|
||||
$pgBin = Find-PgBin
|
||||
if (-not $pgBin) { exit 0 }
|
||||
|
||||
$pgData = Get-PgDataDir -Root $Root
|
||||
if (-not (Test-Path (Join-Path $pgData 'PG_VERSION'))) { exit 0 }
|
||||
|
||||
$pgCtl = Join-Path $pgBin 'pg_ctl.exe'
|
||||
& $pgCtl -D $pgData stop -m fast 2>$null
|
||||
exit 0
|
||||
26
scripts/stop-server.ps1
Normal file
26
scripts/stop-server.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
param([switch]$Db)
|
||||
|
||||
$Root = Split-Path $PSScriptRoot -Parent
|
||||
Set-Location $Root
|
||||
|
||||
Write-Host '================================'
|
||||
Write-Host ' EENE Dashboard - Stop'
|
||||
Write-Host '================================'
|
||||
Write-Host ''
|
||||
|
||||
Write-Host '[1/2] Stopping API/WEB ...'
|
||||
& "$PSScriptRoot\kill-ports.ps1"
|
||||
Write-Host ' Done.'
|
||||
Write-Host ''
|
||||
|
||||
Write-Host '[2/2] Database ...'
|
||||
if ($Db) {
|
||||
& "$PSScriptRoot\stop-local-db.ps1"
|
||||
docker compose down 2>$null
|
||||
Write-Host ' DB stopped (data in data\postgres kept)'
|
||||
} else {
|
||||
Write-Host ' DB kept running - full stop: 서버종료.bat db'
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host ' Done.'
|
||||
18
scripts/wait-api.ps1
Normal file
18
scripts/wait-api.ps1
Normal file
@@ -0,0 +1,18 @@
|
||||
param([int]$Port = 4000, [int]$MaxWaitSec = 90)
|
||||
|
||||
$url = "http://127.0.0.1:$Port/health"
|
||||
|
||||
for ($i = 1; $i -le $MaxWaitSec; $i++) {
|
||||
try {
|
||||
$resp = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
|
||||
if ($resp.StatusCode -eq 200) { return 0 }
|
||||
} catch { }
|
||||
|
||||
if ($i -eq 1) {
|
||||
Write-Host " Waiting for API on port $Port ..."
|
||||
}
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
Write-Host "[ERROR] API not ready on port $Port"
|
||||
return 1
|
||||
@@ -1,9 +1,11 @@
|
||||
$ok = $false
|
||||
1..30 | ForEach-Object {
|
||||
if (Test-NetConnection -ComputerName localhost -Port 5432 -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | Where-Object { $_.TcpTestSucceeded }) {
|
||||
$ok = $true
|
||||
break
|
||||
}
|
||||
param([int]$Port = 54320)
|
||||
|
||||
. "$PSScriptRoot\db-local.ps1"
|
||||
|
||||
for ($i = 1; $i -le 30; $i++) {
|
||||
if (Test-DbPortOpen -Port $Port) { return 0 }
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
if (-not $ok) { exit 1 }
|
||||
|
||||
Write-Host "[ERROR] Database port $Port is not ready."
|
||||
return 1
|
||||
|
||||
Reference in New Issue
Block a user