<# .SYNOPSIS 메가셀링 노드 통합 설치 (사용자 PC) — 웹에서 받아 실행하는 단일 설치 파일 .DESCRIPTION 이 한 파일만 실행하면 본인 PC가 영상 생성 노드가 된다: 1. 저장소 다운로드(또는 업데이트) 2. 백엔드 의존성 설치 (py3.12 venv — faster-whisper GPU 호환) 3. 로컬 AI 설치: Ollama(+모델), ComfyUI(+SDXL, GPU), faster-whisper 4. MeloTTS(로컬 한국어 TTS, 전용 py3.11 venv) — 파이프라인 음성 엔진(폴백 없음) 5. .env 자동 설정(중앙 서버 주소 포함) + 진단 설치 후엔 웹 대시보드에서 딸깍 → 본인 GPU로 영상 생성(₩0). .USAGE 웹에서 받은 뒤 PowerShell에서: .\install_all.ps1 -CentralUrl "https://central.megaselling.app" -Token "<발급 JWT>" 옵션: -SkipSovits (음성 로컬설치 생략, OpenAI 폴백), -InstallDir <경로> #> param( [string]$CentralUrl = "http://localhost:8000", [string]$Token = "", [string]$RepoUrl = "https://github.com/lhs0609a-cpu/global-viral-factory.git", [string]$InstallDir = "$env:USERPROFILE\MegaSelling", [switch]$SkipSovits ) $ErrorActionPreference = "Continue" function Step($m){ Write-Host "`n=== $m ===" -ForegroundColor Cyan } function Ok($m){ Write-Host " [OK] $m" -ForegroundColor Green } function Warn($m){ Write-Host " [!] $m" -ForegroundColor Yellow } function Has($n){ $null -ne (Get-Command $n -ErrorAction SilentlyContinue) } Write-Host "════════════════════════════════════════════════" -ForegroundColor Magenta Write-Host " 메가셀링 노드 설치 — 내 PC를 영상 공장으로" -ForegroundColor Magenta Write-Host "════════════════════════════════════════════════" -ForegroundColor Magenta # ── 0. 준비물 자동 설치 (winget으로 Git · Python 원스톱) ── Step "0/6 준비물 자동 설치 (Git · Python)" function Refresh-Path { $env:Path = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Environment]::GetEnvironmentVariable("Path","User") } $Winget = Has winget if (-not $Winget) { Warn "winget 미지원 OS — 준비물 자동설치 불가(수동 설치 필요)" } # Git 자동 설치 if (-not (Has git)) { if ($Winget) { Warn "Git 없음 → 자동 설치 중..."; winget install --id Git.Git -e --silent --accept-package-agreements --accept-source-agreements | Out-Null; Refresh-Path } } if (-not (Has git)) { Warn "Git 자동설치 실패 → https://git-scm.com 에서 설치 후 재실행"; exit 1 } Ok "git" if (-not (Has nvidia-smi)) { Warn "NVIDIA GPU 미감지 — 무GPU는 매우 느림(CPU). 계속은 가능." } # CUDA 가능 Python(3.11~3.13) 찾기 — 없으면 3.12 자동 설치 (3.14는 CUDA 미지원) function Find-BasePy { foreach($v in @('3.12','3.13','3.11')){ try{ $p=(& py "-$v" -c "import sys;print(sys.executable)" 2>$null); if($LASTEXITCODE -eq 0 -and $p){return $p.Trim()} }catch{} }; return $null } $BasePy = Find-BasePy if (-not $BasePy) { if ($Winget) { Warn "Python 3.12 없음 → 자동 설치 중..."; winget install --id Python.Python.3.12 -e --silent --accept-package-agreements --accept-source-agreements | Out-Null; Refresh-Path; $BasePy = Find-BasePy } } if (-not $BasePy) { Warn "Python 3.11~3.13 필요(3.14는 CUDA 미지원) → python.org에서 3.12 설치 후 재실행"; exit 1 } Ok "Python: $BasePy" # ── 0-2. 기존 실행중인 도우미 종료 (git pull·파일잠금·중복 실행 방지) ── Step "기존 메가셀링 도우미 종료 (실행 중이면)" try { Get-CimInstance Win32_Process -Filter "Name='pythonw.exe' OR Name='python.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*run_node*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } Stop-ScheduledTask -TaskName GVF-Node -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 800 Ok "기존 도우미 종료 (있었다면)" } catch { Warn "기존 도우미 종료 시도(무시): $_" } # ── 1. 저장소 ── Step "1/5 코드 다운로드" # ★ 관리자 설치 → repo 소유자=Administrators, 노드는 일반 사용자로 실행 → # git "dubious ownership"으로 자동 업데이트(git pull)가 조용히 막힌다. # 설치 폴더를 system 범위 safe.directory에 등록해 어느 사용자로 돌든 git이 동작하게. try { git config --system --add safe.directory $InstallDir 2>$null git config --system --add safe.directory ($InstallDir -replace '\\','/') 2>$null Ok "git safe.directory 등록 (자동 업데이트 보장)" } catch { Warn "safe.directory 등록 실패(무시): $_" } if (Test-Path (Join-Path $InstallDir ".git")) { Push-Location $InstallDir; git pull --ff-only; Pop-Location; Ok "업데이트" } else { git clone --depth 1 $RepoUrl $InstallDir; if(-not(Test-Path "$InstallDir\.git")){Warn "클론 실패";exit 1}; Ok "다운로드 완료" } $Backend = Join-Path $InstallDir "backend" # ── 2. 백엔드 base 의존성 (가벼움 — 노드가 이걸로 바로 온라인) ── Step "2/6 백엔드 base 의존성 (py3.12 venv)" $VPy = Join-Path $Backend ".venv\Scripts\python.exe" if (-not (Test-Path $VPy)) { & $BasePy -m venv (Join-Path $Backend ".venv") } & $VPy -m pip install -q --upgrade pip & $VPy -m pip install -q -r (Join-Path $Backend "requirements.txt") Ok "base 의존성 완료 (노드 실행에 필요한 것)" # ── 3. 설정(.env) ── Step "3/6 설정 (.env 중앙 연결)" $envf = Join-Path $Backend ".env" # ★ BOM 없는 UTF-8로 저장 — Set-Content -Encoding utf8(PS5.1)은 BOM을 붙여 .env 첫 키가 # 'CENTRAL_URL'로 깨지고 dotenv가 못 읽어 노드가 localhost로 붙어 영영 오프라인이 됨. function SetEnv($k,$v){ $lines=if(Test-Path $envf){Get-Content $envf}else{@()}; $f=$false; $o=foreach($l in $lines){if($l -match "^\s*$([regex]::Escape($k))\s*="){$f=$true;"$k=$v"}else{$l}}; if(-not $f){$o=@($o)+"$k=$v"}; [System.IO.File]::WriteAllLines($envf, [string[]]$o, (New-Object System.Text.UTF8Encoding($false))) } SetEnv "CENTRAL_URL" $CentralUrl SetEnv "DISABLE_SCHEDULER" "1" if ($Token) { SetEnv "NODE_JWT" $Token } Ok "설정 완료" # ★ UTF-8 사용자 환경변수 — 스케줄러/레지스트리 Run 자동시작이 이 값을 상속해 # 한글(cp949) 콘솔에서 이모지/한글 print가 UnicodeEncodeError로 노드를 죽이는 걸 원천 차단. # (run_node.py도 PYTHONUTF8 재실행 가드가 있어 이중 방어) try { [Environment]::SetEnvironmentVariable('PYTHONUTF8', '1', 'User') $env:PYTHONUTF8 = '1' Ok "PYTHONUTF8=1 (한글 콘솔 크래시 방지)" } catch { Warn "PYTHONUTF8 설정 실패(무시): $_" } # ── 4. ★ 노드 등록·실행 (여기서 온라인!) — 무거운 AI보다 먼저 ★ ── # 핵심: 노드를 먼저 켜서 웹에 🟢 온라인으로 띄운다. AI(Ollama·ComfyUI)는 뒤에서. # (예전엔 AI 다 깐 뒤에야 노드가 켜져, 중간 실패 시 노드가 영영 안 떴음) Step "4/6 노드 등록·실행 (지금 온라인으로)" $Pyw = Join-Path $Backend ".venv\Scripts\pythonw.exe" # 창 없는 백그라운드 실행 $NodePy = Join-Path $Backend "run_node.py" if (-not (Test-Path $Pyw)) { $Pyw = Join-Path $Backend ".venv\Scripts\python.exe" } # 자동실행 등록 헬퍼: 부팅 자동시작을 '이중'으로 건다 — 어느 하나가 실패해도 켜지게. # (A) 작업 스케줄러: 로그온 시 시작 + 크래시 자동 재시작 # (B) HKCU\...\Run 레지스트리: 권한·스케줄러 비활성 환경에서도 백그라운드 자동시작 # 노드는 named-mutex 싱글톤이라 둘 다 떠도 실제 인스턴스는 1개만 산다(중복 안전). function Register-Autostart($name, $exe, $argScript, $workdir) { # ── (A) 작업 스케줄러: 로그온 자동시작 + 크래시 재시작 ── try { $trigger = New-ScheduledTaskTrigger -AtLogOn $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) $act = New-ScheduledTaskAction -Execute $exe -Argument "`"$argScript`"" -WorkingDirectory $workdir # ★ 현재 로그인 사용자로 명시 — 관리자 권한으로 설치돼도 '내 로그온'에 확실히 뜨게. # (Principal 생략 시 UAC 승격 계정에 묶여 일반 로그온에 안 뜨는 일이 있음) $whoami = "$env:USERDOMAIN\$env:USERNAME" try { $principal = New-ScheduledTaskPrincipal -UserId $whoami -LogonType Interactive -RunLevel Highest Register-ScheduledTask -TaskName $name -Action $act -Trigger $trigger -Settings $settings -Principal $principal -Force -ErrorAction Stop | Out-Null } catch { # 일부 환경에서 Principal 등록이 까다로움 → Principal 없이 재시도 Register-ScheduledTask -TaskName $name -Action $act -Trigger $trigger -Settings $settings -Force -ErrorAction Stop | Out-Null } Ok "$name 부팅 자동시작 등록 (작업 스케줄러)" } catch { Warn "$name 작업 스케줄러 등록 실패(레지스트리 Run으로 대체): $_" } # ── (B) HKCU\...\Run: 권한 불필요·세션 안전한 백그라운드 자동시작 ── # pythonw.exe(창 없음)로 바로 실행 — Run 키는 작업디렉터리를 안 주지만 # run_node.py가 __file__ 기준으로 chdir 하므로 문제없음. try { $runKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" if (-not (Test-Path $runKey)) { New-Item -Path $runKey -Force | Out-Null } Set-ItemProperty -Path $runKey -Name $name -Value "`"$exe`" `"$argScript`"" -Force Ok "$name 부팅 자동시작 등록 (레지스트리 Run)" } catch { Warn "$name 레지스트리 Run 등록 실패: $_" } # ── 지금 즉시 실행 — 등록 방식과 무관하게 항상 (설치 후 자동실행 보장) ── try { Start-Process $exe -ArgumentList "`"$argScript`"" -WorkingDirectory $workdir -WindowStyle Hidden Ok "$name 지금 실행됨 (백그라운드)" } catch { Warn "$name 즉시 실행 실패: $_" } } Register-Autostart "GVF-Node" $Pyw $NodePy $Backend Ok "노드 실행됨 — 1~2분 안에 웹 '내 노드 상태'에 🟢 온라인으로 뜹니다" # ── 5. 무거운 로컬 AI (뒤에서 best-effort — 실패해도 노드는 이미 온라인) ── Step "5/6 로컬 AI 설치 (Ollama·ComfyUI·whisper·MeloTTS — 수 GB, 시간 걸림)" Write-Host " (이 단계가 오래 걸리거나 일부 실패해도 노드는 이미 켜져 있어요. 창은 그냥 두세요.)" -ForegroundColor DarkGray try { & $VPy -m pip install -q -r (Join-Path $Backend "requirements-local.txt") } catch { Warn "로컬 의존성 일부 실패(무시): $_" } try { & (Join-Path $Backend "scripts\setup_local_ai.ps1") } catch { Warn "Ollama 설치 일부 실패(무시): $_" } try { & (Join-Path $Backend "scripts\setup_comfyui.ps1") } catch { Warn "ComfyUI 설치 일부 실패(무시): $_" } # ★ MeloTTS (로컬 한국어 TTS) — 파이프라인 음성의 유일한 엔진(폴백 없음)이므로 꼭 깐다. # 전용 Python 3.11 venv + eunjeon(한국어 MeCab) C++ 빌드까지. -InstallBuildTools 로 # MSVC Build Tools 자동 설치(수 GB) → 빌드 실패로 '음성 안 나옴'을 방지. try { & (Join-Path $Backend "scripts\setup_melotts.ps1") -InstallBuildTools Ok "MeloTTS 로컬 한국어 TTS 준비" } catch { Warn "MeloTTS 설치 일부 실패(무시 — 추후 도우미 재시작 시 재시도): $_" } # (레거시) GPT-SoVITS 음성 클로닝 — 현재 파이프라인 미사용. Docker 있으면만 유지. if (-not $SkipSovits -and (Has docker)) { try { & (Join-Path $Backend "scripts\setup_sovits.ps1") } catch { Warn "SoVITS 일부 실패(무시): $_" } } # ── 6. ComfyUI 자동시작 등록 + 마무리 ── Step "6/6 마무리" $ComfyDir = "$env:USERPROFILE\GVF-ComfyUI" $ComfyPyw = Join-Path $ComfyDir ".venv\Scripts\pythonw.exe" if (Test-Path $ComfyPyw) { Register-Autostart "GVF-ComfyUI" $ComfyPyw "$ComfyDir\main.py" $ComfyDir } # ── 실행 검증: 노드 프로세스 생존 + '실제 등록 성공(creds 저장)'까지 확인 ── # process가 떠도 토큰 만료/네트워크로 등록에 실패하면 오프라인이다. 등록 성공의 결정적 # 증거는 node_credentials.json 생성 → 이걸 최대 45초 기다려 조용한 실패를 잡아낸다. $Cred = Join-Path $Backend "data\node_credentials.json" function Node-Alive { Get-CimInstance Win32_Process -Filter "Name='pythonw.exe' OR Name='python.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*run_node*' } } Start-Sleep -Seconds 2 $alive = Node-Alive if (-not $alive) { Warn "노드가 아직 안 떠 1회 더 시동합니다…" try { Start-Process $Pyw -ArgumentList "`"$NodePy`"" -WorkingDirectory $Backend -WindowStyle Hidden } catch {} Start-Sleep -Seconds 2 $alive = Node-Alive } # 등록 성공(creds) 대기 — 최대 45초 $registered = $false $deadline = (Get-Date).AddSeconds(45) while ((Get-Date) -lt $deadline) { if (Test-Path $Cred) { $registered = $true; break } if (-not (Node-Alive)) { break } # 프로세스가 죽었으면 더 기다릴 필요 없음 Start-Sleep -Seconds 2 } $alive = Node-Alive Write-Host "`n════════════════════════════════════════════════" -ForegroundColor Magenta if ($registered) { Write-Host " 설치 완료! 내 PC가 영상 노드로 등록됐습니다. ✓" -ForegroundColor Magenta Write-Host " ✓ 지금 온라인 — 웹 '내 PC 연결'에 🟢 로 뜹니다 (PID $(($alive.ProcessId) -join ', '))" -ForegroundColor Green Write-Host " ✓ 등록 정보 저장됨 → 이후엔 토큰 없이도 자동으로 켜집니다" -ForegroundColor Green } else { # ★ 조용한 실패를 명시적으로 노출 — 대개 토큰 만료(7일)/오타/네트워크 Write-Host " ! 노드는 실행됐지만 '등록 확인'에 실패했습니다." -ForegroundColor Yellow Write-Host " 원인 대부분: 토큰 만료(발급 후 7일) 또는 붙여넣기 오류/네트워크." -ForegroundColor Yellow Write-Host " → 대시보드 '내 PC 연결'에서 [토큰 복사]를 '다시' 눌러 최신 토큰으로 재설치하세요." -ForegroundColor Yellow if ($alive) { Write-Host " (프로세스는 백그라운드에서 재시도 중 — 토큰이 유효하면 곧 온라인)" -ForegroundColor DarkGray } } Write-Host " ✓ 컴퓨터 켤 때마다 자동 시작 (작업 'GVF-Node' + 레지스트리 Run)" -ForegroundColor Green Write-Host " ✓ 새 버전 나오면 자동 업데이트 (git pull → 재시작)" -ForegroundColor Green Write-Host " → 웹 대시보드 '내 PC 연결'에 1~2분 안에 🟢 온라인으로 뜹니다" -ForegroundColor Gray Write-Host " (끄기: 작업 스케줄러 GVF-Node 비활성화 + HKCU\...\Run 의 GVF-Node 삭제)" -ForegroundColor DarkGray Write-Host "════════════════════════════════════════════════" -ForegroundColor Magenta