Remove manual password hashing and web.yml setup in favor of automated generation. Add scripts for both Unix and Windows to create `monitoring/web.yml` using credentials from `.env`. This improves maintainability and reduces manual intervention during setup and configuration.
337 lines
7.9 KiB
PowerShell
337 lines
7.9 KiB
PowerShell
# Shared utilities for Windows scripts
|
|
# Provides: colors, progress indicators, interactive detection
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# Detect if running in interactive shell
|
|
function Test-Interactive
|
|
{
|
|
# Check if both input and output are from terminal
|
|
# In non-interactive environments (CI/CD), this will be false
|
|
$isInteractive = [Environment]::UserInteractive -and
|
|
(-not [Console]::IsInputRedirected) -and
|
|
(-not [Console]::IsOutputRedirected)
|
|
return $isInteractive
|
|
}
|
|
|
|
# Global spinner state
|
|
$script:SpinnerJob = $null
|
|
$script:SpinnerFrames = @('⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏')
|
|
|
|
# Logging functions
|
|
function Write-Info
|
|
{
|
|
param([string]$Message)
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
Write-Host $Message -ForegroundColor Blue
|
|
}
|
|
else
|
|
{
|
|
Write-Output $Message
|
|
}
|
|
}
|
|
|
|
function Write-Success
|
|
{
|
|
param([string]$Message)
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
Write-Host $Message -ForegroundColor Green
|
|
}
|
|
else
|
|
{
|
|
Write-Output $Message
|
|
}
|
|
}
|
|
|
|
function Write-Warning-Custom
|
|
{
|
|
param([string]$Message)
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
Write-Host $Message -ForegroundColor Yellow
|
|
}
|
|
else
|
|
{
|
|
Write-Output "WARNING: $Message"
|
|
}
|
|
}
|
|
|
|
function Write-Error-Custom
|
|
{
|
|
param([string]$Message)
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
Write-Host $Message -ForegroundColor Red
|
|
}
|
|
else
|
|
{
|
|
Write-Output "ERROR: $Message" *>&1
|
|
}
|
|
}
|
|
|
|
function Write-Step
|
|
{
|
|
param([string]$Message)
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
Write-Host $Message -ForegroundColor Magenta
|
|
}
|
|
else
|
|
{
|
|
Write-Output $Message
|
|
}
|
|
}
|
|
|
|
function Write-Result
|
|
{
|
|
param([string]$Message)
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
Write-Host $Message -ForegroundColor Cyan
|
|
}
|
|
else
|
|
{
|
|
Write-Output $Message
|
|
}
|
|
}
|
|
|
|
# Spinner functions (only for interactive shells)
|
|
function Start-Spinner
|
|
{
|
|
param([string]$Message = "Processing...")
|
|
|
|
if (-not (Test-Interactive))
|
|
{
|
|
Write-Output $Message
|
|
return
|
|
}
|
|
|
|
$script:SpinnerJob = Start-Job -ScriptBlock {
|
|
param($Frames, $Msg)
|
|
|
|
$i = 0
|
|
while ($true)
|
|
{
|
|
$frame = $Frames[$i % $Frames.Length]
|
|
[Console]::SetCursorPosition(0, [Console]::CursorTop)
|
|
Write-Host -NoNewline "$frame $Msg"
|
|
Start-Sleep -Milliseconds 100
|
|
$i++
|
|
}
|
|
} -ArgumentList $script:SpinnerFrames, $Message
|
|
}
|
|
|
|
function Stop-Spinner
|
|
{
|
|
if ($script:SpinnerJob -ne $null)
|
|
{
|
|
Stop-Job -Job $script:SpinnerJob -ErrorAction SilentlyContinue
|
|
Remove-Job -Job $script:SpinnerJob -Force -ErrorAction SilentlyContinue
|
|
$script:SpinnerJob = $null
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
# Clear the spinner line
|
|
[Console]::SetCursorPosition(0, [Console]::CursorTop)
|
|
Write-Host (' ' * ([Console]::WindowWidth - 1)) -NoNewline
|
|
[Console]::SetCursorPosition(0, [Console]::CursorTop)
|
|
}
|
|
}
|
|
}
|
|
|
|
function Update-Spinner
|
|
{
|
|
param([string]$Message)
|
|
|
|
if (-not (Test-Interactive))
|
|
{
|
|
Write-Output $Message
|
|
return
|
|
}
|
|
|
|
if ($script:SpinnerJob -ne $null)
|
|
{
|
|
Stop-Spinner
|
|
Start-Spinner -Message $Message
|
|
}
|
|
}
|
|
|
|
# Execute command with progress indicator
|
|
function Invoke-WithProgress
|
|
{
|
|
param(
|
|
[string]$Message,
|
|
[scriptblock]$ScriptBlock
|
|
)
|
|
|
|
if (Test-Interactive)
|
|
{
|
|
Start-Spinner -Message $Message
|
|
|
|
try
|
|
{
|
|
$output = & $ScriptBlock 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
|
|
Stop-Spinner
|
|
|
|
if ($exitCode -eq 0 -or $null -eq $exitCode)
|
|
{
|
|
Write-Success "✓ $Message - Done!"
|
|
}
|
|
else
|
|
{
|
|
Write-Error-Custom "✗ $Message - Failed!"
|
|
if ($output)
|
|
{
|
|
Write-Output $output
|
|
}
|
|
}
|
|
|
|
return $exitCode
|
|
}
|
|
catch
|
|
{
|
|
Stop-Spinner
|
|
Write-Error-Custom "✗ $Message - Failed!"
|
|
throw
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Non-interactive: just show message and run
|
|
Write-Output "→ $Message"
|
|
|
|
try
|
|
{
|
|
& $ScriptBlock
|
|
$exitCode = $LASTEXITCODE
|
|
|
|
if ($exitCode -eq 0 -or $null -eq $exitCode)
|
|
{
|
|
Write-Output "✓ $Message - Done!"
|
|
}
|
|
else
|
|
{
|
|
Write-Output "✗ $Message - Failed!"
|
|
}
|
|
|
|
return $exitCode
|
|
}
|
|
catch
|
|
{
|
|
Write-Output "✗ $Message - Failed!"
|
|
throw
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check system requirements
|
|
function Test-SystemRequirements
|
|
{
|
|
param(
|
|
[int]$MinCpu = 4,
|
|
[int]$MinRamGB = 6,
|
|
[int]$MinDiskGB = 20
|
|
)
|
|
|
|
Write-Step "Checking system requirements..."
|
|
|
|
# Check CPU cores
|
|
$cpuCount = [Environment]::ProcessorCount
|
|
if ($cpuCount -lt $MinCpu)
|
|
{
|
|
Write-Error-Custom "Insufficient CPU cores: $cpuCount (minimum: $MinCpu)"
|
|
return $false
|
|
}
|
|
|
|
# Check RAM
|
|
$totalMemBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory
|
|
$totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2)
|
|
if ($totalMemGB -lt $MinRamGB)
|
|
{
|
|
Write-Error-Custom "Insufficient RAM: ${totalMemGB}GB (minimum: ${MinRamGB}GB)"
|
|
return $false
|
|
}
|
|
|
|
# Check disk space
|
|
$currentDrive = Split-Path -Qualifier $PWD
|
|
$driveLetter = $currentDrive.Substring(0, 1)
|
|
$freeGB = [Math]::Round((Get-PSDrive -Name $driveLetter).Free / 1GB, 2)
|
|
if ($freeGB -lt $MinDiskGB)
|
|
{
|
|
Write-Error-Custom "Insufficient disk space: ${freeGB}GB (minimum: ${MinDiskGB}GB)"
|
|
return $false
|
|
}
|
|
|
|
Write-Success "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB"
|
|
return $true
|
|
}
|
|
|
|
# Generate monitoring/web.yml from PROMETHEUS_USER and PROMETHEUS_PASSWORD in .env
|
|
function New-PrometheusWebConfig
|
|
{
|
|
if (-not (Test-Path '.env')) { return }
|
|
|
|
$envContent = Get-Content '.env'
|
|
$promUserLine = $envContent | Where-Object { $_ -match '^PROMETHEUS_USER=' } | Select-Object -First 1
|
|
$promPassLine = $envContent | Where-Object { $_ -match '^PROMETHEUS_PASSWORD=' } | Select-Object -First 1
|
|
|
|
if (-not $promUserLine -or -not $promPassLine) {
|
|
Write-Warning-Custom "PROMETHEUS_USER or PROMETHEUS_PASSWORD not set in .env, skipping web.yml generation"
|
|
return
|
|
}
|
|
|
|
$promUser = ($promUserLine -replace '^PROMETHEUS_USER=', '').Trim('"')
|
|
$promPassword = ($promPassLine -replace '^PROMETHEUS_PASSWORD=', '').Trim('"')
|
|
|
|
if ([string]::IsNullOrEmpty($promUser) -or [string]::IsNullOrEmpty($promPassword)) {
|
|
Write-Warning-Custom "PROMETHEUS_USER or PROMETHEUS_PASSWORD is empty, skipping web.yml generation"
|
|
return
|
|
}
|
|
|
|
$rawHash = docker run --rm httpd:2-alpine htpasswd -nbBC 12 "" "$promPassword" 2>$null
|
|
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrEmpty($rawHash)) {
|
|
Write-Warning-Custom "Failed to generate Prometheus password hash"
|
|
return
|
|
}
|
|
|
|
# htpasswd outputs ":$2y$..." - strip leading colon and whitespace
|
|
$hash = $rawHash.TrimStart(':').Trim()
|
|
|
|
$content = "basic_auth_users:`n ${promUser}: ${hash}`n"
|
|
[System.IO.File]::WriteAllText((Join-Path $PWD 'monitoring/web.yml'), $content)
|
|
|
|
Write-Success "Prometheus web config generated"
|
|
}
|
|
|
|
# Confirm action
|
|
function Confirm-Action
|
|
{
|
|
param([string]$Prompt = "Are you sure?")
|
|
|
|
if (-not (Test-Interactive))
|
|
{
|
|
# In non-interactive mode, assume yes
|
|
return $true
|
|
}
|
|
|
|
$response = Read-Host "$Prompt [y/N]"
|
|
return $response -match '^[yY]([eE][sS])?$'
|
|
}
|
|
|
|
# Ensure spinner cleanup on exit
|
|
if ($MyInvocation.MyCommand.ScriptBlock.Module) {
|
|
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
|
|
Stop-Spinner
|
|
}
|
|
}
|