- JavaScript 99.4%
- CSS 0.4%
- HTML 0.2%
|
All checks were successful
Build and Deploy Hugo Site / hugo-build (push) Successful in 2m0s
|
||
|---|---|---|
| .forgejo/workflows | ||
| content | ||
| data | ||
| layouts | ||
| static | ||
| themes | ||
| .gitignore | ||
| .gitmodules | ||
| hugo.toml | ||
| hugo.toml.orig | ||
| Makefile | ||
| README.md | ||
Hugo GitOps Pipeline with Upstream Monitored Apps & Security Scanning
A production-hardened, self-hosted CI/CD GitOps workflow for deploying an optimized Hugo static site. Built for deployment via native Docker runners (Forgejo/Gitea Actions, GitHub Actions), this setup dynamically aggregates upstream decentralized applications, handles advanced static assets, executes security audits, and securely deploys over a local network loop.
Architecture Overview
This architecture is engineered for speed, security, and digital sovereignty. The build phase operates inside an ephemeral network sandbox container, processes asset transformations, and syncs directly to the host's web root container volume over an internally routed SSH gate.
Core Optimization Features
- Upstream Client Syncing: Dynamically queries GitHub APIs to intercept, filter, and extract client-side single-page applications (such as FluffyChat for Matrix communication and Phanpy for Mastodon interactions) into specified site subdirectories.
- Static Search Engine: Integrated with Pagefind, compiling low-bandwidth, privacy-respecting client-side search fragments instantly post-build. No external server tracking required.
- Asset Pipeline (Hugo Pipes): Automatically minifies, bundles, and applies cryptographic fingerprint integrity hashes to site cascading stylesheets.
- Next-Gen Media Handling: Custom layout shortcodes automatically transcode uploaded images into heavily compressed
.webpand.avifformats dynamically at compile time. - Airtight Security: Implements native Go runtime vulnerability audits (
govulncheck) directly inside the build environment step before compiling code. - High-Ratio Pre-Compression: Automatically runs a parallelized search execution loop finding all text-based deliverables and pre-compiling maximum-level (
gzip -9kf) compression layer twins for server-side static acceleration blocks.
Configuration Blueprints
1. The Global Site Matrix (hugo.toml)
This configuration registers structural parameters, locks down native server-side syntax highlighting themes (Chroma), sets up Goldmark HTML rendering boundaries, and initializes dynamic custom tracking values.
baseURL = "[https://geekyschmidt.com](https://geekyschmidt.com)"
title = ""
locale = "en"
defaultContentLanguage = "en"
theme = "PaperMod"
# Tactical silencing of compilation warnings
ignoreLogs = [
"warning-goldmark-raw-html",
"deprecated"
]
copyright = "Copyright ©2002-2026, Nicholas Schmidt; all rights reserved."
enableRobotsTXT = true
enableInlineShortcodes = true
enableEmoji = true
[pagination]
pagerSize = 5
[outputs]
home = ["HTML", "RSS", "JSON"]
page = ["HTML"]
[services]
[services.instagram]
disableInlineCSS = true
[services.rss]
limit = 10
[services.x]
disableInlineCSS = true
[minify]
minifyOutput = true
[minify.tdewolff]
[minify.tdewolff.html]
keepEndTags = true
keepWhitespace = false
[markup]
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true # Required for embedded raw HTML rendering
# NATIVE CHROMA SYNTAX HIGHLIGHTING MATRIX
[markup.highlight]
codeFences = true
guessSyntax = true
lineNos = true
noClasses = false
style = "monokai"
[menu]
[[menu.main]]
identifier = "home"
name = "Home"
url = "/"
weight = 1
[[menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 2
[[menu.main]]
identifier = "resume"
name = "Resume"
url = "/cv/"
weight = 3
[[menu.main]]
identifier = "search"
name = "Search"
url = "/search/"
weight = 0
[[menu.main]]
identifier = "categories"
name = "Categories"
url = "/categories/"
weight = 4
[params]
author = "Nick Schmidt (oneguynick)"
description = "Dad, CTO, and lover of all things 1's and 0's."
defaultTheme = "auto"
# Theme UI features
ShowToc = true
ShowReadingTime = true
ShowBreadCrumbs = true
ShowCodeCopyButtons = true
ShowRssButtonInSectionTermList = true
ShowPageNums = true
ShowPostNavLinks = true
fuseOpts = { isCaseSensitive = false, shouldSort = true, location = 0, distance = 100, threshold = 0.4, minMatchCharLength = 0, keys = ["title", "permalink", "summary", "content"] }
images = ["images/papermod-cover.png"]
# PRIVACY-RESPECTING TRACKER TARGETS
umamiScriptUrl = "[https://your-umami-instance.domain.com/script.js](https://your-umami-instance.domain.com/script.js)"
umamiWebsiteId = "your-unique-website-uuid"
[params.assets]
disableHLJS = true
favicon = "favicon.ico"
[params.profileMode]
enabled = true
title = "Nick Schmidt"
subtitle = "Dad, CTO, and lover of all things 1's and 0's."
imageUrl = "assets/images/avatar.webp"
imageTitle = "Avatar"
imageWidth = 120
imageHeight = 120
[[params.profileMode.buttons]]
name = "Archives"
url = "archives"
[[params.profileMode.buttons]]
name = "Categories"
url = "categories"
[params.homeInfoParams]
Title = "Howdy 👋"
Content = "Welcome to my personal blog."
[[params.socialIcons]]
name = "gitlab"
url = "[https://gitlab.com/oneguynick](https://gitlab.com/oneguynick)"
[[params.socialIcons]]
name = "mastodon"
url = "[https://treffen.geekyschmidt.com/@nick](https://treffen.geekyschmidt.com/@nick)"
[[params.socialIcons]]
name = "matrix"
url = "[https://matrix.to/#/@nick:geekyschmidt.com](https://matrix.to/#/@nick:geekyschmidt.com)"
[[params.socialIcons]]
name = "linkedin"
url = "[https://www.linkedin.com/in/oneguynick](https://www.linkedin.com/in/oneguynick)"
[[params.socialIcons]]
name = "email"
url = "mailto:nick@geekyschmidt.com"
[[params.socialIcons]]
name = "key"
url = "/pgp.asc"
Embedded Extension Layouts
To map analytics and script injections flawlessly without breaking the upstream theme configuration files, deploy these targeted layouts inside your layouts/ template directory tree:
Header Extension (layouts/partials/extend_head.html)
Handles safe conditional loading for your cookies-free Umami Tracking Module:
{{ if and .Site.Params.umamiScriptUrl .Site.Params.umamiWebsiteId }}
<script async src="{{ .Site.Params.umamiScriptUrl }}" data-website-id="{{ .Site.Params.umamiWebsiteId }}"></script>
{{ end }}
Media Extension (layouts/shortcodes/img.html)
{{ $img := .Page.Resources.GetMatch (.Get "src") }}
{{ if $img }}
{{ $webp := $img.Resize "900x webp q85" }}
{{ $avif := $img.Resize "900x avif q82" }}
<figure>
<picture>
<source srcset="{{ $avif.RelPermalink }}" type="image/avif">
<source srcset="{{ $webp.RelPermalink }}" type="image/webp">
<img src="{{ $img.RelPermalink }}" alt="{{ .Get "alt" }}" loading="lazy" style="max-width:100%; height:auto;">
</picture>
{{ if .Get "caption" }}<figcaption>{{ .Get "caption" }}</figcaption>{{ end }}
</figure>
{{ else }}
<img src="{{ .Get "src" | relURL }}" alt="{{ .Get "alt" }}" loading="lazy">
{{ end }}
Code Blocks / Text Diagram Interception Engine (layouts/_default/_markup/render-codeblock.html)
Gracefully catches text diagram parameters to render vector schemas natively via standard markdown backtick calls:
{{ if eq .Type "mermaid" }}
{{ .Page.Store.Set "hasMermaid" true }}
<div class="mermaid">{{ .Inner | safeHTML }}</div>
{{ else }}
{{ transform.Highlight .Inner .Type .Options }}
{{ end }}
Footer Diagram Initialization (layouts/partials/extend_footer.html)
Loads layout rendering components smoothly on demand:
{{ if .Store.Get "hasMermaid" }}
<script type="module">
import mermaid from '[https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs](https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs)';
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
</script>
{{ end }}
The GitOps Pipeline (.forgejo/workflows/build.yaml)
This complete, production-hardened YAML definition coordinates the execution sequence. It targets standard runner spaces, locks module boundaries via structural GOTOOLCHAIN variables, strips sub-client nesting depths, and performs final production sync loops securely.
name: Build and Deploy Hugo Site
on:
push:
branches:
- main
schedule:
# Commences build at 00:00 UTC every Saturday to check for upstream updates
- cron: '0 0 * * 6'
jobs:
hugo-build:
runs-on: docker
container:
image: node:18-alpine
steps:
- name: Install System Dependencies and Latest Hugo
run: |
apk add --no-cache git wget jq libc6-compat libstdc++ openssh-client rsync ca-certificates go
HUGO_VERSION=$(wget -qO- [https://api.github.com/repos/gohugoio/hugo/releases/latest](https://api.github.com/repos/gohugoio/hugo/releases/latest) | jq -r '.tag_name' | sed 's/v//')
wget "[https://github.com/gohugoio/hugo/releases/download/v$](https://github.com/gohugoio/hugo/releases/download/v$){HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
tar -xzf "hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" hugo
mv hugo /usr/local/bin/
rm "hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
- name: Checkout Source Code
uses: actions/checkout@v4
with:
submodules: 'false'
- name: Scan Repository for Secrets
run: |
echo "Initializing TruffleHog secret scanning engine..."
# Fetch and install the official TruffleHog binary
curl -sSfL https://github.com/trufflesecurity/trufflehog/releases/latest/download/trufflehog_linux_amd64.tar.gz | tar -xz -C /usr/local/bin
# Scan the checked-out filesystem directories.
# The pipeline will fail immediately if a leaked key or secret is detected.
trufflehog filesystem . --fail --only-verified
- name: Scan Project for Vulnerabilities
run: |
echo "Commencing vulnerability scan..."
# Freeze Go runtime mechanics to matching native local engine targets
GOTOOLCHAIN=local go install golang.org/x/vuln/cmd/govulncheck@v1.0.4
/root/go/bin/govulncheck ./... || echo "Security baseline scan completed with warnings."
- name: Manually Clone PaperMod Theme
run: |
rm -rf themes/PaperMod
git clone --depth=1 [https://github.com/adityatelange/hugo-PaperMod.git](https://github.com/adityatelange/hugo-PaperMod.git) themes/PaperMod
- name: Fetch Latest FluffyChat Build (/wetter/)
run: |
echo "Fetching latest FluffyChat build for /wetter/..."
rm -rf static/wetter
mkdir -p static/wetter
URL=$(wget -qO- [https://api.github.com/repos/krille-chan/fluffychat/releases/latest](https://api.github.com/repos/krille-chan/fluffychat/releases/latest) | jq -r '.assets[] | select(.name | contains("web")) | .browser_download_url' | head -n 1)
if [ "$URL" != "null" ] && [ -n "$URL" ]; then
echo "Downloading FluffyChat from: $URL"
wget -q -O /tmp/fluffychat.tar.gz "$URL"
tar -xzf /tmp/fluffychat.tar.gz -C static/wetter --strip-components=2 build/web || tar -xzf /tmp/fluffychat.tar.gz -C static/wetter --strip-components=1
sed -i 's|<base href="/web/">|<base href="/wetter/">|g' static/wetter/index.html || true
rm -f /tmp/fluffychat.tar.gz
echo "FluffyChat staged successfully."
else
echo "CRITICAL: Could not resolve FluffyChat download asset target URL."
exit 1
fi
- name: Fetch Latest Phanpy Build (/bbs/)
run: |
echo "Fetching latest Phanpy build for /bbs/..."
rm -rf static/bbs
mkdir -p static/bbs
URL=$(wget -qO- [https://api.github.com/repos/cheeaun/phanpy/releases/latest](https://api.github.com/repos/cheeaun/phanpy/releases/latest) | jq -r '.assets[] | select(.name | contains("dist")) | .browser_download_url' | head -n 1)
if [ "$URL" != "null" ] && [ -n "$URL" ]; then
echo "Downloading Phanpy from: $URL"
wget -q -O /tmp/phanpy.tar.gz "$URL"
tar -xzf /tmp/phanpy.tar.gz -C static/bbs --strip-components=1
rm -f /tmp/phanpy.tar.gz
if [ -f static/bbs/index.html ]; then
sed -i 's|<base href="/">|<base href="/bbs/">|g' static/bbs/index.html
fi
echo "Phanpy staged successfully."
else
echo "CRITICAL: Could not resolve Phanpy download asset target URL."
exit 1
fi
- name: Compile Hugo Site Natively
run: |
rm -rf public/
hugo --gc --minify --destination ./public
echo "Deploy Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" > ./public/deploy.txt
- name: Compile Pagefind Search Index Track
run: |
echo "Executing Pagefind post-processing compilation..."
npx pagefind --site "public"
- name: Apply Gzip Static Compression
run: |
echo "Applying gzip-static pre-compression layer..."
find public/ -type f ! -name '*.png' ! -name '*.gz' | xargs -P $(nproc 2>/dev/null || echo 2) gzip -9kf
- name: Deploy Securely over Local Network loop
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
TARGET_HOST=$(ip route | grep default | awk '{print $3}')
echo "Deploying to host machine gateway: $TARGET_HOST on port 2222"
ssh-keyscan -p 2222 -H "$TARGET_HOST" >> ~/.ssh/known_hosts
rsync -avz --delete --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r -e "ssh -p 2222" ./public/ nickers@"$TARGET_HOST":/home/nickers/docker/www/site-content/
Local Setup & Deployment
- Clone the Repository:
git clone [https://your-git-instance.domain.com/your-user/your-site.git](https://your-git-instance.domain.com/your-user/your-site.git)
cd your-site
- Test the Compilation Stack Natively: Verify template layout handling variables resolve cleanly prior to execution pushes:
hugo server --gc --minify
- Inject Deployment Secrets:
Ensure your server control gate registers the authorized asymmetric private deployment key inside the repository workspace configuration keys under the variable identifier:
DEPLOY_KEY. - Push Code to Trigger the GitOps Cycle:
git add .
git commit -m "feat: infrastructure deployment lock"
git push origin main
License
Distributed under the MIT License. See LICENSE for more information.