Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/CreateDevRelease.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Publish Dev Build to GitHub Releases

on:
push:
branches: [ "develop" ]
workflow_dispatch:

permissions:
contents: write

jobs:
dev-release:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set module version to dev build
shell: pwsh
run: |
$modulePath = "PiHoleShell"
$manifest = Get-ChildItem -Path $modulePath -Filter PiHoleShell.psd1 -Recurse | Select-Object -First 1

if (-not $manifest) {
throw "No module manifest (*.psd1) found in $modulePath"
}

$shortSha = "${{ github.sha }}".Substring(0, 7)
$devVersion = "0.0.0-dev.$shortSha"
(Get-Content $manifest.FullName) -replace '0.0.0', $devVersion | Set-Content $manifest.FullName
Write-Host "Set dev module version to $devVersion"

- name: Zip folder
run: |
mkdir -p output
zip -r output/release.zip PiHoleShell

- name: Remove previous dev-latest release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release delete dev-latest --yes --cleanup-tag || true

- name: Create dev pre-release
uses: softprops/action-gh-release@v1
with:
tag_name: dev-latest
name: "Development build (develop @ ${{ github.sha }})"
prerelease: true
body: |
Automatically built from the latest `develop` branch commit ${{ github.sha }}.

This is a test build for local verification only — it is **not** published to the PowerShell Gallery, and this release is overwritten on every push to `develop`.

To try it out, download and extract `release.zip`, then:
```powershell
Import-Module .\PiHoleShell\PiHoleShell.psd1 -Force
```
files: output/release.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions .github/workflows/CreateRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ jobs:
with:
tag_name: ${{ steps.bump.outputs.new_tag }}
name: "Release ${{ steps.bump.outputs.new_tag }}"
generate_release_notes: true
files: output/release.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/SyncReadmeCommandReference.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Sync README Command Reference

on:
pull_request:
types: [opened, synchronize, reopened]
branches: [ "main" ]

permissions:
contents: write

jobs:
sync-readme:
if: github.event.pull_request.head.ref == 'develop'
runs-on: ubuntu-latest

steps:
- name: Checkout PR head branch
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0

- name: Regenerate README command reference
shell: pwsh
run: ./tools/Update-ReadmeCommandReference.ps1

- name: Commit changes if needed
run: |
if [ -n "$(git status --porcelain README.md)" ]; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add README.md
git commit -m "docs: sync README command reference [skip ci]"
git push origin HEAD:${{ github.head_ref }}
else
echo "README.md command reference already up to date."
fi
52 changes: 27 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,68 +80,70 @@ Every function accepts the same core parameters:

## Command Reference

Functions marked 🚧 are still under active development — signatures and output shapes may change.
Functions marked 🚧 are still under active development — signatures and output shapes may change. This section is generated from the module's actual exported functions by `tools/Update-ReadmeCommandReference.ps1`, and kept in sync automatically on every `develop` → `main` pull request — don't hand-edit the block below.

<!-- COMMAND-REFERENCE:START -->
### Actions

| Function | Description |
|---|---|
| `Invoke-PiHoleFlushNetwork` | Flush the network table, removing known devices and their addresses |
| `Restart-PiHoleDnsService` | Restart the `pihole-FTL` service |
| `Update-PiHoleActionsGravity` 🚧 | Run `pihole -g` to rebuild the gravity/adlists database |
| `Invoke-PiHoleFlushNetwork` | Flushes the network table. This includes removing both all known devices and their associated addresses. |
| `Restart-PiHoleDnsService` | Restarts the pihole-FTL service |
| `Update-PiHoleActionsGravity` 🚧 | Update Pi-hole's adlists by running pihole -g. The output of the process is streamed with chunked encoding. Use the optional color query parameter to include ANSI color escape codes in the output. |

### DNS Control

| Function | Description |
|---|---|
| `Get-PiHoleDnsBlockingStatus` | Get current blocking status and any active timer |
| `Set-PiHoleDnsBlocking` | Enable or disable blocking, optionally for a set duration |
| `Get-PiHoleDnsBlockingStatus` | _No description yet_ |
| `Set-PiHoleDnsBlocking` | _No description yet_ |

### Group Management

| Function | Description |
|---|---|
| `Get-PiHoleGroup` | List groups |
| `New-PiHoleGroup` | Create a group |
| `Update-PiHoleGroup` | Update an existing group |
| `Remove-PiHoleGroup` 🚧 | Delete a group |
| `Get-PiHoleGroup` | Get groups |
| `New-PiHoleGroup` | Creates a new group in the groups object. |
| `Remove-PiHoleGroup` 🚧 | Delete group |
| `Update-PiHoleGroup` | Items may be updated by replacing them. |

### List Management

| Function | Description |
|---|---|
| `Get-PiHoleList` 🚧 | List allow/deny lists |
| `Add-PiHoleList` 🚧 | Add a domain to an allow/deny list |
| `Remove-PiHoleList` 🚧 | Remove lists |
| `Search-PiHoleListDomain` | Search all lists for a domain, with optional partial matching |
| `Add-PiHoleList` 🚧 | Add new list |
| `Get-PiHoleList` 🚧 | Get lists |
| `Remove-PiHoleList` 🚧 | Deletes multiple lists in the lists object. |
| `Search-PiHoleListDomain` | _No description yet_ |

### Metrics

| Function | Description |
|---|---|
| `Get-PiHoleStatsSummary` | Overview of query, system, and FTL activity |
| `Get-PiHoleStatsRecentBlocked` | Most recently blocked domain |
| `Get-PiHoleStatsQueryType` | Query breakdown by DNS record type |
| `Get-PiHoleStatsTopDomain` | Top permitted/blocked domains |
| `Get-PiHoleStatsTopClient` | Top clients by query volume |
| `Get-PiHoleStatsQueryType` | _No description yet_ |
| `Get-PiHoleStatsRecentBlocked` | Request most recently blocked domain |
| `Get-PiHoleStatsSummary` | Get overview of Pi-hole activity Request various query, system, and FTL properties |
| `Get-PiHoleStatsTopClient` | Get top clients Request the top clients (by query count) |
| `Get-PiHoleStatsTopDomain` | _No description yet_ |

### Configuration & Diagnostics

| Function | Description |
|---|---|
| `Get-PiHoleConfig` 🚧 | Read the Pi-hole configuration |
| `Get-PiHolePadd` 🚧 | Data used to power the PADD dashboard |
| `Get-PiHoleInfoMessage` | Pi-hole diagnosis messages |
| `Get-PiHoleInfoHost` 🚧 | Host system information |
| `Get-PiHoleConfig` 🚧 | _No description yet_ |
| `Get-PiHoleInfoHost` 🚧 | Get info about various host parameters This API hook returns a collection of host infos. |
| `Get-PiHoleInfoMessage` | Get Pi-hole diagnosis messages Request Pi-hole diagnosis messages |
| `Get-PiHolePadd` 🚧 | _No description yet_ |

### Authentication

Session handling is automatic for every command above, but these are available for managing sessions directly:

| Function | Description |
|---|---|
| `Get-PiHoleCurrentAuthSession` | List active API sessions |
| `Remove-PiHoleAuthSession` | Revoke a session by ID |
| `Get-PiHoleCurrentAuthSession` | List of all current sessions including their validity and further information about the client such as the IP address and user agent. |
| `Remove-PiHoleAuthSession` | Using this endpoint, a session can be deleted by its ID. |
<!-- COMMAND-REFERENCE:END -->

## Testing

Expand Down
131 changes: 131 additions & 0 deletions tools/Update-ReadmeCommandReference.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<#
.SYNOPSIS
Regenerates the "Command Reference" section of README.md from the module's actual exported functions.

.DESCRIPTION
Reads the real exported commands from PiHoleShell.psm1 (not just what's documented) and builds a
markdown table per category, using each function's own comment-based help synopsis and its
'#Work In Progress' marker (if present) for the 🚧 flag. Writes the result between the
<!-- COMMAND-REFERENCE:START --> / <!-- COMMAND-REFERENCE:END --> markers in README.md, leaving
everything else in the file untouched. Exits with a non-zero code if README.md wasn't already
up to date, so it can be used as a CI freshness check as well as a local regeneration tool.
#>
[CmdletBinding()]
param (
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')),
[switch]$Check
)

$ErrorActionPreference = 'Stop'

$displayNames = [ordered]@{
Actions = 'Actions'
DnsControl = 'DNS Control'
GroupManagement = 'Group Management'
ListManagement = 'List Management'
Metrics = 'Metrics'
Config = 'Configuration & Diagnostics'
Padd = 'Configuration & Diagnostics'
FTLInformation = 'Configuration & Diagnostics'
Teleporter = 'Configuration & Diagnostics'
Authentication = 'Authentication'
}
$categoryOrder = @('Actions', 'DnsControl', 'GroupManagement', 'ListManagement', 'Metrics', 'Config', 'Authentication')
$categoryIntros = @{
Authentication = 'Session handling is automatic for every command above, but these are available for managing sessions directly:'
}

$modulePath = Join-Path $RepoRoot 'PiHoleShell/PiHoleShell.psm1'
$publicPath = Join-Path $RepoRoot 'PiHoleShell/Public'
$readmePath = Join-Path $RepoRoot 'README.md'

Import-Module $modulePath -Force
$exported = [System.Collections.Generic.HashSet[string]]::new([string[]](Get-Module PiHoleShell).ExportedCommands.Keys)

function Get-CleanSynopsis {
param([string]$FunctionName)

$help = Get-Help -Name $FunctionName -ErrorAction SilentlyContinue
# Get-Help sometimes bleeds a standalone '#Work In Progress' comment that trails the closing
# '#>' into the Synopsis text itself; that's already surfaced separately via the WIP flag.
$rawSynopsis = ($help.Synopsis | Out-String) -replace '(?im)^\s*Work In Progress\s*$', ''
$synopsis = ($rawSynopsis -replace '\s+', ' ').Trim()

if ([string]::IsNullOrWhiteSpace($synopsis) -or $synopsis -match '^https?://' -or $synopsis -eq $FunctionName) {
return $null
}
return $synopsis
}

$functionsByCategory = [ordered]@{}
foreach ($category in $categoryOrder) { $functionsByCategory[$category] = [System.Collections.Generic.List[object]]::new() }

Get-ChildItem -Path $publicPath -Filter '*.ps1' -Recurse | Sort-Object BaseName | ForEach-Object {
$name = $_.BaseName
if (-not $exported.Contains($name)) { return }

$rawCategory = $_.Directory.Name
$category = if ($categoryOrder -contains $rawCategory) { $rawCategory } else { 'Config' }

$content = Get-Content -Path $_.FullName -Raw
$isWip = $content -match '#\s*Work In Progress'
$synopsis = Get-CleanSynopsis -FunctionName $name
if (-not $synopsis) { $synopsis = '_No description yet_' }

$functionsByCategory[$category].Add([PSCustomObject]@{
Name = $name
Synopsis = $synopsis
IsWip = $isWip
})
}

$sections = [System.Collections.Generic.List[string]]::new()
foreach ($category in $categoryOrder) {
$functions = $functionsByCategory[$category]
if ($functions.Count -eq 0) { continue }

$sections.Add("### $($displayNames[$category])")
$sections.Add('')
if ($categoryIntros.ContainsKey($category)) {
$sections.Add($categoryIntros[$category])
$sections.Add('')
}
$sections.Add('| Function | Description |')
$sections.Add('|---|---|')
foreach ($fn in $functions) {
$flag = if ($fn.IsWip) { ' 🚧' } else { '' }
$sections.Add("| ``$($fn.Name)``$flag | $($fn.Synopsis) |")
}
$sections.Add('')
}

$generated = ($sections -join "`n").TrimEnd()

$readme = Get-Content -Path $readmePath -Raw
$startMarker = '<!-- COMMAND-REFERENCE:START -->'
$endMarker = '<!-- COMMAND-REFERENCE:END -->'

if ($readme -notmatch [regex]::Escape($startMarker) -or $readme -notmatch [regex]::Escape($endMarker)) {
throw "README.md is missing the $startMarker / $endMarker markers."
}

$pattern = [regex]::Escape($startMarker) + '(?s).*?' + [regex]::Escape($endMarker)
$replacement = "$startMarker`n$generated`n$endMarker"
$newReadme = [regex]::Replace($readme, $pattern, { $replacement })

if ($Check) {
if ($newReadme -ne $readme) {
Write-Error 'README.md command reference is out of date. Run tools/Update-ReadmeCommandReference.ps1 to refresh it.'
exit 1
}
Write-Output 'README.md command reference is up to date.'
exit 0
}

if ($newReadme -ne $readme) {
Set-Content -Path $readmePath -Value $newReadme -NoNewline
Write-Output 'README.md command reference updated.'
}
else {
Write-Output 'README.md command reference already up to date.'
}
Loading