From eaffeedf122977c3cba3437c8f99246f595f5a1e Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Tue, 2 Jun 2026 15:48:54 -0400 Subject: [PATCH] Add Gitea runner installer, proxy/SSO support, and release workflow Scripts: - Add scripts/Install-GiteaRunner.ps1: cross-platform installer for the Gitea act_runner daemon (systemd / launchd / Windows Service). - PowerShell 7+ runtime guard (works under irm | iex). - Explicit env var resolution (Process -> User -> Machine) for InstanceUrl and RegistrationToken with named candidates. - UTF-8 (no BOM) for every file write via [System.IO.File] APIs. - System proxy + DefaultNetworkCredentials on all web calls. - Optional -Labels; ServiceName/ServiceDisplayName split prevents systemd 'Invalid unit name' errors caused by whitespace. - config.yaml is always generated before the registration skip-check so upgrades produce a config the daemon can load. Module: - InfisicalHttpClient: enable UseDefaultCredentials and attach the system proxy with DefaultNetworkCredentials so requests work behind authenticated corporate proxies / SSO. - ExportInfisicalSecretsCmdlet: make the UTF-8 (no BOM) case explicit in the encoding resolver. CI/CD (.gitea/workflows/publish-psgallery.yml): - Split into build -> release -> publish with hard `needs:` ordering so publish never runs unless build and release both succeed. - Build job uploads Module/PSInfisicalAPI as an artifact. - Release job downloads the artifact, reads the version from the manifest, zips the module, and creates a Gitea release tagged with the bare version. Release notes include version, full + short commit SHA, build timestamp, merged PR info, workflow run link, and any matching CHANGELOG.md section. Skips cleanly when the tag already exists. - Publish job re-validates the downloaded manifest and runs Publish-Module against PSGallery using PSGALLERY_API_KEY. --- .gitea/workflows/publish-psgallery.yml | 232 +++++- CHANGELOG.md | 676 +++++++++++++++++- Module/PSInfisicalAPI/PSInfisicalAPI.psd1 | 4 +- Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll | Bin 70144 -> 70656 bytes scripts/Install-GiteaRunner.ps1 | 426 +++++++++++ .../Cmdlets/ExportInfisicalSecretsCmdlet.cs | 1 + .../Http/InfisicalHttpClient.cs | 8 + 7 files changed, 1332 insertions(+), 15 deletions(-) create mode 100644 scripts/Install-GiteaRunner.ps1 diff --git a/.gitea/workflows/publish-psgallery.yml b/.gitea/workflows/publish-psgallery.yml index 3db6610..4b21bff 100644 --- a/.gitea/workflows/publish-psgallery.yml +++ b/.gitea/workflows/publish-psgallery.yml @@ -6,7 +6,7 @@ on: branches: [main] jobs: - publish: + build: if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: @@ -41,16 +41,6 @@ jobs: sudo apt-get install -y powershell pwsh --version - - name: Bootstrap PowerShellGet / NuGet provider - shell: pwsh - run: | - $ErrorActionPreference = 'Stop' - Set-PSRepository -Name PSGallery -InstallationPolicy Trusted - if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser | Out-Null - } - Get-PackageProvider -Name NuGet | Format-Table Name,Version - - name: Build and test module shell: pwsh run: ./build.ps1 -RunTests @@ -63,6 +53,218 @@ jobs: $manifest = Test-ModuleManifest -Path $manifestPath Write-Host "Manifest OK: $($manifest.Name) $($manifest.Version)" + - name: Upload module artifact + uses: actions/upload-artifact@v4 + with: + name: PSInfisicalAPI-module + path: Module/PSInfisicalAPI + if-no-files-found: error + retention-days: 7 + + release: + needs: build + if: ${{ success() && github.event.pull_request.merged == true }} + runs-on: ubuntu-latest + outputs: + version: ${{ steps.meta.outputs.version }} + tag: ${{ steps.meta.outputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install PowerShell 7 (if not present) + shell: bash + run: | + set -euo pipefail + if command -v pwsh >/dev/null 2>&1; then + echo "pwsh already installed: $(pwsh --version)" + exit 0 + fi + + sudo apt-get update + sudo apt-get install -y --no-install-recommends wget ca-certificates apt-transport-https gnupg + + source /etc/os-release + wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms-prod.deb + sudo dpkg -i /tmp/ms-prod.deb + rm -f /tmp/ms-prod.deb + + sudo apt-get update + sudo apt-get install -y powershell + pwsh --version + + - name: Download module artifact + uses: actions/download-artifact@v4 + with: + name: PSInfisicalAPI-module + path: Module/PSInfisicalAPI + + - name: Resolve module version and tag + id: meta + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $manifestPath = Join-Path $PWD 'Module/PSInfisicalAPI/PSInfisicalAPI.psd1' + $manifest = Test-ModuleManifest -Path $manifestPath + $version = $manifest.Version.ToString() + $tag = $version + Write-Host "Module version: $version" + Write-Host "Release tag: $tag" + "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 + + - name: Package module as release asset + shell: pwsh + env: + VERSION: ${{ steps.meta.outputs.version }} + run: | + $ErrorActionPreference = 'Stop' + $zipPath = Join-Path $PWD "PSInfisicalAPI-$($env:VERSION).zip" + if (Test-Path $zipPath) { Remove-Item $zipPath -Force } + Compress-Archive -Path 'Module/PSInfisicalAPI/*' -DestinationPath $zipPath -Force + Write-Host "Created: $zipPath ($([math]::Round((Get-Item $zipPath).Length / 1KB, 1)) KB)" + + - name: Create Gitea release + shell: pwsh + env: + GITEA_TOKEN: ${{ github.token }} + API_URL: ${{ github.api_url }} + REPO: ${{ github.repository }} + TAG: ${{ steps.meta.outputs.tag }} + VERSION: ${{ steps.meta.outputs.version }} + COMMIT_SHA: ${{ github.sha }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + SERVER_URL: ${{ github.server_url }} + RUN_ID: ${{ github.run_id }} + run: | + $ErrorActionPreference = 'Stop' + if ([string]::IsNullOrWhiteSpace($env:GITEA_TOKEN)) { + throw "github.token is not available; cannot call the Gitea release API." + } + + $shortSha = $env:COMMIT_SHA.Substring(0, [Math]::Min(12, $env:COMMIT_SHA.Length)) + $buildUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + $runUrl = "$($env:SERVER_URL)/$($env:REPO)/actions/runs/$($env:RUN_ID)" + $prUrl = "$($env:SERVER_URL)/$($env:REPO)/pulls/$($env:PR_NUMBER)" + + $changelogSection = '' + if (Test-Path 'CHANGELOG.md') { + $lines = [System.IO.File]::ReadAllLines('CHANGELOG.md') + $start = -1; $end = $lines.Length + for ($i = 0; $i -lt $lines.Length; $i++) { + if ($lines[$i] -match "^##\s+$([regex]::Escape($env:VERSION))\s*$") { $start = $i + 1; continue } + if ($start -ge 0 -and $lines[$i] -match '^##\s+') { $end = $i; break } + } + if ($start -ge 0) { + $changelogSection = ($lines[$start..($end - 1)] -join "`n").Trim() + } + } + + $body = @" + **PSInfisicalAPI $($env:VERSION)** + + | Field | Value | + | --- | --- | + | Version | ``$($env:VERSION)`` | + | Tag | ``$($env:TAG)`` | + | Commit | [``$shortSha``]($($env:SERVER_URL)/$($env:REPO)/commit/$($env:COMMIT_SHA)) | + | Built (UTC) | $buildUtc | + | Merged PR | [#$($env:PR_NUMBER) $($env:PR_TITLE)]($prUrl) by @$($env:PR_AUTHOR) | + | Workflow run | [$($env:RUN_ID)]($runUrl) | + + ## Changes + $(if ($changelogSection) { $changelogSection } else { '_No CHANGELOG section found for this version._' }) + + ## Install + ``````powershell + Install-Module -Name PSInfisicalAPI -RequiredVersion $($env:VERSION) -Scope CurrentUser + `````` + "@ + + $headers = @{ + Authorization = "token $($env:GITEA_TOKEN)" + Accept = 'application/json' + } + $createUri = "$($env:API_URL)/repos/$($env:REPO)/releases" + + $existing = $null + try { + $existing = Invoke-RestMethod -Method Get -Headers $headers ` + -Uri "$createUri/tags/$($env:TAG)" -ErrorAction Stop + } catch { + if ($_.Exception.Response.StatusCode.value__ -ne 404) { throw } + } + if ($existing) { + Write-Host "Release tag '$($env:TAG)' already exists (id=$($existing.id)); skipping release creation." + return + } + + $payload = @{ + tag_name = $env:TAG + target_commitish = $env:COMMIT_SHA + name = "PSInfisicalAPI $($env:VERSION)" + body = $body + draft = $false + prerelease = $false + } | ConvertTo-Json -Depth 4 + + $release = Invoke-RestMethod -Method Post -Uri $createUri -Headers $headers ` + -ContentType 'application/json' -Body $payload + Write-Host "Created release id=$($release.id) at $($release.html_url)" + + $assetPath = Join-Path $PWD "PSInfisicalAPI-$($env:VERSION).zip" + $uploadUri = "$createUri/$($release.id)/assets?name=PSInfisicalAPI-$($env:VERSION).zip" + $fileBytes = [System.IO.File]::ReadAllBytes($assetPath) + Invoke-RestMethod -Method Post -Uri $uploadUri -Headers $headers ` + -ContentType 'application/zip' -Body $fileBytes | Out-Null + Write-Host "Uploaded asset: PSInfisicalAPI-$($env:VERSION).zip" + + publish: + needs: release + if: ${{ success() && github.event.pull_request.merged == true }} + runs-on: ubuntu-latest + steps: + - name: Install PowerShell 7 (if not present) + shell: bash + run: | + set -euo pipefail + if command -v pwsh >/dev/null 2>&1; then + echo "pwsh already installed: $(pwsh --version)" + exit 0 + fi + + sudo apt-get update + sudo apt-get install -y --no-install-recommends wget ca-certificates apt-transport-https gnupg + + source /etc/os-release + wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms-prod.deb + sudo dpkg -i /tmp/ms-prod.deb + rm -f /tmp/ms-prod.deb + + sudo apt-get update + sudo apt-get install -y powershell + pwsh --version + + - name: Download module artifact + uses: actions/download-artifact@v4 + with: + name: PSInfisicalAPI-module + path: Module/PSInfisicalAPI + + - name: Bootstrap PowerShellGet / NuGet provider + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser | Out-Null + } + Get-PackageProvider -Name NuGet | Format-Table Name,Version + - name: Verify PowerShell Gallery API key is configured shell: pwsh env: @@ -72,6 +274,14 @@ jobs: throw "Repository secret 'PSGALLERY_API_KEY' is not configured." } + - name: Re-validate downloaded module manifest + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $manifestPath = Join-Path $PWD 'Module/PSInfisicalAPI/PSInfisicalAPI.psd1' + $manifest = Test-ModuleManifest -Path $manifestPath + Write-Host "Manifest OK: $($manifest.Name) $($manifest.Version)" + - name: Publish to PowerShell Gallery shell: pwsh env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 25eb16d..faaa5d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,47 +6,383 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + ## Unreleased (carried forward) +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1648 - Build produced from commit 430e3a00c921. ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + ## Unreleased (carried forward) (carried forward) +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1638 - Build produced from commit 3c47d6ff30ec. ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + ## Unreleased (carried forward) +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1648 - Build produced from commit 430e3a00c921. ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. -## Unreleased (carried forward) (carried forward) (carried forward) +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) ## 2026.06.02.1611 @@ -54,47 +390,383 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + ## Unreleased (carried forward) +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1648 - Build produced from commit 430e3a00c921. ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + ## Unreleased (carried forward) (carried forward) +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1638 - Build produced from commit 3c47d6ff30ec. ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + ## Unreleased (carried forward) +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1648 - Build produced from commit 430e3a00c921. ## Unreleased +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) + ## 2026.06.02.1724 - Build produced from commit 5801b4774af5. -## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) + +## 2026.06.02.1737 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) + +## 2026.06.02.1902 + +- Build produced from commit fa65c18bc171. + +## Unreleased + +## 2026.06.02.1907 + +- Build produced from commit fa65c18bc171. + +## Unreleased (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) (carried forward) ### Added diff --git a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 index f69c71f..2b6215b 100644 --- a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 +++ b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'PSInfisicalAPI.psm1' - ModuleVersion = '2026.06.02.1724' + ModuleVersion = '2026.06.02.1907' GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51' Author = 'Grace Solutions' CompanyName = 'Grace Solutions' @@ -27,7 +27,7 @@ LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html' ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI' ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.' - CommitHash = '5801b4774af5' + CommitHash = 'fa65c18bc171' } } } \ No newline at end of file diff --git a/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll b/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll index 82cda94a2c57bf4743c5bcd3d9256daff44d2079..04b0b56b2e9741aa866d40ecb1ecbcfd5f06ef26 100644 GIT binary patch delta 25516 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 XWx{9D{*N_|_T1)li0{O@Qse&tlaiq8 delta 25318 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 YB)s^inj)~B2TGoGIK|gub)oV90FGVSZU6uP diff --git a/scripts/Install-GiteaRunner.ps1 b/scripts/Install-GiteaRunner.ps1 new file mode 100644 index 0000000..0f234ee --- /dev/null +++ b/scripts/Install-GiteaRunner.ps1 @@ -0,0 +1,426 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS + Downloads the Gitea act_runner binary, installs it as a system daemon, and registers it. + +.DESCRIPTION + Cross-platform installer for the Gitea Actions runner. Idempotent: re-running upgrades + in place, skips work when the requested version already matches, and reuses an existing + registration unless -Force is supplied. + + Resolution order for InstanceUrl and RegistrationToken when not passed explicitly: + Process scope env vars -> User scope env vars -> Machine scope env vars + Explicit candidate variable names tried in order: + InstanceUrl: GITEA_INSTANCE_URL, GITEA_URL, + CLOUDINIT_GITEA_INSTANCE_URL, CLOUDINIT_GITEA_URL + RegistrationToken: GITEA_RUNNER_REGISTRATION_TOKEN, GITEA_RUNNER_TOKEN, + GITEA_REGISTRATION_TOKEN, + CLOUDINIT_GITEA_RUNNER_REGISTRATION_TOKEN, + CLOUDINIT_GITEA_RUNNER_TOKEN, + CLOUDINIT_GITEA_REGISTRATION_TOKEN + +.PARAMETER InstanceUrl + Base URL of the Gitea instance (e.g. https://prod.git.gracesolution.info). + +.PARAMETER RegistrationToken + Runner registration token. Treated as secret; never logged. + +.PARAMETER RunnerName + Name presented to Gitea. Defaults to the machine hostname. + +.PARAMETER Labels + Optional comma-separated runner labels (e.g. linux-amd64,docker). When omitted, + no --labels argument is passed to act_runner register and the runner is + registered with whatever labels are configured server-side or in config.yaml. + +.PARAMETER Version + Specific release tag (e.g. v1.0.7) or 'latest'. Defaults to latest. + +.PARAMETER InstallRoot + Override directory. Defaults are /opt/gitea/runner on Linux, + /usr/local/gitea/runner on macOS, and %ProgramData%\Gitea\Runner on Windows. + +.PARAMETER BinaryName + Renamed binary on disk. Defaults to act_runner (act_runner.exe on Windows). + +.PARAMETER ServiceName + System service identifier (used as the systemd unit name, launchd label suffix, + and Windows service name). Must not contain spaces or characters illegal in + a unit/service identifier. Defaults to 'gitea-runner'. + +.PARAMETER ServiceDisplayName + Friendly human-readable name shown in systemctl status, Windows Services MMC, + and similar UIs. Defaults to 'Gitea Runner'. + +.PARAMETER Force + Re-download binary and re-register the runner even when already present. + +.EXAMPLE + pwsh -File Install-GiteaRunner.ps1 -InstanceUrl https://prod.git.gracesolution.info + +.EXAMPLE + Remote one-liner using env vars for InstanceUrl / RegistrationToken: + $env:GITEA_INSTANCE_URL = 'https://prod.git.gracesolution.info' + $env:GITEA_RUNNER_REGISTRATION_TOKEN = '' + irm 'https://prod.git.gracesolution.info/gsadmin/Gitea-Bootstrap/raw/branch/main/scripts/Install-GiteaRunner.ps1' | iex +#> +[CmdletBinding()] +param( + [string] $InstanceUrl, + [string] $RegistrationToken, + [string] $RunnerName = [System.Net.Dns]::GetHostName(), + [string] $Labels, + [string] $Version = 'latest', + [string] $InstallRoot, + [string] $BinaryName, + [string] $ServiceName = 'gitea-runner', + [string] $ServiceDisplayName = 'Gitea Runner', + [switch] $Force +) + +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' + +if ($PSVersionTable.PSVersion.Major -lt 7) { + throw "Install-GiteaRunner requires PowerShell 7 or later. Detected: $($PSVersionTable.PSVersion)." +} + +$script:Utf8NoBom = New-Object System.Text.UTF8Encoding($false) + +function Write-Stage { + param([string] $Message) + $stamp = [DateTimeOffset]::UtcNow.UtcDateTime.ToString('yyyy-MM-ddTHH:mm:ss.fffffffZ') + Write-Host "[$stamp] - [Install-GiteaRunner] - $Message" +} + +function Resolve-FromEnvVarNames { + param( + [Parameter(Mandatory)][string[]] $Names, + [string] $DisplayName + ) + + $scopes = @('Process', 'User', 'Machine') + foreach ($name in $Names) { + foreach ($scope in $scopes) { + try { $value = [System.Environment]::GetEnvironmentVariable($name, [System.EnvironmentVariableTarget]::$scope) } + catch { continue } + + if (-not [string]::IsNullOrWhiteSpace($value)) { + Write-Stage "Resolved $DisplayName from $scope env var '$name'." + return [string] $value + } + } + } + return $null +} + +function Get-PlatformDescriptor { + $arch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant() + switch ($arch) { + 'x64' { $assetArch = 'amd64' } + 'arm64' { $assetArch = 'arm64' } + 'arm' { $assetArch = 'arm-7' } + default { throw "Unsupported architecture: $arch" } + } + + if ($IsWindows) { return @{ Os = 'windows'; Arch = $assetArch; Suffix = '.exe' } } + if ($IsMacOS) { return @{ Os = 'darwin'; Arch = $assetArch; Suffix = '' } } + if ($IsLinux) { return @{ Os = 'linux'; Arch = $assetArch; Suffix = '' } } + throw 'Unable to determine host operating system.' +} + +function Get-DefaultInstallRoot { + if ($IsWindows) { return (Join-Path $env:ProgramData 'Gitea' 'Runner') } + if ($IsMacOS) { return '/usr/local/gitea/runner' } + return '/opt/gitea/runner' +} + +function Assert-Elevated { + if ($IsWindows) { + $principal = New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent()) + if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + throw 'This script must be run from an elevated PowerShell session (Administrator).' + } + } else { + $uid = & id -u + if ($uid -ne '0') { throw 'This script must be run as root (use sudo).' } + } +} + +function Resolve-ReleaseTag { + param([string] $Requested) + if ($Requested -and $Requested -ne 'latest') { return ($Requested.TrimStart('v')) } + + Write-Stage 'Querying Gitea for latest runner release.' + $proxyUri = [System.Net.WebRequest]::GetSystemWebProxy().GetProxy([Uri] 'https://gitea.com') + $invokeArgs = @{ + Uri = 'https://gitea.com/api/v1/repos/gitea/act_runner/releases/latest' + UseBasicParsing = $true + UseDefaultCredentials = $true + } + if ($proxyUri -and $proxyUri.AbsoluteUri -ne 'https://gitea.com/') { + $invokeArgs.Proxy = $proxyUri.AbsoluteUri + $invokeArgs.ProxyUseDefaultCredentials = $true + } + $release = Invoke-RestMethod @invokeArgs + if (-not $release.tag_name) { throw 'Failed to resolve latest act_runner release tag.' } + return ($release.tag_name.TrimStart('v')) +} + + +function Get-InstalledBinaryVersion { + param([string] $BinaryPath) + if (-not (Test-Path -LiteralPath $BinaryPath)) { return $null } + try { + $raw = & $BinaryPath --version 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($raw)) { return $null } + $match = [regex]::Match($raw, '(\d+\.\d+\.\d+(?:[\-\.][0-9A-Za-z\.\-]+)?)') + if ($match.Success) { return $match.Groups[1].Value } + } catch { } + return $null +} + +function Save-BinaryViaWebClient { + param( + [Parameter(Mandatory)][string] $Uri, + [Parameter(Mandatory)][string] $Destination + ) + Write-Stage "Downloading $Uri" + $client = New-Object System.Net.WebClient + try { + $client.Headers['User-Agent'] = 'Install-GiteaRunner' + $client.UseDefaultCredentials = $true + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials + $client.Proxy = $proxy + $client.DownloadFile($Uri, $Destination) + } finally { + $client.Dispose() + } +} + +function Add-ToMachinePath { + param([Parameter(Mandatory)][string] $Directory) + if ($IsWindows) { + $current = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + $parts = @() + if ($current) { $parts = $current.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries) } + if ($parts -notcontains $Directory) { + $updated = ($parts + $Directory) -join ';' + [System.Environment]::SetEnvironmentVariable('Path', $updated, 'Machine') + Write-Stage "Added $Directory to Machine PATH." + } + if (-not ($env:Path -split ';' -contains $Directory)) { $env:Path = "$env:Path;$Directory" } + return + } + + $profilePath = '/etc/profile.d/gitea-runner.sh' + $line = "export PATH=`"$Directory`":`$PATH" + $existing = if ([System.IO.File]::Exists($profilePath)) { [System.IO.File]::ReadAllText($profilePath) } else { '' } + if ($existing -notmatch [regex]::Escape($Directory)) { + [System.IO.File]::WriteAllText($profilePath, $line, $script:Utf8NoBom) + & chmod 0644 $profilePath | Out-Null + Write-Stage "Wrote $profilePath for system-wide PATH." + } + if (($env:PATH -split ':') -notcontains $Directory) { $env:PATH = "$Directory`:$env:PATH" } +} + +function Register-Runner { + param( + [Parameter(Mandatory)][string] $BinaryPath, + [Parameter(Mandatory)][string] $WorkingDirectory, + [Parameter(Mandatory)][string] $InstanceUrl, + [Parameter(Mandatory)][string] $Token, + [Parameter(Mandatory)][string] $RunnerName, + [string] $Labels, + [switch] $Force + ) + + $configPath = Join-Path $WorkingDirectory 'config.yaml' + if ((-not [System.IO.File]::Exists($configPath)) -or $Force) { + Write-Stage 'Generating config.yaml.' + Push-Location $WorkingDirectory + try { + $configContent = (& $BinaryPath generate-config) | Out-String + [System.IO.File]::WriteAllText($configPath, $configContent, $script:Utf8NoBom) + } finally { Pop-Location } + } + + $runnerStateFile = Join-Path $WorkingDirectory '.runner' + if ((Test-Path -LiteralPath $runnerStateFile) -and -not $Force) { + Write-Stage 'Runner already registered (.runner present); skipping registration.' + return + } + + $labelDescription = if ([string]::IsNullOrWhiteSpace($Labels)) { '(no labels specified; using server/config defaults)' } else { "with labels '$Labels'" } + Write-Stage "Registering runner '$RunnerName' $labelDescription." + + Push-Location $WorkingDirectory + try { + $registerArgs = @('register', '--no-interactive', '--instance', $InstanceUrl, + '--token', $Token, '--name', $RunnerName, '--config', $configPath) + if (-not [string]::IsNullOrWhiteSpace($Labels)) { $registerArgs += @('--labels', $Labels) } + & $BinaryPath @registerArgs + if ($LASTEXITCODE -ne 0) { throw "act_runner register exited with code $LASTEXITCODE." } + } finally { Pop-Location } +} + +function Install-SystemdUnit { + param( + [Parameter(Mandatory)][string] $ServiceName, + [Parameter(Mandatory)][string] $ServiceDisplayName, + [Parameter(Mandatory)][string] $BinaryPath, + [Parameter(Mandatory)][string] $WorkingDirectory + ) + $unitPath = "/etc/systemd/system/$ServiceName.service" + $configPath = Join-Path $WorkingDirectory 'config.yaml' + $unit = @" +[Unit] +Description=$ServiceDisplayName +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=$WorkingDirectory +ExecStart=$BinaryPath daemon --config $configPath +Restart=always +RestartSec=5 +User=root + +[Install] +WantedBy=multi-user.target +"@ + [System.IO.File]::WriteAllText($unitPath, $unit, $script:Utf8NoBom) + & systemctl daemon-reload | Out-Null + & systemctl enable $ServiceName | Out-Null + & systemctl restart $ServiceName | Out-Null + Write-Stage "Systemd unit $unitPath installed and started." +} + +function Install-LaunchdPlist { + param( + [Parameter(Mandatory)][string] $ServiceName, + [Parameter(Mandatory)][string] $ServiceDisplayName, + [Parameter(Mandatory)][string] $BinaryPath, + [Parameter(Mandatory)][string] $WorkingDirectory + ) + $label = "com.gitea.$ServiceName" + $plistPath = "/Library/LaunchDaemons/$label.plist" + $configPath = Join-Path $WorkingDirectory 'config.yaml' + $plist = @" + + + + + Label$label + ProgramArguments + + $BinaryPath + daemon + --config + $configPath + + WorkingDirectory$WorkingDirectory + RunAtLoad + KeepAlive + + +"@ + [System.IO.File]::WriteAllText($plistPath, $plist, $script:Utf8NoBom) + & chmod 0644 $plistPath | Out-Null + & launchctl unload $plistPath 2>$null | Out-Null + & launchctl load -w $plistPath | Out-Null + Write-Stage "Launchd plist $plistPath installed and loaded." +} + +function Install-WindowsService { + param( + [Parameter(Mandatory)][string] $ServiceName, + [Parameter(Mandatory)][string] $ServiceDisplayName, + [Parameter(Mandatory)][string] $BinaryPath, + [Parameter(Mandatory)][string] $WorkingDirectory + ) + $configPath = Join-Path $WorkingDirectory 'config.yaml' + $binPath = "`"$BinaryPath`" daemon --config `"$configPath`"" + $existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue + if ($existing) { + Write-Stage "Service $ServiceName exists; updating binary path and restarting." + & sc.exe config $ServiceName binPath= $binPath DisplayName= $ServiceDisplayName | Out-Null + if ($existing.Status -eq 'Running') { Stop-Service -Name $ServiceName -Force } + } else { + Write-Stage "Creating Windows service $ServiceName." + & sc.exe create $ServiceName binPath= $binPath start= auto DisplayName= $ServiceDisplayName | Out-Null + if ($LASTEXITCODE -ne 0) { throw "sc.exe create failed with exit code $LASTEXITCODE." } + } + Start-Service -Name $ServiceName + Write-Stage "Service $ServiceName started." +} + + +# --- Main ---------------------------------------------------------------- + +Assert-Elevated + +if ([string]::IsNullOrWhiteSpace($ServiceName) -or ($ServiceName -match '\s')) { + throw "ServiceName must be a non-empty identifier with no whitespace (got: '$ServiceName'). Use -ServiceDisplayName for the friendly label." +} +if ([string]::IsNullOrWhiteSpace($ServiceDisplayName)) { $ServiceDisplayName = $ServiceName } + +if ([string]::IsNullOrWhiteSpace($InstanceUrl)) { + $InstanceUrl = Resolve-FromEnvVarNames -DisplayName 'InstanceUrl' -Names @( + 'GITEA_INSTANCE_URL', + 'GITEA_URL', + 'CLOUDINIT_GITEA_INSTANCE_URL', + 'CLOUDINIT_GITEA_URL' + ) +} +if ([string]::IsNullOrWhiteSpace($RegistrationToken)) { + $RegistrationToken = Resolve-FromEnvVarNames -DisplayName 'RegistrationToken' -Names @( + 'GITEA_RUNNER_REGISTRATION_TOKEN', + 'GITEA_RUNNER_TOKEN', + 'GITEA_REGISTRATION_TOKEN', + 'CLOUDINIT_GITEA_RUNNER_REGISTRATION_TOKEN', + 'CLOUDINIT_GITEA_RUNNER_TOKEN', + 'CLOUDINIT_GITEA_REGISTRATION_TOKEN' + ) +} + +if ([string]::IsNullOrWhiteSpace($InstanceUrl)) { throw 'InstanceUrl not provided and no matching env var found.' } +if ([string]::IsNullOrWhiteSpace($RegistrationToken)) { throw 'RegistrationToken not provided and no matching env var found.' } + +$platform = Get-PlatformDescriptor +if (-not $InstallRoot) { $InstallRoot = Get-DefaultInstallRoot } +if (-not $BinaryName) { $BinaryName = if ($IsWindows) { 'act_runner.exe' } else { 'act_runner' } } + +$resolvedVersion = Resolve-ReleaseTag -Requested $Version +$assetName = "gitea-runner-$resolvedVersion-$($platform.Os)-$($platform.Arch)$($platform.Suffix)" +$downloadUri = "https://gitea.com/gitea/runner/releases/download/v$resolvedVersion/$assetName" + +if (-not (Test-Path -LiteralPath $InstallRoot)) { + Write-Stage "Creating install root $InstallRoot." + New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null +} + +$binaryPath = Join-Path $InstallRoot $BinaryName +$installedVersion = Get-InstalledBinaryVersion -BinaryPath $binaryPath + +if ($installedVersion -eq $resolvedVersion -and -not $Force) { + Write-Stage "act_runner $installedVersion already installed; skipping download." +} else { + if ($installedVersion) { Write-Stage "Upgrading act_runner $installedVersion -> $resolvedVersion." } + Save-BinaryViaWebClient -Uri $downloadUri -Destination $binaryPath + if (-not $IsWindows) { & chmod 0755 $binaryPath | Out-Null } +} + +Add-ToMachinePath -Directory $InstallRoot + +Register-Runner -BinaryPath $binaryPath -WorkingDirectory $InstallRoot ` + -InstanceUrl $InstanceUrl -Token $RegistrationToken ` + -RunnerName $RunnerName -Labels $Labels -Force:$Force + +if ($IsLinux) { Install-SystemdUnit -ServiceName $ServiceName -ServiceDisplayName $ServiceDisplayName -BinaryPath $binaryPath -WorkingDirectory $InstallRoot } +elseif ($IsMacOS) { Install-LaunchdPlist -ServiceName $ServiceName -ServiceDisplayName $ServiceDisplayName -BinaryPath $binaryPath -WorkingDirectory $InstallRoot } +elseif ($IsWindows) { Install-WindowsService -ServiceName $ServiceName -ServiceDisplayName $ServiceDisplayName -BinaryPath $binaryPath -WorkingDirectory $InstallRoot } + +Write-Stage "Gitea runner installation complete (version $resolvedVersion, service $ServiceName / '$ServiceDisplayName')." \ No newline at end of file diff --git a/src/PSInfisicalAPI/Cmdlets/ExportInfisicalSecretsCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/ExportInfisicalSecretsCmdlet.cs index 9caae56..6a3f19f 100644 --- a/src/PSInfisicalAPI/Cmdlets/ExportInfisicalSecretsCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/ExportInfisicalSecretsCmdlet.cs @@ -89,6 +89,7 @@ namespace PSInfisicalAPI.Cmdlets { switch (encoding) { + case InfisicalExportEncoding.UTF8: return new UTF8Encoding(false); case InfisicalExportEncoding.UTF8Bom: return new UTF8Encoding(true); case InfisicalExportEncoding.Unicode: return new UnicodeEncoding(); default: return new UTF8Encoding(false); diff --git a/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs b/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs index 1c417d0..55fe767 100644 --- a/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs +++ b/src/PSInfisicalAPI/Http/InfisicalHttpClient.cs @@ -42,6 +42,14 @@ namespace PSInfisicalAPI.Http webRequest.UserAgent = "PSInfisicalAPI"; webRequest.Timeout = _timeoutSeconds * 1000; webRequest.ReadWriteTimeout = _timeoutSeconds * 1000; + webRequest.UseDefaultCredentials = true; + + IWebProxy systemProxy = WebRequest.GetSystemWebProxy(); + if (systemProxy != null) + { + systemProxy.Credentials = CredentialCache.DefaultNetworkCredentials; + webRequest.Proxy = systemProxy; + } ApplyHeaders(webRequest, request.Headers);