No description
Find a file
Ole-Morten Duesund f88b8537e3
All checks were successful
Deploy Blog / build-and-deploy (push) Successful in 32s
test: verify pipeline
2025-10-25 19:21:15 +02:00
.forgejo/workflows Replace GitHub action with direct Hugo installation 2025-10-25 18:10:40 +02:00
content test: verify pipeline 2025-10-25 19:21:15 +02:00
deployment Add comprehensive deployment guide for forgejo-pages 2025-10-25 19:03:11 +02:00
.gitignore Initial blog setup: "So You Don't Have To" 2025-10-25 17:05:35 +02:00
config.toml Initial blog setup: "So You Don't Have To" 2025-10-25 17:05:35 +02:00
go.mod Fix go.mod - let Hugo resolve PaperMod version 2025-10-25 18:11:53 +02:00
README.md Update to latest forgejo-runner with GPG signature verification 2025-10-25 17:25:02 +02:00

So You Don't Have To

War stories, lessons learned, and tales from the trenches of software engineering.

Live at: blog.naiv.no

About This Blog

We document the problems we've encountered, the solutions we've implemented, and the wisdom gained along the way—all so you can avoid making the same mistakes.

Topics include:

  • DevOps and infrastructure
  • Container orchestration
  • Legacy system maintenance
  • Debugging adventures
  • Production incident post-mortems
  • Pragmatic solutions over perfect ones

Stack

  • Generator: Hugo with PaperMod theme (via Hugo Modules)
  • Repository: Self-hosted Forgejo at kode.naiv.no
  • CI/CD: Forgejo Actions
  • Hosting: forgejo-pages in Podman container
  • Reverse Proxy: Caddy with automatic HTTPS
  • Domain: blog.naiv.no

Quick Start

Writing a New Post

# Create new post
hugo new content/posts/my-post-title.md

# Edit the file
vim content/posts/my-post-title.md

# Preview locally
hugo server -D

# When ready, set draft: false and push
git add content/posts/my-post-title.md
git commit -m "Add post: My Post Title"
git push

The blog automatically builds and deploys via Forgejo Actions.

Post Frontmatter

---
title: "Your Post Title"
date: 2025-01-25
draft: false
tags: ["tag1", "tag2"]
categories: ["category"]
summary: "Brief description for listings and SEO"
ShowToc: true
---

Repository Structure

.
├── config.toml                    # Hugo configuration
├── go.mod                         # Hugo Modules config
├── content/
│   ├── about.md
│   └── posts/                     # Blog posts here
│       ├── container-init-lessons.md
│       └── deploying-this-blog.md
├── static/                        # Images, files
├── .forgejo/
│   └── workflows/
│       └── deploy.yml             # CI/CD pipeline
└── deployment/                    # Deployment configs
    ├── RUNNER_SETUP.md            # Forgejo runner installation
    ├── podman/
    │   ├── podman-compose.yml
    │   ├── forgejo-pages.env
    │   └── forgejo-pages.service
    └── caddy/
        ├── Caddyfile.snippet      # Reverse proxy (forgejo-pages)
        ├── Caddyfile-direct.snippet # Direct serving
        ├── sync-blog.sh           # Git sync script
        ├── blog-sync.service      # Systemd service
        └── blog-sync.timer        # Systemd timer

Deployment

Prerequisites

⚠️ Forgejo Actions Runner Required

This blog uses Forgejo Actions for CI/CD. You need to set up a runner first:

📖 See: deployment/RUNNER_SETUP.md for complete runner installation guide.

Quick runner setup:

# Download latest forgejo-runner with GPG verification
cd ~/forgejo-runner
export RUNNER_VERSION=$(curl -s -X 'GET' https://data.forgejo.org/api/v1/repos/forgejo/runner/releases/latest | jq .name -r | cut -c 2-)
wget -O forgejo-runner https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-amd64
wget -O forgejo-runner.asc https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-amd64.asc
gpg --keyserver hkps://keys.openpgp.org --recv EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
gpg --verify forgejo-runner.asc forgejo-runner
chmod +x forgejo-runner

# Register with Forgejo (get token from Forgejo UI)
./forgejo-runner register --instance https://kode.naiv.no --token YOUR_TOKEN

# Install systemd service (see RUNNER_SETUP.md for service file)
systemctl --user enable --now forgejo-runner.service

Full Deployment Guide

See the blog post: How This Blog Is Deployed

This blog can be deployed two ways:

  1. forgejo-pages container (webhook-based, instant updates)
  2. Caddy serving directly (simpler, timer-based updates)

Both are documented in the blog post. Quick references below.

Quick Deployment Reference

1. Set up forgejo-pages container:

mkdir -p ~/forgejo-pages
cd ~/forgejo-pages

# Copy configs from this repo
cp deployment/podman/* .

# Edit forgejo-pages.env with your API token

# Create data directory
mkdir -p data

# Start with podman-compose
podman-compose up -d

# Set up systemd service
mkdir -p ~/.config/systemd/user
cp forgejo-pages.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now forgejo-pages.service
loginctl enable-linger $USER

2. Configure Caddy:

# Add to your Caddyfile
cat deployment/caddy/Caddyfile.snippet | sudo tee -a /etc/caddy/Caddyfile

# Or use import pattern
sudo cp deployment/caddy/Caddyfile.snippet /etc/caddy/sites/blog.naiv.no

# Reload
sudo systemctl reload caddy

3. Set up DNS:

Add A record: blog.naiv.no → your server IP

4. Push to Forgejo:

git push origin main

Forgejo Actions builds the site and deploys to the static-pages branch. The forgejo-pages container serves it automatically.


Alternative: Caddy Direct (Simpler Setup)

1. Clone static-pages branch:

mkdir -p ~/www/blog.naiv.no
git clone --single-branch --branch static-pages \
  https://kode.naiv.no/olemd/so-you-dont-have-to-blog.git \
  ~/www/blog.naiv.no

2. Set up auto-sync:

# Copy sync script
mkdir -p ~/bin
cp deployment/caddy/sync-blog.sh ~/bin/
chmod +x ~/bin/sync-blog.sh

# Install systemd service and timer
mkdir -p ~/.config/systemd/user
cp deployment/caddy/blog-sync.service ~/.config/systemd/user/
cp deployment/caddy/blog-sync.timer ~/.config/systemd/user/

# Enable timer
systemctl --user daemon-reload
systemctl --user enable --now blog-sync.timer

3. Configure Caddy:

# Use direct serving config
sudo cp deployment/caddy/Caddyfile-direct.snippet /etc/caddy/sites/blog.naiv.no

# Edit to replace 'youruser' with your username
sudo vim /etc/caddy/sites/blog.naiv.no

# Reload
sudo systemctl reload caddy

4. Push to Forgejo:

git push origin main

Blog updates every 5 minutes automatically via systemd timer.

Local Development

# Install Hugo (if not already installed)
# https://gohugo.io/installation/

# Clone repository
git clone https://kode.naiv.no/olemd/so-you-dont-have-to-blog.git
cd so-you-dont-have-to-blog

# Download theme via Hugo Modules
hugo mod get

# Run development server
hugo server -D

# Visit http://localhost:1313

How It Works

  1. Write: Create Markdown files in content/posts/
  2. Push: git push to main branch
  3. Build: Forgejo Actions workflow triggers
    • Installs Hugo and Go
    • Downloads PaperMod theme via Hugo Modules
    • Builds static site
    • Pushes to static-pages branch
  4. Serve: forgejo-pages container detects update
    • Pulls static-pages branch
    • Serves files on localhost:8888
  5. Proxy: Caddy reverse proxies blog.naiv.no → localhost:8888
    • Automatic HTTPS via Let's Encrypt

Troubleshooting

Check Build Status

Visit: https://kode.naiv.no/olemd/so-you-dont-have-to-blog/actions

Check forgejo-pages

systemctl --user status forgejo-pages.service
journalctl --user -u forgejo-pages.service -f

Check Caddy

sudo systemctl status caddy
sudo journalctl -u caddy -f

Force Rebuild

git commit --allow-empty -m "Rebuild"
git push

Contributing

Found a typo? Have a suggestion?

  1. Fork this repository
  2. Make your changes
  3. Submit a pull request

Or open an issue: https://kode.naiv.no/olemd/so-you-dont-have-to-blog/issues

License

Content is licensed under CC BY-SA 4.0.

Code examples and configurations are licensed under MIT.

Contact


"The best code is the code that keeps working, even if it's not perfect."