#Requires -RunAsAdministrator <# .SYNOPSIS Deploy / update GoMag Import Manager pe Windows Server cu IIS. .DESCRIPTION - Prima rulare: clone repo, setup venv, genereaza start.bat, configureaza IIS - Rulari ulterioare: git pull, reinstaleaza deps, restarteaza serviciul .PARAMETER RepoPath Calea locala unde se cloneaza repo-ul. Default: C:\gomag-vending .PARAMETER Port Portul pe care ruleaza FastAPI. Default: 5003 .PARAMETER IisSiteName Numele site-ului IIS parinte. Default: "Default Web Site" .PARAMETER SkipIIS Sarit configurarea IIS (util daca nu ai ARR/URLRewrite instalate inca) .EXAMPLE .\deploy.ps1 .\deploy.ps1 -RepoPath "D:\apps\gomag-vending" -Port 5003 .\deploy.ps1 -SkipIIS #> param( [string]$RepoPath = "C:\gomag-vending", [int] $Port = 5003, [string]$IisSiteName = "Default Web Site", [switch]$SkipIIS ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" # ───────────────────────────────────────────────────────────────────────────── # Helpers # ───────────────────────────────────────────────────────────────────────────── function Write-Step { param([string]$msg) Write-Host "`n==> $msg" -ForegroundColor Cyan } function Write-OK { param([string]$msg) Write-Host " [OK] $msg" -ForegroundColor Green } function Write-Warn { param([string]$msg) Write-Host " [WARN] $msg" -ForegroundColor Yellow } function Write-Fail { param([string]$msg) Write-Host " [FAIL] $msg" -ForegroundColor Red } function Write-Info { param([string]$msg) Write-Host " $msg" -ForegroundColor Gray } $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition # ───────────────────────────────────────────────────────────────────────────── # 1. Citire token Gitea # ───────────────────────────────────────────────────────────────────────────── Write-Step "Citire token Gitea" $TokenFile = Join-Path $ScriptDir ".gittoken" $GitToken = "" if (Test-Path $TokenFile) { $GitToken = (Get-Content $TokenFile -Raw).Trim() Write-OK "Token citit din $TokenFile" } else { Write-Warn ".gittoken nu exista langa deploy.ps1" Write-Info "Creeaza fisierul $TokenFile cu token-ul tau Gitea (fara newline)" Write-Info "Ex: echo -n 'ghp_xxxx' > .gittoken" Write-Info "" Write-Info "Continui fara token (merge doar daca repo-ul e public sau deja clonat)" } $RepoUrl = if ($GitToken) { "https://$GitToken@gitea.romfast.ro/romfast/gomag-vending.git" } else { "https://gitea.romfast.ro/romfast/gomag-vending.git" } # ───────────────────────────────────────────────────────────────────────────── # 2. Git clone / pull # ───────────────────────────────────────────────────────────────────────────── Write-Step "Git clone / pull" # Verifica git instalat if (-not (Get-Command git -ErrorAction SilentlyContinue)) { Write-Fail "Git nu este instalat!" Write-Info "Descarca Git for Windows de la: https://git-scm.com/download/win" exit 1 } if (Test-Path (Join-Path $RepoPath ".git")) { Write-Info "Repo exista, fac git pull..." Push-Location $RepoPath try { # Update remote URL cu tokenul curent (in caz ca s-a schimbat) if ($GitToken) { git remote set-url origin $RepoUrl 2>$null } git pull --ff-only Write-OK "git pull OK" } finally { Pop-Location } } else { Write-Info "Clonez in $RepoPath ..." $ParentDir = Split-Path -Parent $RepoPath if (-not (Test-Path $ParentDir)) { New-Item -ItemType Directory -Path $ParentDir -Force | Out-Null } git clone $RepoUrl $RepoPath Write-OK "git clone OK" } # ───────────────────────────────────────────────────────────────────────────── # 3. Verificare Python # ───────────────────────────────────────────────────────────────────────────── Write-Step "Verificare Python" $PythonCmd = $null foreach ($candidate in @("python", "python3", "py")) { try { $ver = & $candidate --version 2>&1 if ($ver -match "Python 3\.(\d+)") { $minor = [int]$Matches[1] if ($minor -ge 11) { $PythonCmd = $candidate Write-OK "Python gasit: $ver ($candidate)" break } else { Write-Warn "Python $ver prea vechi (necesar 3.11+)" } } } catch { } } if (-not $PythonCmd) { Write-Fail "Python 3.11+ nu este instalat sau nu e in PATH!" Write-Info "Descarca de la: https://www.python.org/downloads/" Write-Info "IMPORTANT: Bifeaza 'Add Python to PATH' la instalare" exit 1 } # ───────────────────────────────────────────────────────────────────────────── # 4. Creare venv si instalare dependinte # ───────────────────────────────────────────────────────────────────────────── Write-Step "Virtual environment + dependinte" $VenvDir = Join-Path $RepoPath "venv" $VenvPip = Join-Path $VenvDir "Scripts\pip.exe" $VenvPy = Join-Path $VenvDir "Scripts\python.exe" $ReqFile = Join-Path $RepoPath "api\requirements.txt" $DepsFlag = Join-Path $VenvDir ".deps_installed" if (-not (Test-Path $VenvDir)) { Write-Info "Creez venv..." & $PythonCmd -m venv $VenvDir Write-OK "venv creat" } # Reinstaleaza daca requirements.txt e mai nou decat flag-ul $needInstall = $true if (Test-Path $DepsFlag) { $reqTime = (Get-Item $ReqFile).LastWriteTime $flagTime = (Get-Item $DepsFlag).LastWriteTime if ($flagTime -ge $reqTime) { $needInstall = $false } } if ($needInstall) { Write-Info "Instalez dependinte din requirements.txt..." & $VenvPip install --upgrade pip --quiet & $VenvPip install -r $ReqFile New-Item -ItemType File -Path $DepsFlag -Force | Out-Null Write-OK "Dependinte instalate" } else { Write-OK "Dependinte deja up-to-date" } # ───────────────────────────────────────────────────────────────────────────── # 5. Detectare Oracle Home → sugestie INSTANTCLIENTPATH # ───────────────────────────────────────────────────────────────────────────── Write-Step "Detectare Oracle" $OracleHome = $env:ORACLE_HOME $OracleBinPath = "" if ($OracleHome -and (Test-Path $OracleHome)) { $OracleBinPath = Join-Path $OracleHome "bin" Write-OK "ORACLE_HOME detectat: $OracleHome" Write-Info "Seteaza in api\.env: INSTANTCLIENTPATH=$OracleBinPath" } else { # Cauta Oracle in locatii comune $commonPaths = @( "C:\oracle\product\19c\dbhome_1\bin", "C:\oracle\product\21c\dbhome_1\bin", "C:\app\oracle\product\19.0.0\dbhome_1\bin", "C:\oracle\instantclient_19_15", "C:\oracle\instantclient_21_3" ) foreach ($p in $commonPaths) { if (Test-Path "$p\oci.dll") { $OracleBinPath = $p Write-OK "Oracle gasit la: $p" Write-Info "Seteaza in api\.env: INSTANTCLIENTPATH=$p" break } } if (-not $OracleBinPath) { Write-Warn "Oracle Instant Client nu a fost gasit automat" Write-Info "Optiuni:" Write-Info " 1. Thick mode: seteaza INSTANTCLIENTPATH= in api\.env" Write-Info " 2. Thin mode: seteaza FORCE_THIN_MODE=true in api\.env" } } # ───────────────────────────────────────────────────────────────────────────── # 6. Creare .env din template daca lipseste # ───────────────────────────────────────────────────────────────────────────── Write-Step "Fisier configurare api\.env" $EnvFile = Join-Path $RepoPath "api\.env" $EnvExample = Join-Path $RepoPath "api\.env.example" if (-not (Test-Path $EnvFile)) { if (Test-Path $EnvExample) { Copy-Item $EnvExample $EnvFile Write-OK "api\.env creat din .env.example" # Actualizeaza TNS_ADMIN cu calea reala $ApiDir = Join-Path $RepoPath "api" (Get-Content $EnvFile) -replace "TNS_ADMIN=.*", "TNS_ADMIN=$ApiDir" | Set-Content $EnvFile # Seteaza INSTANTCLIENTPATH daca am gasit Oracle if ($OracleBinPath) { (Get-Content $EnvFile) -replace "INSTANTCLIENTPATH=.*", "INSTANTCLIENTPATH=$OracleBinPath" | Set-Content $EnvFile } Write-Warn "IMPORTANT: Editeaza $EnvFile cu credentialele Oracle si GoMag API!" Write-Info " ORACLE_USER, ORACLE_PASSWORD, ORACLE_DSN" Write-Info " GOMAG_API_KEY, GOMAG_API_SHOP" } else { Write-Warn ".env.example nu exista, sari pasul" } } else { Write-OK "api\.env exista deja" } # ───────────────────────────────────────────────────────────────────────────── # 7. Creare directoare necesare # ───────────────────────────────────────────────────────────────────────────── Write-Step "Directoare date" foreach ($dir in @("data", "output", "logs")) { $fullPath = Join-Path $RepoPath $dir if (-not (Test-Path $fullPath)) { New-Item -ItemType Directory -Path $fullPath -Force | Out-Null Write-OK "Creat: $dir\" } else { Write-OK "Exista: $dir\" } } # ───────────────────────────────────────────────────────────────────────────── # 8. Generare start.bat # ───────────────────────────────────────────────────────────────────────────── Write-Step "Generare start.bat" $StartBat = Join-Path $RepoPath "start.bat" # Citeste TNS_ADMIN si INSTANTCLIENTPATH din .env daca exista $TnsAdmin = Join-Path $RepoPath "api" $InstantClient = "" if (Test-Path $EnvFile) { Get-Content $EnvFile | ForEach-Object { if ($_ -match "^TNS_ADMIN=(.+)") { $TnsAdmin = $Matches[1].Trim() } if ($_ -match "^INSTANTCLIENTPATH=(.+)" -and $_ -notmatch "^#") { $InstantClient = $Matches[1].Trim() } } } $OraclePathLine = "" if ($InstantClient) { $OraclePathLine = "set PATH=$InstantClient;%PATH%" } $StartBatContent = @" @echo off :: GoMag Import Manager - Windows Launcher :: Generat de deploy.ps1 - nu edita manual, ruleaza deploy.ps1 din nou cd /d "$RepoPath" set TNS_ADMIN=$TnsAdmin $OraclePathLine echo Starting GoMag Import Manager on http://0.0.0.0:$Port (prefix /gomag) "$VenvPy" -m uvicorn app.main:app --host 0.0.0.0 --port $Port --root-path /gomag --app-dir api "@ Set-Content -Path $StartBat -Value $StartBatContent -Encoding UTF8 Write-OK "start.bat generat: $StartBat" # ───────────────────────────────────────────────────────────────────────────── # 9. IIS — Verificare ARR + URL Rewrite # ───────────────────────────────────────────────────────────────────────────── Write-Step "Verificare module IIS" if ($SkipIIS) { Write-Warn "SkipIIS activ — configurare IIS sarita" } else { $ArrPath = "$env:SystemRoot\System32\inetsrv\arr.dll" $UrlRewritePath = "$env:SystemRoot\System32\inetsrv\rewrite.dll" $ArrOk = Test-Path $ArrPath $UrlRwOk = Test-Path $UrlRewritePath if ($ArrOk) { Write-OK "Application Request Routing (ARR) instalat" } else { Write-Warn "ARR 3.0 NU este instalat" Write-Info "Descarca: https://www.iis.net/downloads/microsoft/application-request-routing" Write-Info "Sau: winget install Microsoft.ARR" } if ($UrlRwOk) { Write-OK "URL Rewrite 2.1 instalat" } else { Write-Warn "URL Rewrite 2.1 NU este instalat" Write-Info "Descarca: https://www.iis.net/downloads/microsoft/url-rewrite" Write-Info "Sau: winget install Microsoft.URLRewrite" } # ───────────────────────────────────────────────────────────────────────── # 10. Configurare IIS — copiere web.config # ───────────────────────────────────────────────────────────────────────── if ($ArrOk -and $UrlRwOk) { Write-Step "Configurare IIS reverse proxy" # Activeaza proxy in ARR (necesar o singura data) try { Import-Module WebAdministration -ErrorAction SilentlyContinue $proxyEnabled = (Get-WebConfigurationProperty ` -pspath "MACHINE/WEBROOT/APPHOST" ` -filter "system.webServer/proxy" ` -name "enabled" ` -ErrorAction SilentlyContinue).Value if (-not $proxyEnabled) { Set-WebConfigurationProperty ` -pspath "MACHINE/WEBROOT/APPHOST" ` -filter "system.webServer/proxy" ` -name "enabled" ` -value $true Write-OK "ARR proxy activat global" } else { Write-OK "ARR proxy deja activ" } } catch { Write-Warn "Nu am putut activa ARR proxy automat: $($_.Exception.Message)" Write-Info "Activeaza manual din IIS Manager → server root → Application Request Routing Cache → Enable Proxy" } # Determina wwwroot site-ului IIS $IisRootPath = $null try { Import-Module WebAdministration -ErrorAction SilentlyContinue $site = Get-Website -Name $IisSiteName -ErrorAction SilentlyContinue if ($site) { $IisRootPath = [System.Environment]::ExpandEnvironmentVariables($site.PhysicalPath) Write-OK "Site IIS '$IisSiteName' gasit: $IisRootPath" } else { Write-Warn "Site IIS '$IisSiteName' nu a fost gasit" } } catch { # Fallback la locatia standard $IisRootPath = "$env:SystemDrive\inetpub\wwwroot" Write-Warn "WebAdministration unavailable, folosesc fallback: $IisRootPath" } if ($IisRootPath) { $SourceWebConfig = Join-Path $RepoPath "iis-web.config" $DestWebConfig = Join-Path $IisRootPath "web.config" if (Test-Path $SourceWebConfig) { # Inlocuieste portul in web.config cu cel configurat $wcContent = Get-Content $SourceWebConfig -Raw $wcContent = $wcContent -replace "localhost:5003", "localhost:$Port" if (Test-Path $DestWebConfig) { # Backup web.config existent $backup = "$DestWebConfig.bak_$(Get-Date -Format 'yyyyMMdd_HHmmss')" Copy-Item $DestWebConfig $backup Write-Info "Backup web.config: $backup" } Set-Content -Path $DestWebConfig -Value $wcContent -Encoding UTF8 Write-OK "web.config copiat in $IisRootPath" } else { Write-Warn "iis-web.config nu exista in repo, sarit" } # Restart IIS try { iisreset /noforce 2>&1 | Out-Null Write-OK "IIS restartat" } catch { Write-Warn "IIS restart esuat: $($_.Exception.Message)" Write-Info "Ruleaza manual: iisreset" } } } else { Write-Warn "IIS nu e configurat complet — instaleaza ARR si URL Rewrite, apoi ruleaza deploy.ps1 din nou" } } # ───────────────────────────────────────────────────────────────────────────── # 11. Serviciu Windows (NSSM sau Task Scheduler) # ───────────────────────────────────────────────────────────────────────────── Write-Step "Serviciu Windows" $ServiceName = "GoMagVending" $NssmExe = "" # Cauta NSSM foreach ($p in @("nssm", "C:\nssm\win64\nssm.exe", "C:\tools\nssm\nssm.exe")) { if (Get-Command $p -ErrorAction SilentlyContinue) { $NssmExe = $p break } } if ($NssmExe) { Write-Info "NSSM gasit: $NssmExe" $existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if ($existingService) { Write-Info "Serviciu existent, restarteaza..." & $NssmExe restart $ServiceName Write-OK "Serviciu $ServiceName restartat" } else { Write-Info "Instalez serviciu $ServiceName cu NSSM..." & $NssmExe install $ServiceName (Join-Path $RepoPath "start.bat") & $NssmExe set $ServiceName AppDirectory $RepoPath & $NssmExe set $ServiceName DisplayName "GoMag Vending Import Manager" & $NssmExe set $ServiceName Description "Import comenzi web GoMag -> ROA Oracle" & $NssmExe set $ServiceName Start SERVICE_AUTO_START & $NssmExe set $ServiceName AppStdout (Join-Path $RepoPath "logs\service_stdout.log") & $NssmExe set $ServiceName AppStderr (Join-Path $RepoPath "logs\service_stderr.log") & $NssmExe set $ServiceName AppRotateFiles 1 & $NssmExe set $ServiceName AppRotateOnline 1 & $NssmExe set $ServiceName AppRotateBytes 10485760 & $NssmExe start $ServiceName Write-OK "Serviciu $ServiceName instalat si pornit" } } else { # Fallback: Task Scheduler Write-Warn "NSSM nu este instalat" Write-Info "Optiuni:" Write-Info " 1. Descarca NSSM: https://nssm.cc/download si pune nssm.exe in PATH" Write-Info " 2. Sau foloseste Task Scheduler (creat mai jos)" # Verifica daca task-ul exista deja $taskExists = Get-ScheduledTask -TaskName $ServiceName -ErrorAction SilentlyContinue if (-not $taskExists) { Write-Info "Creez Task Scheduler task '$ServiceName'..." try { $action = New-ScheduledTaskAction -Execute (Join-Path $RepoPath "start.bat") $trigger = New-ScheduledTaskTrigger -AtStartup $settings = New-ScheduledTaskSettingsSet ` -ExecutionTimeLimit (New-TimeSpan -Days 365) ` -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 1) $principal = New-ScheduledTaskPrincipal ` -UserId "SYSTEM" ` -LogonType ServiceAccount ` -RunLevel Highest Register-ScheduledTask ` -TaskName $ServiceName ` -Action $action ` -Trigger $trigger ` -Settings $settings ` -Principal $principal ` -Description "GoMag Vending Import Manager" ` -Force | Out-Null Start-ScheduledTask -TaskName $ServiceName Write-OK "Task Scheduler '$ServiceName' creat si pornit" } catch { Write-Warn "Task Scheduler esuat: $($_.Exception.Message)" Write-Info "Porneste manual: .\start.bat" } } else { # Restart task Stop-ScheduledTask -TaskName $ServiceName -ErrorAction SilentlyContinue Start-ScheduledTask -TaskName $ServiceName Write-OK "Task '$ServiceName' restartat" } } # ───────────────────────────────────────────────────────────────────────────── # Sumar final # ───────────────────────────────────────────────────────────────────────────── Write-Host "" Write-Host "══════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host " GoMag Vending Deploy — Sumar" -ForegroundColor Cyan Write-Host "══════════════════════════════════════════════════════" -ForegroundColor Cyan Write-Host "" Write-Host " Repo: $RepoPath" -ForegroundColor White Write-Host " FastAPI: http://localhost:$Port/gomag" -ForegroundColor White Write-Host " start.bat generat" -ForegroundColor White Write-Host "" if (-not (Test-Path $EnvFile)) { Write-Host " [!] api\.env lipseste — configureaza inainte de start!" -ForegroundColor Red } else { Write-Host " api\.env: OK" -ForegroundColor Green # Verifica daca mai are valori placeholder $envContent = Get-Content $EnvFile -Raw if ($envContent -match "your_api_key_here|USER_ORACLE|parola_oracle|TNS_ALIAS") { Write-Host " [!] api\.env contine valori placeholder — editeaza!" -ForegroundColor Yellow } } Write-Host "" Write-Host " Acces app: http://SERVER/gomag" -ForegroundColor Cyan Write-Host " Test local: http://localhost:$Port/gomag/health" -ForegroundColor Cyan Write-Host ""