Files
rs_website/README.md
Blake Ridgway 21bd542469 first commit
2026-03-07 21:15:20 -06:00

293 lines
8.2 KiB
Markdown

# 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=<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`:
```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.