commit 21bd542469b245cd7035135e84fc186d84ba6d4e Author: Blake Ridgway Date: Sat Mar 7 21:15:20 2026 -0600 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef280ad --- /dev/null +++ b/README.md @@ -0,0 +1,292 @@ +# 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 OpenBSD 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 + +```sh +go build -o website ./cmd/server +``` + +Requires Go 1.22+. All dependencies are managed with Go modules. + +To download dependencies: +```sh +go mod tidy +``` + +## Running + +```sh +# 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 + +```sh +# 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. + +```markdown +--- +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. + +```json +{ + "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 + +```sh +#!/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 OpenBSD + +### 1. Build + +On your control machine (or directly on the server): + +```sh +GOOS=openbsd GOARCH=amd64 go build -o website ./cmd/server +``` + +Copy the binary and required files to the server: + +```sh +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`: + +```sh +#!/bin/ksh + +daemon="/usr/local/bin/website" +daemon_user="www" +daemon_flags="-f" + +. /etc/rc.d/rc.subr + +rc_cmd $1 +``` + +```sh +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= SESSION_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`: +```sh +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.