feat(sprint2): §2.1/2.2/2.3/2.5 — IP lock/lease, scheduled tasks, retention, versioned snapshot
§2.1 — IP race with capacity>1 (Invoke-CIJob.ps1)
Phases 2-3b (clone+start+IP) now run inside a file-based exclusive mutex
(F:\CI\State\vm-start.lock via FileShare.None). Only one job at a time is
in the VM-start phase, preventing concurrent vmrun starts from racing for
the same DHCP lease. After IP is confirmed unique it is written to an IP
lease file (F:\CI\State\ip-leases\<IP>.lease). Lock is released immediately
so build phases run fully in parallel. Lease released in finally block.
Collision detection: if a lease file already exists for the detected IP,
the job fails fast with a clear error rather than silently sharing a WinRM target.
§2.5 — Versioned snapshot (Invoke-CIJob.ps1 + runner/config.yaml)
$SnapshotName now defaults to GITEA_CI_SNAPSHOT_NAME env var, falling back
to 'BaseClean'. config.yaml documents the commented-out variable for snapshot
refresh workflow (BaseClean_<yyyyMMdd> naming convention).
§2.3 — Artifact/log retention (scripts/Invoke-RetentionPolicy.ps1)
Purges per-job subdirs under F:\CI\Artifacts and F:\CI\Logs older than
RetentionDays (default 30). Switches to AggressiveRetentionDays (default 7)
when F: free space drops below MinFreeGB (default 50 GB). Also removes stale
IP lease files older than 12 hours (defensive cleanup for crashed jobs).
SupportsShouldProcess (-WhatIf) for dry-run.
§2.2 — Scheduled task registration (scripts/Register-CIScheduledTasks.ps1)
Registers two tasks under \CI\ task folder as SYSTEM/HighestPrivilege:
CI-CleanupOrphans — every 6h + AtStartup, -MaxAgeHours 4
CI-RetentionPolicy — daily 03:00 (+30min random) + AtStartup (+15min random)
Idempotent (-Force), SupportsShouldProcess (-WhatIf), validates script paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
#Requires -Version 5.1
|
||||
#Requires -RunAsAdministrator
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Registers Windows Scheduled Tasks for CI system maintenance.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates or updates two tasks under the \CI\ task folder:
|
||||
|
||||
CI-CleanupOrphans — Runs Cleanup-OrphanedBuildVMs.ps1 every 6 hours
|
||||
and at host startup. Destroys stale build VMs
|
||||
(those older than -MaxAgeHours, default 4) that
|
||||
were not cleaned up after a crash or timeout.
|
||||
|
||||
CI-RetentionPolicy — Runs Invoke-RetentionPolicy.ps1 daily at 3:00 AM
|
||||
and at host startup (with a 15-min random delay).
|
||||
Purges old artifact and log directories per the
|
||||
configured retention window.
|
||||
|
||||
Both tasks run as SYSTEM with highest privilege. Idempotent: safe to re-run
|
||||
after updating the scripts — existing tasks are overwritten (-Force).
|
||||
|
||||
.PARAMETER ScriptRoot
|
||||
Directory containing the CI scripts. Default: N:\Code\Workspace\Local-CI-CD-System\scripts
|
||||
|
||||
.PARAMETER MaxAgeHours
|
||||
Passed to Cleanup-OrphanedBuildVMs.ps1. VMs older than this are treated as
|
||||
orphaned. Must exceed the longest expected build duration. Default: 4
|
||||
|
||||
.EXAMPLE
|
||||
# Register (or update) tasks — run from an elevated PowerShell session
|
||||
.\Register-CIScheduledTasks.ps1
|
||||
|
||||
# Preview what would be registered
|
||||
.\Register-CIScheduledTasks.ps1 -WhatIf
|
||||
#>
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[string] $ScriptRoot = 'N:\Code\Workspace\Local-CI-CD-System\scripts',
|
||||
|
||||
[ValidateRange(1, 168)]
|
||||
[int] $MaxAgeHours = 4
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$psExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
$taskPath = '\CI\'
|
||||
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest
|
||||
|
||||
# Ensure task folder exists (Register-ScheduledTask creates it, but be explicit)
|
||||
$schedulerService = New-Object -ComObject 'Schedule.Service'
|
||||
$schedulerService.Connect()
|
||||
$rootFolder = $schedulerService.GetFolder('\')
|
||||
try { $rootFolder.GetFolder('CI') | Out-Null }
|
||||
catch { $rootFolder.CreateFolder('CI') | Out-Null }
|
||||
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($schedulerService) | Out-Null
|
||||
|
||||
# ── Task 1: CI-CleanupOrphans ─────────────────────────────────────────────────
|
||||
$cleanupScript = Join-Path $ScriptRoot 'Cleanup-OrphanedBuildVMs.ps1'
|
||||
if (-not (Test-Path $cleanupScript -PathType Leaf)) {
|
||||
Write-Warning "Script not found — skipping CI-CleanupOrphans: $cleanupScript"
|
||||
} else {
|
||||
$cleanupAction = New-ScheduledTaskAction `
|
||||
-Execute $psExe `
|
||||
-Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$cleanupScript`" -MaxAgeHours $MaxAgeHours"
|
||||
|
||||
# Repeat every 6 hours indefinitely (Once trigger + RepetitionInterval)
|
||||
$repeat6h = New-ScheduledTaskTrigger -Once -At '00:00' `
|
||||
-RepetitionInterval (New-TimeSpan -Hours 6)
|
||||
$atStartup = New-ScheduledTaskTrigger -AtStartup
|
||||
|
||||
$cleanupSettings = New-ScheduledTaskSettingsSet `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
|
||||
-MultipleInstances IgnoreNew `
|
||||
-StartWhenAvailable
|
||||
|
||||
if ($PSCmdlet.ShouldProcess('CI-CleanupOrphans', 'Register/update scheduled task')) {
|
||||
Register-ScheduledTask `
|
||||
-TaskName 'CI-CleanupOrphans' `
|
||||
-TaskPath $taskPath `
|
||||
-Action $cleanupAction `
|
||||
-Trigger @($repeat6h, $atStartup) `
|
||||
-Principal $principal `
|
||||
-Settings $cleanupSettings `
|
||||
-Description "Destroys orphaned CI build VMs older than ${MaxAgeHours}h (Local CI/CD System)" `
|
||||
-Force | Out-Null
|
||||
Write-Host "[Register] CI-CleanupOrphans registered (every 6h + at startup, -MaxAgeHours $MaxAgeHours)." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
# ── Task 2: CI-RetentionPolicy ────────────────────────────────────────────────
|
||||
$retentionScript = Join-Path $ScriptRoot 'Invoke-RetentionPolicy.ps1'
|
||||
if (-not (Test-Path $retentionScript -PathType Leaf)) {
|
||||
Write-Warning "Script not found — skipping CI-RetentionPolicy: $retentionScript"
|
||||
} else {
|
||||
$retentionAction = New-ScheduledTaskAction `
|
||||
-Execute $psExe `
|
||||
-Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$retentionScript`""
|
||||
|
||||
$daily3am = New-ScheduledTaskTrigger -Daily -At '03:00' `
|
||||
-RandomDelay (New-TimeSpan -Minutes 30)
|
||||
$atStartup2 = New-ScheduledTaskTrigger -AtStartup `
|
||||
-RandomDelay (New-TimeSpan -Minutes 15)
|
||||
|
||||
$retentionSettings = New-ScheduledTaskSettingsSet `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
|
||||
-MultipleInstances IgnoreNew `
|
||||
-StartWhenAvailable
|
||||
|
||||
if ($PSCmdlet.ShouldProcess('CI-RetentionPolicy', 'Register/update scheduled task')) {
|
||||
Register-ScheduledTask `
|
||||
-TaskName 'CI-RetentionPolicy' `
|
||||
-TaskPath $taskPath `
|
||||
-Action $retentionAction `
|
||||
-Trigger @($daily3am, $atStartup2) `
|
||||
-Principal $principal `
|
||||
-Settings $retentionSettings `
|
||||
-Description 'Purges old CI artifacts, logs, and stale IP leases (Local CI/CD System)' `
|
||||
-Force | Out-Null
|
||||
Write-Host "[Register] CI-RetentionPolicy registered (daily 03:00 + at startup)." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[Register] Done. Verify with: Get-ScheduledTask -TaskPath '\CI\' | Format-Table TaskName, State" -ForegroundColor Cyan
|
||||
Reference in New Issue
Block a user