2026-04-13 03:51:22 -05:00
2026-03-07 21:16:51 -06:00
2026-03-07 21:16:51 -06:00
2026-03-07 21:16:51 -06:00
2026-03-07 21:16:51 -06:00
2026-03-14 07:54:16 -05:00
2026-03-27 07:57:13 -05:00

ridgwaysystems.org

Personal homelab site. Landing page, build log / blog, infrastructure overview, service status page, and an admin panel for managing content.

Why Go?

  • Compiles to a single static binary. Easy to deploy on FreeBSD with no runtime dependencies.
  • Standard library handles HTTP, HTML templates, and file serving without pulling in a framework.
  • Fast startup, low memory footprint. Fits comfortably behind relayd on the SuperMicro 1U.
  • The tool I know best for this kind of thing.

A static site generator (Hugo, etc.) was considered but ruled out because the admin panel requirement means a server-side process is needed anyway. Combining the site server and admin into one binary keeps the deployment simple.

Project Structure

.
├── cmd/server/main.go          # Entry point
├── internal/
│   ├── blog/                   # Post parsing and store (markdown + YAML frontmatter)
│   ├── feed/                   # RSS 2.0 feed generation
│   ├── handler/                # HTTP handlers (public, admin, auth)
│   └── status/                 # Service status JSON loading/saving
├── templates/
│   ├── base.html               # Shared layout
│   ├── index.html              # Landing page
│   ├── blog.html               # Post list
│   ├── post.html               # Single post
│   ├── infrastructure.html     # Hardware and network diagram
│   ├── status.html             # Service status page
│   ├── about.html              # About / contact
│   └── admin/                  # Admin panel templates
├── static/css/style.css        # Stylesheet (dark mode, responsive)
├── content/posts/              # Blog posts as Markdown files with YAML frontmatter
├── data/status.json            # Service status data (updated by cron or manually)
├── .env.example                # Configuration reference
└── README.md

Building

go build -o website ./cmd/server

Requires Go 1.22+. All dependencies are managed with Go modules.

To download dependencies:

go mod tidy

Running

# Development — reloads templates on each request
DEV=1 PORT=8080 go run ./cmd/server

# With a .env file (requires a shell that sources it, or use envdir/dotenv)
export $(cat .env | grep -v '#' | xargs)
./website

Configuration

All configuration is via environment variables. See .env.example for the full list.

Variable Default Description
PORT 8080 Listen port
SITE_URL https://ridgwaysystems.org Public URL for RSS feed links
CONTENT_DIR content Path to content directory
DATA_DIR data Path to data directory (status.json)
DEV 0 Set 1 for template hot-reloading
ADMIN_PASSWORD_HASH bcrypt hash of admin password
SESSION_SECRET HMAC signing key for session cookies

Generating an admin password hash

# Using htpasswd (from apache2-utils / httpd-tools)
htpasswd -bnBC 12 "" yourpassword | tr -d ':\n'

# Or write a small Go snippet:
# import "golang.org/x/crypto/bcrypt"
# hash, _ := bcrypt.GenerateFromPassword([]byte("yourpassword"), 12)
# fmt.Println(string(hash))

Blog Post Format

Posts are Markdown files in content/posts/. Filenames become slugs unless overridden in frontmatter.

---
title: "Post Title"
date: 2025-01-15
tags: [openbsd, homelab]
slug: optional-custom-slug
description: "Short description for meta tags and post listings."
draft: false
---

Post content here. Standard Markdown with GFM extensions (tables, strikethrough, etc.)
Syntax highlighting for fenced code blocks is automatic.

Status Page JSON Schema

data/status.json is read on each request to the /status page. A cron job or monitoring script can write to this file to update service statuses.

{
  "last_checked": "2025-02-10T12:00:00Z",
  "services": [
    {
      "name": "Web (httpd)",
      "description": "ridgwaysystems.org",
      "url": "https://ridgwaysystems.org",
      "status": "up",
      "note": "Optional note shown on status page"
    }
  ]
}

Valid status values: up, degraded, down, unknown.

Example cron update script

#!/bin/sh
# /usr/local/bin/check-services.sh
# Run every 5 minutes via cron. Updates data/status.json.

SITE_DIR=/var/www/ridgwaysystems
STATUS_FILE=$SITE_DIR/data/status.json
TMPFILE=$(mktemp)

check() {
    name="$1"
    url="$2"
    if curl -sf --max-time 5 "$url" > /dev/null 2>&1; then
        echo "up"
    else
        echo "down"
    fi
}

WEB_STATUS=$(check "web" "https://ridgwaysystems.org")
GITEA_STATUS=$(check "gitea" "https://git.ridgwaysystems.org")

cat > "$TMPFILE" << EOF
{
  "last_checked": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "services": [
    {"name": "Web (httpd)", "description": "ridgwaysystems.org", "status": "$WEB_STATUS"},
    {"name": "Gitea", "description": "git.ridgwaysystems.org", "status": "$GITEA_STATUS"}
  ]
}
EOF

mv "$TMPFILE" "$STATUS_FILE"

Deploying on FreeBSD

1. Build

On your control machine (or directly on the server):

GOOS=openbsd GOARCH=amd64 go build -o website ./cmd/server

Copy the binary and required files to the server:

scp website srv01:/usr/local/bin/website
rsync -av templates/ srv01:/var/www/ridgwaysystems/templates/
rsync -av static/    srv01:/var/www/ridgwaysystems/static/
rsync -av content/   srv01:/var/www/ridgwaysystems/content/
rsync -av data/      srv01:/var/www/ridgwaysystems/data/

2. rc.d service

Create /etc/rc.d/website:

#!/bin/ksh

daemon="/usr/local/bin/website"
daemon_user="www"
daemon_flags="-f"

. /etc/rc.d/rc.subr

rc_cmd $1
chmod +x /etc/rc.d/website
rcctl enable website
rcctl set website env "PORT=8080 SITE_URL=https://ridgwaysystems.org \
  CONTENT_DIR=/var/www/ridgwaysystems/content \
  DATA_DIR=/var/www/ridgwaysystems/data \
  ADMIN_PASSWORD_HASH=<hash> SESSION_SECRET=<secret>"
rcctl start website

3. relayd configuration

Add to /etc/relayd.conf on fw01:

# ridgwaysystems.org
http protocol "ridgway-https" {
    tls { keypair ridgwaysystems.org }
    match request header set "X-Forwarded-For" value "$REMOTE_ADDR"
    match request header set "X-Forwarded-Proto" value "https"
    pass
}

relay "ridgway-web" {
    listen on egress port 443 tls
    protocol "ridgway-https"
    forward to 10.0.10.10 port 8080 check http "/" code 200
}

4. httpd for Let's Encrypt ACME challenges (optional)

If using acme-client on fw01 for TLS certificates:

# /etc/httpd.conf on fw01
server "ridgwaysystems.org" {
    listen on * port 80
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
    location "*" {
        block return 301 "https://ridgwaysystems.org$REQUEST_URI"
    }
}

5. TLS certificates with acme-client

# /etc/acme-client.conf
authority letsencrypt {
    api url "https://acme-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-privkey.pem"
}

domain ridgwaysystems.org {
    domain key "/etc/ssl/private/ridgwaysystems.org.key"
    domain full chain certificate "/etc/ssl/ridgwaysystems.org.fullchain.pem"
    sign with letsencrypt
}

Daily cron renewal in /etc/daily.local:

acme-client ridgwaysystems.org && rcctl reload relayd

Admin Panel

The admin panel is available at /admin. Log in with the password configured in ADMIN_PASSWORD_HASH.

Features:

  • Create, edit, and delete blog posts with Markdown editor and live preview
  • Edit the service status JSON directly
  • Session expires after 24 hours

The admin panel is intentionally minimal — one user, no roles, no audit log. It's for personal use on a self-hosted site.

No External Dependencies

The site loads nothing from external servers. No CDN fonts, no analytics, no third-party scripts. Everything is self-contained in static/. This is intentional.

Description
No description provided
Readme 282 KiB
Languages
Go 42.4%
HTML 35.4%
CSS 19.9%
JavaScript 1.2%
Makefile 1.1%