43 Commits
Author SHA1 Message Date
ahmza 40c58e8d9b harden Hugo build and remove unused CMS
Build Hugo / build (push) Successful in 2m20s
Build Hugo / deploy (push) Successful in 29s
2026-08-23 18:15:52 +00:00
ahmza 00ba36191f revert dockerfile for testing k8
Build Hugo / build (push) Successful in 2m7s
Build Hugo / deploy (push) Successful in 23s
2026-08-22 01:29:42 +00:00
ahmza 2a18854894 added the actual deploy steps
Build Hugo / build (push) Failing after 40s
Build Hugo / deploy (push) Skipped
2026-08-22 01:27:19 +00:00
ahmza c157c599ea Merge branch 'main' of ssh://tea.ahmza.com/ahmza/blog-ahmza-com 2026-08-22 01:22:18 +00:00
ahmza 810069d1fe updated docker file to help with caching 2026-08-22 01:21:32 +00:00
ahmza e2a530045d Update .gitea/workflows/build-docker-images.yml
Build Hugo / build (push) Successful in 1m58s
Build Hugo / deploy (push) Successful in 35s
2026-08-22 01:19:03 +00:00
ahmza 9e69de9fd8 pissing me off
Build Hugo / build (push) Successful in 1m57s
Build Hugo / deploy (push) Failing after 5s
2026-08-22 01:15:52 +00:00
ahmza 808187bef2 pissing me off
Build Hugo / build (push) Successful in 1m51s
Build Hugo / deploy (push) Failing after 12s
2026-08-22 01:02:36 +00:00
ahmza ae74124319 pissing me off
Build Hugo / build (push) Successful in 2m1s
Build Hugo / deploy (push) Failing after 14s
2026-08-22 00:51:39 +00:00
ahmza eefcee2ce3 pissing me off 2026-08-22 00:50:36 +00:00
ahmza d9cf96e594 pissing me off
Build Hugo / build (push) Successful in 6m34s
Build Hugo / deploy (push) Failing after 0s
2026-08-22 00:39:00 +00:00
ahmza b809a14e2b pissing me off
Build Hugo / build (push) Successful in 6m33s
Build Hugo / deploy (push) Failing after 12s
2026-08-22 00:23:02 +00:00
ahmza a0878cf30e pissing me off 2026-08-22 00:21:17 +00:00
ahmza f28aca8484 will it actually work now ?
Build Hugo / build (push) Successful in 6m40s
Build Hugo / deploy (push) Failing after 12s
2026-08-22 00:09:51 +00:00
ahmza 84495ae064 renamed the stuff and stuff to do stff on stuff blablabla
Build Hugo / build (push) Successful in 6m37s
Build Hugo / deploy (push) Failing after 13s
2026-08-21 23:49:28 +00:00
ahmza e8d04b8373 renamed the stuff and stuff to do stff on stuff blablabla
Build Hugo / build (push) Successful in 6m32s
Build Hugo / deploy (push) Failing after 16s
2026-08-21 23:38:37 +00:00
ahmza a4b9e5a040 renamed the stuff and stuff to do stff on stuff blablabla
Build Hugo / build (push) Successful in 6m17s
Build Hugo / deploy (push) Failing after 47s
2026-08-21 23:25:46 +00:00
ahmza 9fc7c371c5 Update README.md
Build Hugo / build (push) Canceled after 0s
Build Hugo / deploy (push) Canceled after 0s
2026-08-21 19:42:16 +00:00
ahmza 1ef8f861c1 Harden Gimli deploy: reconcile in place instead of compose down
Build Hugo / build (push) Successful in 6m15s
Build Hugo / deploy (push) Successful in 13s
'docker compose down' removes caddy_cf and the default network, which
intermittently severs the SSH session's own network path before 'up'
restores it (remote command exited without exit status). Replace with
'pull && up -d --force-recreate --remove-orphans' so the stack is
reconciled in place without tearing the network out from under the
connection, while still picking up the new :latest image.
2026-08-03 15:50:23 +00:00
ahmza 3c5678f8a8 Run Hugo natively in build stage to skip QEMU emulation
Build Hugo / build (push) Successful in 6m31s
Build Hugo / deploy (push) Failing after 24s
Pin the build stage to $BUILDPLATFORM so Hugo runs once on the native
arch instead of once per target arch under QEMU. Hugo output is static
HTML and identical across arches, so the emulated arm64 Hugo run (~59s
vs ~12s native) plus emulated base-image extraction were pure waste.
The runtime stage stays on TARGETPLATFORM, so multi-arch amd64+arm64
images are still produced.
2026-08-03 15:40:22 +00:00
ahmza 319c8fa922 Add NetBird diagnostic checks to deploy job
Build Hugo / build (push) Successful in 7m23s
Build Hugo / deploy (push) Successful in 12s
Surface VPN connection status in the deploy job so netbird failures are
legible instead of a cryptic DeadlineExceeded:
- Pre-flight check for /dev/net/tun + NET_ADMIN before connecting
- id + timeout-minutes:3 on Netbird Connect to bound its wait loop
- Post-connect 'netbird status --detail' with a Management:Connected assert
- Always-run diagnostics + 'netbird down' teardown
2026-08-03 14:51:04 +00:00
ahmza b84843ca3d added footer links
Build Hugo / build (push) Successful in 11m23s
Build Hugo / deploy (push) Failing after 54s
added gitlab as well as gitea footer links.
2026-08-03 13:12:58 +00:00
ahmza 4b0b681302 Merge pull request 'Use dynamic cloudflare trusted_proxies IP source' (#5) from caddy-dynamic-cf-ip into main
Build Hugo / build (push) Successful in 1m37s
Build Hugo / deploy (push) Successful in 41s
Reviewed-on: #5
2026-07-25 04:09:11 +00:00
ahmza 2c6f20fbc3 docs: show dynamic cloudflare trusted_proxies as primary example
caddy-cf now bundles WeidiDeng/caddy-cloudflare-ip, so document
'trusted_proxies cloudflare { interval 12h; timeout 15s }' as the
primary (auto-refreshing) approach, with the static-list method noted
as the fallback for builds without the plugin. Also explains
client_ip_headers (Cf-Connecting-Ip preferred, X-Forwarded-For fallback).
2026-07-25 05:04:52 +01:00
ahmza b88828f92e Use dynamic cloudflare trusted_proxies IP source
caddy-cf now includes WeidiDeng/caddy-cloudflare-ip, so replace the
hardcoded 22-range static list with 'trusted_proxies cloudflare' which
auto-fetches and refreshes Cloudflare's IPv4/IPv6 ranges (interval 12h,
timeout 15s). No static list to keep in sync when Cloudflare updates.

Kept client_ip_headers Cf-Connecting-Ip X-Forwarded-For (prefer CF's
authoritative end-user IP, fall back to XFF) and documented why.

Validated + fmt-clean and tested end-to-end against the updated caddy-cf
image: starts cleanly, serves HTTP 200 with gzip + security headers.
2026-07-25 05:04:52 +01:00
ahmza 09780d9598 Merge pull request 'Fix blank page: add Cloudflare-fronted HTTP-only Caddyfile' (#4) from fix-caddy-cloudflare into main
Build Hugo / build (push) Successful in 1m34s
Build Hugo / deploy (push) Successful in 40s
Reviewed-on: #4
2026-07-25 03:56:07 +00:00
ahmza c24ed01e0a docs: document Cloudflare-fronted HTTP-only Caddy topology
The live production setup is Cloudflare -> Caddy (HTTP :80) -> SWS, which
differed from the DNS-01 topology the doc previously assumed. Add a
'Deployment topologies' overview and a dedicated Cloudflare-fronted
section pointing at the committed root Caddyfile, covering reverse_proxy,
encode, security headers, and Cloudflare trusted_proxies. Label the
existing Compose/K8s examples as the DNS-01 alternative.
2026-07-25 04:29:33 +01:00
ahmza 7e1728a037 Restrict Caddy trusted_proxies to Cloudflare IP ranges
Replace 'private_ranges' with Cloudflare's official published edge ranges
(15 IPv4 + 7 IPv6 from cloudflare.com/ips-v4 and /ips-v6). This ensures
Cf-Connecting-Ip / X-Forwarded-For are only honoured for requests that
genuinely originate from Cloudflare, preventing client-IP header spoofing
from any other source.

Validated + fmt-clean and tested at runtime against the caddy-cf image:
Caddy starts cleanly with all 22 ranges, serves HTTP 200 with gzip and
security headers.
2026-07-25 04:25:25 +01:00
ahmza 30ef38b143 Add HTTP-only Caddyfile for Cloudflare-fronted deployment
The live Caddy was returning a blank page (NOP, status 0) because its
site block had no responder \u2014 no reverse_proxy to the blog. This adds a
known-good HTTP-only Caddyfile matching the actual topology
(Cloudflare terminates TLS -> Caddy :80 -> SWS blog:80):

- reverse_proxy blog:80  (the missing responder that caused the blank page)
- encode zstd gzip        (fixes: origin was serving everything uncompressed)
- security headers        (fixes: HSTS/X-Frame-Options/etc were absent)
- trusted_proxies + client_ip_headers for correct Cloudflare visitor IPs
- http:// scheme so Caddy does not try to provision TLS (Cloudflare does)

Validated + fmt-clean against the caddy-cf image; tested end-to-end
locally (Caddy -> SWS) returning HTTP 200 with gzip and headers.
2026-07-25 04:19:34 +01:00
ahmza f927bc191c Merge pull request 'Fix codebase audit findings: bugs, security, perf, content' (#3) from fix-audit-findings into main
Build Hugo / build (push) Successful in 1m36s
Build Hugo / deploy (push) Successful in 39s
Reviewed-on: #3
2026-07-25 02:38:13 +00:00
ahmza f402556380 Fix audit findings: bugs, security, perf, content
Bugs:
- setup-wsl: fix broken /img/WSL/* image paths (now bundle-relative;
  images were broken on the published post) + better alt text
- pi-docker-swarm: remove broken missing cluster.webp reference
- archetypes/posts.md: fix malformed {{ }} template so 'hugo new' works
- rename arrstack/arrstack.md -> index.md so it's a proper leaf bundle
- static/admin/config.yml: fix 'htpps' -> 'https' site_url typo
- fix silently-dropped <placeholder> text (unsafe=false dropped them):
  wrap <distro>/<username>/<user>/<version>/<ip address> etc in backticks
  across setup-wsl, securing-ssh, pi-docker-swarm, mc-server-on-docker

Security:
- render-link.html: add rel=noopener noreferrer to external target=_blank
- extend-footer.html: fix rel='me' trapped in class attr (broke Mastodon
  verification); add proper rel/target
- static/admin/index.html: pin Sveltia CMS to 0.172.4 via jsDelivr + SRI

Performance:
- hugo.toml: ignoreFiles for *.aseprite/*.bak (stop shipping source art;
  non-page files 23 -> 13)
- video shortcode: preload=metadata; image shortcode: loading=lazy decoding=async
- sws.toml: drop SPA page-fallback so missing paths return real 404 (404.html)

Content/SEO:
- normalize tag casing (HomeLab/RemoteAccess/SSH/HUGO -> lowercase),
  fix 'prodcutivity' typo, arrstack tags:[""] -> [], add welcome description
- delete stray index.md.bak
2026-07-25 03:36:50 +01:00
ahmza 25b6e0575a Merge pull request 'Optimize webm videos and webp images' (#2) from optimize-media into main
Build Hugo / build (push) Successful in 1m31s
Build Hugo / deploy (push) Successful in 40s
Reviewed-on: #2
2026-07-25 02:22:43 +00:00
ahmza e133b01b00 Optimize webm videos and webp images
Re-encode all webm meme clips as VP9 at CRF 38 (audio stripped; they are
muted autoplay loops). Downscale oversized images: background.webp
3394->1920 wide q80, profile.webp 1920->512 q85 (animation preserved).

Results:
- webm: 3.2MB -> 1.37MB (-56%)
- webp: 1.2MB -> 0.70MB (-42%)
- Docker image: 23.1MB -> 18.7MB (~42% smaller than the original nginx image)

All videos verified valid/playable; profile.webp animation (5 frames)
kept intact.
2026-07-25 03:20:57 +01:00
ahmza b7145e1323 Remove temporary Netbird connectivity debug step
Build Hugo / build (push) Successful in 1m32s
Build Hugo / deploy (push) Successful in 39s
The deploy pipeline works end-to-end now (Gimli deploys successfully),
so the diagnostic step used to trace the WireGuard/TUN issue is no
longer needed. RPIC continue-on-error is kept until the cluster returns.
2026-07-25 03:07:00 +01:00
ahmza 87867b9f30 Point compose.yml at Gitea registry image with :latest tag
Build Hugo / build (push) Successful in 1m27s
Build Hugo / deploy (push) Successful in 40s
CI pushes to tea.ahmza.com/ahmza/blog-ahmza-com, not ghcr.io, and a
bare reference could resolve to the non-runnable :buildcache tag. Pin
the image to tea.ahmza.com/ahmza/blog-ahmza-com:latest so docker compose
pulls the actual runnable image.
2026-07-25 03:02:39 +01:00
ahmza 8a7833e8f5 Make RPIC deploy non-blocking while cluster is out of service
Build Hugo / build (push) Successful in 1m26s
Build Hugo / deploy (push) Failing after 38s
The Raspberry Pi cluster is temporarily out of service. Mark its deploy
step continue-on-error so its failure no longer skips the Gimli deploy
or fails the whole job. Revert once the cluster is back online.
2026-07-25 02:42:15 +01:00
ahmza 4426668518 Use registry build cache instead of gha cache
Build Hugo / build (push) Successful in 3m23s
Build Hugo / deploy (push) Failing after 39s
The GitHub-hosted 'gha' cache backend is unreachable from the
self-hosted Gitea runner (dial tcp 172.18.0.2:44181 i/o timeout), which
failed the build step even though the image pushed successfully. Switch
cache-from/cache-to to a registry-backed cache stored as a :buildcache
tag in the same Gitea registry, which works on self-hosted runners.
2026-07-25 02:21:38 +01:00
ahmza eacf71d676 Debug: check runner WireGuard capability instead of UDP ports
Build Hugo / build (push) Failing after 13m45s
Build Hugo / deploy (push) Skipped
Other Netbird peers connect fine, so the server and 51820/udp are OK --
the hang is specific to the Gitea runner. Replace the UDP port probes
with runner-side checks: /dev/net/tun presence, effective capabilities
(NET_ADMIN), WireGuard tooling/module, and a definitive 'ip tuntap add'
test. A runner container lacking TUN/NET_ADMIN cannot create the
WireGuard interface, which explains 'netbird up' timing out after the
control plane connects.
2026-07-25 01:53:09 +01:00
ahmza b05814980f Update Netbird debug to probe correct data-plane ports
Build Hugo / build (push) Successful in 1m47s
Build Hugo / deploy (push) Failing after 1m15s
The Netbird server multiplexes management + signal + dashboard behind
Traefik on :443 (no separate :10000 signal port). The tunnel data plane
is WireGuard on 51820/udp and STUN on 3478/udp. Update the debug step to
probe those UDP ports plus a general outbound-UDP check (public STUN) to
determine whether the runner network blocks UDP egress -- the likely
cause of 'netbird up' timing out after the control plane connects fine.
2026-07-25 01:46:28 +01:00
ahmza eb67d6361e Add Netbird connectivity debug step to deploy job
Build Hugo / build (push) Successful in 1m44s
Build Hugo / deploy (push) Failing after 1m8s
Temporary diagnostic step before 'Netbird Connect' that checks secret
presence (masked), DNS resolution, HTTPS reachability to the management
API, and the signal server TCP port from the runner. Helps distinguish
DNS/connectivity issues from auth/registration failures behind the
recurring 'netbird up' DeadlineExceeded error. Marked continue-on-error
so it never blocks the build.
2026-07-25 01:39:09 +01:00
ahmza 8062b9d208 Read Netbird management URL from secret
Build Hugo / build (push) Successful in 1m46s
Build Hugo / deploy (push) Failing after 58s
Switch the deploy job's Netbird Connect step to read management-url from
the NETBIRD_URL secret instead of a hardcoded value, so the URL can be
managed in the Gitea UI alongside the setup key.
2026-07-25 01:33:47 +01:00
ahmza aa01def998 Merge pull request 'Optimizations: switch to static-web-server + Caddy reverse proxy docs' (#1) from optimizations into main
Build Hugo / build (push) Successful in 1m51s
Build Hugo / deploy (push) Failing after 57s
Reviewed-on: #1
2026-07-25 00:19:53 +00:00
ahmza f23fe01f3c Fix GitLab/Gitea inconsistency in README
Build Hugo / deploy (push) Canceled after 0s
Build Hugo / build (push) Canceled after 48s
Update the README to reflect the actual infrastructure: CI/CD runs on
Gitea Actions (not GitLab), images live in the Gitea container registry,
and the clone URL points to tea.ahmza.com. Also clarify the Blowfish
theme is a Hugo module, so no submodules are needed.
2026-07-25 01:19:29 +01:00
45 changed files with 376 additions and 457 deletions
+18
View File
@@ -0,0 +1,18 @@
# Send only files required by the Hugo build and SWS runtime stages.
*
!Dockerfile
!go.mod
!go.sum
!sws.toml
!assets/
!assets/**
!config/
!config/**
!content/
!content/**
!layouts/
!layouts/**
!static/
!static/**
+135
View File
@@ -0,0 +1,135 @@
name: Build Hugo
on:
push:
branches: [ "main" ]
env:
REGISTRY: tea.ahmza.com
IMAGE_NAME: ${{ gitea.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.repository_owner }}
password: ${{ secrets.GITEATOKEN }}
# QEMU deliberately omitted.
#
# Hugo runs on BUILDPLATFORM in the Dockerfile, so the static site is
# built natively on the runner. The final amd64/arm64 stages only copy
# the resulting static files into the target-platform SWS image.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64, linux/amd64
push: true
tags: |
${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:latest
${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:${{ gitea.event.head_commit.id }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:buildcache,mode=max
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Pre-flight - verify VPN prerequisites
# NetBird brings up a WireGuard interface, which needs a TUN device and
# NET_ADMIN inside the job container. Without them, `netbird up` hangs
# ~50s and dies with a cryptic "DeadlineExceeded". Check up front so the
# failure is obvious and points at the fix (runner config.yaml).
run: |
echo "::group::NetBird prerequisites"
if [ -e /dev/net/tun ]; then
echo "OK: /dev/net/tun present"
else
echo "::error title=Missing TUN device::/dev/net/tun not found. Add --device=/dev/net/tun to the runner's config.yaml container.options."
exit 1
fi
if command -v capsh >/dev/null 2>&1; then
capsh --print | grep -q 'cap_net_admin' \
&& echo "OK: NET_ADMIN present" \
|| echo "::warning title=NET_ADMIN missing::netbird up will likely time out (DeadlineExceeded) configuring wt0. Add --cap-add=NET_ADMIN to the runner's config.yaml container.options."
else
echo "note: capsh unavailable, skipping NET_ADMIN check"
fi
echo "::endgroup::"
- name: Netbird Connect
id: netbird
timeout-minutes: 3
uses: Alemiz112/netbird-connect@v1
with:
setup-key: ${{ secrets.NETBIRD_SETUP_KEY }}
hostname: 'gitea-actions-blog'
management-url: ${{ secrets.NETBIRD_URL }}
- name: NetBird status
# Surface the actual peer/route state in the log and hard-assert that
# the management connection is up before attempting any deploys.
run: |
echo "::group::netbird status"
sudo netbird status --detail || true
echo "::endgroup::"
sudo netbird status | grep -q 'Management: Connected' \
&& echo "NetBird management connected." \
|| { echo "::error title=NetBird not connected::Management not connected after netbird up."; exit 1; }
- name: Deploy image to talos-prod
uses: actions-hub/kubectl@v1.36.2
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
with:
args: >-
-n blog-ahmza-com
set image deployment/blog
blog=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:${{ gitea.event.head_commit.id }}
- name: Wait for rollout
uses: actions-hub/kubectl@v1.36.2
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
with:
args: >-
-n blog-ahmza-com
rollout status deployment/blog
--timeout=5m
- name: Show deployed pods
uses: actions-hub/kubectl@v1.36.2
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
with:
args: >-
-n blog-ahmza-com
get pods -o wide
- name: NetBird diagnostics & disconnect
# Runs even on failure: captures final state for debugging and tears the
# peer down so stale 'gitea-actions-blog' peers don't pile up.
if: always()
run: |
echo "::group::final netbird status"
sudo netbird status --detail || echo "netbird unavailable"
echo "::endgroup::"
sudo netbird down || true
-79
View File
@@ -1,79 +0,0 @@
name: Build Hugo
on:
push:
branches: [ "main" ]
env:
REGISTRY: tea.ahmza.com
IMAGE_NAME: ${{ gitea.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.repository_owner }}
password: ${{ secrets.GITEATOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64, linux/amd64
push: true
build-args: |
BUILDKIT_CONTEXT_KEEP_GIT_DIR=true
tags: |
${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:latest
${{ env.REGISTRY }}/${{ gitea.repository_owner }}/blog-ahmza-com:${{ gitea.event.head_commit.id }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Netbird Connect
uses: Alemiz112/netbird-connect@v1
with:
setup-key: ${{ secrets.NETBIRD_SETUP_KEY }}
hostname: 'gitea-actions-blog'
management-url: 'https://netbird.ahmza.com'
- name: Deploy to RPIC-Cluster
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.RPIC_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.RPIC_KEY }}
port: ${{ secrets.SSH_PORT }}
script: 'kubectl rollout restart deployment blog -n blog-ahmza-com'
- name: Deploy to Gimli
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.GIMLI_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.GIMLI_KEY }}
port: ${{ secrets.SSH_PORT }}
script: 'docker compose down && docker compose pull && docker compose up -d'
-1
View File
@@ -5,5 +5,4 @@ styles/
node_modules node_modules
public/ public/
resources/ resources/
go.sum
themes/ themes/
+59
View File
@@ -0,0 +1,59 @@
# Caddyfile for the blog behind Cloudflare.
#
# Topology: Cloudflare (terminates TLS) -> Caddy (HTTP :80) -> SWS (blog:80)
#
# Cloudflare handles the public HTTPS cert, so Caddy serves plain HTTP on :80
# and does NOT need the Cloudflare DNS-01 / automatic-HTTPS setup. Set the
# Cloudflare SSL/TLS mode to "Full" so CF->origin is encrypted at the network
# layer (e.g. over your tunnel/Netbird), even though this listener is HTTP.
#
# Copy this file to /etc/caddy/Caddyfile on the caddy_cf host and reload:
# docker exec caddy_cf caddy reload --config /etc/caddy/Caddyfile
# or restart the container.
{
# Trust only Cloudflare's edge IPs so real visitor IPs (Cf-Connecting-Ip /
# X-Forwarded-For) are honoured only when the request actually comes from
# Cloudflare. This prevents header spoofing from any other source.
#
# The `cloudflare` IP source (WeidiDeng/caddy-cloudflare-ip, baked into the
# caddy-cf image) auto-fetches the ranges from cloudflare.com/ips-v4 and
# /ips-v6 and refreshes them periodically, so there's no static list to
# keep in sync.
servers {
trusted_proxies cloudflare {
interval 12h
timeout 15s
}
# Read the real visitor IP from these headers (only honoured for
# requests from trusted_proxies above). Cf-Connecting-Ip is
# Cloudflare's single authoritative end-user IP; X-Forwarded-For is
# the fallback if a request ever arrives without the CF header.
client_ip_headers Cf-Connecting-Ip X-Forwarded-For
}
}
# Bind to the hostname on the HTTP port only. Because the address has an
# explicit ":80" scheme/port, Caddy will NOT try to provision a TLS cert.
http://blog.ahmza.com {
# THE responder: proxy every request to the blog (SWS) container.
# This is what was missing before (requests fell through to a NOP -> blank page).
reverse_proxy blog:80
# Compression happens here (SWS has it disabled on purpose).
encode zstd gzip
# Security headers (single source of truth; SWS has security-headers off).
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
# Drop Caddy's default Server header.
-Server
}
log {
output stdout
format console
}
}
+15 -2
View File
@@ -1,6 +1,19 @@
FROM ghcr.io/hugomods/hugo:0.157.0 AS build # syntax=docker/dockerfile:1
# Pin the build stage to the native build platform so Hugo runs once (never
# under QEMU emulation). Hugo output is static HTML — identical across arches —
# so there's no reason to run it per target arch. The runtime stage below stays
# on TARGETPLATFORM, so multi-arch (amd64 + arm64) runtime images are preserved.
FROM --platform=$BUILDPLATFORM ghcr.io/hugomods/hugo:0.165.0 AS build
WORKDIR /tmp/hugo WORKDIR /tmp/hugo
COPY . .
COPY go.mod go.sum ./
COPY config ./config
RUN hugo mod graph
COPY assets ./assets
COPY content ./content
COPY layouts ./layouts
COPY static ./static
RUN hugo --gc --minify RUN hugo --gc --minify
FROM ghcr.io/static-web-server/static-web-server:2 FROM ghcr.io/static-web-server/static-web-server:2
+8 -7
View File
@@ -4,10 +4,10 @@
This repository contains the source code for my personal blog hosted at [blog.ahmza.com](https://blog.ahmza.com). This repository contains the source code for my personal blog hosted at [blog.ahmza.com](https://blog.ahmza.com).
## Features ## Features
Using GitLab provides several advantages: Using Gitea provides several advantages:
- **Highly Customizable CI/CD Pipelines**: Automated processes for building and deploying the blog. - **Highly Customizable CI/CD Pipelines**: Automated processes for building and deploying the blog via Gitea Actions.
- **GitLab Container Image Repository**: Efficient management and storage of container images. - **Gitea Container Image Repository**: Efficient management and storage of container images.
- **Custom GitLab Runners**: Flexibility in managing the CI/CD environment. - **Custom Gitea Runners**: Flexibility in managing the CI/CD environment.
The CI/CD pipeline is configured to build the blog for both amd64 and arm64 The CI/CD pipeline is configured to build the blog for both amd64 and arm64
architectures, allowing me to host the site on my [Pi cluster](https://blog.ahmza.com/posts/pi-docker-swarm/) architectures, allowing me to host the site on my [Pi cluster](https://blog.ahmza.com/posts/pi-docker-swarm/)
@@ -33,7 +33,7 @@ sacrificing performance.
## Implementation ## Implementation
- **Hugo**: Converts Markdown files into static HTML/CSS/JS files. - **Hugo**: Converts Markdown files into static HTML/CSS/JS files.
- **GitLab CI/CD**: Automates the build process and creates Docker images. - **Gitea Actions**: Automates the build process and creates Docker images.
- **Kubernetes Cluster**: Serves the static files to users. - **Kubernetes Cluster**: Serves the static files to users.
## To-Do List ## To-Do List
@@ -47,7 +47,8 @@ sacrificing performance.
## Notes ## Notes
### Cloning the Repository ### Cloning the Repository
To clone this repository along with its submodules, use the following command: The Blowfish theme is managed as a Hugo module, so no submodules are required.
Clone the repository with:
```bash ```bash
git clone git@gitlab.com:ahmza/blog.git git clone ssh://git@tea.ahmza.com/ahmza/blog-ahmza-com.git
``` ```
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
title: {{ replace .TranslationBaseName "-" " " | title }} title: {{ replace .TranslationBaseName "-" " " | title }}
description: "" description: ""
tags: [""] tags: []
author: "ahmza" author: "ahmza"
date: {{ .Date }} date: {{ .Date }}
draft: true draft: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 KiB

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 73 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
services: services:
blog: blog:
image: ghcr.io/ahmzaa/blog-ahmza-com image: tea.ahmza.com/ahmza/blog-ahmza-com:latest
ports: ports:
- 80:80 - 80:80
restart: unless-stopped restart: unless-stopped
+5
View File
@@ -14,6 +14,11 @@ pagination.pagerSize = 10
buildDrafts = false buildDrafts = false
buildFuture = false buildFuture = false
# Keep source-art and editor backup files out of the built site. Without this,
# files inside page bundles (e.g. feature.aseprite) are copied into public/ as
# page resources and served publicly.
ignoreFiles = ['\.aseprite$', '\.bak$']
enableEmoji = true enableEmoji = true
# googleAnalytics = "G-XXXXXXXXX" # googleAnalytics = "G-XXXXXXXXX"
+2 -2
View File
@@ -1,5 +1,5 @@
languageCode = "en" locale = "en"
languageName = "English" label = "English"
weight = 1 weight = 1
title = "ahmza's ramblings" title = "ahmza's ramblings"
+1 -1
View File
@@ -1,3 +1,3 @@
[[imports]] [[imports]]
disable = false disable = false
path = "github.com/nunocoracao/blowfish/v2" path = "github.com/nunocoracao/blowfish/v3"
@@ -1,7 +1,7 @@
--- ---
title: Arrstack title: Arrstack
description: description:
tags: [""] tags: []
author: "ahmza" author: "ahmza"
date: 2025-05-09T23:44:27+01:00 date: 2025-05-09T23:44:27+01:00
draft: true draft: true
@@ -4,7 +4,7 @@ description: In the modern age we are overwhelmed with stimuli. Learn to cut out
tags: tags:
- lifestyle - lifestyle
- minimalism - minimalism
- prodcutivity - productivity
author: ahmza author: ahmza
date: 2024-01-02 date: 2024-01-02
draft: false draft: false
+2 -2
View File
@@ -185,8 +185,8 @@ cd .server-data
2. **Download a Minecraft server jar file:** 2. **Download a Minecraft server jar file:**
- [Vanilla Minecraft Server](https://www.minecraft.net/en-us/download/server) - [Vanilla Minecraft Server](https://www.minecraft.net/en-us/download/server)
- Right-click the green link that says minecraft_server.<version>.jar and copy the link address. - Right-click the green link that says `minecraft_server.<version>.jar` and copy the link address.
- Download with: curl -o server.jar <link you copied here> - Download with: `curl -o server.jar <link you copied here>`
- [Fabric Mod loader server](https://fabricmc.net/use/server/) - [Fabric Mod loader server](https://fabricmc.net/use/server/)
- Select the Minecraft version, Fabric Loader Version, and the Installer Version of your choice. I tend to go with the latest. - Select the Minecraft version, Fabric Loader Version, and the Installer Version of your choice. I tend to go with the latest.
- Download with: Copy the curl command under CLI download - Download with: Copy the curl command under CLI download
@@ -1,243 +0,0 @@
---
title: Minecraft Server on Docker ⛏ 🐳
date: 2022-05-24
lastmod: 2024-07-20
author: "ahmza"
draft: false
weight:
tags: ["games", "docker"]
description: "Ever wanted to build a Minecraft server in docker !! Start here"
---
## **Introduction**
Minecraft is a great game that becomes even more enjoyable when played with
friends. One of the best ways to do this is by setting up a dedicated server.
This guide will help you do just that!
Setting up and running a Minecraft server does not require a lot of resources,
but based on my experience, allocating more resources significantly enhances
performance. Especially when you start to add mods and increase player count.
## **Prerequisites**
- Docker
- Docker Compose
See [Pi Cluster](/posts/pi-docker-swarm/)
for more information about my docker setup.
## **Requirements**
_Requirements Gathered from [here](https://minecraft.fandom.com/wiki/Server/Requirements/Dedicated#Unix_.28Linux.2C_BSD.2C_macOS.29)_
### **Minimum (1-5 Players)**
- Intel Pentium 4 or AMD Athlon based CPU
- 512 MB RAM
- 2 GB Storage space
### **Recommended (10-15 Players)**
- Intel Core or AMD K8 based CPU
- 3 GB RAM
- 16 GB Storage Space
### **Optimal (20+ Players)**
- Intel i5/i7 or AMD Ryzen 5/7 Based CPU
- 6 GB RAM
- 35 GB Storage Space
**My opinion**:
I personally went with the **optimal** settings as I wanted to run mods as well
as have the capacity for ~10 players. This gives the server breathing room.
However if you will be runing no mods or a few light weight mods and only a few
players then there really is no need to go higher than 3-4 GB.
## **Building the Container**
Lets get started on building the Minecraft server
1. **Create a directory for the Minecraft server files**:
```bash
# Make the folder
mkdir minecraft-server
# Enter the folder
cd minecraft-server
```
2. **Create an environment file for the Docker Container**:
- The _.env_ file allows us to specify variables used in the build process.
```bash
# Create a .env file
touch .env
# Open in a text editor
vim .env
# Enter the following variables
CONTAINER_NAME=minecraft-server
SERVER_MEM=4096 #change this to your desired value (in MB)
SERVER_JAR=<downloaded-server-file>.jar # Minecraft server jar file. Change this later
```
3. **Create the _docker-compose.yml_ file**:
```bash
# Create the file
touch docker-compose.yml`
# Open in a text editor
vim docker-compose.yml
```
- _docker-compose.yml_ is the spec of our Minecraft server.
- Build a docker image using context . and pass some ARGS.
- Attach volume .server-data, where Minecraft's files will be stored.
- Open network ports to access to the Docker container
- Give the docker container a name
- The container will always restart unless we tell it to stop.
```yaml
services:
minecraft-server:
build:
context: .
args:
SERVER_MEM: ${SERVER_MEM}
SERVER_JAR: ${SERVER_JAR}
volumes:
- .server-data:/server
ports:
- 25565:25565 # Game
- 25575:25575 # RCON
container_name: ${CONTAINER_NAME}
restart: unless-stopped
volumes:
server-data:
```
4. **Create the _Dockerfile_ file**:
- requested in docker-compose.yml build stage as context .
- The _Dockerfile_ is the spec for software running inside the container
```bash
# Create the file
touch Dockerfile
# Open the file in a text editor
vim Dockerfile
```
- We use the [eclipse-temurin:21](https://hub.docker.com/_/eclipse-temurin) image to provide the java JRE
- Use the required java version for the version of Minecraft you are running
- Collect the ARGs from the .env file
- Set the environment variables
- Set the working directory to /server which on our local system is .server-data
- Run the minecraft server jar with the ENV variables
```Dockerfile
FROM eclipse-temurin:21
ARG SERVER_MEM
ARG SERVER_JAR
ENV SERVER_MEM=${SERVER_MEM}
ENV SERVER_JAR=${SERVER_JAR}
WORKDIR /server
CMD echo "Minecraft Server is starting\n" && java -Xms128M -Xmx${SERVER_MEM}M -jar ${SERVER_JAR} nogui
```
## **Setup the Server files**
We’re almost there, just a few steps to go.
When you try to start the container, you might encounter the following error:
```bash
Minecraft Server is starting
Error: Unable to access jarfile server.jar
```
This happens because we haven't downloaded the server jar file yet. Let's do
that now
1. **Create a folder in the minecraft-server folder called .server-data**:
```bash
mkdir .server-data
cd .server-data
```
2. **Download a Minecraft server jar file**:
- [Vanilla Minecraft Server](https://www.minecraft.net/en-us/download/server)
- Right click the green link that says minecraft_server.<version>.jar and copy the link address
- Download with: `curl -o server.jar <link you copied here>`
- [Fabric Mod loader server](https://fabricmc.net/use/server/)
- Select the Minecraft version, Fabric Loader Version and the Installer Version of your choice. I tend to go with the latest.
- Download with: `Copy the curl command under CLI download`
3. **Go back to the root minecraft-server folder**
```bash
cd ..
```
4. **Edit SERVER_JAR in .env with the name of the .jar you just downloaded**:
```bash
# Open in a text editor
vim .env
# Enter the following variables
CONTAINER_NAME=minecraft-server
SERVER_MEM=4096
SERVER_JAR=<downloaded-server-file>.jar # Change this to the name of the .jar you downloaded
```
5. **Run the container to generate necessary files like 'eula.txt'**:
```bash
docker compose up
```
you will see something like this:
```bash
minecraft-server | Starting net.minecraft.server.Main
minecraft-server | [18:05:50] [ServerMain/INFO]: You need to agree to the EULA in order to run the server. Go to eula.txt for more info.
minecraft-server exited with code 0
```
6. **Accept the EULA**:
```bash
# edit eula.txt in a text editor
vim .server-data/eula.txt
# change `eula=false` to `eula=true`
eula=true
```
7. **Make any additional changes you want**:
- Notable files:
- **server.properties** - Configure server name, game mode, world type etc…
- **ops.json** - server operators (can be set in-game)
- **whitelist.json** - players allowed to join the server (can be set in-game)
## Run the Minecraft Server
1. **Run the container in detached mode**:
```bash
# The -d flag allows the container to run in detached mode, i.e., in the background
docker compose up -d
```
```bash
# monitor the logs as the server starts. Use CTRL+C to exit
docker logs minecraft-server --follow
# If you see the following lines, your server is up and running:
[18:17:01] [Server thread/INFO]: Time elapsed: 25529 ms
[18:17:01] [Server thread/INFO]: Done (34.571s)! For help, type "help"
```
## **Conclusion**
Congratulations! You've successfully set up a Minecraft server. Now it's time
to play!
There are many exciting possibilities from here, such as installing mods or
creating scripts to manage the server. Enjoy your Minecraft adventures!
+1 -3
View File
@@ -39,8 +39,6 @@ a **bare metal server that sucks a lot of power to a low power solution.**
- 4 x PoE hats - 4 x PoE hats
- 1 x 5 port PoE Switch - 1 x 5 port PoE Switch
![Cluster(fix this image)](/img/pi-docker-swarm/cluster.webp)
### **Point Of This post** ### **Point Of This post**
After following this post you will have setup _Docker swarm_ on a cluster of Pis After following this post you will have setup _Docker swarm_ on a cluster of Pis
@@ -329,7 +327,7 @@ vim hosts
``` ```
- **_[piCluster]_** - specifies a group of hosts - **_[piCluster]_** - specifies a group of hosts
- **_<ip address>_** - change these to the Pis IP addresses - **_`<ip address>`_** - change these to the Pis IP addresses
**create the Pi Intercommunication keys** **create the Pi Intercommunication keys**
+4 -4
View File
@@ -2,9 +2,9 @@
title: Securing SSH 🤖 title: Securing SSH 🤖
description: Ever wanted to make your SSH connections more secure. Read this description: Ever wanted to make your SSH connections more secure. Read this
tags: tags:
- HomeLab - homelab
- RemoteAccess - remoteaccess
- SSH - ssh
author: ahmza author: ahmza
date: 2021-12-07 date: 2021-12-07
draft: false draft: false
@@ -146,7 +146,7 @@ fine 😊
- Max Auth Tries 5 - Max Auth Tries 5
- PasswordAuthentication no - PasswordAuthentication no
- PermitEmptyPasswords no - PermitEmptyPasswords no
- AllowUsers <user> - AllowUsers `<user>`
- ClientAliveInterval 180 - ClientAliveInterval 180
``` bash ``` bash
+1 -1
View File
@@ -3,7 +3,7 @@ title: Setup a HUGO Site
description: Always wanted to setup a static website. Look no further !! In this post we go over the basics of setting up a HUGO site description: Always wanted to setup a static website. Look no further !! In this post we go over the basics of setting up a HUGO site
tags: tags:
- blog - blog
- HUGO - hugo
- web - web
- development - development
author: ahmza author: ahmza
+5 -5
View File
@@ -36,7 +36,7 @@ wsl --install
Go to the Microsoft store and search for a Linux distribution. I prefer Go to the Microsoft store and search for a Linux distribution. I prefer
to use the "Ubuntu" distribution. to use the "Ubuntu" distribution.
![Microsoft Store](/img/WSL/ubuntu.webp) ![Ubuntu in the Microsoft Store](ubuntu.webp)
Once installed, go ahead and run the distribution, after it finishes setting Once installed, go ahead and run the distribution, after it finishes setting
itself up it will ask you for a username and password. These credentials are itself up it will ask you for a username and password. These credentials are
@@ -51,7 +51,7 @@ I use the windows terminal app to run the Ubuntu distribution, it makes
customising the environment a lot easier. To download it head over to the customising the environment a lot easier. To download it head over to the
Microsoft store and search for Windows Terminal and download it. Microsoft store and search for Windows Terminal and download it.
![Microsoft Store](/img/WSL/winterm.webp) ![Windows Terminal in the Microsoft Store](winterm.webp)
When you login to WSL it will not default to your home directory. To fix that go When you login to WSL it will not default to your home directory. To fix that go
to the Windows Terminal Settings look for Ubuntu or your distributions name and to the Windows Terminal Settings look for Ubuntu or your distributions name and
@@ -61,11 +61,11 @@ change the Starting directory to
\\wsl$\<distro>\home\<username> \\wsl$\<distro>\home\<username>
``` ```
> Be sure to change "<distro>" to your distributions name > Be sure to change `<distro>` to your distributions name
> Be sure to change "<username>" to your username > Be sure to change `<username>` to your username
![winterm-settings](/img/WSL/winterm-settings.webp) ![Windows Terminal settings](winterm-settings.webp)
## **Conclusion** ## **Conclusion**
+1
View File
@@ -1,5 +1,6 @@
--- ---
title: Welcome title: Welcome
description: The mandatory hello world post — a quick intro to who I am and what this blog is about.
tags: tags:
- blog - blog
- misc - misc
+98 -8
View File
@@ -1,15 +1,44 @@
# Caddy as a Reverse Proxy # Caddy as a Reverse Proxy
This document describes how to run [Caddy](https://caddyserver.com/) as a This document describes how to run [Caddy](https://caddyserver.com/) as a
reverse proxy in front of the blog. It covers both deployment targets used by reverse proxy in front of the blog.
this project: the Kubernetes (Raspberry Pi) cluster and the Docker Compose host
(Gimli).
It uses the custom [`caddy-cf`](https://github.com/ahmzaa/caddy-cf) image It uses the custom [`caddy-cf`](https://github.com/ahmzaa/caddy-cf) image
(`ghcr.io/ahmzaa/caddy-cf:latest`) — a Caddy build with the **Cloudflare DNS (`ghcr.io/ahmzaa/caddy-cf:latest`) — a Caddy build with the **Cloudflare DNS
module** baked in. The stock Caddy image cannot solve the ACME **DNS-01** module** baked in.
challenge for Cloudflare; this custom build can, which is what lets us obtain
certificates without exposing ports `80`/`443` to the public internet. ## Deployment topologies
There are two supported ways to run Caddy in front of the blog. **Pick one.**
### 1. Cloudflare in front (current production) — HTTP only
```
Cloudflare (terminates TLS) -> Caddy (HTTP :80) -> SWS (blog:80)
```
Cloudflare proxies the domain and provides the public HTTPS certificate, so
Caddy only needs to serve plain **HTTP on :80** and reverse-proxy to the blog.
Caddy does **not** provision a TLS cert in this mode. This is the live setup and
its config is the committed [`Caddyfile`](../Caddyfile) at the repo root —
see [Cloudflare-fronted setup](#cloudflare-fronted-setup-http-only) below.
Set the Cloudflare **SSL/TLS mode to "Full"** so the Cloudflare→origin hop is
still encrypted at the network layer (e.g. over Netbird), even though Caddy's
listener is HTTP.
### 2. Caddy terminates TLS via Cloudflare DNS-01
```
Client -> Caddy (HTTPS :443, ACME DNS-01) -> SWS (blog:80)
```
Caddy obtains/renews the certificate itself using the ACME **DNS-01** challenge
through the Cloudflare API — no inbound `80`/`443` from the public internet is
required, which suits a Netbird-only setup with no Cloudflare proxy in front.
The stock Caddy image cannot do this; the `caddy-cf` build bundles the
Cloudflare DNS provider plugin. See the Docker Compose / Kubernetes examples
later in this document.
## Why a reverse proxy? ## Why a reverse proxy?
@@ -21,7 +50,7 @@ the reverse proxy so that each layer owns exactly one job:
| Concern | Owned by | Notes | | Concern | Owned by | Notes |
| -------------------- | -------- | ------------------------------------------------- | | -------------------- | -------- | ------------------------------------------------- |
| TLS / HTTPS certs | Caddy | Automatic certs via Cloudflare **DNS-01** challenge | | TLS / HTTPS certs | Cloudflare *or* Caddy | Cloudflare in topology 1; Caddy (DNS-01) in topology 2 |
| Compression (gzip/zstd) | Caddy | `encode` directive; SWS compression is off | | Compression (gzip/zstd) | Caddy | `encode` directive; SWS compression is off |
| Security headers | Caddy | Single source of truth, avoids duplicate headers | | Security headers | Caddy | Single source of truth, avoids duplicate headers |
| Serving static files | SWS | Reads from `/public`, sets `Cache-Control`/`ETag` | | Serving static files | SWS | Reads from `/public`, sets `Cache-Control`/`ETag` |
@@ -50,11 +79,72 @@ Whatever the deployment, the SWS origin exposes:
- **Health endpoint:** `GET /health` → `200 OK` - **Health endpoint:** `GET /health` → `200 OK`
- **Cache headers:** already set by SWS (Caddy should pass them through, not override) - **Cache headers:** already set by SWS (Caddy should pass them through, not override)
Caddy only needs to terminate TLS (via Cloudflare DNS-01), add compression + Caddy only needs to handle TLS (or let Cloudflare do it), add compression +
security headers, and forward requests to that origin. security headers, and forward requests to that origin.
--- ---
## Cloudflare-fronted setup (HTTP only)
**This is the current production topology.** The canonical config is the
committed [`Caddyfile`](../Caddyfile) at the repo root; copy it to the
`caddy_cf` host and reload. Highlights:
- **`http://blog.ahmza.com`** — the explicit `http://` scheme binds Caddy to
`:80` only and stops it from trying to provision a TLS certificate (Cloudflare
already serves HTTPS to visitors).
- **`reverse_proxy blog:80`** — proxies to the blog (SWS) container by its
compose service name. Caddy and the blog container must share a Docker
network. Omitting this responder makes Caddy return an empty `NOP` response —
i.e. a **blank page**.
- **`encode zstd gzip`** — compression happens here (SWS has it disabled).
- **`header { ... }`** — security headers (SWS has them disabled).
- **`trusted_proxies cloudflare { ... }`** + **`client_ip_headers
Cf-Connecting-Ip X-Forwarded-For`** — so the real visitor IP is used for
logging/matchers, but only when the request genuinely comes from Cloudflare
(prevents header spoofing from any other source). The `cloudflare` IP source
([WeidiDeng/caddy-cloudflare-ip](https://github.com/WeidiDeng/caddy-cloudflare-ip),
baked into the `caddy-cf` image) auto-fetches the ranges from
<https://www.cloudflare.com/ips-v4/> and <https://www.cloudflare.com/ips-v6/>
and refreshes them on the configured `interval`, so there is no static list to
maintain:
```caddy
trusted_proxies cloudflare {
interval 12h
timeout 15s
}
```
`client_ip_headers` lists which header holds the real visitor IP:
`Cf-Connecting-Ip` is Cloudflare's single authoritative end-user IP, with
`X-Forwarded-For` as the fallback. It is only trusted for requests coming from
the `trusted_proxies` above.
> If you ever run without the `caddy-cloudflare-ip` plugin, you can instead
> pin the static ranges with `trusted_proxies static <ip/cidr ...>` copied
> from the Cloudflare IP pages, but then you must keep them in sync manually.
Deploy and reload:
```bash
# Put the Caddyfile in place on the caddy_cf host (e.g. into the ./conf mount),
# then reload without downtime:
docker exec caddy_cf caddy reload --config /etc/caddy/Caddyfile
# Verify from the host (expect HTTP 200, Content-Encoding: gzip, X-Frame-Options):
curl -sI -H "Accept-Encoding: gzip" http://localhost/ | grep -iE 'HTTP|content-encoding|x-frame'
```
> **Cloudflare SSL/TLS mode:** set it to **Full** so the Cloudflare→origin hop
> is encrypted at the network layer (e.g. over Netbird), even though Caddy's
> listener is plain HTTP.
---
The remaining sections describe **topology 2** (Caddy terminates TLS itself via
the Cloudflare DNS-01 challenge) for setups without a Cloudflare proxy in front.
## Docker Compose (Gimli) ## Docker Compose (Gimli)
Run Caddy and the blog container on the same Docker network. Caddy reaches the Run Caddy and the blog container on the same Docker network. Caddy reaches the
+1 -1
View File
@@ -2,4 +2,4 @@ module github.com/ahmzaa/blog-ahmza-com
go 1.24.3 go 1.24.3
require github.com/nunocoracao/blowfish/v2 v2.86.0 // indirect require github.com/nunocoracao/blowfish/v3 v3.4.0
+2
View File
@@ -0,0 +1,2 @@
github.com/nunocoracao/blowfish/v3 v3.4.0 h1:Fv7mS97lagllTGzHA2V1H6+N5fuQd3DuWmOQlFFV4Aw=
github.com/nunocoracao/blowfish/v3 v3.4.0/go.mod h1:8lJwR95VgcyWDhjKvUJMdOQXogrmhYSxkWh4ogSJfCg=
+1 -1
View File
@@ -1 +1 @@
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank"{{ end }}>{{ .Text | safeHTML }}</a> <a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener noreferrer"{{ end }}>{{ .Text | safeHTML }}</a>
+5 -3
View File
@@ -1,6 +1,8 @@
<br> <br>
<div class="flex items-center gap-x-5"> <div class="flex items-center gap-x-5">
<a class="text-sm text-neutral-500 dark:text-neutral-400 rel='me'" href="https://mastodon.social/@ahmza">Mastodon</a> <a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://mastodon.social/@ahmza">Mastodon</a>
<a class="text-sm text-neutral-500 dark:text-neutral-400 rel='me'" href="https://github.com/ahmzaa">Github</a> <a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://www.linkedin.com/in/ahmza/">Linkedin</a>
<a class="text-sm text-neutral-500 dark:text-neutral-400 rel='me'" href="https://www.linkedin.com/in/ahmza/">Linkedin</a> <a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://github.com/ahmzaa">Github</a>
<a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://gitlab.com/ahmza">Gitlab</a>
<a class="text-sm text-neutral-500 dark:text-neutral-400" rel="me noopener noreferrer" target="_blank" href="https://tea.ahmza.com/ahmza">Gitea</a>
</div> </div>
+1 -1
View File
@@ -1 +1 @@
<img src="{{ .Get "url"}}" alt="{{ .Get "alt"}}" style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;"> <img src="{{ .Get "url"}}" alt="{{ .Get "alt"}}" loading="lazy" decoding="async" style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;">
+1 -1
View File
@@ -1,4 +1,4 @@
<video autoplay loop muted playsinline aria-label='{{ .Get "label"}}' style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;"> <video autoplay loop muted playsinline preload="metadata" aria-label='{{ .Get "label"}}' style="display:block;margin-left:auto;margin-right:auto;width:50%;height:auto;padding:10px;">
{{ with .Get "mp4" }}<source src="{{ . }}" type="video/mp4">{{ end }} {{ with .Get "mp4" }}<source src="{{ . }}" type="video/mp4">{{ end }}
{{ with .Get "webm" }}<source src="{{ . }}" type="video/webm">{{ end }} {{ with .Get "webm" }}<source src="{{ . }}" type="video/webm">{{ end }}
</video> </video>
-69
View File
@@ -1,69 +0,0 @@
## Commit messages
backend:
name: github
repo: ahmzaa/blog-ahmza-com
branch: main
squash_merges: true
base_url: https://sveltia-cms-auth.hamzaahmed12328.workers.dev
use_graphql: true
commit_messages:
create: Create {{collection}} "{{slug}}"
update: Update {{collection}} "{{slug}}"
delete: Delete {{collection}} "{{slug}}"
uploadMedia: Upload "{{path}}"
deleteMedia: Delete "{{path}}"
openAuthoring: '{{message}}'
## Publish mode
publish_mode: editorial_workflow # Allowed Values: simple, editorial_workflow
## Media and Public folders
#Media
media_folder: "assets/uploaded/img"
public_folder: "/uploaded/img"
## SiteTitle
site_url: htpps://blog.ahmza.com
display_url: blog.ahmza.com
logo_url: https://blog.ahmza.com/logo.webp
## Collections
collections:
- name: posts
identifier_field: name
label: posts
label_singular: post
icon: article
description: sites main content, blog posts
folder: "content/posts"
path: "{{slug}}/index"
media_folder: ""
public_folder: ""
create: true
slug: '{{slug}}'
fields:
- {label: "Title", name: "title", widget: "string"}
- {label: "Featured Image", name: "featureimage", widget: "image"}
- {label: "Featured Image Caption", name: "featureimagecaption", widget: "string"}
- {label: "Publish Date", name: "date", widget: "datetime", default: "{{now}}", date_format: "YYYY-MM-DD"}
- {label: "Last Modified", name: "lastmod", widget: "datetime", default: "{{now}}", date_format: "YYYY-MM-DD"}
- {label: "Author", name: "author", widget: "string", default: "ahmza"}
- {label: "Draft", name: "draft", widget: "boolean", default: "false"}
- {label: "Tags", name: "tags", widget: "list"}
- {label: "Description", name: "description", widget: "string"}
- {label: "Body", name: "body", widget: "markdown"}
extension: md
sortable_fields: ['title', 'date', 'lastmod', 'tags']
view_filters:
- label: "ahmza's posts"
field: author
pattern: 'ahmza'
- label: Drafts
field: draft
pattern: true
view_groups:
- label: Year
field: date
pattern: \d{4}
- label: Drafts
field: draft
-14
View File
@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>Content Manager</title>
<!-- Include the script that enables Netlify Identity on this page. -->
</head>
<body>
<!-- Include the script that builds the page and powers Decap CMS -->
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
</body>
</html>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -3
View File
@@ -31,9 +31,10 @@ cache-control-headers = true
#### Routing #### Routing
# SPA-style fallback: serve index.html when a path is not found on disk, # No SPA-style page fallback: this is a static Hugo site, not a single-page app.
# mirroring the previous nginx `try_files $uri $uri/ /index.html`. # Missing paths should return Hugo's generated 404.html with a real 404 status
page-fallback = "/public/index.html" # (SWS serves /public/404.html automatically) rather than masking broken links
# with a 200 homepage response, which harms SEO (soft 404s).
#### Security #### Security