diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ce3ac3..919dbc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: PowerShell CI +name: CI Pipeline on: push: @@ -9,14 +9,14 @@ on: jobs: lint: - name: Lint and Style Check + name: Lint PowerShell Scripts runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Check for UTF-8 BOM + - name: Check UTF-8 BOM encoding shell: pwsh run: | $invalidFiles = Get-ChildItem -Path . -Include *.ps1,*.psm1,*.psd1 -Recurse | Where-Object { @@ -28,15 +28,14 @@ jobs: foreach ($file in $invalidFiles) { Write-Error "File '$($file.FullName)' is not encoded in UTF-8 BOM." } - throw "Invalid file encoding found. All .ps1 files must be UTF-8 BOM." + throw "Invalid file encoding found." + } else { + Write-Host "::notice title=Encoding Check::All PowerShell files are UTF-8 BOM." } - Write-Host "::notice title=Style Check::All PowerShell files are correctly encoded in UTF-8 BOM." - - name: Install PSScriptAnalyzer shell: pwsh - run: | - Install-Module PSScriptAnalyzer -Force -Scope CurrentUser + run: Install-Module PSScriptAnalyzer -Force -Scope CurrentUser - name: Run PSScriptAnalyzer shell: pwsh @@ -44,13 +43,13 @@ jobs: $results = Invoke-ScriptAnalyzer -Path . -Recurse -Severity Error,Warning if ($results) { $results | Format-Table - Write-Host "::notice title=Linting::PSScriptAnalyzer found issues." throw "PSScriptAnalyzer found issues" + } else { + Write-Host "::notice title=Linting::No issues found." } - Write-Host "::notice title=Linting::PSScriptAnalyzer passed with no errors or warnings." test: - name: Pester Tests + name: Run Pester Tests runs-on: ubuntu-latest needs: lint @@ -60,22 +59,18 @@ jobs: - name: Install Pester shell: pwsh - run: | - Install-Module Pester -Force -Scope CurrentUser -SkipPublisherCheck + run: Install-Module Pester -Force -Scope CurrentUser -SkipPublisherCheck - name: Run Pester Tests shell: pwsh run: | - Import-Module Pester - if (Test-Path ./tests) { + Import-Module Pester $config = New-PesterConfiguration $config.Run.Path = "./tests" $config.Run.Exit = $true $config.Output.Verbosity = 'Detailed' Invoke-Pester -Configuration $config - } - else { - Write-Host "::notice title=Testing::No tests found in ./tests directory." - throw "Tests not found." - } + } else { + Write-Host "::notice title=Testing::No tests found." + } \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index af570a7..ae1a77b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,57 +2,71 @@ name: "CodeQL Analysis" on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] schedule: - cron: '0 0 * * 1' # Every Monday at midnight + workflow_dispatch: jobs: analyze: - name: Analyze + name: CodeQL JavaScript/TypeScript Scan runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write - strategy: fail-fast: false matrix: - language: [ 'javascript-typescript' ] # For node-based scripts in bin/ + language: ['javascript-typescript'] + timeout-minutes: 30 steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - queries: security-and-quality + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: security-and-quality - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: ${{ matrix.language }} analyze-powershell: name: PowerShell Security Scan runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + timeout-minutes: 20 + steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Run PSScriptAnalyzer (Security Only) + - name: Install PSScriptAnalyzer + shell: pwsh + run: Install-Module PSScriptAnalyzer -Force -Scope CurrentUser + + - name: Run PowerShell Security Scan shell: pwsh run: | - Install-Module PSScriptAnalyzer -Force -Scope CurrentUser - \$results = Invoke-ScriptAnalyzer -Path . -Recurse -Severity Error,Warning | Where-Object { \$_.RuleName -like "*Security*" } - if (\$results) { - \$results | Format-Table - Write-Host "::notice title=Security Scan::Potential security issues found in PowerShell scripts." - # Note: We might not want to fail the build on all warnings, but security is priority. + # Adjust folder path if needed, e.g., .\bin or .\src + $psFiles = Get-ChildItem -Path . -Include *.ps1,*.psm1 -Recurse + $results = Invoke-ScriptAnalyzer -Path $psFiles -Recurse -Severity Error,Warning | + Where-Object { $_.RuleName -like "*Security*" } + + if ($results) { + $results | Format-Table + Write-Host "::error title=Security Scan::Potential security issues found in PowerShell scripts." + throw "Security issues detected by PSScriptAnalyzer" } else { Write-Host "::notice title=Security Scan::No common security issues found in PowerShell scripts." - } + } \ No newline at end of file diff --git a/.github/workflows/deno.yml b/.github/workflows/deno.yml deleted file mode 100644 index 782af35..0000000 --- a/.github/workflows/deno.yml +++ /dev/null @@ -1,42 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow will install Deno then run `deno lint` and `deno test`. -# For more information see: https://github.com/denoland/setup-deno - -name: Deno - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -permissions: - contents: read - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Setup repo - uses: actions/checkout@v4 - - - name: Setup Deno - # uses: denoland/setup-deno@v1 - uses: denoland/setup-deno@61fe2df320078202e33d7d5ad347e7dcfa0e8f31 # v1.1.2 - with: - deno-version: v1.x - - # Uncomment this step to verify the use of 'deno fmt' on each commit. - # - name: Verify formatting - # run: deno fmt --check - - - name: Run linter - run: deno lint - - - name: Run tests - run: deno test -A diff --git a/.github/workflows/download-cert.yml b/.github/workflows/download-cert.yml new file mode 100644 index 0000000..fc3cd33 --- /dev/null +++ b/.github/workflows/download-cert.yml @@ -0,0 +1,29 @@ +name: Download TLS Certificate + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + download: + runs-on: windows-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Download TLS Certificate + shell: pwsh + run: | + $certPath = ".\bin\checkScripts\download_ca_cert.pem" + if (Test-Path $certPath) { + $timestamp = Get-Date -Format yyyyMMddHHmmss + Rename-Item $certPath "$certPath.$timestamp.bak" + Write-Host "Existing certificate backed up." + } + node .\bin\checkScripts\runDownloadCert.js github.com diff --git a/.github/workflows/npm-publish-github-packages.yml b/.github/workflows/npm-publish-github-packages.yml deleted file mode 100644 index ea2d329..0000000 --- a/.github/workflows/npm-publish-github-packages.yml +++ /dev/null @@ -1,36 +0,0 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages - -name: Node.js Package - -on: - release: - types: [created] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: npm ci - - run: npm test - - publish-gpr: - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: https://npm.pkg.github.com/ - - run: npm ci - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/powershell-ci.yml b/.github/workflows/powershell-ci.yml new file mode 100644 index 0000000..138089a --- /dev/null +++ b/.github/workflows/powershell-ci.yml @@ -0,0 +1,42 @@ +name: PowerShell CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + lint-and-test: + runs-on: windows-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install PSScriptAnalyzer + shell: pwsh + run: Install-Module PSScriptAnalyzer -Force -Scope CurrentUser + + - name: Run PSScriptAnalyzer + shell: pwsh + run: | + $results = Invoke-ScriptAnalyzer -Path . -Recurse -Severity Error,Warning + if ($results) { + $results | Format-Table + throw "PSScriptAnalyzer found issues" + } + + - name: Install Pester + shell: pwsh + run: Install-Module Pester -Force -Scope CurrentUser -SkipPublisherCheck + + - name: Run Pester Tests + shell: pwsh + run: | + if (Test-Path ./tests) { + Import-Module Pester + Invoke-Pester -Path ./tests -Verbose + } else { + Write-Host "No Pester tests found." + } diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml deleted file mode 100644 index 9626ff6..0000000 --- a/.github/workflows/webpack.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: NodeJS with Webpack - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [18.x, 20.x, 22.x] - - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Build - run: | - npm install - npx webpack diff --git a/.gitignore b/.gitignore index 0c6c4fe..0e30fcc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ __pycache__/ # Logs *.log +logs/ # IDE .vscode/ @@ -22,7 +23,7 @@ __pycache__/ *.key *.pem *.token -gh-/ +gh-*/ # GitHub runner artifacts .runner @@ -32,6 +33,10 @@ build/ dist/ externals/ +# Allow specific script inside bin +!bin/checkScripts/ +!bin/checkScripts/runDownloadCert.js + # runner scripts run-helper.cmd run-helper.cmd.template @@ -42,7 +47,4 @@ config.cmd # python venv/ __pycache__/ -*.pyc - -# logs -logs/ +*.pyc \ No newline at end of file diff --git a/bin/checkScripts/downloadCert.js b/bin/checkScripts/downloadCert.js new file mode 100644 index 0000000..3ddf096 --- /dev/null +++ b/bin/checkScripts/downloadCert.js @@ -0,0 +1,26 @@ +// downloadCert.js +// Function to download a TLS certificate from a host and save to a file +const fs = require('fs'); +const tls = require('tls'); +const net = require('net'); + +async function downloadCert(host, port = 443, outputFile) { + return new Promise((resolve, reject) => { + const socket = tls.connect(port, host, { rejectUnauthorized: false }, () => { + const cert = socket.getPeerCertificate(); + if (!cert || !cert.raw) { + return reject(new Error('Unable to retrieve certificate.')); + } + // Save PEM encoded certificate + const pem = `-----BEGIN CERTIFICATE-----\n${cert.raw.toString('base64')}\n-----END CERTIFICATE-----\n`; + fs.writeFileSync(outputFile, pem); + socket.end(); + resolve(); + }); + + socket.on('error', (err) => reject(err)); + }); +} + +// Export function +module.exports = downloadCert; \ No newline at end of file diff --git a/bin/checkScripts/runDownloadCert.js b/bin/checkScripts/runDownloadCert.js new file mode 100644 index 0000000..cc8e989 --- /dev/null +++ b/bin/checkScripts/runDownloadCert.js @@ -0,0 +1,26 @@ +// runDownloadCert.js +// Script to run the certificate download and handle backups +const fs = require('fs'); +const path = require('path'); +const downloadCert = require('./downloadCert'); + +// Output certificate path +const OUTPUT_FILE = path.resolve(__dirname, 'download_ca_cert.pem'); + +(async () => { + try { + // Backup existing certs + if (fs.existsSync(OUTPUT_FILE)) { + const backupFile = `${OUTPUT_FILE}.${Date.now()}.bak`; + fs.renameSync(OUTPUT_FILE, backupFile); + console.log(`Existing certificate backed up as ${backupFile}`); + } + + // Download from github.com + await downloadCert('github.com', 443, OUTPUT_FILE); + console.log(`Certificate successfully saved to ${OUTPUT_FILE}`); + } catch (err) { + console.error('Error downloading certificate:', err); + process.exit(1); // Fail workflow if download fails + } +})(); \ No newline at end of file diff --git a/cleanup-old-workflows.ps1 b/cleanup-old-workflows.ps1 new file mode 100644 index 0000000..8e526a0 --- /dev/null +++ b/cleanup-old-workflows.ps1 @@ -0,0 +1,24 @@ +# Files to remove (relative to repo root) +$filesToDelete = @( + ".github\workflows\create-powershell-ci.ps1", + ".github\workflows\download-cert.yml", + ".github\workflows\powershell-ci.yml.*.bak" +) + +foreach ($file in $filesToDelete) { + # Expand wildcard if any + $matches = Get-ChildItem -Path $file -ErrorAction SilentlyContinue + if ($matches) { + foreach ($match in $matches) { + $answer = Read-Host "Do you want to delete '$($match.FullName)'? (Y/N)" + if ($answer -eq 'Y') { + Remove-Item -Path $match.FullName -Force + Write-Host "Deleted: $($match.FullName)" + } else { + Write-Host "Skipped: $($match.FullName)" + } + } + } else { + Write-Host "File not found: $file" + } +} \ No newline at end of file diff --git a/create-download-cert.ps1 b/create-download-cert.ps1 new file mode 100644 index 0000000..bad9999 --- /dev/null +++ b/create-download-cert.ps1 @@ -0,0 +1,93 @@ +# Workflow folder +$workflowDir = ".github\workflows" +if (-not (Test-Path $workflowDir)) { + New-Item -ItemType Directory -Path $workflowDir -Force | Out-Null +} + +# download-cert.yml content +$downloadCertYaml = @' +name: Download TLS Certificate + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + download: + runs-on: windows-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Download TLS Certificate + shell: pwsh + run: | + $certPath = ".\bin\checkScripts\download_ca_cert.pem" + if (Test-Path $certPath) { + $timestamp = Get-Date -Format yyyyMMddHHmmss + Rename-Item $certPath "$certPath.$timestamp.bak" + Write-Host "Existing certificate backed up." + } + node .\bin\checkScripts\runDownloadCert.js github.com +'@ + +# Save download-cert.yml +$downloadCertFile = Join-Path $workflowDir "download-cert.yml" +$downloadCertYaml | Set-Content -Path $downloadCertFile -Force +Write-Host "$downloadCertFile created successfully." + +# Optionally, create powershell-ci.yml (minimal version) +$powershellCiYaml = @' +name: PowerShell CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + lint-and-test: + runs-on: windows-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install PSScriptAnalyzer + shell: pwsh + run: Install-Module PSScriptAnalyzer -Force -Scope CurrentUser + + - name: Run PSScriptAnalyzer + shell: pwsh + run: | + $results = Invoke-ScriptAnalyzer -Path . -Recurse -Severity Error,Warning + if ($results) { + $results | Format-Table + throw "PSScriptAnalyzer found issues" + } + + - name: Install Pester + shell: pwsh + run: Install-Module Pester -Force -Scope CurrentUser -SkipPublisherCheck + + - name: Run Pester Tests + shell: pwsh + run: | + if (Test-Path ./tests) { + Import-Module Pester + Invoke-Pester -Path ./tests -Verbose + } else { + Write-Host "No Pester tests found." + } +'@ + +$powershellCiFile = Join-Path $workflowDir "powershell-ci.yml" +$powershellCiYaml | Set-Content -Path $powershellCiFile -Force +Write-Host "$powershellCiFile created successfully." \ No newline at end of file diff --git a/create-powershell-ci.ps1 b/create-powershell-ci.ps1 new file mode 100644 index 0000000..e69de29 diff --git a/run-workflow-manually.ps1 b/run-workflow-manually.ps1 new file mode 100644 index 0000000..30cbda9 --- /dev/null +++ b/run-workflow-manually.ps1 @@ -0,0 +1,50 @@ +# Hardcoded GitHub Personal Access Token (with repo/workflows scope) +$token = $env:GITHUB_TOKEN + +# GitHub repo info +$owner = "Ruh-Al-Tarikh" +$repo = "System-Automation-Hub" + +# Get list of workflow files +$workflowDir = ".github/workflows" +$workflows = Get-ChildItem $workflowDir -Filter *.yml | Select-Object -ExpandProperty Name + +Write-Host "Available workflows to run:" +$workflows | ForEach-Object { Write-Host "- $_" } + +# Prompt user to choose workflow +$workflowFile = Read-Host "Enter the workflow file to trigger (e.g., ci.yml)" + +if (-not ($workflows -contains $workflowFile)) { + Write-Host "Workflow file not found!" -ForegroundColor Red + return +} + +# Confirm before dispatch +$answer = Read-Host "Trigger workflow '$workflowFile'? (Y/N)" +if ($answer -ne 'Y') { + Write-Host "Cancelled." + return +} + +# Use token directly +$token = $env:GITHUB_TOKEN + +# GitHub API endpoint +$uri = "https://api.github.com/repos/$owner/$repo/actions/workflows/$workflowFile/dispatches" + +# Prepare request headers +$headers = @{ + Authorization = "Bearer $token" + "User-Agent" = "PowerShell" + Accept = "application/vnd.github+json" +} + +# Body with branch (main) to run workflow on +$body = @{ ref = "main" } | ConvertTo-Json + +# Trigger the workflow +Invoke-RestMethod -Uri $uri -Headers $headers -Method Post -Body $body + +Write-Host "Workflow '$workflowFile' dispatched successfully!" +Write-Host "Check https://github.com/$owner/$repo/actions for status." \ No newline at end of file