feat(scripts): add gen-bookmarks.ps1 + checked-in bookmark lists
scripts/gen-bookmarks.ps1 parses Caddyfile (site blocks grouped by banner comments) and stacks/*/docker-compose.yml (services with published ports) into two Netscape-format HTML bookmark files importable by any browser. Run after editing Caddyfile or stack compose; commit the regenerated HTMLs alongside the source change.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||
<TITLE>Homelab - Domains (via Caddy)</TITLE>
|
||||
<H1>Homelab - Domains (via Caddy)</H1>
|
||||
<DL><p>
|
||||
<DT><H3>Homelab - Domains</H3>
|
||||
<DL><p>
|
||||
|
||||
<DT><H3>FOUNDRY VTT (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://foundry.ginnoir.com">foundry</A>
|
||||
<DT><A HREF="https://foundry2.ginnoir.com">foundry2</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>TABLETOP TOOLS (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://5etools.ginnoir.com">5etools</A>
|
||||
<DT><A HREF="https://pf2e.ginnoir.com">pf2e</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>FILE STORAGE (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://files.ginnoir.com">files</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>STATIC SITES (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://ffttsystems.ginnoir.com">ffttsystems</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>MEDIA REQUESTS (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://requests.ginnoir.com">requests</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>MEDIA MANAGEMENT (internal)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://sonarr.ginnoir.com">sonarr</A>
|
||||
<DT><A HREF="https://radarr.ginnoir.com">radarr</A>
|
||||
<DT><A HREF="https://bazarr.ginnoir.com">bazarr</A>
|
||||
<DT><A HREF="https://prowlarr.ginnoir.com">prowlarr</A>
|
||||
<DT><A HREF="https://tautulli.ginnoir.com">tautulli</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>DOWNLOAD CLIENTS (internal)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://qbittorrent.ginnoir.com">qbittorrent</A>
|
||||
<DT><A HREF="https://deluge.ginnoir.com">deluge</A>
|
||||
<DT><A HREF="https://nzbget.ginnoir.com">nzbget</A>
|
||||
<DT><A HREF="https://whisparr.ginnoir.com">whisparr</A>
|
||||
<DT><A HREF="https://stash.ginnoir.com">stash</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>NOTIFICATIONS & RSS (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://ntfy.ginnoir.com">ntfy</A>
|
||||
<DT><A HREF="https://freshrss.ginnoir.com">freshrss</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>RESUME / PORTFOLIO (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://resume.ginnoir.com">resume</A>
|
||||
<DT><A HREF="https://j-costa.com">j-costa.com</A>
|
||||
<DT><A HREF="https://storage.j-costa.com">storage.j-costa.com</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>MINIO CONSOLE (internal)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://minio.ginnoir.com">minio</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>FAMAPP & AUTH (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://fam.ginnoir.com">fam</A>
|
||||
<DT><A HREF="https://auth.ginnoir.com">auth</A>
|
||||
<DT><A HREF="https://dev.ginnoir.com">dev</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>MANAGEMENT (internal)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://portainer.ginnoir.com">portainer</A>
|
||||
<DT><A HREF="https://uptime.ginnoir.com">uptime</A>
|
||||
<DT><A HREF="https://homarr.ginnoir.com">homarr</A>
|
||||
<DT><A HREF="https://router.ginnoir.com">router</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>DEV STACK (internal)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://gitea.ginnoir.com">gitea</A>
|
||||
<DT><A HREF="https://code.ginnoir.com">code</A>
|
||||
<DT><A HREF="https://registry.ginnoir.com">registry</A>
|
||||
<DT><A HREF="https://dbx.ginnoir.com">dbx</A>
|
||||
<DT><A HREF="https://vault.ginnoir.com">vault</A>
|
||||
<DT><A HREF="https://docs.ginnoir.com">docs</A>
|
||||
<DT><A HREF="https://plane.ginnoir.com">plane</A>
|
||||
</DL><p>
|
||||
|
||||
</DL><p>
|
||||
</DL><p>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||
<TITLE>Homelab - Backdoor Ports (direct to valhalla)</TITLE>
|
||||
<H1>Homelab - Backdoor Ports (direct to valhalla)</H1>
|
||||
<DL><p>
|
||||
<DT><H3>Homelab - Backdoor Ports</H3>
|
||||
<DL><p>
|
||||
|
||||
<DT><H3>Authentik</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:9200">authentik_server :9200</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Dev</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="ssh://git@valhalla:2222">gitea :2222</A>
|
||||
<DT><A HREF="http://valhalla:3030">gitea :3030</A>
|
||||
<DT><A HREF="http://valhalla:8443">code_server</A>
|
||||
<DT><A HREF="http://valhalla:5000">registry</A>
|
||||
<DT><A HREF="http://valhalla:4224">dbx</A>
|
||||
<DT><A HREF="http://valhalla:6875">bookstack :6875</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Famapp</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:3010">famapp :3010</A>
|
||||
<DT><A HREF="http://valhalla:9002">minio_famapp :9002</A>
|
||||
<DT><A HREF="http://valhalla:9003">minio_famapp :9003</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Foundry</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:30000">foundry</A>
|
||||
<DT><A HREF="http://valhalla:30001">foundry2 :30001</A>
|
||||
<DT><A HREF="http://valhalla:9009">5etools :9009</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Media</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:8112">deluge</A>
|
||||
<DT><A HREF="http://valhalla:6789">nzbget</A>
|
||||
<DT><A HREF="http://valhalla:8989">sonarr</A>
|
||||
<DT><A HREF="http://valhalla:7878">radarr</A>
|
||||
<DT><A HREF="http://valhalla:6767">bazarr</A>
|
||||
<DT><A HREF="http://valhalla:9696">prowlarr</A>
|
||||
<DT><A HREF="http://valhalla:5055">overseerr</A>
|
||||
<DT><A HREF="http://valhalla:8181">tautulli</A>
|
||||
<DT><A HREF="http://valhalla:3232">qbittorrent :3232</A>
|
||||
<DT><A HREF="http://valhalla:6881">qbittorrent :6881 (TCP/UDP)</A>
|
||||
<DT><A HREF="http://valhalla:6969">whisparr</A>
|
||||
<DT><A HREF="http://valhalla:6971">stash :6971</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Monitoring</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:3001">uptime_kuma</A>
|
||||
<DT><A HREF="http://valhalla:7575">homarr</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Notify</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:8000">ntfy :8000</A>
|
||||
<DT><A HREF="http://valhalla:9999">freshrss :9999</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Owncloud</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:8080">owncloud_server</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Proxy</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:80">caddy :80</A>
|
||||
<DT><A HREF="https://valhalla:443">caddy :443</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Remote</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:21117">hbbr</A>
|
||||
<DT><A HREF="http://valhalla:21115">hbbs :21115</A>
|
||||
<DT><A HREF="http://valhalla:21116">hbbs :21116 (TCP/UDP)</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Resume</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:9000">minio_resume :9000</A>
|
||||
<DT><A HREF="http://valhalla:9004">minio_resume :9004</A>
|
||||
<DT><A HREF="http://valhalla:3000">resume</A>
|
||||
</DL><p>
|
||||
|
||||
</DL><p>
|
||||
</DL><p>
|
||||
@@ -0,0 +1,305 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# scripts/gen-bookmarks.ps1
|
||||
#
|
||||
# Regenerate bookmarks-domains.html and bookmarks-ports.html from the live
|
||||
# Caddyfile and stacks/*/docker-compose.yml. Run after editing either, then
|
||||
# commit the regenerated HTMLs alongside the source change.
|
||||
#
|
||||
# ./scripts/gen-bookmarks.ps1
|
||||
# ./scripts/gen-bookmarks.ps1 -Hostname myhost
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
|
||||
[string]$Hostname = 'valhalla'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
function Format-HtmlEscape([string]$s) {
|
||||
if ($null -eq $s) { return '' }
|
||||
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||
}
|
||||
|
||||
# Split a text blob into LF-terminated lines (handles CRLF inputs).
|
||||
function Get-LfLines([string]$path) {
|
||||
$raw = Get-Content -Raw -Path $path -Encoding UTF8
|
||||
return ($raw -replace "`r", '') -split "`n"
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Caddyfile -> [{Host, Section, Internal, Upstream}, ...]
|
||||
# -------------------------------------------------------------------------
|
||||
function Get-CaddySites([string]$caddyFile) {
|
||||
$lines = Get-LfLines $caddyFile
|
||||
$sites = [System.Collections.Generic.List[object]]::new()
|
||||
$currentSection = 'Other'
|
||||
$i = 0
|
||||
while ($i -lt $lines.Count) {
|
||||
$trim = $lines[$i].Trim()
|
||||
|
||||
# Section banner: `# ===`, then `# NAME`, then `# ===`
|
||||
# NOTE: capture the name BEFORE running the second -match, since
|
||||
# PowerShell's -match overwrites $matches with each call.
|
||||
if ($trim -match '^#\s+=+\s*$' -and $i + 2 -lt $lines.Count) {
|
||||
$next = $lines[$i + 1].Trim()
|
||||
$after = $lines[$i + 2].Trim()
|
||||
if ($next -match '^#\s+(.+?)\s*$') {
|
||||
$name = $matches[1]
|
||||
if ($after -match '^#\s+=+\s*$') {
|
||||
$currentSection = ($name -replace '\s*[-—]\s*(public|internal only)\s*$','').Trim()
|
||||
$i += 3
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Site header at column 0: `<hosts> {`
|
||||
if ($trim -match '^([a-zA-Z0-9][^{}]*)\{\s*$') {
|
||||
$hostList = $matches[1].Trim()
|
||||
$hosts = $hostList -split ',' | ForEach-Object { $_.Trim() } |
|
||||
Where-Object { $_ -match '^[a-zA-Z0-9.-]+$' -and $_ -match '\.' }
|
||||
|
||||
# Walk to matching brace; capture markers.
|
||||
$depth = 1
|
||||
$internal = $false
|
||||
$upstream = $null
|
||||
$j = $i + 1
|
||||
while ($j -lt $lines.Count -and $depth -gt 0) {
|
||||
$bl = $lines[$j].Trim()
|
||||
$depth += ([regex]::Matches($bl,'\{')).Count
|
||||
$depth -= ([regex]::Matches($bl,'\}')).Count
|
||||
if ($bl -match 'import\s+internal_only') { $internal = $true }
|
||||
if (-not $upstream -and $bl -match 'reverse_proxy\s+(\S+)') { $upstream = $matches[1] }
|
||||
if (-not $upstream -and $bl -match 'file_server') { $upstream = '(file_server)' }
|
||||
$j++
|
||||
}
|
||||
foreach ($h in $hosts) {
|
||||
$sites.Add([pscustomobject]@{
|
||||
Host = $h
|
||||
Section = $currentSection
|
||||
Internal = $internal
|
||||
Upstream = $upstream
|
||||
})
|
||||
}
|
||||
$i = $j
|
||||
continue
|
||||
}
|
||||
$i++
|
||||
}
|
||||
return ,$sites
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# stacks/*/docker-compose.yml -> [{Stack, Service, ContainerName, Ports[]}, ...]
|
||||
# -------------------------------------------------------------------------
|
||||
function Get-StackServices([string]$stacksDir) {
|
||||
$result = [System.Collections.Generic.List[object]]::new()
|
||||
Get-ChildItem -Path $stacksDir -Directory | Sort-Object Name | ForEach-Object {
|
||||
$stackName = $_.Name
|
||||
$composeFile = Join-Path $_.FullName 'docker-compose.yml'
|
||||
if (-not (Test-Path $composeFile)) { return }
|
||||
|
||||
$lines = Get-LfLines $composeFile
|
||||
|
||||
$state = 'TOP' # TOP | SERVICES
|
||||
$service = $null
|
||||
$services = [ordered]@{}
|
||||
$inPorts = $false
|
||||
|
||||
foreach ($raw in $lines) {
|
||||
$line = $raw -replace '\s+$',''
|
||||
if ($line -match '^\s*#') { continue }
|
||||
if ($line -eq '') { $inPorts = $false; continue }
|
||||
|
||||
# Top-level key at column 0
|
||||
if ($line -match '^([a-zA-Z_][a-zA-Z0-9_-]*):\s*$') {
|
||||
$state = if ($matches[1] -eq 'services') { 'SERVICES' } else { 'TOP' }
|
||||
$service = $null
|
||||
$inPorts = $false
|
||||
continue
|
||||
}
|
||||
|
||||
if ($state -ne 'SERVICES') { continue }
|
||||
|
||||
# Service header at indent 2
|
||||
if ($line -match '^ ([a-zA-Z0-9_-]+):\s*$') {
|
||||
$service = $matches[1]
|
||||
$services[$service] = [pscustomobject]@{
|
||||
Name = $service
|
||||
ContainerName = $service
|
||||
Ports = [System.Collections.Generic.List[object]]::new()
|
||||
}
|
||||
$inPorts = $false
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $service) { continue }
|
||||
|
||||
# Property at indent 4
|
||||
if ($line -match '^ container_name:\s*(\S+)\s*$') {
|
||||
$services[$service].ContainerName = $matches[1]
|
||||
$inPorts = $false
|
||||
continue
|
||||
}
|
||||
if ($line -match '^ ports:\s*$') { $inPorts = $true; continue }
|
||||
if ($line -match '^ [a-zA-Z_][a-zA-Z0-9_-]*:') { $inPorts = $false; continue }
|
||||
|
||||
# Port entry at indent 6
|
||||
if ($inPorts -and $line -match '^ -\s*["'']?(\d+):(\d+)(/(tcp|udp))?["'']?\s*$') {
|
||||
$services[$service].Ports.Add([pscustomobject]@{
|
||||
Host = [int]$matches[1]
|
||||
Container = [int]$matches[2]
|
||||
Proto = if ($matches[4]) { $matches[4] } else { 'tcp' }
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($svc in $services.Values) {
|
||||
if ($svc.Ports.Count -gt 0) {
|
||||
$result.Add([pscustomobject]@{
|
||||
Stack = $stackName
|
||||
Service = $svc.Name
|
||||
ContainerName = $svc.ContainerName
|
||||
Ports = $svc.Ports
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return ,$result
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Netscape bookmark HTML renderers
|
||||
# -------------------------------------------------------------------------
|
||||
function New-DomainsHtml($sites) {
|
||||
$out = [System.Collections.Generic.List[string]]::new()
|
||||
$out.Add('<!DOCTYPE NETSCAPE-Bookmark-file-1>')
|
||||
$out.Add('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">')
|
||||
$out.Add('<TITLE>Homelab - Domains (via Caddy)</TITLE>')
|
||||
$out.Add('<H1>Homelab - Domains (via Caddy)</H1>')
|
||||
$out.Add('<DL><p>')
|
||||
$out.Add(' <DT><H3>Homelab - Domains</H3>')
|
||||
$out.Add(' <DL><p>')
|
||||
|
||||
# Group by section, preserving first-seen order.
|
||||
$groups = [ordered]@{}
|
||||
foreach ($s in $sites) {
|
||||
if (-not $groups.Contains($s.Section)) {
|
||||
$groups[$s.Section] = [System.Collections.Generic.List[object]]::new()
|
||||
}
|
||||
$groups[$s.Section].Add($s)
|
||||
}
|
||||
|
||||
foreach ($section in $groups.Keys) {
|
||||
$items = $groups[$section]
|
||||
$publicCount = @($items | Where-Object { -not $_.Internal }).Count
|
||||
$internalCount = @($items | Where-Object { $_.Internal }).Count
|
||||
$suffix =
|
||||
if ($publicCount -eq 0) { ' (internal)' }
|
||||
elseif ($internalCount -eq 0) { ' (public)' }
|
||||
else { '' }
|
||||
$heading = Format-HtmlEscape "$section$suffix"
|
||||
$out.Add('')
|
||||
$out.Add(" <DT><H3>$heading</H3>")
|
||||
$out.Add(' <DL><p>')
|
||||
foreach ($s in $items) {
|
||||
$label = $s.Host -replace '\.ginnoir\.com$',''
|
||||
if ($s.Internal -and $publicCount -gt 0) { $label = "$label [internal]" }
|
||||
$href = Format-HtmlEscape "https://$($s.Host)"
|
||||
$text = Format-HtmlEscape $label
|
||||
$out.Add(" <DT><A HREF=`"$href`">$text</A>")
|
||||
}
|
||||
$out.Add(' </DL><p>')
|
||||
}
|
||||
|
||||
$out.Add('')
|
||||
$out.Add(' </DL><p>')
|
||||
$out.Add('</DL><p>')
|
||||
return ($out -join "`n") + "`n"
|
||||
}
|
||||
|
||||
function New-PortsHtml($services, [string]$hostname) {
|
||||
$out = [System.Collections.Generic.List[string]]::new()
|
||||
$out.Add('<!DOCTYPE NETSCAPE-Bookmark-file-1>')
|
||||
$out.Add('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">')
|
||||
$out.Add("<TITLE>Homelab - Backdoor Ports (direct to $hostname)</TITLE>")
|
||||
$out.Add("<H1>Homelab - Backdoor Ports (direct to $hostname)</H1>")
|
||||
$out.Add('<DL><p>')
|
||||
$out.Add(' <DT><H3>Homelab - Backdoor Ports</H3>')
|
||||
$out.Add(' <DL><p>')
|
||||
|
||||
$byStack = [ordered]@{}
|
||||
foreach ($svc in $services) {
|
||||
if (-not $byStack.Contains($svc.Stack)) {
|
||||
$byStack[$svc.Stack] = [System.Collections.Generic.List[object]]::new()
|
||||
}
|
||||
$byStack[$svc.Stack].Add($svc)
|
||||
}
|
||||
|
||||
$ti = (Get-Culture).TextInfo
|
||||
foreach ($stack in $byStack.Keys) {
|
||||
$items = $byStack[$stack]
|
||||
$heading = Format-HtmlEscape ($ti.ToTitleCase($stack))
|
||||
$out.Add('')
|
||||
$out.Add(" <DT><H3>$heading</H3>")
|
||||
$out.Add(' <DL><p>')
|
||||
foreach ($svc in $items) {
|
||||
# Collapse same host-port across protocols (TCP+UDP -> one entry
|
||||
# labelled TCP/UDP). Browsers can't open UDP anyway.
|
||||
$byHostPort = @($svc.Ports | Group-Object -Property Host)
|
||||
$multi = $byHostPort.Count -gt 1
|
||||
foreach ($g in ($byHostPort | Sort-Object { [int]$_.Name })) {
|
||||
$group = @($g.Group)
|
||||
$p = $group[0]
|
||||
$protos = ($group | ForEach-Object { $_.Proto } | Sort-Object -Unique) -join '/'
|
||||
$scheme =
|
||||
if ($p.Container -eq 22) { 'ssh' }
|
||||
elseif ($p.Container -eq 443) { 'https' }
|
||||
else { 'http' }
|
||||
$userPrefix = if ($scheme -eq 'ssh') { 'git@' } else { '' }
|
||||
$href = Format-HtmlEscape ("{0}://{1}{2}:{3}" -f $scheme, $userPrefix, $hostname, $p.Host)
|
||||
$portTag = if ($multi -or $p.Container -ne $p.Host) { " :$($p.Host)" } else { '' }
|
||||
$protoTag = if ($protos -ne 'tcp') { " ($($protos.ToUpper()))" } else { '' }
|
||||
$text = Format-HtmlEscape "$($svc.ContainerName)$portTag$protoTag"
|
||||
$out.Add(" <DT><A HREF=`"$href`">$text</A>")
|
||||
}
|
||||
}
|
||||
$out.Add(' </DL><p>')
|
||||
}
|
||||
|
||||
$out.Add('')
|
||||
$out.Add(' </DL><p>')
|
||||
$out.Add('</DL><p>')
|
||||
return ($out -join "`n") + "`n"
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Main
|
||||
# -------------------------------------------------------------------------
|
||||
$caddyFile = Join-Path $RepoRoot 'Caddyfile'
|
||||
$stacksDir = Join-Path $RepoRoot 'stacks'
|
||||
$domainsOut = Join-Path $RepoRoot 'bookmarks-domains.html'
|
||||
$portsOut = Join-Path $RepoRoot 'bookmarks-ports.html'
|
||||
|
||||
Write-Host "Parsing Caddyfile..."
|
||||
$sites = Get-CaddySites $caddyFile
|
||||
Write-Host " found $($sites.Count) site(s)"
|
||||
|
||||
Write-Host "Parsing stacks..."
|
||||
$services = Get-StackServices $stacksDir
|
||||
$portCount = 0
|
||||
foreach ($s in $services) { $portCount += $s.Ports.Count }
|
||||
Write-Host " found $($services.Count) service(s) with $portCount published port(s)"
|
||||
|
||||
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
||||
|
||||
Write-Host "Writing $domainsOut"
|
||||
[System.IO.File]::WriteAllText($domainsOut, (New-DomainsHtml $sites), $utf8NoBom)
|
||||
|
||||
Write-Host "Writing $portsOut"
|
||||
[System.IO.File]::WriteAllText($portsOut, (New-PortsHtml $services $Hostname), $utf8NoBom)
|
||||
|
||||
Write-Host "Done."
|
||||
Reference in New Issue
Block a user