docs: second pass - consolidation and deduplication #60
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test Scripts | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: # Allow manual trigger | |
| jobs: | |
| test-windows-scripts: | |
| name: Test Windows PowerShell Scripts | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Pester and PSScriptAnalyzer | |
| shell: pwsh | |
| run: | | |
| Install-Module -Name Pester -Force -SkipPublisherCheck -Scope CurrentUser | |
| Install-Module -Name PSScriptAnalyzer -Force -SkipPublisherCheck -Scope CurrentUser | |
| Import-Module Pester | |
| Import-Module PSScriptAnalyzer | |
| - name: Enable SSH Agent Service | |
| shell: pwsh | |
| run: | | |
| Write-Host "[i] Enabling SSH agent service for tests..." | |
| # Check if OpenSSH is installed | |
| $sshAgent = Get-Service -Name "ssh-agent" -ErrorAction SilentlyContinue | |
| if (-not $sshAgent) { | |
| Write-Host "[i] Installing OpenSSH..." | |
| Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 | |
| } | |
| # Set service to Manual and start it | |
| Set-Service -Name "ssh-agent" -StartupType Manual | |
| Start-Service -Name "ssh-agent" | |
| # Verify | |
| $service = Get-Service -Name "ssh-agent" | |
| Write-Host "[+] SSH agent status: $($service.Status)" | |
| - name: Run PSScriptAnalyzer | |
| shell: pwsh | |
| run: | | |
| Write-Host "[i] Running PSScriptAnalyzer on PowerShell scripts..." -ForegroundColor Blue | |
| $results = @() | |
| $scripts = Get-ChildItem -Path ".\Windows" -Recurse -Include "*.ps1", "*.psm1" -File | |
| foreach ($script in $scripts) { | |
| $analysis = Invoke-ScriptAnalyzer -Path $script.FullName -Severity Warning, Error | |
| if ($analysis) { | |
| $results += $analysis | |
| } | |
| } | |
| if ($results.Count -gt 0) { | |
| Write-Host "" | |
| Write-Host "[!] PSScriptAnalyzer found $($results.Count) issues:" -ForegroundColor Yellow | |
| $results | Group-Object -Property Severity | ForEach-Object { | |
| Write-Host " $($_.Name): $($_.Count)" -ForegroundColor $(if ($_.Name -eq 'Error') { 'Red' } else { 'Yellow' }) | |
| } | |
| Write-Host "" | |
| # Show top issues by rule | |
| Write-Host "[i] Top issues by rule:" -ForegroundColor Blue | |
| $results | Group-Object -Property RuleName | Sort-Object Count -Descending | Select-Object -First 5 | ForEach-Object { | |
| Write-Host " $($_.Name): $($_.Count)" -ForegroundColor Cyan | |
| } | |
| # Fail only on errors, warn on warnings | |
| $errors = $results | Where-Object { $_.Severity -eq 'Error' } | |
| if ($errors.Count -gt 0) { | |
| Write-Host "" | |
| Write-Host "[-] PSScriptAnalyzer found $($errors.Count) errors:" -ForegroundColor Red | |
| $errors | ForEach-Object { | |
| Write-Host " $($_.ScriptName):$($_.Line) - $($_.RuleName)" -ForegroundColor Red | |
| } | |
| exit 1 | |
| } | |
| Write-Host "" | |
| Write-Host "[+] No critical errors found (warnings are informational)" -ForegroundColor Green | |
| } else { | |
| Write-Host "[+] PSScriptAnalyzer: No issues found!" -ForegroundColor Green | |
| } | |
| - name: Run Pester tests with coverage | |
| shell: pwsh | |
| run: | | |
| $Config = New-PesterConfiguration | |
| $Config.Run.Path = ".\tests\Windows" | |
| $Config.Output.Verbosity = "Detailed" | |
| # Test results | |
| $Config.TestResult.Enabled = $true | |
| $Config.TestResult.OutputPath = "test-results-windows.xml" | |
| $Config.TestResult.OutputFormat = "NUnitXml" | |
| # Code coverage | |
| $Config.CodeCoverage.Enabled = $true | |
| $Config.CodeCoverage.Path = @(".\Windows\**\*.ps1") | |
| $Config.CodeCoverage.OutputPath = "coverage.xml" | |
| $Config.CodeCoverage.OutputFormat = "JaCoCo" | |
| $Config.Run.PassThru = $true | |
| $Result = Invoke-Pester -Configuration $Config | |
| # Display coverage summary | |
| Write-Host "" | |
| Write-Host "[i] Code Coverage Summary:" -ForegroundColor Blue | |
| Write-Host " Total Commands: $($Result.CodeCoverage.CommandsExecutedCount + $Result.CodeCoverage.CommandsMissedCount)" | |
| Write-Host " Commands Executed: $($Result.CodeCoverage.CommandsExecutedCount)" -ForegroundColor Green | |
| Write-Host " Commands Missed: $($Result.CodeCoverage.CommandsMissedCount)" -ForegroundColor Yellow | |
| if ($Result.CodeCoverage.CommandsAnalyzedCount -gt 0) { | |
| $coveragePercent = [math]::Round(($Result.CodeCoverage.CommandsExecutedCount / $Result.CodeCoverage.CommandsAnalyzedCount) * 100, 2) | |
| Write-Host " Coverage: $coveragePercent%" -ForegroundColor $(if ($coveragePercent -ge 70) { "Green" } elseif ($coveragePercent -ge 50) { "Yellow" } else { "Red" }) | |
| } | |
| - name: Upload test results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: windows-test-results | |
| path: test-results-windows.xml | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: coverage-report | |
| path: coverage.xml | |
| - name: Add coverage comment to PR | |
| if: github.event_name == 'pull_request' | |
| uses: madrapps/jacoco-report@v1.7.1 | |
| with: | |
| paths: coverage.xml | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| min-coverage-overall: 70 | |
| min-coverage-changed-files: 80 | |
| title: 'Code Coverage Report' | |
| update-comment: true | |
| - name: Generate coverage badge | |
| if: github.ref == 'refs/heads/main' | |
| shell: pwsh | |
| run: | | |
| # Calculate coverage percentage | |
| [xml]$coverage = Get-Content coverage.xml | |
| $total = [int]$coverage.report.counter | Where-Object { $_.type -eq "INSTRUCTION" } | Select-Object -ExpandProperty covered | |
| $missed = [int]$coverage.report.counter | Where-Object { $_.type -eq "INSTRUCTION" } | Select-Object -ExpandProperty missed | |
| $percent = if (($total + $missed) -gt 0) { [math]::Round(($total / ($total + $missed)) * 100, 0) } else { 0 } | |
| Write-Host "[i] Coverage: $percent%" | |
| # Badge color | |
| $color = if ($percent -ge 80) { "brightgreen" } elseif ($percent -ge 60) { "green" } elseif ($percent -ge 40) { "yellow" } else { "red" } | |
| # Save for potential badge generation | |
| "COVERAGE_PERCENT=$percent" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "COVERAGE_COLOR=$color" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| test-linux-scripts: | |
| name: Test Linux Bash Scripts | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install BATS | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y bats | |
| - name: Run BATS tests | |
| run: | | |
| bats tests/Linux/maintenance.bats | |
| bats tests/Linux/CommonFunctions.bats | |
| bats tests/Linux/SystemHealthCheck.bats | |
| bats tests/Linux/SecurityHardening.bats | |
| bats tests/Linux/ServiceHealthMonitor.bats | |
| - name: Check script syntax (shellcheck) | |
| run: | | |
| sudo apt-get install -y shellcheck | |
| echo "[i] Running shellcheck on Linux scripts..." | |
| # Run shellcheck with specific exclusions for acceptable patterns | |
| # SC2034: Unused variables (often used for configuration) | |
| # SC1091: Cannot follow sourced file (common-functions.sh) | |
| # SC2154: Variable referenced but not assigned (from sourced files) | |
| find Linux -name "*.sh" -type f -exec shellcheck -x \ | |
| --exclude=SC2034,SC1091,SC2154,SC2155,SC2046,SC2178,SC2128 \ | |
| --severity=warning {} \; | |
| echo "[+] Shellcheck passed" | |
| - name: Verify no emojis in scripts | |
| run: | | |
| echo "[i] Checking for emojis in Linux scripts..." | |
| if grep -rP '[\x{1F300}-\x{1F9FF}]|✅|❌|⚠️|ℹ️' Linux/ --include="*.sh"; then | |
| echo "[-] ERROR: Found emojis in Linux scripts!" | |
| exit 1 | |
| else | |
| echo "[+] No emojis found - scripts comply with CLAUDE.md rules" | |
| fi | |
| security-scan: | |
| name: Security Scan for Secrets | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Full history for better detection | |
| - name: Check for hardcoded secrets | |
| run: | | |
| echo "[i] Scanning for hardcoded credentials..." | |
| # Check for potential passwords | |
| if grep -rE 'password\s*=\s*["\x27].*["\x27]' . --exclude-dir=.git --exclude-dir=tests --exclude="*.md"; then | |
| echo "[-] ERROR: Found hardcoded passwords!" | |
| exit 1 | |
| fi | |
| # Check for API keys | |
| if grep -rE 'api[_-]?key\s*=\s*["\x27].*["\x27]' . --exclude-dir=.git --exclude-dir=tests --exclude="*.md"; then | |
| echo "[-] ERROR: Found hardcoded API keys!" | |
| exit 1 | |
| fi | |
| # Check for private IPs (should use examples only) | |
| if grep -rE '10\.143\.31\.' . --exclude-dir=.git --exclude="*.md" --exclude="CLAUDE.md"; then | |
| echo "[-] ERROR: Found hardcoded private IPs!" | |
| exit 1 | |
| fi | |
| echo "[+] No hardcoded secrets found" | |
| - name: Check for SSH private keys | |
| run: | | |
| echo "[i] Scanning for SSH private keys..." | |
| if grep -r "BEGIN.*PRIVATE KEY" . --exclude-dir=.git --exclude-dir=tests --exclude-dir=.github --exclude-dir=.githooks; then | |
| echo "[-] ERROR: Found SSH private keys in repository!" | |
| exit 1 | |
| fi | |
| echo "[+] No SSH private keys found" | |
| validate-structure: | |
| name: Validate Repository Structure | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Check required files exist | |
| run: | | |
| echo "[i] Validating repository structure..." | |
| # Required documentation | |
| test -f README.md || (echo "[-] Missing README.md" && exit 1) | |
| test -f .gitignore || (echo "[-] Missing .gitignore" && exit 1) | |
| test -f SECURITY.md || (echo "[-] Missing SECURITY.md" && exit 1) | |
| test -f CONTRIBUTING.md || (echo "[-] Missing CONTRIBUTING.md" && exit 1) | |
| # Required directories | |
| test -d Windows/first-time-setup || (echo "[-] Missing Windows/first-time-setup" && exit 1) | |
| test -d Windows/ssh || (echo "[-] Missing Windows/ssh" && exit 1) | |
| test -d Linux/maintenance || (echo "[-] Missing Linux/maintenance" && exit 1) | |
| test -d tests || (echo "[-] Missing tests directory" && exit 1) | |
| echo "[+] Repository structure is valid" | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libxml2-utils | |
| - name: Check Windows package exports | |
| run: | | |
| echo "[i] Validating Windows package exports..." | |
| test -f Windows/first-time-setup/winget-packages.json || (echo "[-] Missing winget-packages.json" && exit 1) | |
| test -f Windows/first-time-setup/chocolatey-packages.config || (echo "[-] Missing chocolatey-packages.config" && exit 1) | |
| # Validate JSON syntax | |
| python3 -m json.tool Windows/first-time-setup/winget-packages.json > /dev/null || (echo "[-] Invalid JSON in winget-packages.json" && exit 1) | |
| # Validate XML syntax | |
| xmllint --noout Windows/first-time-setup/chocolatey-packages.config || (echo "[-] Invalid XML in chocolatey-packages.config" && exit 1) | |
| echo "[+] Package exports are valid" | |
| markdown-lint: | |
| name: Lint Markdown Documentation | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install markdownlint | |
| run: | | |
| sudo npm install -g markdownlint-cli | |
| - name: Run markdownlint | |
| run: | | |
| markdownlint '**/*.md' --ignore node_modules --ignore .git || true | |
| - name: Check for broken links (basic) | |
| run: | | |
| echo "[i] Checking for obviously broken markdown links..." | |
| if grep -r '\[.*\](.*/\s\|.*\s/.*\|\s.*\s)' . --include="*.md" --exclude-dir=.git; then | |
| echo "[!] WARNING: Found potentially malformed markdown links" | |
| else | |
| echo "[+] No obviously broken links found" | |
| fi | |
| test-summary: | |
| name: Test Summary | |
| runs-on: ubuntu-latest | |
| needs: [test-windows-scripts, test-linux-scripts, security-scan, validate-structure] | |
| if: always() | |
| steps: | |
| - name: Check test results | |
| run: | | |
| echo "[*] Test Summary:" | |
| echo " - Windows Scripts: ${{ needs.test-windows-scripts.result }}" | |
| echo " - Linux Scripts: ${{ needs.test-linux-scripts.result }}" | |
| echo " - Security Scan: ${{ needs.security-scan.result }}" | |
| echo " - Structure Validation: ${{ needs.validate-structure.result }}" | |
| if [ "${{ needs.test-windows-scripts.result }}" != "success" ] || \ | |
| [ "${{ needs.test-linux-scripts.result }}" != "success" ] || \ | |
| [ "${{ needs.security-scan.result }}" != "success" ] || \ | |
| [ "${{ needs.validate-structure.result }}" != "success" ]; then | |
| echo "[-] Some tests failed!" | |
| exit 1 | |
| fi | |
| echo "[+] All tests passed!" |