Reorganize oracle/ and chatbot/ into proxmox/ per LXC/VM structure
- Move oracle/migration-scripts/ to proxmox/lxc108-oracle/migration/ - Move oracle/roa/ and oracle/roa-romconstruct/ to proxmox/lxc108-oracle/sql/ - Move oracle/standby-server-scripts/ to proxmox/vm109-windows-dr/ - Move chatbot/ to proxmox/lxc104-flowise/ - Update proxmox/README.md with new structure and navigation - Update all documentation with correct directory references - Remove unused input/claude-agent-sdk/ files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
36
proxmox/vm109-windows-dr/scripts/add_system_key_dr.ps1
Normal file
36
proxmox/vm109-windows-dr/scripts/add_system_key_dr.ps1
Normal file
@@ -0,0 +1,36 @@
|
||||
# Add PRIMARY SYSTEM user SSH key to DR VM
|
||||
# Run this on DR VM (10.0.20.37) as Administrator
|
||||
|
||||
$systemKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3EdHswdNDuDC9kJdUli2zGGPVlEWJjmqtb4eABYWwjQnWqjGp8oAFbQ+r2TxR544WtEyhDL9BU6oO3EFH957DBGQJHJvfRgx2VnkNZEzN/XX/7HK6Cp5rlINGGp26PjHulKkZjARmjC3YK0aUFEkiyNyUBqhtQpmcYP4+wjUfiiO2xUkF9mzGplbWGK3ZmEdkWNd5BNddqxmxyLvd2KHAo8F7Vux9SyPWqZ8bwiDyidAMDU7kCXS/RabUMl2LGajzFbRnR87YA7cIaVFl/IWExO/fsYlgkwmmmIAMdjINp0IWDdydnmG1XNNhM8h/BKY/eK3uile8CvyEbmbuf0+ePm3Ex9vTjn4jYN2vFE148FgQGGTibibJ+sXFoQ87VFNGEuog/V0aajVk/xcOihszsEvzD2IV/tSKJFdI6klnYLuieuMZf7Dvs/6sC1l3dnsBtcpvjnU48altRRZvQzaJf3gIkG1lRGBgyW1n+WHe/7StIveYTVNFtx+fcnqB8gm9fZQxBp2WRbLNFpY/Qj+6BF66b1A2ZxH/3F9Z/6VT91EActOf+AMxjsI+09d7IRYIvzr8OxMPYOHU2bglp3o86xZEMUXfcjB8Sw/8KMsCjBp3ABEN9/bwv1496aw9IC67ZBQ2cDDfgdBej5DAkT4NS2XIx7wbM7sBtLYjcXMi7w== administrator@ROA-CARAPETRU2"
|
||||
|
||||
$authKeysFile = "C:\ProgramData\ssh\administrators_authorized_keys"
|
||||
|
||||
Write-Host "Adding PRIMARY SYSTEM user SSH key to DR VM..." -ForegroundColor Cyan
|
||||
|
||||
# Check if key already exists
|
||||
$currentContent = Get-Content $authKeysFile -ErrorAction SilentlyContinue
|
||||
if ($currentContent -match "administrator@ROA-CARAPETRU2") {
|
||||
Write-Host "Key already exists in authorized_keys" -ForegroundColor Yellow
|
||||
} else {
|
||||
# Add the key
|
||||
Add-Content -Path $authKeysFile -Value $systemKey
|
||||
Write-Host "Key added successfully" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Show current keys
|
||||
Write-Host ""
|
||||
Write-Host "Current authorized keys:" -ForegroundColor Cyan
|
||||
Get-Content $authKeysFile | ForEach-Object {
|
||||
if ($_ -match "ssh-rsa .+ (.+)$") {
|
||||
Write-Host " - $($matches[1])" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
|
||||
# Restart SSH service
|
||||
Write-Host ""
|
||||
Write-Host "Restarting SSH service..." -ForegroundColor Yellow
|
||||
Restart-Service sshd
|
||||
Write-Host "SSH service restarted" -ForegroundColor Green
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Done! SYSTEM user from PRIMARY can now connect via SSH." -ForegroundColor Green
|
||||
133
proxmox/vm109-windows-dr/scripts/cleanup_database.ps1
Normal file
133
proxmox/vm109-windows-dr/scripts/cleanup_database.ps1
Normal file
@@ -0,0 +1,133 @@
|
||||
# Oracle Database Complete Cleanup Script (PowerShell)
|
||||
# Purpose: Remove all database files and services to restore DR VM to clean state
|
||||
# Run as: Administrator
|
||||
# Location: D:\oracle\scripts\cleanup_database.ps1
|
||||
#
|
||||
# Parameters:
|
||||
# /SILENT - Non-interactive mode
|
||||
# /AFTER - Cleanup AFTER restore (shutdown instance + stop service before deleting files)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
$env:ORACLE_HOME = "C:\Users\Administrator\Downloads\WINDOWS.X64_193000_db_home"
|
||||
$env:ORACLE_SID = "ROA"
|
||||
$env:PATH = "$env:ORACLE_HOME\bin;$env:PATH"
|
||||
|
||||
Write-Host "============================================"
|
||||
Write-Host "Oracle Database Cleanup Script"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
Write-Host "This script will:"
|
||||
Write-Host " 1. Stop and delete Oracle service"
|
||||
Write-Host " 2. Delete all database files (datafiles, control files, redo logs)"
|
||||
Write-Host " 3. Delete local FRA (backups are on F:\, safe to delete)"
|
||||
Write-Host " 4. Delete trace files"
|
||||
Write-Host " 5. Leave VM in completely clean state (no service, no DB files)"
|
||||
Write-Host ""
|
||||
|
||||
# Check parameters
|
||||
$silent = $args -contains "/SILENT" -or $args -contains "/AUTO"
|
||||
$afterRestore = $args -contains "/AFTER"
|
||||
|
||||
if (-not $silent) {
|
||||
Write-Host "WARNING: This will DELETE the entire database!" -ForegroundColor Red
|
||||
Write-Host "Starting cleanup in 3 seconds... (Press Ctrl+C to cancel)"
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
|
||||
# Create directories
|
||||
New-Item -ItemType Directory -Path "D:\oracle\temp" -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path "D:\oracle\logs" -Force | Out-Null
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[1/6] Shutting down database and stopping service..."
|
||||
|
||||
# Check if Oracle service exists
|
||||
$service = Get-Service -Name "OracleServiceROA" -ErrorAction SilentlyContinue
|
||||
if ($service) {
|
||||
Write-Host " Oracle service found, ensuring clean shutdown..."
|
||||
|
||||
# Shutdown instance using SQL*Plus (always, not just /AFTER)
|
||||
$shutdownSQL = "WHENEVER SQLERROR CONTINUE`nSHUTDOWN ABORT;`nEXIT;"
|
||||
try {
|
||||
$shutdownSQL | & sqlplus -S / as sysdba 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Host " Instance shut down (ABORT for fast cleanup)"
|
||||
} catch {
|
||||
Write-Host " Shutdown command sent (errors ignored)"
|
||||
}
|
||||
|
||||
# ALWAYS stop Oracle service to ensure clean state
|
||||
if ($service.Status -eq "Running") {
|
||||
Write-Host " Stopping Oracle service to ensure clean state..."
|
||||
try {
|
||||
Stop-Service -Name "OracleServiceROA" -Force -ErrorAction Stop
|
||||
Start-Sleep -Seconds 3
|
||||
Write-Host " Service stopped successfully"
|
||||
} catch {
|
||||
Write-Host " WARNING: Failed to stop service: $_" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host " Service already stopped"
|
||||
}
|
||||
|
||||
# Force kill any remaining Oracle processes to ensure clean state
|
||||
Write-Host " Cleaning up any remaining Oracle processes..."
|
||||
Get-Process -Name "sqlplus" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
Get-Process -Name "oracle" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Host " All Oracle processes terminated"
|
||||
} else {
|
||||
Write-Host " Oracle service not found, will be created during restore"
|
||||
}
|
||||
|
||||
Write-Host "[2/6] Oracle service stopped (clean state for restore)"
|
||||
Write-Host " Service will be started fresh during restore"
|
||||
Write-Host " This ensures no state inconsistencies (prevents ORA-00600)"
|
||||
|
||||
Write-Host "[3/6] Deleting database files + SPFILE..."
|
||||
Write-Host " Deleting datafiles..."
|
||||
Remove-Item "C:\Users\oracle\oradata\ROA\*.dbf" -Force -ErrorAction SilentlyContinue
|
||||
Write-Host " Deleting control files..."
|
||||
Remove-Item "C:\Users\oracle\oradata\ROA\*.ctl" -Force -ErrorAction SilentlyContinue
|
||||
Write-Host " Deleting redo logs..."
|
||||
Remove-Item "C:\Users\oracle\oradata\ROA\*.log" -Force -ErrorAction SilentlyContinue
|
||||
Write-Host " Deleting SPFILE (ensures PFILE-based startup)..."
|
||||
Remove-Item "$env:ORACLE_HOME\database\SPFILE*.ORA" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host "[4/6] Deleting local FRA (backups are on F:\)..."
|
||||
if (Test-Path "C:\Users\oracle\recovery_area\ROA") {
|
||||
Remove-Item "C:\Users\oracle\recovery_area\ROA" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Path "C:\Users\oracle\recovery_area\ROA" -Force | Out-Null
|
||||
Write-Host " FRA cleared"
|
||||
} else {
|
||||
New-Item -ItemType Directory -Path "C:\Users\oracle\recovery_area\ROA" -Force | Out-Null
|
||||
Write-Host " FRA directory created"
|
||||
}
|
||||
|
||||
Write-Host "[5/6] Deleting trace files (to save space)..."
|
||||
Remove-Item "C:\Users\oracle\diag\rdbms\roa\ROA\trace\*.trc" -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "C:\Users\oracle\diag\rdbms\roa\ROA\trace\*.trm" -Force -ErrorAction SilentlyContinue
|
||||
Write-Host " Trace files deleted"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "============================================"
|
||||
Write-Host "Database Cleanup Complete!"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
Write-Host "Current state:"
|
||||
Write-Host " [YES] Oracle software installed"
|
||||
Write-Host " [YES] PFILE exists (C:\Users\oracle\admin\ROA\pfile\initROA.ora)"
|
||||
Write-Host " [YES] Oracle service (STOPPED for clean restore)"
|
||||
Write-Host " [NO] SPFILE (deleted to ensure PFILE startup)"
|
||||
Write-Host " [NO] Database files (will be restored from backups)"
|
||||
Write-Host " [NO] Control files (will be restored from backups)"
|
||||
Write-Host " [NO] Datafiles (will be restored from backups)"
|
||||
Write-Host ""
|
||||
Write-Host "VM is now in CLEAN STATE (service stopped, ready for fresh start)!"
|
||||
Write-Host ""
|
||||
Write-Host "Next step: Run D:\oracle\scripts\rman_restore_from_zero.ps1"
|
||||
Write-Host " (It will start the service fresh and restore the database)"
|
||||
Write-Host ""
|
||||
|
||||
exit 0
|
||||
158
proxmox/vm109-windows-dr/scripts/configure_listener_dr.ps1
Normal file
158
proxmox/vm109-windows-dr/scripts/configure_listener_dr.ps1
Normal file
@@ -0,0 +1,158 @@
|
||||
# Configure Oracle Listener on DR VM
|
||||
# Run this script AFTER Oracle installation
|
||||
# Run AS ADMINISTRATOR on DR VM (10.0.20.37)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$ORACLE_HOME = "C:\Users\Administrator\Downloads\WINDOWS.X64_193000_db_home"
|
||||
$ORACLE_BASE = "C:\Users\oracle"
|
||||
$DR_IP = "10.0.20.37"
|
||||
$LISTENER_PORT = 1521
|
||||
|
||||
Write-Host "=== Configure Oracle Listener on DR VM ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Set environment
|
||||
$env:ORACLE_HOME = $ORACLE_HOME
|
||||
$env:ORACLE_BASE = $ORACLE_BASE
|
||||
$env:PATH = "$ORACLE_HOME\bin;$env:PATH"
|
||||
|
||||
# Step 1: Create network admin directory
|
||||
Write-Host "[1/5] Creating network admin directory..." -ForegroundColor Yellow
|
||||
$netAdminDir = "$ORACLE_HOME\network\admin"
|
||||
if (!(Test-Path $netAdminDir)) {
|
||||
New-Item -ItemType Directory -Path $netAdminDir -Force | Out-Null
|
||||
}
|
||||
Write-Host " Directory: $netAdminDir" -ForegroundColor Green
|
||||
|
||||
# Step 2: Create listener.ora
|
||||
Write-Host "[2/5] Creating listener.ora..." -ForegroundColor Yellow
|
||||
$listenerOra = @"
|
||||
# Listener Configuration for DR VM
|
||||
# Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
LISTENER =
|
||||
(DESCRIPTION_LIST =
|
||||
(DESCRIPTION =
|
||||
(ADDRESS = (PROTOCOL = TCP)(HOST = $DR_IP)(PORT = $LISTENER_PORT))
|
||||
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))
|
||||
)
|
||||
)
|
||||
|
||||
SID_LIST_LISTENER =
|
||||
(SID_LIST =
|
||||
(SID_DESC =
|
||||
(GLOBAL_DBNAME = ROA)
|
||||
(ORACLE_HOME = $($ORACLE_HOME -replace '\\', '/'))
|
||||
(SID_NAME = ROA)
|
||||
)
|
||||
)
|
||||
|
||||
# Listener control parameters
|
||||
INBOUND_CONNECT_TIMEOUT_LISTENER = 120
|
||||
SUBSCRIBE_FOR_NODE_DOWN_EVENT_LISTENER = OFF
|
||||
VALID_NODE_CHECKING_REGISTRATION_LISTENER = OFF
|
||||
|
||||
# Logging
|
||||
LOG_DIRECTORY_LISTENER = $($ORACLE_BASE -replace '\\', '/')/diag/tnslsnr/ORACLE-DR/listener/alert
|
||||
TRACE_DIRECTORY_LISTENER = $($ORACLE_BASE -replace '\\', '/')/diag/tnslsnr/ORACLE-DR/listener/trace
|
||||
TRACE_LEVEL_LISTENER = OFF
|
||||
"@
|
||||
|
||||
$listenerOra | Out-File -FilePath "$netAdminDir\listener.ora" -Encoding ASCII -Force
|
||||
Write-Host " Created: $netAdminDir\listener.ora" -ForegroundColor Green
|
||||
|
||||
# Step 3: Create tnsnames.ora
|
||||
Write-Host "[3/5] Creating tnsnames.ora..." -ForegroundColor Yellow
|
||||
$tnsnamesOra = @"
|
||||
# TNS Names Configuration for DR VM
|
||||
# Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
ROA =
|
||||
(DESCRIPTION =
|
||||
(ADDRESS = (PROTOCOL = TCP)(HOST = $DR_IP)(PORT = $LISTENER_PORT))
|
||||
(CONNECT_DATA =
|
||||
(SERVER = DEDICATED)
|
||||
(SERVICE_NAME = ROA)
|
||||
)
|
||||
)
|
||||
|
||||
# Localhost connection
|
||||
ROA_LOCAL =
|
||||
(DESCRIPTION =
|
||||
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = $LISTENER_PORT))
|
||||
(CONNECT_DATA =
|
||||
(SERVER = DEDICATED)
|
||||
(SERVICE_NAME = ROA)
|
||||
)
|
||||
)
|
||||
"@
|
||||
|
||||
$tnsnamesOra | Out-File -FilePath "$netAdminDir\tnsnames.ora" -Encoding ASCII -Force
|
||||
Write-Host " Created: $netAdminDir\tnsnames.ora" -ForegroundColor Green
|
||||
|
||||
# Step 4: Create sqlnet.ora
|
||||
Write-Host "[4/5] Creating sqlnet.ora..." -ForegroundColor Yellow
|
||||
$sqlnetOra = @"
|
||||
# SQL*Net Configuration for DR VM
|
||||
# Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
NAMES.DIRECTORY_PATH = (TNSNAMES, EZCONNECT, HOSTNAME)
|
||||
|
||||
# Security settings
|
||||
SQLNET.AUTHENTICATION_SERVICES = (NTS)
|
||||
SQLNET.EXPIRE_TIME = 10
|
||||
|
||||
# Encryption (optional, enable if needed)
|
||||
# SQLNET.ENCRYPTION_SERVER = REQUIRED
|
||||
# SQLNET.CRYPTO_CHECKSUM_SERVER = REQUIRED
|
||||
"@
|
||||
|
||||
$sqlnetOra | Out-File -FilePath "$netAdminDir\sqlnet.ora" -Encoding ASCII -Force
|
||||
Write-Host " Created: $netAdminDir\sqlnet.ora" -ForegroundColor Green
|
||||
|
||||
# Step 5: Start listener
|
||||
Write-Host "[5/5] Starting Oracle Listener..." -ForegroundColor Yellow
|
||||
|
||||
# Stop listener if already running
|
||||
try {
|
||||
& lsnrctl stop 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
} catch {
|
||||
# Listener not running, continue
|
||||
}
|
||||
|
||||
# Start listener
|
||||
try {
|
||||
$output = & lsnrctl start 2>&1 | Out-String
|
||||
if ($output -match "completed successfully" -or $output -match "successfully") {
|
||||
Write-Host " Listener started successfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " WARNING: Check listener status manually" -ForegroundColor Yellow
|
||||
Write-Host $output -ForegroundColor Gray
|
||||
}
|
||||
} catch {
|
||||
Write-Host " ERROR: Failed to start listener: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Listener Configuration Complete ===" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Verify listener status
|
||||
Write-Host "Listener Status:" -ForegroundColor Cyan
|
||||
& lsnrctl status
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Configuration files created:" -ForegroundColor Yellow
|
||||
Write-Host " $netAdminDir\listener.ora" -ForegroundColor White
|
||||
Write-Host " $netAdminDir\tnsnames.ora" -ForegroundColor White
|
||||
Write-Host " $netAdminDir\sqlnet.ora" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Test connectivity:" -ForegroundColor Yellow
|
||||
Write-Host " tnsping ROA" -ForegroundColor White
|
||||
Write-Host " sqlplus sys/password@ROA as sysdba" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Next step: Create RMAN restore script" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
@@ -0,0 +1,78 @@
|
||||
# Copy Existing SSH Key to Proxmox
|
||||
# Rulează acest script pe PRIMARY ca Administrator
|
||||
#
|
||||
# Acest script copiază cheia publică SSH existentă din profilul SYSTEM pe Proxmox
|
||||
|
||||
param(
|
||||
[string]$ProxmoxHost = "10.0.20.202",
|
||||
[string]$ProxmoxUser = "root"
|
||||
)
|
||||
|
||||
Write-Host "=========================================" -ForegroundColor Cyan
|
||||
Write-Host "Copiere Cheie SSH Existentă → Proxmox DR" -ForegroundColor Cyan
|
||||
Write-Host "=========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$SystemSSHDir = "C:\Windows\System32\config\systemprofile\.ssh"
|
||||
$PublicKeyPath = "$SystemSSHDir\id_rsa.pub"
|
||||
$PrivateKeyPath = "$SystemSSHDir\id_rsa"
|
||||
|
||||
# Verifică dacă cheia există
|
||||
if (-not (Test-Path $PublicKeyPath)) {
|
||||
Write-Host "✗ Cheia publică nu există: $PublicKeyPath" -ForegroundColor Red
|
||||
Write-Host " Scriptul trebuie rulat ca Administrator!" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "✓ Cheie publică găsită: $PublicKeyPath" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "→ Copiez cheia publică pe Proxmox ($ProxmoxHost)..." -ForegroundColor Yellow
|
||||
Write-Host " IMPORTANT: Vei fi întrebat de parola root pentru Proxmox!" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Metodă simplă: folosim SCP pentru a copia fișierul temporar, apoi cat
|
||||
$tempFile = "C:\Windows\Temp\temp_pubkey.pub"
|
||||
Copy-Item $PublicKeyPath $tempFile -Force
|
||||
|
||||
# Copiază fișierul pe Proxmox
|
||||
& scp $tempFile "${ProxmoxUser}@${ProxmoxHost}:/tmp/temp_pubkey.pub"
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "✗ Eroare la copierea fișierului cu SCP" -ForegroundColor Red
|
||||
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Adaugă cheia în authorized_keys
|
||||
& ssh "${ProxmoxUser}@${ProxmoxHost}" "mkdir -p /root/.ssh; chmod 700 /root/.ssh; cat /tmp/temp_pubkey.pub >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys; rm /tmp/temp_pubkey.pub; echo 'SSH key added'"
|
||||
|
||||
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✓ Cheie publică copiată pe Proxmox!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "✗ Eroare la adăugarea cheii în authorized_keys" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "→ Testez conexiunea SSH fără parolă..." -ForegroundColor Yellow
|
||||
|
||||
# Testează conexiunea (cu cheia din profilul SYSTEM)
|
||||
$testResult = & ssh -o StrictHostKeyChecking=no -i $PrivateKeyPath "${ProxmoxUser}@${ProxmoxHost}" "echo 'SSH connection OK'" 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "✓ Conexiune SSH funcționează fără parolă!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "✗ Conexiunea SSH nu funcționează direct din acest cont" -ForegroundColor Yellow
|
||||
Write-Host " Dar cheia a fost adăugată - scheduled tasks (SYSTEM) ar trebui să funcționeze" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=========================================" -ForegroundColor Green
|
||||
Write-Host "✓ Setup complet!" -ForegroundColor Green
|
||||
Write-Host "=========================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Cheia din profilul SYSTEM: $PrivateKeyPath" -ForegroundColor Cyan
|
||||
Write-Host "Scheduled tasks vor folosi această cheie automat." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
80
proxmox/vm109-windows-dr/scripts/fix_ssh_via_service.ps1
Normal file
80
proxmox/vm109-windows-dr/scripts/fix_ssh_via_service.ps1
Normal file
@@ -0,0 +1,80 @@
|
||||
# Fix SSH Keys by recreating through SSH service
|
||||
# Run as Administrator on DR VM (10.0.20.37)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "=== Fix SSH Keys via Service Method ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Step 1: Stop SSH service
|
||||
Write-Host "[1/4] Stopping SSH service..." -ForegroundColor Yellow
|
||||
Stop-Service sshd
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Host " SSH service stopped" -ForegroundColor Green
|
||||
|
||||
# Step 2: Delete the problematic file while service is stopped
|
||||
Write-Host "[2/4] Deleting old authorized_keys file..." -ForegroundColor Yellow
|
||||
$authKeysFile = "C:\ProgramData\ssh\administrators_authorized_keys"
|
||||
|
||||
if (Test-Path $authKeysFile) {
|
||||
# Try to take ownership first
|
||||
takeown /F $authKeysFile /A
|
||||
icacls $authKeysFile /grant Administrators:F
|
||||
Remove-Item $authKeysFile -Force
|
||||
Write-Host " Old file deleted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " File doesn't exist" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Step 3: Create new file with both keys
|
||||
Write-Host "[3/4] Creating new authorized_keys file..." -ForegroundColor Yellow
|
||||
|
||||
$bothKeys = @"
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC88mX/oQnAoU54kazp6iYmmg91IR8pbnYV3aw5aJfSsiSBUjqo+XbvrWRvq//lli48k2kuNfq8olKrPvqKHcIccbcbgFrES5k2ErSyXjvbUlxuyHFRIfBoXvAhMMX6LZR+4Qc0i3VThQ1PgY0tYDbf2XQBAyrog5EU9H/q2NzJEulTs7kSR0FIt1goWXqKJYLA9Pn7Ardt7doPzR8EH/spB8hXctO0BaAorX3p3rd4bvOZoOcht4pTmyJBRzoZRRlscCZRCOxjQDk+y4v9eOPzwMc0dRlVxIbqt8Sua5khGTlmeQTmDqxCmdtgrTNWT4hwPVG1L4Jfw2bgX3IqCGKB4juDUF+Eh6hrQeuTIF7xbCIGGy9N/lKIKO3vr4sTf51gVM9CWJ0bE/CTKbiRPfWbUXIUA4yZ96gJf0QAqcIcutnntomdtkdV8G1RYVKSQEE4oxF3mCRxR+1d5Fn/UXGlms9Q2u/QAq7n5BYLPczUFSkdBdfITOqiCIzlX8WpPD7v/vt8Wsbyf24B/FSYvp+X0AcX5qQbNeljChAxqRy6VNhmh5ucUkMFxfUSTWij+AVqmCPvxVVFKPw32G6jN59BmwirmIxd0i6wTRj3rrUuyO/6+kjErjthkYKFIDBAgdCnV0rrkrPRNKmbS0DtgRcID3ILq2UqR3AYmDf2azf8hQ== mmarius28@gmail.com
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3EdHswdNDuDC9kJdUli2zGGPVlEWJjmqtb4eABYWwjQnWqjGp8oAFbQ+r2TxR544WtEyhDL9BU6oO3EFH957DBGQJHJvfRgx2VnkNZEzN/XX/7HK6Cp5rlINGGp26PjHulKkZjARmjC3YK0aUFEkiyNyUBqhtQpmcYP4+wjUfiiO2xUkF9mzGplbWGK3ZmEdkWNd5BNddqxmxyLvd2KHAo8F7Vux9SyPWqZ8bwiDyidAMDU7kCXS/RabUMl2LGajzFbRnR87YA7cIaVFl/IWExO/fsYlgkwmmmIAMdjINp0IWDdydnmG1XNNhM8h/BKY/eK3uile8CvyEbmbuf0+ePm3Ex9vTjn4jYN2vFE148FgQGGTibibJ+sXFoQ87VFNGEuog/V0aajVk/xcOihszsEvzD2IV/tSKJFdI6klnYLuieuMZf7Dvs/6sC1l3dnsBtcpvjnU48altRRZvQzaJf3gIkG1lRGBgyW1n+WHe/7StIveYTVNFtx+fcnqB8gm9fZQxBp2WRbLNFpY/Qj+6BF66b1A2ZxH/3F9Z/6VT91EActOf+AMxjsI+09d7IRYIvzr8OxMPYOHU2bglp3o86xZEMUXfcjB8Sw/8KMsCjBp3ABEN9/bwv1496aw9IC67ZBQ2cDDfgdBej5DAkT4NS2XIx7wbM7sBtLYjcXMi7w== administrator@ROA-CARAPETRU2
|
||||
"@
|
||||
|
||||
# Create the file
|
||||
$bothKeys | Out-File -FilePath $authKeysFile -Encoding ASCII -NoNewline -Force
|
||||
|
||||
# Set permissions using icacls (more reliable than PowerShell ACL)
|
||||
icacls $authKeysFile /inheritance:r
|
||||
icacls $authKeysFile /grant "NT AUTHORITY\SYSTEM:(F)"
|
||||
icacls $authKeysFile /grant "BUILTIN\Administrators:(R)"
|
||||
|
||||
Write-Host " New file created with correct permissions" -ForegroundColor Green
|
||||
|
||||
# Step 4: Start SSH service
|
||||
Write-Host "[4/4] Starting SSH service..." -ForegroundColor Yellow
|
||||
Start-Service sshd
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Host " SSH service started" -ForegroundColor Green
|
||||
|
||||
# Verification
|
||||
Write-Host ""
|
||||
Write-Host "=== Verification ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "File permissions:" -ForegroundColor Yellow
|
||||
icacls $authKeysFile
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "File content (number of lines):" -ForegroundColor Yellow
|
||||
$lines = Get-Content $authKeysFile
|
||||
Write-Host " Total keys: $($lines.Count)" -ForegroundColor White
|
||||
|
||||
foreach ($line in $lines) {
|
||||
if ($line -match "ssh-rsa .+ (.+)$") {
|
||||
Write-Host " ✓ $($matches[1])" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "SSH service status:" -ForegroundColor Yellow
|
||||
Get-Service sshd | Format-Table Name, Status, StartType -AutoSize
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Setup Complete ===" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next: Test SSH connection from PRIMARY server" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
34
proxmox/vm109-windows-dr/scripts/initROA.ora
Normal file
34
proxmox/vm109-windows-dr/scripts/initROA.ora
Normal file
@@ -0,0 +1,34 @@
|
||||
# Initialization Parameters for ROA Database - DR VM
|
||||
# Generated: 2025-10-09 04:07:45
|
||||
|
||||
# Database Identification
|
||||
db_name=ROA
|
||||
db_unique_name=ROA
|
||||
|
||||
# Memory Configuration
|
||||
memory_target=1024M
|
||||
memory_max_target=1024M
|
||||
|
||||
# File Locations
|
||||
control_files=('C:\Users\oracle\oradata\ROA\control01.ctl', 'C:\Users\oracle\recovery_area\ROA\control02.ctl')
|
||||
db_recovery_file_dest='C:\Users\oracle\recovery_area'
|
||||
db_recovery_file_dest_size=20G
|
||||
audit_file_dest='C:\Users\oracle\admin\ROA\adump'
|
||||
|
||||
# Redo and Archive Log
|
||||
log_archive_format=%t_%s_%r.dbf
|
||||
|
||||
# Compatibility
|
||||
compatible=19.0.0
|
||||
|
||||
# Character Set
|
||||
nls_language=AMERICAN
|
||||
nls_territory=AMERICA
|
||||
|
||||
# Processes and Sessions
|
||||
processes=300
|
||||
sessions=472
|
||||
|
||||
# Miscellaneous
|
||||
diagnostic_dest='C:\Users\oracle'
|
||||
_allow_resetlogs_corruption=TRUE
|
||||
@@ -0,0 +1,512 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Oracle Backup Monitor for Proxmox with PVE::Notify
|
||||
# Monitors Oracle backups and sends notifications via Proxmox notification system
|
||||
#
|
||||
# Location: /opt/scripts/oracle-backup-monitor-proxmox.sh (on Proxmox host)
|
||||
# Schedule: Add to cron for daily execution
|
||||
#
|
||||
# This script is SELF-SUFFICIENT:
|
||||
# - Automatically creates notification templates if they don't exist
|
||||
# - Uses Proxmox native notification system (same as HA alerts)
|
||||
# - No email configuration needed - uses existing Proxmox setup
|
||||
#
|
||||
# Installation:
|
||||
# cp oracle-backup-monitor-proxmox.sh /opt/scripts/
|
||||
# chmod +x /opt/scripts/oracle-backup-monitor-proxmox.sh
|
||||
# /opt/scripts/oracle-backup-monitor-proxmox.sh --install # Creates templates
|
||||
# crontab -e # Add: 0 9 * * * /opt/scripts/oracle-backup-monitor-proxmox.sh
|
||||
#
|
||||
# Author: Claude (based on ha-monitor.sh pattern)
|
||||
# Version: 1.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
PRIMARY_HOST="10.0.20.36"
|
||||
PRIMARY_PORT="22122"
|
||||
PRIMARY_USER="Administrator"
|
||||
BACKUP_PATH="/mnt/pve/oracle-backups/ROA/autobackup"
|
||||
MAX_FULL_AGE_HOURS=25
|
||||
MAX_CUMULATIVE_AGE_HOURS=7
|
||||
TEMPLATE_DIR="/usr/share/pve-manager/templates/default"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Function to create notification templates
|
||||
create_templates() {
|
||||
echo -e "${GREEN}Creating Oracle backup notification templates...${NC}"
|
||||
|
||||
# Create templates directory if needed
|
||||
mkdir -p "$TEMPLATE_DIR"
|
||||
|
||||
# Subject template
|
||||
cat > "$TEMPLATE_DIR/oracle-backup-subject.txt.hbs" <<'EOF'
|
||||
Oracle Backup {{status}} | {{node}}
|
||||
EOF
|
||||
|
||||
# Text body template
|
||||
cat > "$TEMPLATE_DIR/oracle-backup-body.txt.hbs" <<'EOF'
|
||||
Oracle Backup {{status}} | {{node}}
|
||||
Date: {{date}}
|
||||
|
||||
SUMMARY
|
||||
- Full backup: {{full_backup_age}}h (limit {{full_backup_limit}}h) -> {{#if full_backup_ok}}OK{{else}}CHECK{{/if}}
|
||||
- Incremental: {{cumulative_backup_age}}h (limit {{cumulative_backup_limit}}h) -> {{#if cumulative_backup_ok}}OK{{else}}CHECK{{/if}}
|
||||
- Backups: {{total_backups}} files ({{total_size_label}})
|
||||
- Disk usage: {{disk_usage}}%
|
||||
|
||||
{{#if has_errors}}
|
||||
ISSUES
|
||||
{{#each errors}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if has_warnings}}
|
||||
WARNINGS
|
||||
{{#each warnings}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
FULL BACKUPS ({{full_backup_count}} files)
|
||||
{{#if has_full_backups}}
|
||||
{{#each full_backup_list}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
- none detected
|
||||
{{/if}}
|
||||
|
||||
INCREMENTAL BACKUPS ({{incr_backup_count}} files)
|
||||
{{#if has_incr_backups}}
|
||||
{{#each incr_backup_list}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
- none detected
|
||||
{{/if}}
|
||||
|
||||
Next check: +24h via Proxmox Monitor
|
||||
EOF
|
||||
|
||||
# HTML body template (lightweight Gmail-friendly)
|
||||
cat > "$TEMPLATE_DIR/oracle-backup-body.html.hbs" <<'EOF'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Oracle Backup {{status}} | {{node}}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:16px;font-family:Arial,Helvetica,sans-serif;background:#ffffff;color:#2c3e50;">
|
||||
<table style="width:100%;max-width:640px;margin:0 auto;border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="padding:0 0 12px 0;font-size:18px;font-weight:600;">
|
||||
Oracle Backup {{status}} | {{node}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 0 16px 0;font-size:13px;color:#6c757d;">
|
||||
{{date}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:12px;border:1px solid #e1e4e8;border-radius:4px;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;">
|
||||
<tr>
|
||||
<td style="padding:4px 0;">Full backup</td>
|
||||
<td style="padding:4px 0;text-align:right;">
|
||||
{{full_backup_age}}h / {{full_backup_limit}}h · {{#if full_backup_ok}}OK{{else}}CHECK{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:4px 0;">Incremental</td>
|
||||
<td style="padding:4px 0;text-align:right;">
|
||||
{{cumulative_backup_age}}h / {{cumulative_backup_limit}}h · {{#if cumulative_backup_ok}}OK{{else}}CHECK{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:4px 0;">Backups</td>
|
||||
<td style="padding:4px 0;text-align:right;">{{total_backups}} files ({{total_size_label}})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:4px 0;">Disk usage</td>
|
||||
<td style="padding:4px 0;text-align:right;">{{disk_usage}}%</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{#if has_errors}}
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;background:#fff5f5;border:1px solid #f1b0b7;border-radius:4px;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;color:#c82333;">Issues</td></tr>
|
||||
{{#each errors}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #f8d7da;">• {{this}}</td></tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
{{#if has_warnings}}
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;background:#fff8e5;border:1px solid #ffe8a1;border-radius:4px;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;color:#856404;">Warnings</td></tr>
|
||||
{{#each warnings}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #ffe8a1;">• {{this}}</td></tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px;border:1px solid #e1e4e8;border-radius:4px;background:#f9fafb;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;">FULL Backups ({{full_backup_count}} files)</td></tr>
|
||||
{{#if has_full_backups}}
|
||||
{{#each full_backup_list}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">• {{this}}</td></tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">• none detected</td></tr>
|
||||
{{/if}}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px;border:1px solid #e1e4e8;border-radius:4px;background:#f9fafb;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;">INCREMENTAL Backups ({{incr_backup_count}} files)</td></tr>
|
||||
{{#if has_incr_backups}}
|
||||
{{#each incr_backup_list}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">• {{this}}</td></tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">• none detected</td></tr>
|
||||
{{/if}}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;font-size:12px;color:#6c757d;">
|
||||
Next automated check: +24h via Proxmox Monitor
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}Templates created successfully in $TEMPLATE_DIR${NC}"
|
||||
}
|
||||
|
||||
# Function to send notification via PVE::Notify
|
||||
send_pve_notification() {
|
||||
local severity="$1"
|
||||
local status="$2"
|
||||
local data="$3"
|
||||
|
||||
# Create Perl script to call PVE::Notify
|
||||
cat > /tmp/oracle-notify.pl <<'PERL_SCRIPT'
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Notify;
|
||||
use JSON;
|
||||
|
||||
my $json_data = do { local $/; <STDIN> };
|
||||
my $data = decode_json($json_data);
|
||||
|
||||
my $severity = $data->{severity} // 'info';
|
||||
my $template_name = 'oracle-backup';
|
||||
|
||||
# Add fields for matching rules
|
||||
my $fields = {
|
||||
type => 'oracle-backup',
|
||||
severity => $severity,
|
||||
hostname => $data->{node} // 'unknown',
|
||||
};
|
||||
|
||||
# Send notification
|
||||
eval {
|
||||
PVE::Notify::notify(
|
||||
$severity,
|
||||
$template_name,
|
||||
$data,
|
||||
$fields
|
||||
);
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
print "Error sending notification: $@\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
print "Notification sent successfully\n";
|
||||
PERL_SCRIPT
|
||||
|
||||
chmod +x /tmp/oracle-notify.pl
|
||||
|
||||
# Send notification
|
||||
echo "$data" | perl /tmp/oracle-notify.pl
|
||||
|
||||
rm -f /tmp/oracle-notify.pl
|
||||
}
|
||||
|
||||
# Function to check backups
|
||||
check_backups() {
|
||||
local status="OK"
|
||||
local errors=()
|
||||
local warnings=()
|
||||
|
||||
echo "Checking Oracle backups..."
|
||||
|
||||
local total_backups=0
|
||||
local total_size_label="0G"
|
||||
local full_age_hours="N/A"
|
||||
local cumulative_age_hours="N/A"
|
||||
local full_backup_ok=false
|
||||
local cumulative_backup_ok=false
|
||||
local disk_usage=0
|
||||
local -a backup_entries=()
|
||||
|
||||
if [ ! -d "$BACKUP_PATH" ]; then
|
||||
status="ERROR"
|
||||
errors+=("Backup path $BACKUP_PATH not accessible")
|
||||
else
|
||||
if compgen -G "$BACKUP_PATH"/*.BKP > /dev/null; then
|
||||
total_backups=$(find "$BACKUP_PATH" -maxdepth 1 -type f -name '*.BKP' | wc -l)
|
||||
total_backups=${total_backups//[[:space:]]/}
|
||||
[ -z "$total_backups" ] && total_backups=0
|
||||
local total_size=$(du -shc "$BACKUP_PATH"/*.BKP 2>/dev/null | tail -1 | awk '{print $1}')
|
||||
[ -z "$total_size" ] && total_size="0G"
|
||||
total_size_label="$total_size"
|
||||
|
||||
# Search for FULL backups (both old and new naming conventions)
|
||||
# Old format: *FULL*.BKP, New format: L0_*.BKP
|
||||
local latest_full=$(find "$BACKUP_PATH" -maxdepth 1 -type f \( -name '*FULL*.BKP' -o -name 'L0_*.BKP' \) -printf '%T@ %p\n' | sort -nr | head -1 | cut -d' ' -f2-)
|
||||
if [ -n "$latest_full" ]; then
|
||||
local full_timestamp=$(stat -c %Y "$latest_full")
|
||||
local current_timestamp=$(date +%s)
|
||||
full_age_hours=$(( (current_timestamp - full_timestamp) / 3600 ))
|
||||
if [ "$full_age_hours" -gt "$MAX_FULL_AGE_HOURS" ]; then
|
||||
status="WARNING"
|
||||
warnings+=("FULL backup is $full_age_hours hours old (threshold: $MAX_FULL_AGE_HOURS)")
|
||||
else
|
||||
full_backup_ok=true
|
||||
fi
|
||||
else
|
||||
status="ERROR"
|
||||
errors+=("No FULL backup found")
|
||||
fi
|
||||
|
||||
# Search for INCREMENTAL backups (both old and new naming conventions)
|
||||
# Old format: *INCR*.BKP, *INCREMENTAL*.BKP, *CUMULATIVE*.BKP
|
||||
# New format: L1_*.BKP
|
||||
local latest_cumulative=$(find "$BACKUP_PATH" -maxdepth 1 -type f \( -name '*INCR*.BKP' -o -name '*INCREMENTAL*.BKP' -o -name '*CUMULATIVE*.BKP' -o -name 'L1_*.BKP' \) -printf '%T@ %p\n' | sort -nr | head -1 | cut -d' ' -f2-)
|
||||
if [ -n "$latest_cumulative" ]; then
|
||||
local cumulative_timestamp=$(stat -c %Y "$latest_cumulative")
|
||||
local current_timestamp=$(date +%s)
|
||||
cumulative_age_hours=$(( (current_timestamp - cumulative_timestamp) / 3600 ))
|
||||
if [ "$cumulative_age_hours" -gt "$MAX_CUMULATIVE_AGE_HOURS" ]; then
|
||||
if [ "$status" != "ERROR" ]; then status="WARNING"; fi
|
||||
warnings+=("CUMULATIVE backup is $cumulative_age_hours hours old (threshold: $MAX_CUMULATIVE_AGE_HOURS)")
|
||||
else
|
||||
cumulative_backup_ok=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Collect ALL FULL backups (both old and new naming conventions)
|
||||
local -a full_backups=()
|
||||
local -a full_backup_entries=()
|
||||
if readarray -t full_backups < <(find "$BACKUP_PATH" -maxdepth 1 -type f \( -name '*FULL*.BKP' -o -name 'L0_*.BKP' \) -printf '%T@ %p\n' | sort -nr | cut -d' ' -f2-); then
|
||||
for backup_file in "${full_backups[@]}"; do
|
||||
[ -z "$backup_file" ] && continue
|
||||
local backup_name=$(basename "$backup_file")
|
||||
local backup_time=$(date -r "$backup_file" '+%Y-%m-%d %H:%M')
|
||||
local backup_size=$(du -sh "$backup_file" 2>/dev/null | cut -f1)
|
||||
[ -z "$backup_size" ] && backup_size="N/A"
|
||||
full_backup_entries+=("$backup_time | $backup_name | $backup_size")
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect ALL INCREMENTAL backups (both old and new naming conventions)
|
||||
local -a incr_backups=()
|
||||
local -a incr_backup_entries=()
|
||||
if readarray -t incr_backups < <(find "$BACKUP_PATH" -maxdepth 1 -type f \( -name '*INCR*.BKP' -o -name '*INCREMENTAL*.BKP' -o -name '*CUMULATIVE*.BKP' -o -name 'L1_*.BKP' \) -printf '%T@ %p\n' | sort -nr | cut -d' ' -f2-); then
|
||||
for backup_file in "${incr_backups[@]}"; do
|
||||
[ -z "$backup_file" ] && continue
|
||||
local backup_name=$(basename "$backup_file")
|
||||
local backup_time=$(date -r "$backup_file" '+%Y-%m-%d %H:%M')
|
||||
local backup_size=$(du -sh "$backup_file" 2>/dev/null | cut -f1)
|
||||
[ -z "$backup_size" ] && backup_size="N/A"
|
||||
incr_backup_entries+=("$backup_time | $backup_name | $backup_size")
|
||||
done
|
||||
fi
|
||||
else
|
||||
status="ERROR"
|
||||
errors+=("No backup files found in $BACKUP_PATH")
|
||||
fi
|
||||
|
||||
local disk_usage_raw=$(df "$BACKUP_PATH" 2>/dev/null | tail -1 | awk '{print int($5)}')
|
||||
if [ -n "$disk_usage_raw" ]; then
|
||||
disk_usage="$disk_usage_raw"
|
||||
else
|
||||
if [ "$status" = "OK" ]; then status="WARNING"; fi
|
||||
warnings+=("Unable to determine disk usage for $BACKUP_PATH")
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$disk_usage" -gt 90 ]; then
|
||||
status="ERROR"
|
||||
errors+=("Disk usage critical: ${disk_usage}%")
|
||||
elif [ "$disk_usage" -gt 80 ]; then
|
||||
if [ "$status" != "ERROR" ]; then status="WARNING"; fi
|
||||
warnings+=("Disk usage high: ${disk_usage}%")
|
||||
fi
|
||||
|
||||
local severity="info"
|
||||
[ "$status" = "WARNING" ] && severity="warning"
|
||||
[ "$status" = "ERROR" ] && severity="error"
|
||||
|
||||
local errors_json
|
||||
if [ ${#errors[@]} -eq 0 ]; then
|
||||
errors_json='[]'
|
||||
else
|
||||
errors_json=$(printf '%s\n' "${errors[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
|
||||
local warnings_json
|
||||
if [ ${#warnings[@]} -eq 0 ]; then
|
||||
warnings_json='[]'
|
||||
else
|
||||
warnings_json=$(printf '%s\n' "${warnings[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
|
||||
local full_backup_list_json
|
||||
if [ ${#full_backup_entries[@]} -eq 0 ]; then
|
||||
full_backup_list_json='[]'
|
||||
else
|
||||
full_backup_list_json=$(printf '%s\n' "${full_backup_entries[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
|
||||
local incr_backup_list_json
|
||||
if [ ${#incr_backup_entries[@]} -eq 0 ]; then
|
||||
incr_backup_list_json='[]'
|
||||
else
|
||||
incr_backup_list_json=$(printf '%s\n' "${incr_backup_entries[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
|
||||
local has_errors=false
|
||||
local has_warnings=false
|
||||
local has_full_backups=false
|
||||
local has_incr_backups=false
|
||||
[ ${#errors[@]} -gt 0 ] && has_errors=true
|
||||
[ ${#warnings[@]} -gt 0 ] && has_warnings=true
|
||||
[ ${#full_backup_entries[@]} -gt 0 ] && has_full_backups=true
|
||||
[ ${#incr_backup_entries[@]} -gt 0 ] && has_incr_backups=true
|
||||
|
||||
local json_data=$(cat <<JSON
|
||||
{
|
||||
"severity": "$severity",
|
||||
"node": "$(hostname)",
|
||||
"date": "$(date +'%Y-%m-%d %H:%M:%S')",
|
||||
"status": "$status",
|
||||
"errors": $errors_json,
|
||||
"warnings": $warnings_json,
|
||||
"has_errors": $has_errors,
|
||||
"has_warnings": $has_warnings,
|
||||
"total_backups": $total_backups,
|
||||
"total_size_gb": "${total_size_label%G}",
|
||||
"total_size_label": "$total_size_label",
|
||||
"full_backup_age": "${full_age_hours}",
|
||||
"cumulative_backup_age": "${cumulative_age_hours}",
|
||||
"disk_usage": "${disk_usage}",
|
||||
"full_backup_ok": $([ "$full_backup_ok" = "true" ] && echo "true" || echo "false"),
|
||||
"cumulative_backup_ok": $([ "$cumulative_backup_ok" = "true" ] && echo "true" || echo "false"),
|
||||
"is_error": $([ "$status" = "ERROR" ] && echo "true" || echo "false"),
|
||||
"is_warning": $([ "$status" = "WARNING" ] && echo "true" || echo "false"),
|
||||
"full_backup_list": $full_backup_list_json,
|
||||
"incr_backup_list": $incr_backup_list_json,
|
||||
"has_full_backups": $has_full_backups,
|
||||
"has_incr_backups": $has_incr_backups,
|
||||
"full_backup_count": ${#full_backup_entries[@]},
|
||||
"incr_backup_count": ${#incr_backup_entries[@]},
|
||||
"full_backup_limit": "$MAX_FULL_AGE_HOURS",
|
||||
"cumulative_backup_limit": "$MAX_CUMULATIVE_AGE_HOURS"
|
||||
}
|
||||
JSON
|
||||
)
|
||||
|
||||
if [ "$status" != "OK" ]; then
|
||||
echo -e "${YELLOW}Issues detected, sending notification...${NC}"
|
||||
send_pve_notification "$severity" "$status" "$json_data"
|
||||
else
|
||||
echo -e "${GREEN}All backups are healthy${NC}"
|
||||
# Optionally send success notification (uncomment if desired)
|
||||
# send_pve_notification "info" "$status" "$json_data"
|
||||
fi
|
||||
|
||||
echo "Status: $status"
|
||||
echo "Total backups: $total_backups"
|
||||
echo "Total size: $total_size_label"
|
||||
echo "FULL backup age: $full_age_hours hours"
|
||||
echo "CUMULATIVE backup age: $cumulative_age_hours hours"
|
||||
echo "Disk usage: ${disk_usage}%"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
--install)
|
||||
create_templates
|
||||
echo ""
|
||||
echo -e "${GREEN}Installation complete!${NC}"
|
||||
echo "Next steps:"
|
||||
echo "1. Test the monitor: /opt/scripts/oracle-backup-monitor-proxmox.sh"
|
||||
echo "2. Add to cron: crontab -e"
|
||||
echo " Add line: 0 9 * * * /opt/scripts/oracle-backup-monitor-proxmox.sh"
|
||||
echo "3. Configure notifications in Proxmox GUI if needed:"
|
||||
echo " Datacenter > Notifications > Add matching rules for 'oracle-backup'"
|
||||
;;
|
||||
--help)
|
||||
echo "Oracle Backup Monitor for Proxmox"
|
||||
echo "Usage:"
|
||||
echo " $0 - Check backups and send alerts if issues found"
|
||||
echo " $0 --install - Create notification templates"
|
||||
echo " $0 --help - Show this help"
|
||||
;;
|
||||
*)
|
||||
# Check if templates exist, create if missing
|
||||
if [ ! -f "$TEMPLATE_DIR/oracle-backup-subject.txt.hbs" ]; then
|
||||
echo -e "${YELLOW}Templates not found, creating...${NC}"
|
||||
create_templates
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Run backup check
|
||||
check_backups
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check dependencies
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo -e "${RED}Error: jq is not installed${NC}"
|
||||
echo "Install with: apt-get install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
main "$@"
|
||||
43
proxmox/vm109-windows-dr/scripts/rman_backup.bat
Normal file
43
proxmox/vm109-windows-dr/scripts/rman_backup.bat
Normal file
@@ -0,0 +1,43 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM ===================================================================
|
||||
REM Oracle RMAN FULL Backup with Logging
|
||||
REM Runs at: 02:30 AM (scheduled task)
|
||||
REM Duration: ~30 minutes
|
||||
REM ===================================================================
|
||||
|
||||
REM Get script directory (where this .bat file is located)
|
||||
set SCRIPTDIR=%~dp0
|
||||
set LOGDIR=%SCRIPTDIR%logs
|
||||
set TIMESTAMP=%date:~-4,4%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%%time:~6,2%
|
||||
set TIMESTAMP=%TIMESTAMP: =0%
|
||||
set LOGFILE=%LOGDIR%\backup_full_%TIMESTAMP%.log
|
||||
|
||||
REM Create log directory if not exists
|
||||
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
|
||||
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
echo Oracle RMAN FULL Backup Started >> "%LOGFILE%" 2>&1
|
||||
echo Date: %date% %time% >> "%LOGFILE%" 2>&1
|
||||
echo Script Directory: %SCRIPTDIR% >> "%LOGFILE%" 2>&1
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
echo. >> "%LOGFILE%" 2>&1
|
||||
|
||||
REM Run RMAN backup - show in console AND save to log (using PowerShell tee)
|
||||
powershell -Command "& cmd.exe /c 'rman target sys/romfastsoft@roa cmdfile=\"%SCRIPTDIR%rman_backup.txt\"' 2>&1 | Tee-Object -FilePath '%LOGFILE%' -Append"
|
||||
|
||||
set EXITCODE=%ERRORLEVEL%
|
||||
|
||||
echo. >> "%LOGFILE%" 2>&1
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
echo Oracle RMAN FULL Backup Completed >> "%LOGFILE%" 2>&1
|
||||
echo Date: %date% %time% >> "%LOGFILE%" 2>&1
|
||||
echo Exit Code: %EXITCODE% >> "%LOGFILE%" 2>&1
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
|
||||
REM Print summary to console
|
||||
echo [%date% %time%] RMAN FULL backup completed with exit code: %EXITCODE%
|
||||
echo [%date% %time%] Log file: %LOGFILE%
|
||||
|
||||
exit /b %EXITCODE%
|
||||
30
proxmox/vm109-windows-dr/scripts/rman_backup.txt
Normal file
30
proxmox/vm109-windows-dr/scripts/rman_backup.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
RUN {
|
||||
CONFIGURE RETENTION POLICY TO REDUNDANCY 2;
|
||||
CONFIGURE CONTROLFILE AUTOBACKUP ON;
|
||||
CONFIGURE COMPRESSION ALGORITHM 'BASIC';
|
||||
|
||||
# Full backup COMPRESSED + Archive logs (șterge logs după backup)
|
||||
# FORMAT: L0_<dbname>_<YYYYMMDD>_<set#>_<piece#>
|
||||
# Files will be stored in recovery area for easier transfer to DR
|
||||
BACKUP AS COMPRESSED BACKUPSET
|
||||
INCREMENTAL LEVEL 0
|
||||
TAG 'DAILY_FULL_COMPRESSED'
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\L0_%d_%T_%s_%p.BKP'
|
||||
DATABASE
|
||||
PLUS ARCHIVELOG DELETE INPUT
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\ARC_%d_%T_%s_%p.BKP';
|
||||
|
||||
# Backup SPFILE și Control File
|
||||
BACKUP AS COMPRESSED BACKUPSET
|
||||
TAG 'SPFILE_BACKUP'
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\SPFILE_%d_%T_%s_%p.BKP'
|
||||
SPFILE;
|
||||
|
||||
BACKUP
|
||||
TAG 'CONTROLFILE_BACKUP'
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\CF_%d_%T_%s_%p.BKP'
|
||||
CURRENT CONTROLFILE;
|
||||
|
||||
# Cleanup old backups (păstrează ultimele 2 - REDUNDANCY 2)
|
||||
DELETE NOPROMPT OBSOLETE;
|
||||
}
|
||||
43
proxmox/vm109-windows-dr/scripts/rman_backup_incremental.bat
Normal file
43
proxmox/vm109-windows-dr/scripts/rman_backup_incremental.bat
Normal file
@@ -0,0 +1,43 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM ===================================================================
|
||||
REM Oracle RMAN INCREMENTAL/CUMULATIVE Backup with Logging
|
||||
REM Runs at: 13:00 and 18:00 (scheduled tasks)
|
||||
REM Duration: ~5-10 minutes
|
||||
REM ===================================================================
|
||||
|
||||
REM Get script directory (where this .bat file is located)
|
||||
set SCRIPTDIR=%~dp0
|
||||
set LOGDIR=%SCRIPTDIR%logs
|
||||
set TIMESTAMP=%date:~-4,4%%date:~-7,2%%date:~-10,2%_%time:~0,2%%time:~3,2%%time:~6,2%
|
||||
set TIMESTAMP=%TIMESTAMP: =0%
|
||||
set LOGFILE=%LOGDIR%\backup_incremental_%TIMESTAMP%.log
|
||||
|
||||
REM Create log directory if not exists
|
||||
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
|
||||
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
echo Oracle RMAN INCREMENTAL Backup Started >> "%LOGFILE%" 2>&1
|
||||
echo Date: %date% %time% >> "%LOGFILE%" 2>&1
|
||||
echo Script Directory: %SCRIPTDIR% >> "%LOGFILE%" 2>&1
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
echo. >> "%LOGFILE%" 2>&1
|
||||
|
||||
REM Run RMAN backup - show in console AND save to log (using PowerShell tee)
|
||||
powershell -Command "& cmd.exe /c 'rman target sys/romfastsoft@roa cmdfile=\"%SCRIPTDIR%rman_backup_incremental.txt\"' 2>&1 | Tee-Object -FilePath '%LOGFILE%' -Append"
|
||||
|
||||
set EXITCODE=%ERRORLEVEL%
|
||||
|
||||
echo. >> "%LOGFILE%" 2>&1
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
echo Oracle RMAN INCREMENTAL Backup Completed >> "%LOGFILE%" 2>&1
|
||||
echo Date: %date% %time% >> "%LOGFILE%" 2>&1
|
||||
echo Exit Code: %EXITCODE% >> "%LOGFILE%" 2>&1
|
||||
echo ========================================= >> "%LOGFILE%" 2>&1
|
||||
|
||||
REM Print summary to console
|
||||
echo [%date% %time%] RMAN INCREMENTAL backup completed with exit code: %EXITCODE%
|
||||
echo [%date% %time%] Log file: %LOGFILE%
|
||||
|
||||
exit /b %EXITCODE%
|
||||
26
proxmox/vm109-windows-dr/scripts/rman_backup_incremental.txt
Normal file
26
proxmox/vm109-windows-dr/scripts/rman_backup_incremental.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
RUN {
|
||||
# Incremental Level 1 CUMULATIVE backup
|
||||
# Backup doar modificările de la ultimul Level 0 (full backup de la 02:00 AM)
|
||||
# FORMAT: L1_<dbname>_<YYYYMMDD>_<set#>_<piece#>
|
||||
# Files will be stored in recovery area for easier transfer to DR
|
||||
BACKUP AS COMPRESSED BACKUPSET
|
||||
INCREMENTAL LEVEL 1 CUMULATIVE
|
||||
TAG 'MIDDAY_INCREMENTAL'
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\L1_%d_%T_%s_%p.BKP'
|
||||
DATABASE
|
||||
PLUS ARCHIVELOG DELETE INPUT
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\ARC_%d_%T_%s_%p.BKP';
|
||||
|
||||
# Backup SPFILE și controlfile (pentru siguranță)
|
||||
BACKUP AS COMPRESSED BACKUPSET
|
||||
TAG 'SPFILE_BACKUP'
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\SPFILE_%d_%T_%s_%p.BKP'
|
||||
SPFILE;
|
||||
|
||||
BACKUP
|
||||
TAG 'CONTROLFILE_BACKUP'
|
||||
FORMAT 'C:\Users\oracle\recovery_area\ROA\autobackup\CF_%d_%T_%s_%p.BKP'
|
||||
CURRENT CONTROLFILE;
|
||||
|
||||
# NU ștergem obsolete aici - se face la full backup
|
||||
}
|
||||
550
proxmox/vm109-windows-dr/scripts/rman_restore_from_zero.ps1
Normal file
550
proxmox/vm109-windows-dr/scripts/rman_restore_from_zero.ps1
Normal file
@@ -0,0 +1,550 @@
|
||||
# RMAN Restore Database FROM ZERO - Clean State (PowerShell)
|
||||
# Backups are on F:\ (NFS mount from Proxmox host)
|
||||
# Run as: Administrator
|
||||
# Location: D:\oracle\scripts\rman_restore_from_zero.ps1
|
||||
#
|
||||
# Parameters:
|
||||
# -TestMode: Skip service reconfiguration and Listener startup (for weekly DR tests)
|
||||
|
||||
param(
|
||||
[switch]$TestMode
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
$env:ORACLE_HOME = "C:\Users\Administrator\Downloads\WINDOWS.X64_193000_db_home"
|
||||
$env:ORACLE_SID = "ROA"
|
||||
$env:PATH = "$env:ORACLE_HOME\bin;$env:PATH"
|
||||
|
||||
Write-Host "============================================"
|
||||
Write-Host "RMAN Database Restore FROM ZERO"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
Write-Host "Database: ROA"
|
||||
Write-Host "DBID: 1363569330"
|
||||
Write-Host "Backups: F:\ROA\autobackup (NFS mount from Proxmox)"
|
||||
Write-Host ""
|
||||
Write-Host "This script will:"
|
||||
Write-Host " 1. CLEANUP: Delete any existing database files"
|
||||
Write-Host " 2. RESTORE: Restore from F:\ backups"
|
||||
Write-Host " 3. VERIFY: Check database is working"
|
||||
Write-Host ""
|
||||
|
||||
# Verify F:\ mount is accessible
|
||||
if (-not (Test-Path "F:\ROA\autobackup")) {
|
||||
Write-Host "ERROR: F:\ROA\autobackup not accessible!" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Please verify:"
|
||||
Write-Host " 1. F:\ drive is mounted: dir F:\"
|
||||
Write-Host " 2. NFS mount command: mount -o rw,nolock,mtype=hard,timeout=60 10.0.20.202:/mnt/pve/oracle-backups F:"
|
||||
Write-Host " 3. Proxmox host is reachable: ping 10.0.20.202"
|
||||
Write-Host ""
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[OK] F:\ROA\autobackup is accessible"
|
||||
Write-Host ""
|
||||
|
||||
# Create directories with proper permissions
|
||||
try {
|
||||
New-Item -ItemType Directory -Path "D:\oracle\temp" -Force -ErrorAction Stop | Out-Null
|
||||
New-Item -ItemType Directory -Path "D:\oracle\logs" -Force -ErrorAction Stop | Out-Null
|
||||
Write-Host "[OK] Created required directories"
|
||||
} catch {
|
||||
Write-Host "ERROR: Failed to create directories: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "============================================"
|
||||
Write-Host "STEP 1: CLEANUP - Delete existing database"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
Write-Host "Calling cleanup_database.ps1..."
|
||||
Write-Host ""
|
||||
|
||||
# Call cleanup script with /SILENT flag
|
||||
& "D:\oracle\scripts\cleanup_database.ps1" /SILENT
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Cleanup failed!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[OK] Cleanup complete - VM is in clean state"
|
||||
Write-Host ""
|
||||
Write-Host "Starting restore in 2 seconds..."
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
Write-Host "============================================"
|
||||
Write-Host "STEP 2: RESTORE - Restore from F:\ backups"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
|
||||
# Step 2.1: Check Oracle service (create only if missing)
|
||||
Write-Host "[2.1] Checking Oracle service..."
|
||||
$service = Get-Service -Name "OracleServiceROA" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($service) {
|
||||
Write-Host "[OK] Oracle service already exists, skipping creation (15s saved!)" -ForegroundColor Green
|
||||
Write-Host " Service will be reused for this restore"
|
||||
|
||||
# Ensure service is running (required for SQL*Plus connection)
|
||||
if ($service.Status -ne "Running") {
|
||||
Write-Host " Service is stopped, starting it (may take 30-60 seconds)..."
|
||||
|
||||
# Start service in background job to avoid blocking
|
||||
$startJob = Start-Job -ScriptBlock {
|
||||
Start-Service -Name "OracleServiceROA" -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Poll for service status with timeout
|
||||
$maxWait = 60
|
||||
$elapsed = 0
|
||||
$pollInterval = 3
|
||||
$serviceStarted = $false
|
||||
|
||||
while ($elapsed -lt $maxWait) {
|
||||
Start-Sleep -Seconds $pollInterval
|
||||
$elapsed += $pollInterval
|
||||
|
||||
# Refresh service status
|
||||
$currentService = Get-Service -Name "OracleServiceROA" -ErrorAction SilentlyContinue
|
||||
if ($currentService -and $currentService.Status -eq "Running") {
|
||||
$serviceStarted = $true
|
||||
Write-Host " [OK] Service started successfully after $elapsed seconds"
|
||||
break
|
||||
}
|
||||
|
||||
if ($elapsed % 15 -eq 0) {
|
||||
Write-Host " Still waiting for service to start... ($elapsed/$maxWait seconds)"
|
||||
}
|
||||
}
|
||||
|
||||
# Cleanup background job
|
||||
Stop-Job -Job $startJob -ErrorAction SilentlyContinue
|
||||
Remove-Job -Job $startJob -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $serviceStarted) {
|
||||
Write-Host " WARNING: Service did not start within $maxWait seconds" -ForegroundColor Yellow
|
||||
Write-Host " This may cause SQL*Plus connection issues (ORA-12560)"
|
||||
Write-Host " Attempting to continue anyway..."
|
||||
}
|
||||
} else {
|
||||
Write-Host " Service is already running"
|
||||
}
|
||||
} else {
|
||||
Write-Host " Oracle service not found, creating from PFILE..."
|
||||
|
||||
# Check if PFILE exists, create if missing
|
||||
$pfilePath = "C:\Users\oracle\admin\ROA\pfile\initROA.ora"
|
||||
if (-not (Test-Path $pfilePath)) {
|
||||
Write-Host " PFILE not found, creating default initROA.ora..." -ForegroundColor Yellow
|
||||
|
||||
# Create directory if needed
|
||||
$pfileDir = Split-Path $pfilePath -Parent
|
||||
if (-not (Test-Path $pfileDir)) {
|
||||
New-Item -ItemType Directory -Path $pfileDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Create PFILE with tested configuration
|
||||
$pfileContent = @"
|
||||
# Initialization Parameters for ROA Database - DR VM
|
||||
# Auto-generated by rman_restore_from_zero.ps1 - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
|
||||
|
||||
# Database Identification
|
||||
db_name=ROA
|
||||
db_unique_name=ROA
|
||||
|
||||
# Memory Configuration
|
||||
memory_target=1024M
|
||||
memory_max_target=1024M
|
||||
|
||||
# File Locations
|
||||
control_files=('C:\Users\oracle\oradata\ROA\control01.ctl', 'C:\Users\oracle\recovery_area\ROA\control02.ctl')
|
||||
db_recovery_file_dest='C:\Users\oracle\recovery_area'
|
||||
db_recovery_file_dest_size=50G
|
||||
audit_file_dest='C:\Users\oracle\admin\ROA\adump'
|
||||
|
||||
# Redo and Archive Log
|
||||
log_archive_format=%t_%s_%r.dbf
|
||||
|
||||
# Compatibility
|
||||
compatible=19.0.0
|
||||
|
||||
# Character Set
|
||||
nls_language=AMERICAN
|
||||
nls_territory=AMERICA
|
||||
|
||||
# Processes and Sessions
|
||||
processes=300
|
||||
sessions=472
|
||||
|
||||
# Miscellaneous
|
||||
diagnostic_dest='C:\Users\oracle'
|
||||
_allow_resetlogs_corruption=TRUE
|
||||
"@
|
||||
|
||||
try {
|
||||
$pfileContent | Out-File -FilePath $pfilePath -Encoding ASCII -ErrorAction Stop
|
||||
Write-Host " [OK] Created PFILE: $pfilePath" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "ERROR: Failed to create PFILE: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
& oradim -new -sid ROA -startmode auto -pfile $pfilePath 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "ERROR: Failed to create Oracle service" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "[OK] Oracle service created successfully (AUTOMATIC startup)"
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
# Step 2.2: Startup NOMOUNT
|
||||
Write-Host "[2.2] Starting database in NOMOUNT mode..."
|
||||
|
||||
# First, ensure any partially started instance is shut down
|
||||
# (Service auto-start may have started instance in error state without control files)
|
||||
Write-Host " Ensuring clean state - shutting down any existing instance..."
|
||||
$cleanupSQL = @"
|
||||
WHENEVER SQLERROR CONTINUE
|
||||
SHUTDOWN ABORT;
|
||||
EXIT;
|
||||
"@
|
||||
$cleanupSQL | & sqlplus -S / as sysdba 2>&1 | Out-Null
|
||||
|
||||
# Now start cleanly in NOMOUNT
|
||||
Write-Host " Starting fresh instance in NOMOUNT mode..."
|
||||
$nomountSQL = @"
|
||||
STARTUP NOMOUNT PFILE='C:\Users\oracle\admin\ROA\pfile\initROA.ora';
|
||||
EXIT;
|
||||
"@
|
||||
|
||||
$nomountSQL | & sqlplus -S / as sysdba 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "ERROR: Failed to startup NOMOUNT" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "[OK] Database started in NOMOUNT mode"
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Step 2.3: Copy backups and create RMAN script
|
||||
Write-Host "[2.3] Preparing RMAN restore..."
|
||||
$rmanScript = "D:\oracle\temp\restore_from_zero.rman"
|
||||
$logFile = "D:\oracle\logs\restore_from_zero.log"
|
||||
|
||||
# Copy backups from F:\ to recovery area (mode-dependent)
|
||||
New-Item -ItemType Directory -Path "C:\Users\oracle\recovery_area\ROA\autobackup" -Force | Out-Null
|
||||
|
||||
if ($TestMode) {
|
||||
Write-Host "[INFO] Copying selected backups from F:\ROA\autobackup to recovery area..."
|
||||
Write-Host " TEST MODE: Only latest backup set (faster for weekly tests)"
|
||||
} else {
|
||||
Write-Host "[INFO] Copying all backups from F:\ROA\autobackup to recovery area..."
|
||||
Write-Host " STANDALONE MODE: All backups for maximum DR safety"
|
||||
}
|
||||
|
||||
# Select backup files based on mode (TestMode vs Standalone)
|
||||
try {
|
||||
if ($TestMode) {
|
||||
# TEST MODE: Copy latest L0 backup set + all incrementals/archives
|
||||
Write-Host "[INFO] TEST MODE: Selecting latest backup set using naming convention..." -ForegroundColor Cyan
|
||||
|
||||
# Check if new naming convention is in use (L0_*, L1_*, etc.)
|
||||
$l0Backups = Get-ChildItem "F:\ROA\autobackup\L0_*.BKP" -ErrorAction Continue |
|
||||
Sort-Object LastWriteTime -Descending
|
||||
|
||||
if ($l0Backups.Count -gt 0) {
|
||||
# New naming convention detected - use smart selection
|
||||
Write-Host "[INFO] Using naming convention for optimized backup selection" -ForegroundColor Cyan
|
||||
|
||||
$latestL0 = $l0Backups[0]
|
||||
# Extract date from filename: L0_ROA_20251011_123_1.BKP -> 20251011
|
||||
if ($latestL0.Name -match 'L0_\w+_(\d{8})_') {
|
||||
$backupDate = $Matches[1]
|
||||
Write-Host "[INFO] Latest Level 0 backup date: $backupDate" -ForegroundColor Cyan
|
||||
Write-Host " Base file: $($latestL0.Name)" -ForegroundColor Cyan
|
||||
|
||||
# Select all files from this backup set:
|
||||
# - All L0_*_<date>_* (all pieces of Level 0)
|
||||
# - All L1_*_<date>_* or later (incrementals from same day or after)
|
||||
# - All ARC_*_<date>_* or later (archive logs)
|
||||
# - All SPFILE_* and CF_* (needed for restore)
|
||||
|
||||
$backupFiles = Get-ChildItem "F:\ROA\autobackup\*.BKP" -ErrorAction Continue |
|
||||
Where-Object {
|
||||
$_.Name -match "^L0_\w+_${backupDate}_" -or # Level 0 pieces
|
||||
$_.Name -match "^L1_\w+_\d{8}_" -or # All Level 1 incrementals
|
||||
$_.Name -match "^ARC_\w+_\d{8}_" -or # All archive logs
|
||||
$_.Name -match "^SPFILE_\w+_${backupDate}_" -or # SPFILE from same day
|
||||
$_.Name -match "^CF_\w+_${backupDate}_" -or # Controlfile from same day
|
||||
$_.Name -match "^O1_MF_S_" # Autobackup control files (always needed)
|
||||
}
|
||||
|
||||
Write-Host "[INFO] Selected $($backupFiles.Count) files for restore:" -ForegroundColor Cyan
|
||||
Write-Host " - Level 0 pieces for date $backupDate" -ForegroundColor Cyan
|
||||
Write-Host " - All Level 1 incrementals" -ForegroundColor Cyan
|
||||
Write-Host " - All archive logs" -ForegroundColor Cyan
|
||||
Write-Host " - SPFILE and Control file backups" -ForegroundColor Cyan
|
||||
|
||||
} else {
|
||||
Write-Host "WARNING: Cannot parse date from L0 filename, using ALL L0/L1/ARC files" -ForegroundColor Yellow
|
||||
$backupFiles = Get-ChildItem "F:\ROA\autobackup\*.BKP" -ErrorAction Continue |
|
||||
Where-Object { $_.Name -match "^(L0_|L1_|ARC_|SPFILE_|CF_)" }
|
||||
}
|
||||
|
||||
} else {
|
||||
# Old naming convention (autobackup format) - fallback to copying all
|
||||
Write-Host "[INFO] Old naming convention detected - copying ALL backups for safety" -ForegroundColor Yellow
|
||||
Write-Host " (New naming convention will be used after next backup runs)" -ForegroundColor Yellow
|
||||
$backupFiles = Get-ChildItem "F:\ROA\autobackup\*.BKP" -ErrorAction Continue
|
||||
}
|
||||
|
||||
Write-Host "[INFO] Total files selected: $($backupFiles.Count)" -ForegroundColor Cyan
|
||||
|
||||
} else {
|
||||
# STANDALONE MODE: Copy ALL backups (disaster recovery - maximum safety with fallback)
|
||||
Write-Host "[INFO] STANDALONE MODE: Copying ALL backups for maximum DR safety..." -ForegroundColor Yellow
|
||||
$backupFiles = Get-ChildItem "F:\ROA\autobackup\*.BKP" -ErrorAction Continue
|
||||
Write-Host "[INFO] Full DR restore - will copy all available backups (includes redundancy)" -ForegroundColor Yellow
|
||||
}
|
||||
} catch {
|
||||
Write-Host "WARNING: Cannot enumerate backup files on F: drive - $_" -ForegroundColor Yellow
|
||||
$backupFiles = @()
|
||||
}
|
||||
|
||||
# Validate backup count
|
||||
$minRequired = 2
|
||||
if ($backupFiles.Count -lt $minRequired) {
|
||||
Write-Host "ERROR: Insufficient backup files found on F: drive (found: $($backupFiles.Count))" -ForegroundColor Red
|
||||
Write-Host " At least $minRequired backup files required for successful restore"
|
||||
Write-Host " Checking F:\ROA\autobackup directory..."
|
||||
try {
|
||||
$dirCheck = Get-ChildItem "F:\ROA\autobackup" -ErrorAction Continue
|
||||
Write-Host " Directory contents: $($dirCheck.Count) files"
|
||||
foreach ($file in $dirCheck) {
|
||||
Write-Host " $($file.Name) - $([math]::Round($file.Length / 1GB, 2)) GB" -ForegroundColor Gray
|
||||
}
|
||||
} catch {
|
||||
Write-Host " Cannot access directory: $_" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[INFO] Found $($backupFiles.Count) backup files, total size: $([math]::Round(($backupFiles | Measure-Object -Property Length -Sum).Sum / 1GB, 2)) GB"
|
||||
|
||||
# Copy backups with better error handling
|
||||
Write-Host "[INFO] Starting backup copy operation..."
|
||||
$copyErrors = @()
|
||||
foreach ($backupFile in $backupFiles) {
|
||||
try {
|
||||
Write-Host "[INFO] Copying $($backupFile.Name)..."
|
||||
Copy-Item $backupFile.FullName "C:\Users\oracle\recovery_area\ROA\autobackup\" -Force -ErrorAction Stop
|
||||
Write-Host "[OK] Copied $($backupFile.Name)" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "ERROR: Failed to copy $($backupFile.Name) - $_" -ForegroundColor Red
|
||||
$copyErrors += "$($backupFile.Name): $_"
|
||||
}
|
||||
}
|
||||
|
||||
if ($copyErrors.Count -gt 0) {
|
||||
Write-Host "ERROR: Backup copy failed for $($copyErrors.Count) files" -ForegroundColor Red
|
||||
foreach ($error in $copyErrors) {
|
||||
Write-Host " $error" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Verify copied backups
|
||||
try {
|
||||
$copiedFiles = Get-ChildItem "C:\Users\oracle\recovery_area\ROA\autobackup\*.BKP" -ErrorAction Continue
|
||||
} catch {
|
||||
Write-Host "ERROR: Cannot verify copied backups - $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($copiedFiles.Count -ne $backupFiles.Count) {
|
||||
Write-Host "ERROR: Backup copy verification failed - file count mismatch" -ForegroundColor Red
|
||||
Write-Host " Expected: $($backupFiles.Count), Copied: $($copiedFiles.Count)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[OK] All $($copiedFiles.Count) backups copied and verified to recovery area"
|
||||
|
||||
# Create RMAN script
|
||||
$rmanContent = @"
|
||||
SET DBID 1363569330;
|
||||
|
||||
RUN {
|
||||
ALLOCATE CHANNEL ch1 DEVICE TYPE DISK;
|
||||
RESTORE CONTROLFILE FROM AUTOBACKUP;
|
||||
RELEASE CHANNEL ch1;
|
||||
}
|
||||
|
||||
ALTER DATABASE MOUNT;
|
||||
|
||||
CATALOG START WITH 'C:/USERS/ORACLE/RECOVERY_AREA/ROA/AUTOBACKUP' NOPROMPT;
|
||||
|
||||
CROSSCHECK BACKUP;
|
||||
DELETE NOPROMPT EXPIRED BACKUP;
|
||||
|
||||
RUN {
|
||||
ALLOCATE CHANNEL ch1 DEVICE TYPE DISK;
|
||||
ALLOCATE CHANNEL ch2 DEVICE TYPE DISK;
|
||||
RESTORE DATABASE;
|
||||
RELEASE CHANNEL ch1;
|
||||
RELEASE CHANNEL ch2;
|
||||
}
|
||||
|
||||
RUN {
|
||||
ALLOCATE CHANNEL ch1 DEVICE TYPE DISK;
|
||||
RECOVER DATABASE NOREDO;
|
||||
RELEASE CHANNEL ch1;
|
||||
}
|
||||
|
||||
ALTER DATABASE OPEN RESETLOGS;
|
||||
|
||||
DELETE NOPROMPT OBSOLETE;
|
||||
|
||||
EXIT;
|
||||
"@
|
||||
|
||||
$rmanContent | Out-File -FilePath $rmanScript -Encoding ASCII
|
||||
Write-Host "[OK] RMAN script created: $rmanScript"
|
||||
|
||||
# Step 2.4: Run RMAN restore
|
||||
Write-Host "[2.4] Running RMAN restore (this will take 10-20 minutes)..."
|
||||
Write-Host " Log file: $logFile"
|
||||
Write-Host ""
|
||||
|
||||
& rman target / cmdfile=$rmanScript log=$logFile
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: RMAN restore failed!" -ForegroundColor Red
|
||||
Write-Host "Check log: $logFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[OK] RMAN restore completed successfully!"
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "============================================"
|
||||
Write-Host "STEP 3: VERIFY - Check database status"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
Write-Host "[3.1] Verifying database..."
|
||||
|
||||
$verifySQL = @"
|
||||
SET PAGESIZE 100 LINESIZE 200
|
||||
COLUMN info FORMAT A80
|
||||
SELECT 'DB_NAME: ' || NAME || ', OPEN_MODE: ' || OPEN_MODE AS info FROM V`$DATABASE;
|
||||
SELECT 'INSTANCE: ' || INSTANCE_NAME || ', STATUS: ' || STATUS AS info FROM V`$INSTANCE;
|
||||
SELECT 'TABLESPACES: ' || COUNT(*) AS info FROM DBA_TABLESPACES;
|
||||
SELECT 'DATAFILES: ' || COUNT(*) AS info FROM DBA_DATA_FILES;
|
||||
SELECT 'TABLES: ' || COUNT(*) AS info FROM DBA_TABLES WHERE OWNER NOT IN ('SYS','SYSTEM');
|
||||
EXIT;
|
||||
"@
|
||||
|
||||
$verifySQL | & sqlplus -S / as sysdba
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[3.2] Creating SPFILE for database persistence..."
|
||||
$spfileSQL = @"
|
||||
CREATE SPFILE FROM PFILE='C:\Users\oracle\admin\ROA\pfile\initROA.ora';
|
||||
EXIT;
|
||||
"@
|
||||
|
||||
$spfileSQL | & sqlplus -S / as sysdba 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "WARNING: Failed to create SPFILE - database may not persist after connections close" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "[OK] SPFILE created successfully"
|
||||
|
||||
# Check if running in TestMode (weekly DR test)
|
||||
if ($TestMode) {
|
||||
Write-Host "[3.3] Running in TEST MODE - skipping service reconfiguration"
|
||||
Write-Host " Database is OPEN and ready for verification"
|
||||
Write-Host " Service will remain configured with PFILE (OK for testing)"
|
||||
} else {
|
||||
# Full configuration for standalone/production use
|
||||
Write-Host "[3.3] Reconfiguring Oracle service to use SPFILE..."
|
||||
|
||||
# Shutdown database cleanly
|
||||
Write-Host " Shutting down database temporarily..."
|
||||
$shutdownSQL = @"
|
||||
SHUTDOWN IMMEDIATE;
|
||||
EXIT;
|
||||
"@
|
||||
$shutdownSQL | & sqlplus -S / as sysdba 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Delete and recreate service with SPFILE
|
||||
Write-Host " Recreating service with SPFILE..."
|
||||
& oradim -delete -sid ROA 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
& oradim -new -sid ROA -startmode auto -spfile 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host " WARNING: Failed to recreate service with SPFILE" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " [OK] Service now configured with SPFILE and AUTOMATIC startup"
|
||||
}
|
||||
|
||||
# Restart database
|
||||
Write-Host " Starting database with SPFILE..."
|
||||
$startupSQL = @"
|
||||
STARTUP;
|
||||
EXIT;
|
||||
"@
|
||||
$startupSQL | & sqlplus -S / as sysdba 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
Write-Host "[OK] Database restarted with SPFILE configuration"
|
||||
|
||||
# Start Oracle Listener
|
||||
Write-Host "[3.4] Starting Oracle Listener..."
|
||||
|
||||
# Set Listener service to AUTOMATIC and start it
|
||||
Set-Service -Name "OracleOraDB19Home1TNSListener" -StartupType Automatic -ErrorAction SilentlyContinue
|
||||
Start-Service -Name "OracleOraDB19Home1TNSListener" -ErrorAction SilentlyContinue
|
||||
|
||||
if ((Get-Service -Name "OracleOraDB19Home1TNSListener" -ErrorAction SilentlyContinue).Status -eq "Running") {
|
||||
Write-Host "[OK] Listener started successfully"
|
||||
} else {
|
||||
Write-Host "WARNING: Failed to start Listener automatically, trying lsnrctl..." -ForegroundColor Yellow
|
||||
& lsnrctl start 2>&1 | Out-Null
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Register database with listener
|
||||
$registerSQL = @"
|
||||
ALTER SYSTEM REGISTER;
|
||||
EXIT;
|
||||
"@
|
||||
$registerSQL | & sqlplus -S / as sysdba 2>&1 | Out-Null
|
||||
Write-Host "[OK] Database registered with Listener"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "============================================"
|
||||
Write-Host "Database Restore FROM ZERO Complete!"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
Write-Host "Restore log: $logFile"
|
||||
Write-Host ""
|
||||
Write-Host "Database is OPEN and ready for testing!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:"
|
||||
Write-Host " 1. Test application connectivity"
|
||||
Write-Host " 2. Verify data integrity"
|
||||
Write-Host " 3. Run cleanup_database.ps1 to remove database after test"
|
||||
Write-Host " 4. Shutdown DR VM to conserve resources"
|
||||
Write-Host ""
|
||||
|
||||
exit 0
|
||||
231
proxmox/vm109-windows-dr/scripts/transfer_backups.ps1
Normal file
231
proxmox/vm109-windows-dr/scripts/transfer_backups.ps1
Normal file
@@ -0,0 +1,231 @@
|
||||
# Transfer Oracle Backups (Full + Incremental) towards DR Server
|
||||
# Script UNIFICAT - poate fi rulat după orice tip de backup
|
||||
# Transferă TOATE fișierele backup, skip-uiește duplicatele automat
|
||||
#
|
||||
# Poate fi apelat de:
|
||||
# - Task Scheduler după full backup (03:00 AM)
|
||||
# - Task Scheduler după incremental backup (14:30)
|
||||
# - Manual oricând pentru recovery
|
||||
|
||||
param(
|
||||
[string]$SourceFRA = "C:\Users\Oracle\recovery_area\ROA",
|
||||
[string]$DRHost = "10.0.20.202",
|
||||
[int]$DRPort = 22,
|
||||
[string]$DRUser = "root",
|
||||
[string]$DRPath = "/mnt/pve/oracle-backups/ROA/autobackup",
|
||||
[string]$SSHKeyPath = "$env:USERPROFILE\.ssh\id_rsa",
|
||||
[int]$RetentionDays = 2,
|
||||
[string]$LogFile = "D:\rman_backup\logs\transfer_$(Get-Date -Format 'yyyyMMdd_HHmm').log"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
$logLine = "[$timestamp] [$Level] $Message"
|
||||
Write-Host $logLine
|
||||
Add-Content -Path $LogFile -Value $logLine -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Test-SSHConnection {
|
||||
Write-Log "Testing SSH connection to $DRHost`:$DRPort..."
|
||||
|
||||
try {
|
||||
# Folosește -n pentru a nu citi din stdin (fix pentru blocare)
|
||||
$null = & ssh -n -p $DRPort -i $SSHKeyPath -o StrictHostKeyChecking=no -o ConnectTimeout=10 "${DRUser}@${DRHost}" "exit 0" 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log "SSH connection successful" "SUCCESS"
|
||||
return $true
|
||||
} else {
|
||||
Write-Log "SSH connection failed with exit code: $LASTEXITCODE" "ERROR"
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Log "SSH connection error: $_" "ERROR"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AllBackupFiles {
|
||||
Write-Log "Searching for all backup files in FRA..."
|
||||
|
||||
$backupFiles = @()
|
||||
|
||||
$searchPaths = @(
|
||||
"$SourceFRA\BACKUPSET",
|
||||
"$SourceFRA\AUTOBACKUP"
|
||||
)
|
||||
|
||||
foreach ($path in $searchPaths) {
|
||||
if (Test-Path $path) {
|
||||
# Get ALL backup files (duplicates will be skipped during transfer)
|
||||
$files = Get-ChildItem -Path $path -Recurse -File -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.Name -match '\.(BKP|bkp)$' -and
|
||||
$_.Name -notlike "*__TAG_*" # Exclude old uncompressed backups
|
||||
} |
|
||||
Sort-Object LastWriteTime -Descending
|
||||
|
||||
$backupFiles += $files
|
||||
}
|
||||
}
|
||||
|
||||
if ($backupFiles.Count -eq 0) {
|
||||
Write-Log "No backup files found!" "WARNING"
|
||||
return @()
|
||||
}
|
||||
|
||||
$totalSizeGB = ($backupFiles | Measure-Object -Property Length -Sum).Sum / 1GB
|
||||
Write-Log "Found $($backupFiles.Count) backup files, total size: $([math]::Round($totalSizeGB, 2)) GB"
|
||||
|
||||
return $backupFiles
|
||||
}
|
||||
|
||||
function Transfer-FileToDR {
|
||||
param([System.IO.FileInfo]$File, [string]$DestPath)
|
||||
|
||||
$fileName = $File.Name
|
||||
$fileSizeMB = [math]::Round($File.Length / 1MB, 2)
|
||||
|
||||
try {
|
||||
# Check dacă fișierul există deja pe DR (skip duplicates) - Linux bash command
|
||||
$checkCmd = "test -f '$DestPath/$fileName' && echo 'True' || echo 'False'"
|
||||
$checkResult = & ssh -n -p $DRPort -i $SSHKeyPath "${DRUser}@${DRHost}" $checkCmd 2>&1
|
||||
|
||||
if ($checkResult -match "True") {
|
||||
Write-Log "Skipping (already on DR): $fileName" "INFO"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Log "Transferring: $fileName ($fileSizeMB MB)"
|
||||
|
||||
# SCP transfer - NO compression (files already compressed by RMAN)
|
||||
# Use cipher aes128-gcm for better performance
|
||||
$null = & scp -P $DRPort -i $SSHKeyPath -o StrictHostKeyChecking=no -o Compression=no -o Cipher=aes128-gcm@openssh.com $File.FullName "${DRUser}@${DRHost}:${DestPath}/" 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log "Transferred: $fileName" "SUCCESS"
|
||||
return $true
|
||||
} else {
|
||||
Write-Log "Failed to transfer: $fileName (exit code: $LASTEXITCODE)" "ERROR"
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Log "Transfer error for $fileName : $_" "ERROR"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Cleanup-OldBackupsOnDR {
|
||||
Write-Log "Cleaning up old backups on DR (keeping last $RetentionDays days)..."
|
||||
|
||||
try {
|
||||
# Count fișiere înainte de cleanup
|
||||
$countBefore = & ssh -n -p $DRPort -i $SSHKeyPath "${DRUser}@${DRHost}" "find '$DRPath' -name '*.BKP' -type f | wc -l" 2>&1
|
||||
Write-Log "Backups before cleanup: $countBefore"
|
||||
|
||||
# Cleanup: șterge fișiere mai vechi de $RetentionDays zile - Linux find command
|
||||
# -mtime +N înseamnă "mai vechi de N zile", deci pentru a păstra RetentionDays zile, folosim +($RetentionDays - 1)
|
||||
$mtimeDays = $RetentionDays - 1
|
||||
$cleanupCmd = "find '$DRPath' -name '*.BKP' -type f -mtime +$mtimeDays -delete 2>&1"
|
||||
$result = & ssh -n -p $DRPort -i $SSHKeyPath "${DRUser}@${DRHost}" $cleanupCmd 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Log "Cleanup warning: $result" "WARNING"
|
||||
}
|
||||
|
||||
# Count fișiere după cleanup
|
||||
$countAfter = & ssh -n -p $DRPort -i $SSHKeyPath "${DRUser}@${DRHost}" "find '$DRPath' -name '*.BKP' -type f | wc -l" 2>&1
|
||||
$deleted = [int]$countBefore - [int]$countAfter
|
||||
|
||||
Write-Log "Cleanup completed: Deleted $deleted old backup files, $countAfter remaining"
|
||||
} catch {
|
||||
Write-Log "Cleanup error: $_" "WARNING"
|
||||
}
|
||||
}
|
||||
|
||||
# ==================== MAIN ====================
|
||||
|
||||
try {
|
||||
Write-Log "========================================="
|
||||
Write-Log "Oracle Backup Transfer Started (UNIFIED)"
|
||||
Write-Log "========================================="
|
||||
Write-Log "Source FRA: $SourceFRA"
|
||||
Write-Log "DR Server: $DRHost"
|
||||
Write-Log "DR Path: $DRPath"
|
||||
|
||||
# Verificare prerequisite
|
||||
if (-not (Test-Path $SourceFRA)) {
|
||||
throw "Source FRA path not found: $SourceFRA"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $SSHKeyPath)) {
|
||||
throw "SSH key not found: $SSHKeyPath"
|
||||
}
|
||||
|
||||
# Test SSH connection
|
||||
if (-not (Test-SSHConnection)) {
|
||||
throw "Cannot connect to DR server via SSH"
|
||||
}
|
||||
|
||||
# Creare director pe DR - Linux mkdir command
|
||||
Write-Log "Ensuring DR directory exists..."
|
||||
$null = & ssh -n -p $DRPort -i $SSHKeyPath "${DRUser}@${DRHost}" "mkdir -p '$DRPath'" 2>&1
|
||||
|
||||
# Găsește TOATE backup-urile
|
||||
$backupFiles = Get-AllBackupFiles
|
||||
|
||||
if ($backupFiles.Count -eq 0) {
|
||||
Write-Log "No backup files to transfer (this might be normal for first run)" "WARNING"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Transfer fișiere
|
||||
Write-Log "Starting file transfer..."
|
||||
$successCount = 0
|
||||
$failCount = 0
|
||||
$skippedCount = 0
|
||||
|
||||
foreach ($file in $backupFiles) {
|
||||
$result = Transfer-FileToDR -File $file -DestPath $DRPath
|
||||
|
||||
if ($result) {
|
||||
# Check if it was skipped or transferred
|
||||
$fileName = $file.Name
|
||||
$checkCmd = "test -f '$DRPath/$fileName' && echo 'True' || echo 'False'"
|
||||
$checkResult = & ssh -n -p $DRPort -i $SSHKeyPath "${DRUser}@${DRHost}" $checkCmd 2>&1
|
||||
|
||||
if ($checkResult -match "True") {
|
||||
$successCount++
|
||||
}
|
||||
} else {
|
||||
$failCount++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Transfer summary: $successCount succeeded, $failCount failed"
|
||||
|
||||
if ($failCount -gt 0) {
|
||||
Write-Log "Some transfers failed!" "WARNING"
|
||||
}
|
||||
|
||||
# Cleanup old backups pe DR
|
||||
Cleanup-OldBackupsOnDR
|
||||
|
||||
Write-Log "========================================="
|
||||
Write-Log "Backup Transfer Completed Successfully"
|
||||
Write-Log "========================================="
|
||||
Write-Log "Files processed: $($backupFiles.Count)"
|
||||
Write-Log "Successful: $successCount"
|
||||
Write-Log "Failed: $failCount"
|
||||
Write-Log "DR Server: ${DRHost}:${DRPath}"
|
||||
|
||||
exit 0
|
||||
|
||||
} catch {
|
||||
Write-Log "CRITICAL ERROR: $($_.Exception.Message)" "ERROR"
|
||||
Write-Log "Stack trace: $($_.ScriptStackTrace)" "ERROR"
|
||||
exit 1
|
||||
}
|
||||
649
proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh
Normal file
649
proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh
Normal file
@@ -0,0 +1,649 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Oracle DR Weekly Test with Proxmox PVE::Notify
|
||||
# Automated DR test with notifications via Proxmox notification system
|
||||
#
|
||||
# Location: /opt/scripts/weekly-dr-test-proxmox.sh (on Proxmox host)
|
||||
# Schedule: Add to cron for weekly execution (Saturdays)
|
||||
#
|
||||
# This script is SELF-SUFFICIENT:
|
||||
# - Automatically creates notification templates if they don't exist
|
||||
# - Uses Proxmox native notification system
|
||||
# - No email configuration needed - uses existing Proxmox setup
|
||||
#
|
||||
# Installation:
|
||||
# cp weekly-dr-test-proxmox.sh /opt/scripts/
|
||||
# chmod +x /opt/scripts/weekly-dr-test-proxmox.sh
|
||||
# /opt/scripts/weekly-dr-test-proxmox.sh --install # Creates templates
|
||||
# crontab -e # Add: 0 6 * * 6 /opt/scripts/weekly-dr-test-proxmox.sh
|
||||
#
|
||||
# Author: Claude (based on ha-monitor.sh pattern)
|
||||
# Version: 1.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Set proper PATH for cron execution
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
# Configuration
|
||||
DR_VM_ID="109"
|
||||
DR_VM_IP="10.0.20.37"
|
||||
DR_VM_PORT="22122"
|
||||
DR_VM_USER="romfast"
|
||||
BACKUP_PATH="/mnt/pve/oracle-backups/ROA/autobackup"
|
||||
MAX_RESTORE_TIME_MIN=30
|
||||
TEMPLATE_DIR="/usr/share/pve-manager/templates/default"
|
||||
LOG_DIR="/var/log/oracle-dr"
|
||||
LOG_FILE="$LOG_DIR/dr_test_$(date +%Y%m%d_%H%M%S).log"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Create log directory
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# Function to create notification templates
|
||||
create_templates() {
|
||||
echo -e "${GREEN}Creating Oracle DR test notification templates...${NC}"
|
||||
|
||||
# Create templates directory if needed
|
||||
mkdir -p "$TEMPLATE_DIR"
|
||||
|
||||
# Subject template
|
||||
cat > "$TEMPLATE_DIR/oracle-dr-test-subject.txt.hbs" <<'EOF'
|
||||
Oracle DR Test {{test_result}} | {{date}}
|
||||
EOF
|
||||
|
||||
# Text body template
|
||||
cat > "$TEMPLATE_DIR/oracle-dr-test-body.txt.hbs" <<'EOF'
|
||||
Oracle DR Test {{test_result}} | {{date}}
|
||||
Severity: {{severity}}
|
||||
|
||||
SUMMARY
|
||||
- Outcome: {{test_result}}
|
||||
- Duration: {{total_duration}} min (restore {{restore_duration}} min)
|
||||
- Backups used: {{backup_count}}
|
||||
- Tables restored: {{tables_restored}}
|
||||
|
||||
COMPONENTS
|
||||
- VM {{vm_id}} ({{vm_ip}}): {{vm_status}}
|
||||
- NFS: {{nfs_status}}
|
||||
- Database: {{database_status}}
|
||||
- Cleanup: {{disk_freed}} GB freed
|
||||
|
||||
STEPS
|
||||
{{#each test_steps}}
|
||||
- {{#if this.passed}}✓{{else}}✗{{/if}} {{this.name}} ({{this.duration}}s){{#if this.status}} - {{this.status}}{{/if}}
|
||||
{{/each}}
|
||||
|
||||
{{#if has_errors}}
|
||||
ISSUES
|
||||
{{#each errors}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{#if has_warnings}}
|
||||
WARNINGS
|
||||
{{#each warnings}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
RMAN RESTORE LOG (complete)
|
||||
---
|
||||
{{restore_log}}
|
||||
---
|
||||
|
||||
BASH SCRIPT LOG (last 100 lines)
|
||||
---
|
||||
{{bash_log}}
|
||||
---
|
||||
|
||||
Full log: {{log_file}}
|
||||
Next test: Saturday 06:00
|
||||
EOF
|
||||
|
||||
# HTML body template (compact Gmail-friendly layout)
|
||||
cat > "$TEMPLATE_DIR/oracle-dr-test-body.html.hbs" <<'EOF'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Oracle DR Test {{test_result}} | {{date}}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:16px;font-family:Arial,Helvetica,sans-serif;background:#ffffff;color:#2c3e50;">
|
||||
<table style="width:100%;max-width:640px;margin:0 auto;border-collapse:collapse;">
|
||||
<tr>
|
||||
<td style="padding:0 0 12px 0;font-size:18px;font-weight:600;">
|
||||
Oracle DR Test {{test_result}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 0 8px 0;font-size:13px;color:#6c757d;">{{date}} · Severity: {{severity}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:12px;border:1px solid #e1e4e8;border-radius:4px;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;">
|
||||
<tr><td style="padding:4px 0;">Outcome</td><td style="padding:4px 0;text-align:right;">{{test_result}}</td></tr>
|
||||
<tr><td style="padding:4px 0;">Duration</td><td style="padding:4px 0;text-align:right;">{{total_duration}} min (restore {{restore_duration}} min)</td></tr>
|
||||
<tr><td style="padding:4px 0;">Backups used</td><td style="padding:4px 0;text-align:right;">{{backup_count}}</td></tr>
|
||||
<tr><td style="padding:4px 0;">Tables restored</td><td style="padding:4px 0;text-align:right;">{{tables_restored}}</td></tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;border:1px solid #e1e4e8;border-radius:4px;background:#f9fafb;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;">Components</td></tr>
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">VM {{vm_id}} ({{vm_ip}}): {{vm_status}}</td></tr>
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">NFS: {{nfs_status}}</td></tr>
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">Database: {{database_status}}</td></tr>
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #e1e4e8;">Cleanup: {{disk_freed}} GB freed</td></tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;">
|
||||
<tr><td style="padding:0 0 6px 0;font-weight:600;">Steps</td></tr>
|
||||
{{#each test_steps}}
|
||||
<tr>
|
||||
<td style="padding:4px 0;border-bottom:1px solid #f1f1f1;">{{#if this.passed}}✓{{else}}✗{{/if}} {{this.name}} ({{this.duration}}s){{#if this.status}} – {{this.status}}{{/if}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{#if has_errors}}
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;background:#fff5f5;border:1px solid #f1b0b7;border-radius:4px;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;color:#c82333;">Issues</td></tr>
|
||||
{{#each errors}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #f8d7da;">• {{this}}</td></tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
{{#if has_warnings}}
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:14px;background:#fff8e5;border:1px solid #ffe8a1;border-radius:4px;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;color:#856404;">Warnings</td></tr>
|
||||
{{#each warnings}}
|
||||
<tr><td style="padding:6px 12px;border-top:1px solid #ffe8a1;">• {{this}}</td></tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;border:1px solid #e1e4e8;border-radius:4px;background:#f9fafb;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;font-size:13px;">RMAN Restore Log (complete)</td></tr>
|
||||
<tr><td style="padding:8px 12px;font-family:monospace;white-space:pre-wrap;word-wrap:break-word;border-top:1px solid #e1e4e8;">{{restore_log}}</td></tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px;border:1px solid #e1e4e8;border-radius:4px;background:#f9fafb;">
|
||||
<tr><td style="padding:8px 12px;font-weight:600;font-size:13px;">Bash Script Log (last 100 lines)</td></tr>
|
||||
<tr><td style="padding:8px 12px;font-family:monospace;white-space:pre-wrap;word-wrap:break-word;border-top:1px solid #e1e4e8;">{{bash_log}}</td></tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding:16px 0 0 0;font-size:12px;color:#6c757d;">
|
||||
Full log: {{log_file}} · Next test: Saturday 06:00
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}Templates created successfully in $TEMPLATE_DIR${NC}"
|
||||
}
|
||||
|
||||
# Function to send notification via PVE::Notify
|
||||
send_pve_notification() {
|
||||
local severity="$1"
|
||||
local data="$2"
|
||||
|
||||
# Create Perl script to call PVE::Notify
|
||||
cat > /tmp/oracle-dr-notify.pl <<'PERL_SCRIPT'
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use PVE::Notify;
|
||||
use JSON;
|
||||
|
||||
my $json_data = do { local $/; <STDIN> };
|
||||
my $data = decode_json($json_data);
|
||||
|
||||
my $severity = $data->{severity} // 'info';
|
||||
my $template_name = 'oracle-dr-test';
|
||||
|
||||
# Add fields for matching rules
|
||||
my $fields = {
|
||||
type => 'oracle-dr-test',
|
||||
severity => $severity,
|
||||
test_result => $data->{test_result},
|
||||
};
|
||||
|
||||
# Send notification
|
||||
eval {
|
||||
PVE::Notify::notify(
|
||||
$severity,
|
||||
$template_name,
|
||||
$data,
|
||||
$fields
|
||||
);
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
print "Error sending notification: $@\n";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
print "Notification sent successfully\n";
|
||||
PERL_SCRIPT
|
||||
|
||||
chmod +x /tmp/oracle-dr-notify.pl
|
||||
|
||||
# Send notification
|
||||
echo "$data" | perl /tmp/oracle-dr-notify.pl
|
||||
|
||||
rm -f /tmp/oracle-dr-notify.pl
|
||||
}
|
||||
|
||||
# Logging functions
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Test tracking
|
||||
TEST_STEPS=()
|
||||
ERRORS=()
|
||||
WARNINGS=()
|
||||
TEST_START_TIME=$(date +%s)
|
||||
|
||||
# Function to track test steps
|
||||
track_step() {
|
||||
local name="$1"
|
||||
local passed="$2"
|
||||
local status="$3"
|
||||
local start_time="$4"
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
|
||||
local step_json
|
||||
step_json=$(jq -n \
|
||||
--arg name "$name" \
|
||||
--arg status "$status" \
|
||||
--arg duration "$duration" \
|
||||
--arg passed "$passed" \
|
||||
'{name:$name, status:$status, duration:($duration|tonumber), passed:($passed == "true")}'
|
||||
)
|
||||
|
||||
TEST_STEPS+=("$step_json")
|
||||
|
||||
if [ "$passed" = "false" ]; then
|
||||
ERRORS+=("$name: $status")
|
||||
fi
|
||||
}
|
||||
|
||||
# Main test workflow
|
||||
run_dr_test() {
|
||||
local test_result="FAILED"
|
||||
local severity="error"
|
||||
local is_success=false
|
||||
local restore_duration=0
|
||||
local tables_restored=0
|
||||
local db_status="UNKNOWN"
|
||||
local nfs_status="Not checked"
|
||||
local vm_status_label="Not started"
|
||||
local cleanup_freed=0
|
||||
local backup_count=0
|
||||
local restore_log="Not collected"
|
||||
|
||||
log "=========================================="
|
||||
log "Oracle DR Weekly Test - Starting"
|
||||
log "=========================================="
|
||||
|
||||
# Step 1: Pre-flight checks
|
||||
local step_start=$(date +%s)
|
||||
log "STEP 1: Pre-flight checks"
|
||||
|
||||
# Check backups exist
|
||||
backup_count=$(find "$BACKUP_PATH" -maxdepth 1 -type f -name '*.BKP' 2>/dev/null | wc -l)
|
||||
|
||||
if [ "$backup_count" -lt 2 ]; then
|
||||
track_step "Pre-flight checks" false "Insufficient backups (found: $backup_count)" "$step_start"
|
||||
test_result="FAILED - No backups"
|
||||
else
|
||||
track_step "Pre-flight checks" true "Found $backup_count backups" "$step_start"
|
||||
|
||||
# Step 2: Start VM
|
||||
step_start=$(date +%s)
|
||||
log "STEP 2: Starting DR VM"
|
||||
|
||||
if qm start "$DR_VM_ID" 2>/dev/null; then
|
||||
vm_status_label="Running"
|
||||
|
||||
# Intelligent VM boot wait with polling (max 180s)
|
||||
local MAX_BOOT_WAIT=180
|
||||
local POLL_INTERVAL=5
|
||||
local boot_elapsed=0
|
||||
local vm_ready=false
|
||||
|
||||
log "Waiting for VM to become ready (SSH + PowerShell, max ${MAX_BOOT_WAIT}s)..."
|
||||
|
||||
while [ $boot_elapsed -lt $MAX_BOOT_WAIT ]; do
|
||||
# Check 1: VM running status in Proxmox
|
||||
local vm_qm_status
|
||||
vm_qm_status=$(qm status "$DR_VM_ID" 2>/dev/null | grep -o "running" || echo "")
|
||||
|
||||
if [ "$vm_qm_status" = "running" ]; then
|
||||
# Check 2: SSH connectivity and PowerShell availability (what we actually need)
|
||||
if ssh -p "$DR_VM_PORT" -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o BatchMode=yes "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command 'Write-Output ready'" >/dev/null 2>&1; then
|
||||
log "VM ready after ${boot_elapsed}s (SSH and PowerShell responding)"
|
||||
vm_ready=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep $POLL_INTERVAL
|
||||
boot_elapsed=$((boot_elapsed + POLL_INTERVAL))
|
||||
|
||||
# Progress logging every 30 seconds
|
||||
if [ $((boot_elapsed % 30)) -eq 0 ] && [ $boot_elapsed -lt $MAX_BOOT_WAIT ]; then
|
||||
log "Still waiting for VM... (${boot_elapsed}s/${MAX_BOOT_WAIT}s elapsed)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$vm_ready" = false ]; then
|
||||
log_warning "VM did not respond within ${MAX_BOOT_WAIT}s, continuing anyway (may cause subsequent failures)"
|
||||
fi
|
||||
|
||||
track_step "VM Startup" true "VM $DR_VM_ID started and ready (${boot_elapsed}s)" "$step_start"
|
||||
|
||||
# Step 3: Verify NFS mount
|
||||
step_start=$(date +%s)
|
||||
log "STEP 3: Verifying NFS mount"
|
||||
|
||||
nfs_status="Not Mounted"
|
||||
if ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command 'Test-Path F:\\ROA\\autobackup'" 2>/dev/null; then
|
||||
nfs_status="Mounted"
|
||||
track_step "NFS Mount Check" true "F:\\ drive accessible" "$step_start"
|
||||
else
|
||||
track_step "NFS Mount Check" false "F:\\ drive not accessible" "$step_start"
|
||||
WARNINGS+=("NFS mount may need manual intervention")
|
||||
fi
|
||||
|
||||
# Step 4: Run restore
|
||||
step_start=$(date +%s)
|
||||
local restore_start=$step_start
|
||||
log "STEP 4: Running database restore"
|
||||
|
||||
if ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\rman_restore_from_zero.ps1 -TestMode" 2>&1 | tee -a "$LOG_FILE"; then
|
||||
|
||||
local restore_end=$(date +%s)
|
||||
restore_duration=$(( (restore_end - restore_start) / 60 ))
|
||||
|
||||
track_step "Database Restore" true "Restored in $restore_duration minutes" "$step_start"
|
||||
|
||||
# Step 5: Verify database
|
||||
step_start=$(date +%s)
|
||||
log "STEP 5: Verifying database"
|
||||
|
||||
# Parse database status from LOG_FILE (rman_restore_from_zero.ps1 already verified it)
|
||||
# Look for "OPEN_MODE: READ WRITE" in the captured output
|
||||
if grep -q "OPEN_MODE: READ WRITE" "$LOG_FILE" 2>/dev/null; then
|
||||
db_status="READ WRITE"
|
||||
else
|
||||
db_status=""
|
||||
fi
|
||||
|
||||
# Parse table count from LOG_FILE (already captured in STEP 3 output)
|
||||
# Look for "TABLES: <number>" in the output
|
||||
tables_restored=$(grep -oP "TABLES:\s*\K\d+" "$LOG_FILE" 2>/dev/null | tail -1 || echo "0")
|
||||
tables_restored=$(echo "$tables_restored" | tr -cd '0-9')
|
||||
[ -z "$tables_restored" ] && tables_restored=0
|
||||
|
||||
if [[ "$db_status" == "READ WRITE" ]] && [ "$tables_restored" -gt 0 ]; then
|
||||
track_step "Database Verification" true "Database OPEN, $tables_restored tables" "$step_start"
|
||||
test_result="PASSED"
|
||||
severity="info"
|
||||
is_success=true
|
||||
else
|
||||
track_step "Database Verification" false "Database not OPEN" "$step_start"
|
||||
fi
|
||||
|
||||
# Collect restore log from VM (always attempt collection - FULL log)
|
||||
log "Collecting restore log from DR VM..."
|
||||
restore_log=$(ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command \"Get-Content 'D:\\oracle\\logs\\restore_from_zero.log' -ErrorAction SilentlyContinue\"" 2>/dev/null || echo "")
|
||||
|
||||
# If not found, try alternate locations
|
||||
if [ -z "$restore_log" ]; then
|
||||
restore_log=$(ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command \"Get-Content 'D:\\oracle\\temp\\restore_from_zero.log' -ErrorAction SilentlyContinue\"" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
# Still not found, use fallback message
|
||||
if [ -z "$restore_log" ]; then
|
||||
restore_log="Restore log not available (file may not exist or was not generated)"
|
||||
fi
|
||||
|
||||
# Step 6: Cleanup (AFTER restore - stop service to release file locks)
|
||||
step_start=$(date +%s)
|
||||
log "STEP 6: Running cleanup"
|
||||
|
||||
ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\cleanup_database.ps1 /SILENT /AFTER" 2>/dev/null
|
||||
|
||||
cleanup_freed=8
|
||||
track_step "Cleanup" true "Database cleaned, ~${cleanup_freed}GB freed" "$step_start"
|
||||
|
||||
else
|
||||
# Collect restore log even when restore fails (FULL log)
|
||||
log "Collecting restore log after failure..."
|
||||
restore_log=$(ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command \"Get-Content 'D:\\oracle\\logs\\restore_from_zero.log' -ErrorAction SilentlyContinue\"" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$restore_log" ]; then
|
||||
restore_log=$(ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command \"Get-Content 'D:\\oracle\\temp\\restore_from_zero.log' -ErrorAction SilentlyContinue\"" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
# Always try to get some error output from RMAN script
|
||||
if [ -z "$restore_log" ]; then
|
||||
last_error=$(ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command \"Get-Content 'D:\\oracle\\temp\\*.rman' -Tail 20 -ErrorAction SilentlyContinue\"" 2>/dev/null || echo "")
|
||||
if [ -n "$last_error" ]; then
|
||||
restore_log="RMAN script content (last 20 lines):\n$last_error"
|
||||
else
|
||||
restore_log="No restore logs or RMAN scripts found"
|
||||
fi
|
||||
fi
|
||||
|
||||
track_step "Database Restore" false "Restore failed" "$step_start"
|
||||
fi
|
||||
|
||||
# Step 7: Shutdown VM
|
||||
step_start=$(date +%s)
|
||||
log "STEP 7: Shutting down VM"
|
||||
|
||||
ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 30" 2>/dev/null
|
||||
sleep 60
|
||||
qm stop "$DR_VM_ID" 2>/dev/null
|
||||
|
||||
track_step "VM Shutdown" true "VM stopped" "$step_start"
|
||||
vm_status_label="Stopped"
|
||||
|
||||
else
|
||||
track_step "VM Startup" false "Failed to start VM $DR_VM_ID" "$step_start"
|
||||
vm_status_label="Failed to start"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Calculate total duration
|
||||
local test_end_time=$(date +%s)
|
||||
local total_duration=$(( (test_end_time - TEST_START_TIME) / 60 ))
|
||||
|
||||
# Prepare notification data
|
||||
local steps_json
|
||||
if [ ${#TEST_STEPS[@]} -eq 0 ]; then
|
||||
steps_json='[]'
|
||||
else
|
||||
steps_json=$(printf '%s\n' "${TEST_STEPS[@]}" | jq -s '.')
|
||||
fi
|
||||
|
||||
local errors_json
|
||||
if [ ${#ERRORS[@]} -eq 0 ]; then
|
||||
errors_json='[]'
|
||||
else
|
||||
errors_json=$(printf '%s\n' "${ERRORS[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
|
||||
local warnings_json
|
||||
if [ ${#WARNINGS[@]} -eq 0 ]; then
|
||||
warnings_json='[]'
|
||||
else
|
||||
warnings_json=$(printf '%s\n' "${WARNINGS[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
|
||||
local has_errors=false
|
||||
local has_warnings=false
|
||||
[ ${#ERRORS[@]} -gt 0 ] && has_errors=true
|
||||
[ ${#WARNINGS[@]} -gt 0 ] && has_warnings=true
|
||||
|
||||
if [ "$is_success" = true ] && [ "$has_warnings" = true ]; then
|
||||
severity="warning"
|
||||
fi
|
||||
|
||||
local db_status_clean=$(echo "$db_status" | tr -d '\r' | sed 's/^ *//;s/ *$//')
|
||||
|
||||
# Escape restore log for JSON
|
||||
local restore_log_json
|
||||
restore_log_json=$(echo "$restore_log" | jq -Rs .)
|
||||
|
||||
# Collect last 100 lines of bash script log
|
||||
local bash_log
|
||||
bash_log=$(tail -100 "$LOG_FILE" 2>/dev/null || echo "Bash log not available")
|
||||
local bash_log_json
|
||||
bash_log_json=$(echo "$bash_log" | jq -Rs .)
|
||||
|
||||
local json_data=$(cat <<JSON
|
||||
{
|
||||
"severity": "$severity",
|
||||
"test_result": "$test_result",
|
||||
"date": "$(date '+%Y-%m-%d %H:%M:%S')",
|
||||
"total_duration": $total_duration,
|
||||
"is_success": $is_success,
|
||||
"has_errors": $has_errors,
|
||||
"has_warnings": $has_warnings,
|
||||
"test_steps": $steps_json,
|
||||
"errors": $errors_json,
|
||||
"warnings": $warnings_json,
|
||||
"backup_count": $backup_count,
|
||||
"restore_duration": $restore_duration,
|
||||
"tables_restored": ${tables_restored:-0},
|
||||
"database_status": "${db_status_clean:-UNKNOWN}",
|
||||
"disk_freed": $cleanup_freed,
|
||||
"vm_id": "$DR_VM_ID",
|
||||
"vm_ip": "$DR_VM_IP",
|
||||
"vm_status": "$vm_status_label",
|
||||
"nfs_status": "${nfs_status:-Unknown}",
|
||||
"log_file": "$LOG_FILE",
|
||||
"restore_log": $restore_log_json,
|
||||
"bash_log": $bash_log_json
|
||||
}
|
||||
JSON
|
||||
)
|
||||
|
||||
# Send notification
|
||||
log "Sending notification..."
|
||||
send_pve_notification "$severity" "$json_data"
|
||||
|
||||
# Final summary
|
||||
log "=========================================="
|
||||
log "Oracle DR Test Complete: $test_result"
|
||||
log "Duration: $total_duration minutes"
|
||||
log "Log: $LOG_FILE"
|
||||
log "=========================================="
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
--install)
|
||||
create_templates
|
||||
echo ""
|
||||
echo -e "${GREEN}Installation complete!${NC}"
|
||||
echo "Next steps:"
|
||||
echo "1. Test the script: /opt/scripts/weekly-dr-test-proxmox.sh"
|
||||
echo "2. Add to cron: crontab -e"
|
||||
echo " Add line: 0 6 * * 6 /opt/scripts/weekly-dr-test-proxmox.sh"
|
||||
echo "3. Configure notifications in Proxmox GUI if needed:"
|
||||
echo " Datacenter > Notifications > Add matching rules for 'oracle-dr-test'"
|
||||
;;
|
||||
--help)
|
||||
echo "Oracle DR Weekly Test for Proxmox"
|
||||
echo "Usage:"
|
||||
echo " $0 - Run DR test"
|
||||
echo " $0 --install - Create notification templates"
|
||||
echo " $0 --help - Show this help"
|
||||
;;
|
||||
*)
|
||||
# Check if templates exist, create if missing
|
||||
if [ ! -f "$TEMPLATE_DIR/oracle-dr-test-subject.txt.hbs" ]; then
|
||||
echo -e "${YELLOW}Templates not found, creating...${NC}"
|
||||
create_templates
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Run DR test
|
||||
run_dr_test
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check dependencies
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo -e "${RED}Error: jq is not installed${NC}"
|
||||
echo "Install with: apt-get install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user