#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 backup\ backupora.exe + sablonul de setari.ini FAZA1.cmd / FAZA2.cmd instalare in doua faze (recomandat la migrari) INSTALEAZA.cmd instalare intr-o singura rulare 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. MIGRARE IN DOUA FAZE (recomandat: downtime de minute, nu de ore) Ideea: pe CONTAFIN_ORACLE nu se lucreaza activ, deci poate fi exportat de pe serverul vechi inca din timpul programului. Doar schemele de firma se schimba sub picioare. Deci tot ce nu depinde de firme se face ziua, iar seara raman doar firmele. FAZA 1 - in timpul programului, utilizatorii lucreaza mai departe pe serverul vechi: 1. Exporta DOAR contafin_oracle de pe serverul vechi si pune-l in DMPDIR\ peste sablon. 2. FAZA1.cmd /vechi: /parolavechi: Se instaleaza baza, obiectele SYS, contafin-ul clientului, sinonimele, LICENTELE de pe serverul vechi, directoarele ROAUPDATE, publicarea in IIS si exportul zilnic. La final, verificarea va spune "N company schemas are missing" - e NORMAL, firmele vin abia in faza 2. FAZA 2 - dupa ce utilizatorii au terminat: 1. Exporta schemele de firma de pe serverul vechi. 2. Pune DMP-urile in C:\DMPDIR (sau da /dmp:). 3. FAZA2.cmd Daca intre faze s-a adaugat o firma noua sau s-au modificat nomenclatoare pe serverul vechi, adauga /reimportcontafin - altfel NOM_FIRME ramane in urma. B. CLIENT NOU, fara firme anterioare (o singura rulare) 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. Sau, echivalent si cu IIS + export zilnic incluse: FAZA1.cmd /faralicente C. MIGRARE INTR-O SINGURA RULARE (cand downtime-ul nu conteaza) 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] Atentie: INSTALEAZA.cmd NU copiaza licentele, NU publica in IIS si NU configureaza exportul zilnic. Ruleaza dupa el, manual: Run.cmd 09-copy-licenses.ps1 -OldHost -OldSysPassword -Replace Run.cmd 10-setup-roaupdate-iis.ps1 Run.cmd 11-setup-backup-export.ps1 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\. - LICENTELE nu vin din DMP-uri. Stau in SYS.AUTH_SERII pe serverul vechi si se copiaza separat, cu 09-copy-licenses.ps1. Daca sari peste pasul asta, aplicatia porneste dar refuza fiecare modul. FAZA1.cmd il ruleaza singur daca ii dai /vechi si /parolavechi. - Fara backup\backupora.exe in kit, pasul 11 (exportul zilnic) se opreste cu eroare. Restul instalarii nu depinde de el. - setari.ini pentru backupora.exe se genereaza din backup\setari.ini.template prin inlocuirea marcajelor {{...}}. Daca formatul difera de ce asteapta executabilul, corecteaza SABLONUL, nu scriptul. "@ 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 " FAZA1.cmd /vechi: /parolavechi:" -ForegroundColor Cyan Write-Host " FAZA2.cmd (dupa program)" -ForegroundColor Cyan Write-Host " INSTALEAZA.cmd curat (client nou, o rulare)" -ForegroundColor Cyan Write-Host "" if (-not (Test-Path (Join-Path $KitDir 'backup\backupora.exe'))) { Write-Warn "backup\backupora.exe lipseste din kit - pasul 11 (export zilnic) va esua la client." 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 } }