| .forgejo/workflows | ||
| .github/workflows | ||
| docs | ||
| infrastructure | ||
| .dockerignore | ||
| .gitignore | ||
| README.md | ||
| renovate.json | ||
richardnixon.dev
Self-hosted infrastructure (Traefik + Docker Compose) running every service on the richardnixon.dev apex and subdomains.
The public blog at richardnixon.dev is a static Hugo site — its source lives in a companion repo: Richard/richardnixon.dev-hugo. Deploys are driven by Forgejo Actions; the runner in this stack builds the site and writes directly into the blog-static bind-mount.
Architecture
graph TD
DNS[Cloudflare DNS] --> Traefik[Traefik + Let's Encrypt]
Traefik --> CrowdSec[CrowdSec IPS]
Traefik --> Blog[Hugo blog-static<br>richardnixon.dev]
Traefik --> WP[WordPress<br>richardemanu.com]
Traefik --> LocFlow[LocFlow API<br>locflow.richardnixon.dev]
Traefik --> EireScope[EireScope<br>eirescope.richardnixon.dev]
Traefik --> Forgejo[Forgejo + runner<br>git.richardnixon.dev]
Traefik --> Umami[Umami Analytics<br>analytics.richardnixon.dev]
Traefik --> Authentik[Authentik SSO<br>auth.richardnixon.dev]
Traefik --> Grafana[Grafana<br>status.richardnixon.dev]
Traefik --> Portainer[Portainer<br>portainer.richardnixon.dev]
Traefik --> VStatus[Valheim Status<br>valheim.richardnixon.dev]
LocFlow --> LocFlowDB[(PostgreSQL)]
Umami --> UmamiDB[(PostgreSQL)]
Forgejo --> ForgejoDB[(PostgreSQL)]
Authentik --> AuthDB[(PostgreSQL + Redis)]
WP --> WPDB[(MariaDB)]
Grafana --> Prometheus[Prometheus + Loki]
VStatus --> Valheim[Valheim Server<br>UDP:2456-2458]
Services
| Service | Domain | Description |
|---|---|---|
| Hugo blog (blog-static) | richardnixon.dev | Static blog (Hugo + PaperMod) — content in richardnixon.dev-hugo |
| Forgejo | git.richardnixon.dev | Self-hosted Git forge (Actions enabled, SSO via Authentik) |
| Forgejo runner | (internal) | CI runner for Hugo blog deploys |
| WordPress | richardemanu.com | Personal site |
| LocFlow | locflow.richardnixon.dev | Localization automation platform (REST API) |
| EireScope | eirescope.richardnixon.dev | OSINT dashboard |
| Umami | analytics.richardnixon.dev | Privacy-focused analytics |
| Authentik | auth.richardnixon.dev | SSO/OIDC provider |
| Grafana | status.richardnixon.dev | Observability dashboards |
| Portainer | portainer.richardnixon.dev | Docker management |
| Valheim Status | valheim.richardnixon.dev | Game server status page |
| Valheim Server | valheim.richardnixon.dev:2456 | Dedicated game server |
Technology Stack
Edge
- Traefik v3 as reverse proxy with Let's Encrypt SSL
- CrowdSec as IPS with community threat intelligence + Traefik bouncer
- Authentik for SSO/OIDC on admin-only routes
Public blog (richardnixon.dev)
- Hugo 0.123.7 extended + PaperMod theme (pinned
v8.0) - nginx:alpine (
blog-static) serves/root/richardnixon.dev-hugo/public/via bind-mount - Forgejo Actions runner builds on push and copies output into the volume
Observability
- Prometheus, Grafana, Loki, Promtail, cAdvisor, Node Exporter as the monitoring stack
Host hardening
- Fail2ban: SSH brute-force protection (3 attempts = 24h ban)
- GeoIP: country-based filtering for SSH
- CrowdSec: web + SSH attack detection
Project Structure
richardnixon.dev/
├── docs/
│ └── deployment.md
├── infrastructure/
│ ├── docker-compose.yml
│ ├── .env.example
│ ├── traefik/
│ │ ├── traefik.yml
│ │ └── dynamic.yml
│ ├── blog-static/
│ │ └── nginx.conf
│ ├── forgejo-runner/
│ │ ├── config.yml
│ │ └── entrypoint.sh
│ ├── prometheus/
│ ├── loki/
│ ├── promtail/
│ ├── grafana/
│ │ └── provisioning/
│ ├── crowdsec/
│ └── valheim-status/
└── README.md
Quick Start
Prerequisites
- Docker and Docker Compose v2
- Domain with DNS configured
Deployment
- Clone both repositories:
cd /root
git clone https://git.richardnixon.dev/Richard/richardnixon.dev.git
git clone --recurse-submodules https://git.richardnixon.dev/Richard/richardnixon.dev-hugo.git
- Configure environment:
cd richardnixon.dev/infrastructure
cp .env.example .env
# Edit .env with your credentials
- Start services:
docker compose up -d
Traefik provisions Let's Encrypt certificates on first start. Detailed steps live in docs/deployment.md, including Forgejo Actions runner registration.
URL Routes
Configured in infrastructure/traefik/dynamic.yml.
| Path | Backend | Notes |
|---|---|---|
/ |
blog-static (Hugo) | Redirects to /pt-br/ (default content language) |
/pt-br/*, /en/* |
blog-static (Hugo) | Bilingual blog content |
/feed.xml, /sitemap.xml, /robots.txt |
blog-static (Hugo) | Static SEO/feed files |
All other domains route by Host() rule to their respective services.
Environment Variables
See infrastructure/.env.example for the full list. Key categories:
| Category | Examples |
|---|---|
| Forgejo | FORGEJO_DB_PASSWORD, FORGEJO_SECRET_KEY, FORGEJO_INTERNAL_TOKEN, FORGEJO_OAUTH2_JWT_SECRET, FORGEJO_RUNNER_REGISTRATION_TOKEN |
| LocFlow | LOCFLOW_DB_PASSWORD, LOCFLOW_SECRET_KEY, LOCFLOW_UMAMI_WEBSITE_ID |
| EireScope | EIRESCOPE_SECRET_KEY, EIRESCOPE_UMAMI_WEBSITE_ID |
| Authentik | AUTHENTIK_SECRET_KEY, AUTHENTIK_DB_PASSWORD |
| Umami | UMAMI_DB_PASSWORD, UMAMI_HASH_SALT |
| WordPress | MYSQL_ROOT_PASSWORD, WP_DB_PASSWORD |
| CrowdSec | CROWDSEC_BOUNCER_KEY |
| Grafana | GRAFANA_ADMIN_PASSWORD |
| Valheim | VALHEIM_PASSWORD, ADMINLIST_IDS, PERMITTEDLIST_IDS |
Grafana Dashboards
URL: https://status.richardnixon.dev
| Dashboard | Description |
|---|---|
| VPS System | CPU, memory, disk, load average, uptime |
| CrowdSec Security | Active bans, alerts, attack types |
| Traefik Proxy | Requests/s, latency, status codes |
| Network & Firewall | Bandwidth, TCP connections, security events |
| Container Logs | Real-time log viewer with search (Loki-backed) |
| Valheim Server | Players online, server status, resource usage |
Prometheus Metrics
| Job | Target | Metrics |
|---|---|---|
| prometheus | localhost:9090 | Prometheus self-metrics |
| cadvisor | cadvisor:8080 | Container metrics |
| traefik | traefik:8080 | HTTP requests, latency |
| node | node-exporter:9100 | VPS system metrics |
| crowdsec | crowdsec:6060 | Security metrics |
| valheim | valheim-metrics:3903 | Game server metrics |
| forgejo | forgejo:3000 | Git forge metrics (repos, users, HTTP) |
Dependency updates and CVE scanning
Two automated workflows run via Forgejo Actions:
- Renovate (
.forgejo/workflows/renovate.yml) — opens PRs every Monday with version bumps for Docker images, Forgejo Actions, Hugo submodules, and Hugo binary. Config inrenovate.jsonat the root of each repo. UsesRENOVATE_AUTODISCOVERto handle bothRichard/richardnixon.devandRichard/richardnixon.dev-hugofrom a single workflow. - Trivy (
.forgejo/workflows/trivy.yml) — scans the compose images and IaC configs daily for HIGH/CRITICAL CVEs. Same scan also runs on every push that touches the compose file.
Required secret: RENOVATE_TOKEN
Both repos need a Forgejo PAT with write:repository scope:
https://git.richardnixon.dev/user/settings/applications→ Generate New Token (name:renovate, scope:write:repository).- Add it as a secret named
RENOVATE_TOKENin each repo:https://git.richardnixon.dev/Richard/richardnixon.dev/settings/actions/secretshttps://git.richardnixon.dev/Richard/richardnixon.dev-hugo/settings/actions/secrets
- Trigger a first run manually via the workflow dispatch button to confirm Renovate authenticates.
A "Dependency Dashboard" issue will be opened in each repo summarizing pending updates.
Forgejo Actions (Hugo blog CI)
The forgejo-runner service registers itself on first boot using FORGEJO_RUNNER_REGISTRATION_TOKEN and joins the forgejo-internal network so job containers can resolve the internal forgejo hostname for actions/checkout.
Config (infrastructure/forgejo-runner/config.yml):
container.network: infrastructure_forgejo-internal— without this, checkout fails to resolveforgejo.container.docker_host: unix:///var/run/docker.sock— required value;"automatic"is not accepted by recent runner versions.valid_volumes: ["/root/richardnixon.dev-hugo/public"]— the only host path the workflow is allowed to write to.
The deploy workflow itself lives in the Hugo repo at .forgejo/workflows/deploy.yml. Pushing to its main branch builds Hugo (downloaded fresh per run) and copies public/ into the bind-mount served by blog-static. End-to-end push → live is ~30–60s.
Security
CrowdSec
Installed collections:
crowdsecurity/traefikcrowdsecurity/http-cvecrowdsecurity/linuxcrowdsecurity/whitelist-good-actors
Country blocks (web traffic): RU, CN, KP, IR, UA.
Useful commands:
docker exec crowdsec cscli metrics
docker exec crowdsec cscli decisions list
docker exec crowdsec cscli alerts list
docker exec crowdsec cscli decisions add --ip 1.2.3.4 --duration 24h --reason "manual"
SSH Security
| Protection | Configuration |
|---|---|
| Fail2ban | 3 attempts = 24h ban |
| GeoIP | Country-based filtering |
| CrowdSec | SSH brute-force detection |
Valheim Server
Configuration
| Setting | Value |
|---|---|
| Server Name | Emerald Realms |
| World Name | EmeraldRealms |
| Ports | UDP 2456-2458 |
| Backups | Every 12 hours, 7 days retention |
| Status Page | valheim.richardnixon.dev |
Status Page Features
- Server online/offline indicator
- Players online count with connection duration
- World name and game version
- Server uptime
- Copy-to-clipboard server address
Admin Commands (F5 console in-game)
kick [name/steamID] - Kick player
ban [name/steamID] - Ban player
unban [steamID] - Unban player
banned - List banned players
save - Force world save
info - Server info
Managing Admins
Edit infrastructure/.env:
ADMINLIST_IDS=76561198012345678,76561198087654321
PERMITTEDLIST_IDS=76561198012345678,76561198087654321
Then restart:
docker compose up -d valheim --force-recreate
Common Commands
# Status
docker compose ps
# Logs
docker compose logs -f <service>
# Restart a service
docker compose restart <service>
# Rebuild a service that has a build context
docker compose build <service> && docker compose up -d <service>
# Pull image updates
docker compose pull && docker compose up -d
License
MIT License