#Requires -Version 5.1 <# .SYNOPSIS Construieste kitul de instalare ROA pentru un client: DMP-urile sablon exportate din LXC 108 + scripturile de instalare/migrare, intr-un singur director gata de copiat pe serverul clientului. .DESCRIPTION Ce face, in ordine: 1. Ruleaza exportul pe LXC 108 (`export-roa2.sh`, acelasi script folosit de `export-roa2.ps1`). Sursa e PDB-ul ROA2, nu ROA - ROA e instanta de productie ROA_CENTRAL si nu se scrie in ea. 2. Aduce arhivele .tar.gz pe statia locala. Sunt trei niveluri: container Docker -> LXC 108 -> host pvemini -> statia ta. `docker cp` te scoate doar din container, de aceea e nevoie si de `pct pull`. 3. Despacheteaza si redenumeste DMP-urile la numele pe care le asteapta scripturile: `contafin_oracle.dmp` si `FIRMANOUA.dmp`. 4. Citeste logul expdp si raporteaza cate firme are NOM_FIRME in dump. Asa vezi daca ai construit un sablon curat sau ai prins din greseala datele unui client. 5. Copiaza peste ele continutul din `roa-windows-setup/`. Rezultatul e un director care se dezarhiveaza la client si se ruleaza cu `INSTALEAZA.cmd curat` sau `INSTALEAZA.cmd migrare`. .PARAMETER OracleVersion Sursa exportului: '21' (PDB ROA2, VERSION=19) sau '18' (container oracle18-xe, VERSION=11.2, pentru baze mai vechi). Implicit '21'. .PARAMETER OutputDir Directorul parinte in care se creeaza kitul. Implicit E:\backups\roa-kit. .PARAMETER KitName Numele directorului de kit. Implicit roa-kit-. .PARAMETER SkipExport Nu ruleaza export nou; foloseste cele mai recente arhive deja prezente pe LXC 108. Util cand ai exportat deja si vrei doar sa reconstruiesti kitul. .PARAMETER Zip Creeaza si o arhiva .zip a kitului, langa director. .PARAMETER PushTo Optional, destinatie scp pentru kit dupa constructie, in forma `utilizator@gazda:/cale`. Se trimite directorul intreg, recursiv. .EXAMPLE .\build-client-kit.ps1 Export nou din ROA2 si kit in E:\backups\roa-kit\roa-kit-. .EXAMPLE .\build-client-kit.ps1 -Zip -KitName "roa-kit-clientX" Kit cu nume fix, plus arhiva .zip de trimis clientului. .EXAMPLE .\build-client-kit.ps1 -SkipExport -PushTo "romfast@10.0.20.130:C:/roa-kit" Refoloseste ultimele arhive si trimite kitul direct pe serverul tinta. .NOTES Necesita ssh/scp (OpenSSH din Windows 10+) si tar (bsdtar, tot din Windows). Acces cu cheie la host-ul Proxmox - vezi docs/acces-ssh-chei-angajati.md. #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [ValidateSet('21', '18')] [string]$OracleVersion = '21', [Parameter(Mandatory = $false)] [string]$OutputDir = 'E:\backups\roa-kit', [Parameter(Mandatory = $false)] [string]$KitName, [Parameter(Mandatory = $false)] [switch]$SkipExport, [Parameter(Mandatory = $false)] [switch]$Zip, [Parameter(Mandatory = $false)] [string]$PushTo, [Parameter(Mandatory = $false)] [string]$ProxmoxHost = 'root@10.0.20.201', [Parameter(Mandatory = $false)] [int]$Ctid = 108 ) $ErrorActionPreference = 'Stop' # Schemele exportate si numele sub care ajung in kit. FIRMANOUA.dmp trebuie sa # aiba exact numele asta: SYS.NEWSCHEMA il compune ca || '.dmp'. $SchemaMap = [ordered]@{ 'CONTAFIN_ORACLE' = 'contafin_oracle.dmp' 'FIRMANOUA' = 'FIRMANOUA.dmp' } if ($OracleVersion -eq '18') { $Container = 'oracle18-xe' $RemoteDmpDir = '/opt/oracle18/oradata/dmpdir' $SourceLabel = 'Oracle 18c (XEPDB1), VERSION=11.2' } else { $Container = 'oracle-xe' $RemoteDmpDir = '/opt/oracle/oradata/dmpdir' $SourceLabel = 'Oracle 21c (PDB ROA2), VERSION=19' } function Write-Step { param([string]$m) Write-Host "`n=== $m ===" -ForegroundColor Cyan } function Write-Ok { param([string]$m) Write-Host " [OK] $m" -ForegroundColor Green } function Write-Info { param([string]$m) Write-Host " $m" } function Write-Warn { param([string]$m) Write-Host " [ATENTIE] $m" -ForegroundColor Yellow } function Invoke-Remote { <# Ruleaza o comanda pe host-ul Proxmox si intoarce iesirea. Opreste scriptul daca ssh iese cu cod nenul. #> param( [Parameter(Mandatory = $true)][string]$Command, [switch]$AllowFailure ) # Fara "2>&1": in Windows PowerShell 5.1 redirectarea stderr-ului unei # comenzi native impacheteaza fiecare linie intr-un ErrorRecord, iar # mesajul de eroare devine "System.Management.Automation.RemoteException". # Lasam stderr sa curga direct in consola si ne bazam pe codul de iesire. $out = & ssh -o BatchMode=yes $ProxmoxHost $Command if ($LASTEXITCODE -ne 0 -and -not $AllowFailure) { throw "Comanda pe $ProxmoxHost a esuat (cod $LASTEXITCODE): $Command" } return $out } # ============================================================================= # 0. Validari locale # ============================================================================= Write-Step "Verificari initiale" $SetupDir = Join-Path (Split-Path $PSScriptRoot -Parent) 'roa-windows-setup' if (-not (Test-Path $SetupDir -PathType Container)) { throw "Nu gasesc directorul cu scripturile de instalare: $SetupDir" } $InstallCmd = Join-Path $SetupDir 'INSTALEAZA.cmd' if (-not (Test-Path $InstallCmd)) { throw "Nu gasesc $InstallCmd - kitul ar fi inutilizabil fara el." } Write-Ok "Scripturi de instalare: $SetupDir" foreach ($tool in @('ssh', 'scp', 'tar')) { if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) { throw "Lipseste '$tool' din PATH. Pe Windows 10+ vine cu OpenSSH / bsdtar." } } Write-Ok "ssh, scp, tar disponibile" if (-not $KitName) { $KitName = "roa-kit-$(Get-Date -Format 'yyyyMMdd_HHmmss')" } $KitDir = Join-Path $OutputDir $KitName if (Test-Path $KitDir) { throw "Directorul $KitDir exista deja. Sterge-l sau foloseste alt -KitName." } $Staging = Join-Path ([System.IO.Path]::GetTempPath()) "roa-kit-staging-$([System.Guid]::NewGuid().ToString('N').Substring(0,8))" New-Item -ItemType Directory -Path $Staging -Force | Out-Null try { # ========================================================================= # 1. Starea LXC 108 si a containerului Oracle # ========================================================================= Write-Step "Starea LXC $Ctid si a containerului $Container" $lxcStatus = Invoke-Remote "pct status $Ctid" if ($lxcStatus -notmatch 'running') { throw "LXC $Ctid nu ruleaza ($lxcStatus). Porneste-l cu: ssh $ProxmoxHost `"pct start $Ctid`"" } Write-Ok "LXC $Ctid ruleaza" $running = Invoke-Remote "pct exec $Ctid -- docker ps --format '{{.Names}}'" if ($running -notcontains $Container) { throw "Containerul $Container nu ruleaza in LXC $Ctid. Porneste-l cu: ssh $ProxmoxHost `"pct exec $Ctid -- docker start $Container`" si asteapta ~60s." } Write-Ok "Containerul $Container ruleaza" Write-Info "Sursa exportului: $SourceLabel" # ========================================================================= # 2. Exportul # ========================================================================= if ($SkipExport) { Write-Step "Export sarit (-SkipExport) - folosesc arhivele existente" } else { Write-Step "Export din $SourceLabel" Write-Info "Dureaza cateva minute. Ruleaza export-roa2.sh pe LXC $Ctid..." $exportOut = Invoke-Remote "pct exec $Ctid -- bash /opt/oracle/oradata/export-roa2.sh $OracleVersion" $exportOut | Where-Object { $_ -match 'EROARE|ORA-|successfully completed|elapsed' } | ForEach-Object { Write-Info $_ } Write-Ok "Export terminat" } # ========================================================================= # 3. Aducerea arhivelor: container -> LXC -> host -> statie # ========================================================================= Write-Step "Aduc arhivele pe statia locala" $archives = @{} foreach ($schema in $SchemaMap.Keys) { $newest = (Invoke-Remote "pct exec $Ctid -- bash -c 'ls -t $RemoteDmpDir/${schema}*.tar.gz 2>/dev/null | head -1'" | Select-Object -First 1) if ([string]::IsNullOrWhiteSpace($newest)) { throw "Nu am gasit nicio arhiva ${schema}*.tar.gz in $RemoteDmpDir. Ruleaza fara -SkipExport." } $newest = $newest.Trim() $leaf = Split-Path $newest -Leaf # pct pull scoate fisierul din LXC pe host-ul Proxmox; scp il aduce mai # departe. Fara pct pull, scp ar citi de pe host, unde nu exista nimic. Invoke-Remote "pct pull $Ctid $newest /tmp/$leaf" | Out-Null & scp -o BatchMode=yes "${ProxmoxHost}:/tmp/$leaf" (Join-Path $Staging $leaf) | Out-Null if ($LASTEXITCODE -ne 0) { throw "scp a esuat pentru $leaf" } Invoke-Remote "rm -f /tmp/$leaf" -AllowFailure | Out-Null $localArchive = Join-Path $Staging $leaf $sizeMb = [math]::Round((Get-Item $localArchive).Length / 1MB, 1) Write-Ok "$leaf ($sizeMb MB)" $archives[$schema] = $localArchive } # ========================================================================= # 4. Despachetare, redenumire, verificarea continutului # ========================================================================= Write-Step "Despachetez si verific" $KitDmpDir = Join-Path $KitDir 'DMPDIR' New-Item -ItemType Directory -Path $KitDmpDir -Force | Out-Null $nomFirmeRows = $null foreach ($schema in $SchemaMap.Keys) { $extractDir = Join-Path $Staging "x_$schema" New-Item -ItemType Directory -Path $extractDir -Force | Out-Null & tar -xzf $archives[$schema] -C $extractDir | Out-Null if ($LASTEXITCODE -ne 0) { throw "tar a esuat la despachetarea $($archives[$schema])" } $dmp = Get-ChildItem -Path $extractDir -Filter '*.dmp' -File | Select-Object -First 1 if (-not $dmp) { throw "Arhiva pentru $schema nu contine niciun fisier .dmp" } $target = Join-Path $KitDmpDir $SchemaMap[$schema] Copy-Item -Path $dmp.FullName -Destination $target -Force $sizeMb = [math]::Round((Get-Item $target).Length / 1MB, 1) Write-Ok "$($SchemaMap[$schema]) ($sizeMb MB)" # Logul expdp spune exact cate randuri are fiecare tabela. Pentru # CONTAFIN_ORACLE, NOM_FIRME e indicatorul care separa un sablon curat # de datele unui client. $log = Get-ChildItem -Path $extractDir -Filter '*.log' -File | Select-Object -First 1 if ($log -and $schema -eq 'CONTAFIN_ORACLE') { $m = Select-String -Path $log.FullName -Pattern '"NOM_FIRME"\s+\S+\s+\S+\s+(\d+)\s+rows' | Select-Object -First 1 if ($m) { $nomFirmeRows = [int]$m.Matches[0].Groups[1].Value } } } if ($null -eq $nomFirmeRows) { Write-Warn "Nu am putut citi numarul de firme din logul expdp. Verifica manual continutul lui NOM_FIRME." } elseif ($nomFirmeRows -le 5) { Write-Ok "NOM_FIRME: $nomFirmeRows randuri - sablon curat, potrivit pentru instalare pe curat" } else { Write-Warn "NOM_FIRME are $nomFirmeRows randuri - NU e un sablon curat!" Write-Warn "Dump-ul contine firmele unei baze reale. Nu-l trimite altui client." } # ========================================================================= # 5. Scripturile de instalare # ========================================================================= Write-Step "Copiez scripturile de instalare" # Kitul are structura lui roa-windows-setup direct in radacina, plus DMPDIR: # Run.cmd si INSTALEAZA.cmd se bazeaza pe %~dp0scripts, deci trebuie sa fie # la acelasi nivel cu scripts\. $excludeNames = @('logs', 'test') # Gunoiul din working tree nu are ce cauta la client. *.dmp e exclus ca # masura de siguranta: daca cineva a lasat dump-ul unui client in # roa-windows-setup, nu vrem sa plece mai departe. $excludePatterns = @('*.stackdump', '*.zip', '*.dmp', '*.log', 'config.ps1') Get-ChildItem -Path $SetupDir -Force | Where-Object { $item = $_ ($excludeNames -notcontains $item.Name) -and (-not ($excludePatterns | Where-Object { $item.Name -like $_ })) } | ForEach-Object { Copy-Item -Path $_.FullName -Destination $KitDir -Recurse -Force } # Acelasi filtru, aplicat si in subdirectoare (config.ps1 poate contine # parolele altui client, logurile pot contine nume de firme). # DMPDIR e scutit: acolo tocmai am pus sabloanele, iar '*.dmp' e in lista de # excluderi. Fara filtrul asta, pasul de curatare isi sterge propriul rezultat. foreach ($pat in $excludePatterns) { Get-ChildItem -Path $KitDir -Filter $pat -Recurse -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.DirectoryName -ne $KitDmpDir } | Remove-Item -Force -ErrorAction SilentlyContinue } New-Item -ItemType Directory -Path (Join-Path $KitDir 'logs') -Force | Out-Null # Plasa de siguranta: in kit au voie exact cele doua sabloane, nimic altceva. $strayDmp = Get-ChildItem -Path $KitDir -Filter '*.dmp' -Recurse -File | Where-Object { $_.DirectoryName -ne $KitDmpDir } if ($strayDmp) { throw "Fisiere .dmp neasteptate in kit, in afara DMPDIR: $($strayDmp.FullName -join ', ')" } # ...si invers: sabloanele TREBUIE sa fie acolo. Verificarea asta prinde # orice pas ulterior care le-ar sterge din greseala. foreach ($expected in $SchemaMap.Values) { $p = Join-Path $KitDmpDir $expected if (-not (Test-Path $p)) { throw "Lipseste $expected din kit - constructia e incompleta." } if ((Get-Item $p).Length -lt 1MB) { throw "$expected are sub 1 MB - fisier trunchiat sau gresit." } } $copied = (Get-ChildItem -Path $KitDir -Recurse -File).Count Write-Ok "$copied fisiere in kit" # ========================================================================= # 6. Instructiunile # ========================================================================= $sursa = if ($SkipExport) { "$SourceLabel (arhive existente, neexportate acum)" } else { $SourceLabel } $firme = if ($null -ne $nomFirmeRows) { "$nomFirmeRows" } else { "necunoscut" } $citeste = @" KIT DE INSTALARE ROA ORACLE =========================== Construit: $(Get-Date -Format 'yyyy-MM-dd HH:mm') Sursa DMP-urilor: $sursa Firme in NOM_FIRME din contafin_oracle.dmp: $firme CE E IN KIT ----------- DMPDIR\contafin_oracle.dmp schema comuna (drepturi, actualizari, NOM_FIRME) DMPDIR\FIRMANOUA.dmp sablon de firma goala scripts\, sql\, config\ scripturile de instalare INSTALEAZA.cmd ruleaza toti pasii, in ordine README.md documentatia completa CUM SE INSTALEAZA ----------------- Inainte de orice: instaleaza Oracle 21c XE pe server (docs\00-INSTALL-ORACLE-XE.md), cu parola SYS/SYSTEM 'romfastsoft'. Apoi, din radacina kitului: A. CLIENT NOU, fara firme anterioare: INSTALEAZA.cmd curat [ServerSMTP] Prima firma se creeaza DUPA aceea, DIN APLICATIA ROA - nu din scripturi. Aplicatia scrie firma in CONTAFIN_ORACLE.NOM_FIRME si apeleaza SYS.NEWSCHEMA, care ii importa structura din FIRMANOUA.dmp. B. MIGRARE de pe un Oracle mai vechi: 1. Inlocuieste DMPDIR\contafin_oracle.dmp cu cel exportat de la client (cel din kit are NOM_FIRME goala). 2. Pune in DMPDIR si cate un .dmp per firma a clientului. 3. LASA FIRMANOUA.dmp pe loc - nu vine de la client, dar e obligatoriu. 4. INSTALEAZA.cmd migrare [ServerSMTP] Instalarea e gata cand ultimul pas (07-verify-installation) iese cu codul 0. DE STIUT -------- - FIRMANOUA.dmp ramane in C:\DMPDIR si dupa instalare. E folosit la fiecare firma noua, oricand pe viitor. Daca il stergi, adaugarea de firme va esua cu o eroare in DBMS_DATAPUMP.ADD_FILE. - Pasul 03 (import contafin) iese cu codul 5 si asta e NORMAL: e codul Data Pump pentru "completed with errors", din ORA-31684 (userul exista deja, creat de pasul 01) si ORA-39082 (warning-uri de compilare, rezolvate la pasul 04). INSTALEAZA.cmd il trateaza corect. - Conectarea se face la serviciul XEPDB1, niciodata la XE (CDB root). - Daca reiei instalarea de la zero: scripts\99-uninstall-roa.ps1 trebuie sa iasa cu codul 0. Orice alt cod inseamna ca au ramas obiecte in baza (uzual o sesiune activa tinea userul ocupat) - nu porni instalarea peste ele. - Logurile fiecarui pas sunt in logs\. "@ Set-Content -Path (Join-Path $KitDir 'CITESTE-MA.txt') -Value $citeste -Encoding UTF8 # ========================================================================= # 7. Arhivare si livrare # ========================================================================= $zipPath = $null if ($Zip) { Write-Step "Creez arhiva .zip" $zipPath = Join-Path $OutputDir "$KitName.zip" if (Test-Path $zipPath) { Remove-Item $zipPath -Force } Compress-Archive -Path (Join-Path $KitDir '*') -DestinationPath $zipPath -CompressionLevel Optimal $zipMb = [math]::Round((Get-Item $zipPath).Length / 1MB, 1) Write-Ok "$zipPath ($zipMb MB)" } if ($PushTo) { Write-Step "Trimit kitul catre $PushTo" & scp -o BatchMode=yes -r $KitDir $PushTo | Out-Null if ($LASTEXITCODE -ne 0) { throw "scp catre $PushTo a esuat (cod $LASTEXITCODE)" } Write-Ok "Kit trimis" } # ========================================================================= # 8. Sumar # ========================================================================= $totalMb = [math]::Round(((Get-ChildItem -Path $KitDir -Recurse -File | Measure-Object Length -Sum).Sum) / 1MB, 1) Write-Host "" Write-Host "============================================================" -ForegroundColor Green Write-Host " KIT GATA" -ForegroundColor Green Write-Host "============================================================" -ForegroundColor Green Write-Host "" Write-Host " Director: $KitDir" if ($zipPath) { Write-Host " Arhiva: $zipPath" } Write-Host " Marime: $totalMb MB" Write-Host " NOM_FIRME: $firme randuri in contafin_oracle.dmp" Write-Host "" Write-Host " La client, din radacina kitului:" Write-Host " INSTALEAZA.cmd curat (client nou)" -ForegroundColor Cyan Write-Host " INSTALEAZA.cmd migrare (are date existente)" -ForegroundColor Cyan Write-Host "" Write-Host " Detalii complete: $KitDir\CITESTE-MA.txt" Write-Host "" } catch { Write-Host "" Write-Host "[EROARE] $_" -ForegroundColor Red if (Test-Path $KitDir) { Write-Host "[INFO] Kit incomplet la $KitDir - sterge-l inainte de a relua." -ForegroundColor Yellow } exit 1 } finally { if (Test-Path $Staging) { Remove-Item -Path $Staging -Recurse -Force -ErrorAction SilentlyContinue } }