Skip to content

Commit 3dcae60

Browse files
committed
Rotate logs and backup databases on Windows VMs
Sets up a weekly schedule to backup databases every Sunday at 2AM. Sets up a daily schedule to rotate logs at 2:30AM. 5 rotated logs are retained.
1 parent 9d3e46f commit 3dcae60

7 files changed

Lines changed: 449 additions & 133 deletions

File tree

scripts/bash/package_release.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ echo ""
6161

6262
# ── Validate required files ───────────────────────────────────────────────────
6363

64-
REQUIRED_FILES=("src/" "sample_images/" "scripts/python/" "pyproject.toml" "uv.lock" "README.md" "LICENCE.md")
64+
REQUIRED_FILES=("src/" "sample_images/" "scripts/python/database.py" \
65+
"scripts/powershell/maintenance.ps1" "pyproject.toml" "uv.lock" "README.md" "LICENCE.md")
6566

6667
for item in "${REQUIRED_FILES[@]}"; do
6768
if [[ ! -e "${REPO_ROOT}/${item}" ]]; then

scripts/bat/backup_database.bat

Lines changed: 0 additions & 6 deletions
This file was deleted.

scripts/bat/schtasks.bat

Lines changed: 0 additions & 7 deletions
This file was deleted.

scripts/powershell/deploy.ps1

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,54 @@ if (-not $cutoverFailed) {
560560
}
561561
}
562562

563+
# -- Maintenance schedule -------------------------------------------------------------
564+
565+
if (-not $cutoverFailed) {
566+
$maintenanceScriptPath = Join-Path $BaseInstallPath "current\scripts\powershell\maintenance.ps1"
567+
$backupMWLAction = New-ScheduledTaskAction `
568+
-Execute "powershell.exe" `
569+
-Argument "-ExecutionPolicy Bypass -File `"$maintenanceScriptPath`" -Action BackupMWLDatabase"
570+
571+
$backupMWLTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 2AM
572+
573+
Register-ScheduledTask `
574+
-TaskName "Gateway-MWL-Maintenance" `
575+
-Action $backupMWLAction `
576+
-Trigger $backupMWLTrigger `
577+
-User "SYSTEM" `
578+
-RunLevel Highest `
579+
-Force
580+
581+
$backupPACSAction = New-ScheduledTaskAction `
582+
-Execute "powershell.exe" `
583+
-Argument "-ExecutionPolicy Bypass -File `"$maintenanceScriptPath`" -Action BackupPACSDatabase"
584+
585+
$backupPACSTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 2.15AM
586+
587+
Register-ScheduledTask `
588+
-TaskName "Gateway-PACS-Maintenance" `
589+
-Action $backupPACSAction `
590+
-Trigger $backupPACSTrigger `
591+
-User "SYSTEM" `
592+
-RunLevel Highest `
593+
-Force
594+
595+
$rotateLogsAction = New-ScheduledTaskAction `
596+
-Execute "powershell.exe" `
597+
-Argument "-ExecutionPolicy Bypass -File `"$maintenanceScriptPath`" -Action RotateLogs"
598+
599+
$rotateLogsTrigger = New-ScheduledTaskTrigger -Daily -At 2.30AM
600+
601+
Register-ScheduledTask `
602+
-TaskName "Gateway-Logs-Maintenance" `
603+
-Action $rotateLogsAction `
604+
-Trigger $rotateLogsTrigger `
605+
-User "SYSTEM" `
606+
-RunLevel Highest `
607+
-Force
608+
609+
}
610+
563611
# -- Rollback on Failure ------------------------------------------------------
564612

565613
if ($cutoverFailed) {

scripts/powershell/maintenance.ps1

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
param(
2+
[ValidateSet("BackupPACSDatabase","BackupMWLDatabase","RotateLogs")]
3+
[string]$BaseInstallPath = "C:\Program Files\NHS\ManageBreastScreeningGateway",
4+
[string]$Action
5+
)
6+
7+
# -- Set common vars -------------------------------------------------------
8+
9+
$logsDir = Join-Path $BaseInstallPath "logs"
10+
$maintenanceLogFile = Join-Path $logsDir "maintenance.log"
11+
12+
$services = Get-Service |
13+
Where-Object { $_.Name -like "Gateway-*" } |
14+
ForEach-Object {
15+
@{ Name = $_.Name }
16+
}
17+
18+
function Write-Log {
19+
param([string]$Message, [string]$Level = "INFO")
20+
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff"
21+
$logEntry = "[$timestamp] [$Level] $Message"
22+
Add-Content -Path $maintenanceLogFile -Value $logEntry
23+
switch ($Level) {
24+
"ERROR" { Write-Host $logEntry -ForegroundColor Red }
25+
"WARNING" { Write-Host $logEntry -ForegroundColor Yellow }
26+
"SUCCESS" { Write-Host $logEntry -ForegroundColor Green }
27+
default { Write-Host $logEntry -ForegroundColor Gray }
28+
}
29+
}
30+
31+
# -- Service control helpers -------------------------------------------------------
32+
33+
function Stop-AllServices {
34+
param([array]$Services, [int]$TimeoutSeconds)
35+
foreach ($svc in $Services) {
36+
$status = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
37+
if (-not $status -or $status.Status -eq 'Stopped') { continue }
38+
39+
Write-Log "Stopping $($svc.Name) (timeout: ${TimeoutSeconds}s)..." "INFO"
40+
Stop-Service -Name $svc.Name -Force
41+
42+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
43+
while ($stopwatch.Elapsed.TotalSeconds -lt $TimeoutSeconds) {
44+
$current = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
45+
if (-not $current -or $current.Status -eq 'Stopped') { break }
46+
Start-Sleep -Milliseconds 500
47+
}
48+
$stopwatch.Stop()
49+
50+
$finalStatus = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
51+
if ($finalStatus -and $finalStatus.Status -ne 'Stopped') {
52+
throw "Service $($svc.Name) did not stop within ${TimeoutSeconds}s (state: $($finalStatus.Status))."
53+
}
54+
Write-Log "$($svc.Name) stopped in $([math]::Round($stopwatch.Elapsed.TotalSeconds, 1))s." "INFO"
55+
}
56+
}
57+
58+
function Start-AllServices {
59+
param(
60+
[array]$Services, [int]$TimeoutSeconds
61+
)
62+
63+
foreach ($svc in $Services) {
64+
Write-Log "Starting $($svc.Name)..." "INFO"
65+
66+
Start-Service -Name $svc.Name
67+
68+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
69+
while ($stopwatch.Elapsed.TotalSeconds -lt $TimeoutSeconds) {
70+
$status = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
71+
if ($status -and $status.Status -eq 'Running') { break }
72+
Start-Sleep -Milliseconds 500
73+
}
74+
75+
Write-Log "$($svc.Name) started." "SUCCESS"
76+
}
77+
}
78+
79+
# -- Database backup ----------------------------------------------------------
80+
81+
function Invoke-DatabaseBackup {
82+
param(
83+
[string]$BaseInstallPath,
84+
[string]$DbServiceName
85+
)
86+
87+
Write-Log "Starting database backup..." "INFO"
88+
89+
Set-Item -Path env:PYTHONPATH -Value "$BaseInstallPath\current\scripts\python"
90+
Set-Item -Path env:BACKUP_PATH -Value "$BaseInstallPath\data\backups"
91+
Set-Item -Path env:MAX_BACKUPS -Value 3
92+
93+
if ($DbServiceName -eq "MWL") {
94+
Set-Item -Path env:DB_PATH -Value "$BaseInstallPath\data\worklist.db"
95+
Set-Item -Path env:TABLE_NAME -Value "worklist_items"
96+
}
97+
if ($DbServiceName -eq "PACS") {
98+
Set-Item -Path env:DB_PATH -Value "$BaseInstallPath\data\pacs.db"
99+
Set-Item -Path env:TABLE_NAME -Value "stored_instances"
100+
}
101+
.\..\python\database.py
102+
103+
if ($LASTEXITCODE -ne 0) {
104+
Write-Log "Database backup failed with exit code $LASTEXITCODE" "ERROR"
105+
throw "Database backup failed with exit code $LASTEXITCODE"
106+
}
107+
108+
Write-Log "Database backup completed successfully." "SUCCESS"
109+
110+
}
111+
112+
# -- Log Rotation -------------------------------------------------------------
113+
114+
function Rotate-LogFile {
115+
param(
116+
[Parameter(Mandatory)]
117+
[string]$LogFile,
118+
119+
[int]$RetainCount = 5
120+
)
121+
122+
if (-not (Test-Path $LogFile)) {
123+
return
124+
}
125+
126+
# Remove oldest
127+
$oldest = "$LogFile.$RetainCount"
128+
if (Test-Path $oldest) {
129+
Remove-Item $oldest -Force
130+
}
131+
132+
# Shift existing rotations
133+
for ($i = $RetainCount - 1; $i -ge 1; $i--) {
134+
$src = "$LogFile.$i"
135+
$dst = "$LogFile." + ($i + 1)
136+
137+
if (Test-Path $src) {
138+
Move-Item $src $dst -Force
139+
}
140+
}
141+
142+
Move-Item $LogFile "$LogFile.1" -Force
143+
}
144+
145+
function Rotate-ServiceLogs {
146+
param(
147+
[array]$Services,
148+
[string]$LogsDir
149+
)
150+
151+
foreach ($svc in $Services) {
152+
$logFile = Join-Path $LogsDir "$($svc.Name).log"
153+
Rotate-LogFile -LogFile $logFile -RetainCount 5
154+
}
155+
}
156+
157+
# -- Main ----------------------------------------------------------------------
158+
159+
switch ($Action) {
160+
161+
"RotateLogs" {
162+
Stop-AllServices -Services $Services
163+
Rotate-ServiceLogs -Services $services -LogsDir $logsDir
164+
Start-AllServices -Services $Services
165+
}
166+
167+
"BackupPACSDatabase" {
168+
Stop-AllServices -Services $Services
169+
Invoke-DatabaseBackup -BaseInstallPath $BaseInstallPath -DbServiceName "PACS"
170+
Start-AllServices -Services $Services
171+
}
172+
173+
"BackupMWLDatabase" {
174+
Stop-AllServices -Services $Services
175+
Invoke-DatabaseBackup -BaseInstallPath $BaseInstallPath -DbServiceName "MWL"
176+
Start-AllServices -Services $Services
177+
}
178+
179+
}
180+
181+
exit 0

0 commit comments

Comments
 (0)