From 953a9d52af1dc0ff788941186c9f87aef3ce7095 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Nov 2025 12:11:03 +0000 Subject: [PATCH] feat: Add Caddy static sites and implement domain strategy Domain Strategy: - fig.systems: Homelab services only (removed edfig.dev fallback from all services) - edfig.dev: Professional/public sites (personal site, blog) - figgy.foo: Experimental/private content (SSO protected) Removed edfig.dev Fallbacks: - Updated 22 compose files to remove || Host(...edfig.dev) pattern - All homelab services now use fig.systems only - Traefik email remains admin@edfig.dev Added Caddy Static Sites Service: - compose/services/static-sites/ with Caddy 2 - Serves three domains with different configurations: * edfig.dev (personal/professional) - Public, no SSO * blog.edfig.dev (blog) - Public, Markdown rendering, templates * figgy.foo (experimental) - SSO protected, directory browsing - Example sites with modern, responsive designs - Comprehensive README with usage examples - Auto-reload on config changes (no restarts needed) Features: - Markdown rendering (write .md, serves as HTML) - Go templates for dynamic content - Directory browsing (figgy.foo) - Automatic gzip compression - Static asset caching - Zero-downtime config reloads Updated Documentation: - README.md: Added domain strategy section, static sites in directory structure - README.md: Added static sites to service URLs table - README.md: Updated deployment instructions --- README.md | 24 +- compose/core/lldap/compose.yaml | 2 +- compose/core/tinyauth/compose.yaml | 2 +- compose/core/traefik/compose.yaml | 2 +- .../media/automation/profilarr/compose.yaml | 2 +- .../media/automation/qbittorrent/compose.yaml | 2 +- compose/media/automation/radarr/compose.yaml | 2 +- compose/media/automation/sabnzbd/compose.yaml | 2 +- compose/media/automation/sonarr/compose.yaml | 2 +- compose/media/frontend/immich/compose.yaml | 2 +- compose/media/frontend/jellyfin/compose.yaml | 2 +- compose/media/frontend/jellyseer/compose.yaml | 2 +- compose/monitoring/logging/compose.yaml | 4 +- compose/monitoring/uptime/compose.yaml | 2 +- compose/services/FreshRSS/compose.yaml | 2 +- compose/services/backrest/compose.yaml | 2 +- compose/services/booklore/compose.yaml | 2 +- compose/services/karakeep/compose.yaml | 2 +- compose/services/microbin/compose.yaml | 2 +- compose/services/ollama/compose.yaml | 2 +- compose/services/rsshub/compose.yaml | 2 +- compose/services/static-sites/.env | 7 + compose/services/static-sites/.gitignore | 10 + compose/services/static-sites/Caddyfile | 57 ++ compose/services/static-sites/README.md | 604 ++++++++++++++++++ compose/services/static-sites/compose.yaml | 63 ++ .../sites/blog.edfig.dev/index.html | 160 +++++ .../blog.edfig.dev/posts/example-post.md | 68 ++ .../static-sites/sites/edfig.dev/index.html | 121 ++++ .../static-sites/sites/figgy.foo/index.html | 191 ++++++ compose/services/vikunja/compose.yaml | 2 +- 31 files changed, 1324 insertions(+), 25 deletions(-) create mode 100644 compose/services/static-sites/.env create mode 100644 compose/services/static-sites/.gitignore create mode 100644 compose/services/static-sites/Caddyfile create mode 100644 compose/services/static-sites/README.md create mode 100644 compose/services/static-sites/compose.yaml create mode 100644 compose/services/static-sites/sites/blog.edfig.dev/index.html create mode 100644 compose/services/static-sites/sites/blog.edfig.dev/posts/example-post.md create mode 100644 compose/services/static-sites/sites/edfig.dev/index.html create mode 100644 compose/services/static-sites/sites/figgy.foo/index.html diff --git a/README.md b/README.md index 964a9c1..21f7a67 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ compose/ └── services/ # Utility services ├── homarr/ # Dashboard (home.fig.systems) ├── backrest/ # Backup manager (backup.fig.systems) + ├── static-sites/ # Static websites (Caddy) ├── karakeep/ # Bookmark manager with AI (links.fig.systems) ├── ollama/ # Local LLM server (ollama.fig.systems) ├── vikunja/ # Task management (tasks.fig.systems) @@ -74,9 +75,21 @@ compose/ ## 🌐 Domains -All services are accessible via: -- Primary: `*.fig.systems` -- Secondary: `*.edfig.dev` +Three domains are used with different purposes: + +### fig.systems (Homelab Services) +Primary domain for all self-hosted homelab services: +- `*.fig.systems` - All services listed below + +### edfig.dev (Professional/Public) +Professional and public-facing sites: +- `edfig.dev` / `www.edfig.dev` - Personal website/portfolio +- `blog.edfig.dev` - Technical blog + +### figgy.foo (Experimental/Private) +Testing and experimental services: +- `figgy.foo` - Experimental lab (SSO protected) +- `*.figgy.foo` - Test instances of services ### Service URLs @@ -85,6 +98,10 @@ All services are accessible via: | Traefik Dashboard | traefik.fig.systems | ✅ | | LLDAP | lldap.fig.systems | ✅ | | Tinyauth | auth.fig.systems | ❌ | +| **Static Sites** | | | +| Personal Site | edfig.dev | ❌ | +| Blog | blog.edfig.dev | ❌ | +| Experimental Lab | figgy.foo | ✅ | | **Monitoring** | | | | Grafana (Logs) | logs.fig.systems | ❌* | | Loki (API) | loki.fig.systems | ✅ | @@ -183,6 +200,7 @@ cd compose/media/automation/recyclarr && docker compose up -d cd compose/media/automation/profilarr && docker compose up -d # Utility services +cd compose/services/static-sites && docker compose up -d # Static websites (edfig.dev, blog, figgy.foo) cd compose/services/karakeep && docker compose up -d cd compose/services/ollama && docker compose up -d cd compose/services/vikunja && docker compose up -d diff --git a/compose/core/lldap/compose.yaml b/compose/core/lldap/compose.yaml index 406acab..49899d7 100644 --- a/compose/core/lldap/compose.yaml +++ b/compose/core/lldap/compose.yaml @@ -13,7 +13,7 @@ services: - homelab labels: traefik.enable: true - traefik.http.routers.lldap.rule: Host(`lldap.fig.systems`) || Host(`lldap.edfig.dev`) + traefik.http.routers.lldap.rule: Host(`lldap.fig.systems`) traefik.http.routers.lldap.entrypoints: websecure traefik.http.routers.lldap.tls.certresolver: letsencrypt traefik.http.services.lldap.loadbalancer.server.port: 17170 diff --git a/compose/core/tinyauth/compose.yaml b/compose/core/tinyauth/compose.yaml index 9a0b0a6..6eabc34 100644 --- a/compose/core/tinyauth/compose.yaml +++ b/compose/core/tinyauth/compose.yaml @@ -11,7 +11,7 @@ services: labels: traefik.enable: true # Web UI routing - traefik.http.routers.tinyauth.rule: Host(`auth.fig.systems`) || Host(`auth.edfig.dev`) + traefik.http.routers.tinyauth.rule: Host(`auth.fig.systems`) traefik.http.routers.tinyauth.entrypoints: websecure traefik.http.routers.tinyauth.tls.certresolver: letsencrypt traefik.http.routers.tinyauth.service: tinyauth-ui diff --git a/compose/core/traefik/compose.yaml b/compose/core/traefik/compose.yaml index ef75ca5..da28b49 100644 --- a/compose/core/traefik/compose.yaml +++ b/compose/core/traefik/compose.yaml @@ -34,7 +34,7 @@ services: labels: traefik.enable: true # Dashboard routing - traefik.http.routers.traefik.rule: Host(`traefik.fig.systems`) || Host(`traefik.edfig.dev`) + traefik.http.routers.traefik.rule: Host(`traefik.fig.systems`) traefik.http.routers.traefik.entrypoints: websecure traefik.http.routers.traefik.tls.certresolver: letsencrypt traefik.http.routers.traefik.service: api@internal diff --git a/compose/media/automation/profilarr/compose.yaml b/compose/media/automation/profilarr/compose.yaml index 86c53a7..013b836 100644 --- a/compose/media/automation/profilarr/compose.yaml +++ b/compose/media/automation/profilarr/compose.yaml @@ -22,7 +22,7 @@ services: traefik.docker.network: homelab # Web UI - traefik.http.routers.profilarr.rule: Host(`profilarr.fig.systems`) || Host(`profilarr.edfig.dev`) + traefik.http.routers.profilarr.rule: Host(`profilarr.fig.systems`) traefik.http.routers.profilarr.entrypoints: websecure traefik.http.routers.profilarr.tls.certresolver: letsencrypt traefik.http.services.profilarr.loadbalancer.server.port: 6868 diff --git a/compose/media/automation/qbittorrent/compose.yaml b/compose/media/automation/qbittorrent/compose.yaml index 9320737..b4cf349 100644 --- a/compose/media/automation/qbittorrent/compose.yaml +++ b/compose/media/automation/qbittorrent/compose.yaml @@ -20,7 +20,7 @@ services: - homelab labels: traefik.enable: true - traefik.http.routers.qbittorrent.rule: Host(`qbt.fig.systems`) || Host(`qbt.edfig.dev`) + traefik.http.routers.qbittorrent.rule: Host(`qbt.fig.systems`) traefik.http.routers.qbittorrent.entrypoints: websecure traefik.http.routers.qbittorrent.tls.certresolver: letsencrypt traefik.http.services.qbittorrent.loadbalancer.server.port: 8080 diff --git a/compose/media/automation/radarr/compose.yaml b/compose/media/automation/radarr/compose.yaml index 572aeb2..9de458c 100644 --- a/compose/media/automation/radarr/compose.yaml +++ b/compose/media/automation/radarr/compose.yaml @@ -20,7 +20,7 @@ services: - homelab labels: traefik.enable: true - traefik.http.routers.radarr.rule: Host(`radarr.fig.systems`) || Host(`radarr.edfig.dev`) + traefik.http.routers.radarr.rule: Host(`radarr.fig.systems`) traefik.http.routers.radarr.entrypoints: websecure traefik.http.routers.radarr.tls.certresolver: letsencrypt traefik.http.services.radarr.loadbalancer.server.port: 7878 diff --git a/compose/media/automation/sabnzbd/compose.yaml b/compose/media/automation/sabnzbd/compose.yaml index 23315f7..9bf84c3 100644 --- a/compose/media/automation/sabnzbd/compose.yaml +++ b/compose/media/automation/sabnzbd/compose.yaml @@ -17,7 +17,7 @@ services: - homelab labels: traefik.enable: true - traefik.http.routers.sabnzbd.rule: Host(`sabnzbd.fig.systems`) || Host(`sabnzbd.edfig.dev`) + traefik.http.routers.sabnzbd.rule: Host(`sabnzbd.fig.systems`) traefik.http.routers.sabnzbd.entrypoints: websecure traefik.http.routers.sabnzbd.tls.certresolver: letsencrypt traefik.http.services.sabnzbd.loadbalancer.server.port: 8080 diff --git a/compose/media/automation/sonarr/compose.yaml b/compose/media/automation/sonarr/compose.yaml index 3a24076..79b4c42 100644 --- a/compose/media/automation/sonarr/compose.yaml +++ b/compose/media/automation/sonarr/compose.yaml @@ -20,7 +20,7 @@ services: - homelab labels: traefik.enable: true - traefik.http.routers.sonarr.rule: Host(`sonarr.fig.systems`) || Host(`sonarr.edfig.dev`) + traefik.http.routers.sonarr.rule: Host(`sonarr.fig.systems`) traefik.http.routers.sonarr.entrypoints: websecure traefik.http.routers.sonarr.tls.certresolver: letsencrypt traefik.http.services.sonarr.loadbalancer.server.port: 8989 diff --git a/compose/media/frontend/immich/compose.yaml b/compose/media/frontend/immich/compose.yaml index 47096ae..d40d745 100644 --- a/compose/media/frontend/immich/compose.yaml +++ b/compose/media/frontend/immich/compose.yaml @@ -40,7 +40,7 @@ services: labels: traefik.enable: true traefik.docker.network: homelab - traefik.http.routers.immich.rule: Host(`photos.fig.systems`) || Host(`photos.edfig.dev`) + traefik.http.routers.immich.rule: Host(`photos.fig.systems`) traefik.http.routers.immich.entrypoints: websecure traefik.http.routers.immich.tls.certresolver: letsencrypt traefik.http.services.immich.loadbalancer.server.port: 2283 diff --git a/compose/media/frontend/jellyfin/compose.yaml b/compose/media/frontend/jellyfin/compose.yaml index f2cf897..59ec0d8 100644 --- a/compose/media/frontend/jellyfin/compose.yaml +++ b/compose/media/frontend/jellyfin/compose.yaml @@ -25,7 +25,7 @@ services: - homelab labels: traefik.enable: true - traefik.http.routers.jellyfin.rule: Host(`flix.fig.systems`) || Host(`flix.edfig.dev`) + traefik.http.routers.jellyfin.rule: Host(`flix.fig.systems`) traefik.http.routers.jellyfin.entrypoints: websecure traefik.http.routers.jellyfin.tls.certresolver: letsencrypt traefik.http.services.jellyfin.loadbalancer.server.port: 8096 diff --git a/compose/media/frontend/jellyseer/compose.yaml b/compose/media/frontend/jellyseer/compose.yaml index 98c9a8f..bdfb783 100644 --- a/compose/media/frontend/jellyseer/compose.yaml +++ b/compose/media/frontend/jellyseer/compose.yaml @@ -14,7 +14,7 @@ services: - homelab labels: traefik.enable: true - traefik.http.routers.jellyseerr.rule: Host(`requests.fig.systems`) || Host(`requests.edfig.dev`) + traefik.http.routers.jellyseerr.rule: Host(`requests.fig.systems`) traefik.http.routers.jellyseerr.entrypoints: websecure traefik.http.routers.jellyseerr.tls.certresolver: letsencrypt traefik.http.services.jellyseerr.loadbalancer.server.port: 5055 diff --git a/compose/monitoring/logging/compose.yaml b/compose/monitoring/logging/compose.yaml index 7ec0ff8..4933085 100644 --- a/compose/monitoring/logging/compose.yaml +++ b/compose/monitoring/logging/compose.yaml @@ -26,7 +26,7 @@ services: traefik.docker.network: homelab # Loki API - traefik.http.routers.loki.rule: Host(`loki.fig.systems`) || Host(`loki.edfig.dev`) + traefik.http.routers.loki.rule: Host(`loki.fig.systems`) traefik.http.routers.loki.entrypoints: websecure traefik.http.routers.loki.tls.certresolver: letsencrypt traefik.http.services.loki.loadbalancer.server.port: 3100 @@ -95,7 +95,7 @@ services: traefik.docker.network: homelab # Grafana Web UI - traefik.http.routers.grafana.rule: Host(`logs.fig.systems`) || Host(`logs.edfig.dev`) + traefik.http.routers.grafana.rule: Host(`logs.fig.systems`) traefik.http.routers.grafana.entrypoints: websecure traefik.http.routers.grafana.tls.certresolver: letsencrypt traefik.http.services.grafana.loadbalancer.server.port: 3000 diff --git a/compose/monitoring/uptime/compose.yaml b/compose/monitoring/uptime/compose.yaml index 080b0b9..d6cf1f3 100644 --- a/compose/monitoring/uptime/compose.yaml +++ b/compose/monitoring/uptime/compose.yaml @@ -22,7 +22,7 @@ services: traefik.docker.network: homelab # Web UI - traefik.http.routers.uptime-kuma.rule: Host(`status.fig.systems`) || Host(`status.edfig.dev`) + traefik.http.routers.uptime-kuma.rule: Host(`status.fig.systems`) traefik.http.routers.uptime-kuma.entrypoints: websecure traefik.http.routers.uptime-kuma.tls.certresolver: letsencrypt traefik.http.services.uptime-kuma.loadbalancer.server.port: 3001 diff --git a/compose/services/FreshRSS/compose.yaml b/compose/services/FreshRSS/compose.yaml index 44e36ed..5b1fce5 100644 --- a/compose/services/FreshRSS/compose.yaml +++ b/compose/services/FreshRSS/compose.yaml @@ -22,7 +22,7 @@ services: traefik.docker.network: homelab # Web UI - traefik.http.routers.freshrss.rule: Host(`rss.fig.systems`) || Host(`rss.edfig.dev`) + traefik.http.routers.freshrss.rule: Host(`rss.fig.systems`) traefik.http.routers.freshrss.entrypoints: websecure traefik.http.routers.freshrss.tls.certresolver: letsencrypt traefik.http.services.freshrss.loadbalancer.server.port: 80 diff --git a/compose/services/backrest/compose.yaml b/compose/services/backrest/compose.yaml index 090417d..864f823 100644 --- a/compose/services/backrest/compose.yaml +++ b/compose/services/backrest/compose.yaml @@ -21,7 +21,7 @@ services: labels: # Traefik traefik.enable: true - traefik.http.routers.backrest.rule: Host(`backup.fig.systems`) || Host(`backup.edfig.dev`) + traefik.http.routers.backrest.rule: Host(`backup.fig.systems`) traefik.http.routers.backrest.entrypoints: websecure traefik.http.routers.backrest.tls.certresolver: letsencrypt traefik.http.services.backrest.loadbalancer.server.port: 9898 diff --git a/compose/services/booklore/compose.yaml b/compose/services/booklore/compose.yaml index 2f94e79..2ac9420 100644 --- a/compose/services/booklore/compose.yaml +++ b/compose/services/booklore/compose.yaml @@ -22,7 +22,7 @@ services: traefik.docker.network: homelab # Web UI - traefik.http.routers.booklore.rule: Host(`booklore.fig.systems`) || Host(`booklore.edfig.dev`) + traefik.http.routers.booklore.rule: Host(`booklore.fig.systems`) traefik.http.routers.booklore.entrypoints: websecure traefik.http.routers.booklore.tls.certresolver: letsencrypt traefik.http.services.booklore.loadbalancer.server.port: 3000 diff --git a/compose/services/karakeep/compose.yaml b/compose/services/karakeep/compose.yaml index a9a366b..de398c6 100644 --- a/compose/services/karakeep/compose.yaml +++ b/compose/services/karakeep/compose.yaml @@ -28,7 +28,7 @@ services: traefik.docker.network: homelab # Web UI - traefik.http.routers.karakeep.rule: Host(`links.fig.systems`) || Host(`links.edfig.dev`) + traefik.http.routers.karakeep.rule: Host(`links.fig.systems`) traefik.http.routers.karakeep.entrypoints: websecure traefik.http.routers.karakeep.tls.certresolver: letsencrypt traefik.http.services.karakeep.loadbalancer.server.port: 3000 diff --git a/compose/services/microbin/compose.yaml b/compose/services/microbin/compose.yaml index 694c679..0962b70 100644 --- a/compose/services/microbin/compose.yaml +++ b/compose/services/microbin/compose.yaml @@ -22,7 +22,7 @@ services: traefik.docker.network: homelab # Web UI - traefik.http.routers.microbin.rule: Host(`paste.fig.systems`) || Host(`paste.edfig.dev`) + traefik.http.routers.microbin.rule: Host(`paste.fig.systems`) traefik.http.routers.microbin.entrypoints: websecure traefik.http.routers.microbin.tls.certresolver: letsencrypt traefik.http.services.microbin.loadbalancer.server.port: 8080 diff --git a/compose/services/ollama/compose.yaml b/compose/services/ollama/compose.yaml index fccf305..6b7964e 100644 --- a/compose/services/ollama/compose.yaml +++ b/compose/services/ollama/compose.yaml @@ -35,7 +35,7 @@ services: traefik.docker.network: homelab # API endpoint - traefik.http.routers.ollama.rule: Host(`ollama.fig.systems`) || Host(`ollama.edfig.dev`) + traefik.http.routers.ollama.rule: Host(`ollama.fig.systems`) traefik.http.routers.ollama.entrypoints: websecure traefik.http.routers.ollama.tls.certresolver: letsencrypt traefik.http.services.ollama.loadbalancer.server.port: 11434 diff --git a/compose/services/rsshub/compose.yaml b/compose/services/rsshub/compose.yaml index 6876873..b131437 100644 --- a/compose/services/rsshub/compose.yaml +++ b/compose/services/rsshub/compose.yaml @@ -23,7 +23,7 @@ services: traefik.docker.network: homelab # Web UI - traefik.http.routers.rsshub.rule: Host(`rsshub.fig.systems`) || Host(`rsshub.edfig.dev`) + traefik.http.routers.rsshub.rule: Host(`rsshub.fig.systems`) traefik.http.routers.rsshub.entrypoints: websecure traefik.http.routers.rsshub.tls.certresolver: letsencrypt traefik.http.services.rsshub.loadbalancer.server.port: 1200 diff --git a/compose/services/static-sites/.env b/compose/services/static-sites/.env new file mode 100644 index 0000000..a2c2b33 --- /dev/null +++ b/compose/services/static-sites/.env @@ -0,0 +1,7 @@ +# Caddy Static Sites Configuration + +# Timezone +TZ=America/Los_Angeles + +# Optional: Caddy admin API (disabled by default in Caddyfile) +# CADDY_ADMIN=localhost:2019 diff --git a/compose/services/static-sites/.gitignore b/compose/services/static-sites/.gitignore new file mode 100644 index 0000000..f4fc963 --- /dev/null +++ b/compose/services/static-sites/.gitignore @@ -0,0 +1,10 @@ +# Caddy data and config +caddy_data/ +caddy_config/ + +# Keep example sites but ignore actual site content +# (uncomment if you want to version control your sites) +# sites/ + +# Keep .env.example if created +!.env.example diff --git a/compose/services/static-sites/Caddyfile b/compose/services/static-sites/Caddyfile new file mode 100644 index 0000000..0c1104f --- /dev/null +++ b/compose/services/static-sites/Caddyfile @@ -0,0 +1,57 @@ +# Caddyfile - Static Sites Configuration +# Docs: https://caddyserver.com/docs/caddyfile + +# Global options +{ + # Listen on port 80 (Traefik handles SSL) + auto_https off + admin off +} + +# Personal/Professional Site (edfig.dev) +www.edfig.dev, edfig.dev { + root * /srv/edfig.dev + file_server + encode gzip + + # Try files, then index, then 404 + try_files {path} {path}/index.html index.html + + # Cache static assets + @static { + path *.css *.js *.jpg *.jpeg *.png *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot + } + header @static Cache-Control "public, max-age=604800, immutable" +} + +# Blog (blog.edfig.dev) +blog.edfig.dev { + root * /srv/blog.edfig.dev + file_server + encode gzip + + # Enable templates for dynamic content + templates + + # Markdown files automatically render as HTML + try_files {path} {path}/index.html {path}.md {path}/index.md index.html + + # Cache static assets + @static { + path *.css *.js *.jpg *.jpeg *.png *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot + } + header @static Cache-Control "public, max-age=604800, immutable" +} + +# Experimental/Private (figgy.foo) +figgy.foo, www.figgy.foo { + root * /srv/figgy.foo + + # Enable directory browsing for experiments + file_server browse + + encode gzip + + # Templates enabled for dynamic pages + templates +} diff --git a/compose/services/static-sites/README.md b/compose/services/static-sites/README.md new file mode 100644 index 0000000..a4831e3 --- /dev/null +++ b/compose/services/static-sites/README.md @@ -0,0 +1,604 @@ +# Caddy Static Sites Server + +Serves static websites for edfig.dev (professional), blog.edfig.dev (blog), and figgy.foo (experimental). + +## Overview + +**Caddy** is a modern web server with automatic HTTPS and simple configuration: + +- ✅ **Static file serving** - HTML, CSS, JavaScript, images +- ✅ **Markdown rendering** - Write `.md` files, served as HTML automatically +- ✅ **Templates** - Dynamic content with Go templates +- ✅ **Directory browsing** - Beautiful file listing (figgy.foo) +- ✅ **Auto-compression** - Gzip for all responses +- ✅ **Zero-downtime reloads** - Config changes apply instantly + +## Domain Strategy + +### edfig.dev (Professional/Public) +- **Purpose**: Personal website, portfolio +- **URL**: https://edfig.dev or https://www.edfig.dev +- **SSO**: No (public site) +- **Content**: `/sites/edfig.dev/` + +### blog.edfig.dev (Blog/Public) +- **Purpose**: Technical blog, articles +- **URL**: https://blog.edfig.dev +- **SSO**: No (public blog) +- **Content**: `/sites/blog.edfig.dev/` +- **Features**: Markdown auto-rendering, templates + +### figgy.foo (Experimental/Private) +- **Purpose**: Testing, development, experiments +- **URL**: https://figgy.foo or https://www.figgy.foo +- **SSO**: Yes (protected by Tinyauth) +- **Content**: `/sites/figgy.foo/` +- **Features**: Directory browsing, templates + +## Quick Start + +### 1. Deploy + +```bash +cd ~/homelab/compose/services/static-sites +docker compose up -d +``` + +### 2. Access Sites + +- **edfig.dev**: https://edfig.dev +- **Blog**: https://blog.edfig.dev +- **Experimental**: https://figgy.foo (requires SSO login) + +### 3. Verify + +```bash +# Check container is running +docker ps | grep caddy-static + +# Check logs +docker logs caddy-static + +# Test sites +curl -I https://edfig.dev +curl -I https://blog.edfig.dev +``` + +## Directory Structure + +``` +static-sites/ +├── compose.yaml # Docker Compose + Traefik labels +├── Caddyfile # Caddy configuration +├── .env # Environment variables +├── .gitignore # Ignored files +├── README.md # This file +└── sites/ # Site content (can be version controlled) + ├── edfig.dev/ + │ ├── index.html + │ ├── assets/ + │ │ ├── css/ + │ │ ├── js/ + │ │ └── images/ + │ └── ... + ├── blog.edfig.dev/ + │ ├── index.html + │ └── posts/ + │ ├── example-post.md # Markdown posts + │ └── ... + └── figgy.foo/ + ├── index.html + └── experiments/ + └── ... +``` + +## Managing Content + +### Adding/Editing HTML + +Simply edit files in the `sites/` directory: + +```bash +# Edit main site +vim sites/edfig.dev/index.html + +# Add new page +echo "

About Me

" > sites/edfig.dev/about.html + +# Changes are live immediately (no restart needed!) +``` + +### Writing Blog Posts (Markdown) + +Create `.md` files in `sites/blog.edfig.dev/posts/`: + +```bash +# Create new post +cat > sites/blog.edfig.dev/posts/my-post.md << 'EOF' +# My New Blog Post + +**Published:** January 10, 2025 + +This is my blog post content... + +## Code Example + +```bash +docker compose up -d +``` + +[Back to Blog](/) +EOF + +# Access at: https://blog.edfig.dev/posts/my-post.md +# (renders as HTML automatically!) +``` + +**Markdown features:** +- Headers (`#`, `##`, `###`) +- **Bold**, *italic*, `code` +- Links, images +- Lists (ordered/unordered) +- Code blocks with syntax highlighting +- Tables +- Blockquotes + +### Using Templates + +Caddy supports Go templates for dynamic content: + +**Example - Current time:** +```html + +

Page generated at: {{.Now.Format "2006-01-02 15:04:05"}}

+``` + +**Example - Include header:** +```html +{{include "header.html"}} +
+

My Page

+
+{{include "footer.html"}} +``` + +**Template variables:** +- `{{.Now}}` - Current time +- `{{.Req.URL}}` - Request URL +- `{{.Req.Host}}` - Request hostname +- `{{.Req.Method}}` - HTTP method +- `{{env "VARIABLE"}}` - Environment variable + +See [Caddy Templates Docs](https://caddyserver.com/docs/caddyfile/directives/templates) + +### Directory Browsing (figgy.foo) + +figgy.foo has directory browsing enabled: + +```bash +# Add files to browse +cp some-file.txt sites/figgy.foo/experiments/ + +# Access: https://figgy.foo/experiments/ +# Shows beautiful file listing with search! +``` + +## Adding New Sites + +### Option 1: New Subdomain (same domain) + +**Add to Caddyfile:** +```caddy +test.figgy.foo { + root * /srv/test.figgy.foo + file_server + encode gzip +} +``` + +**Add Traefik labels to compose.yaml:** +```yaml +# test.figgy.foo +traefik.http.routers.figgy-test.rule: Host(`test.figgy.foo`) +traefik.http.routers.figgy-test.entrypoints: websecure +traefik.http.routers.figgy-test.tls.certresolver: letsencrypt +traefik.http.routers.figgy-test.service: caddy-static +traefik.http.routers.figgy-test.middlewares: tinyauth # If SSO needed +``` + +**Create site directory:** +```bash +mkdir -p sites/test.figgy.foo +echo "

Test Site

" > sites/test.figgy.foo/index.html +``` + +**Reload (instant, no restart):** +```bash +# Caddy auto-reloads when Caddyfile changes! +# Just wait 1-2 seconds, then access https://test.figgy.foo +``` + +### Option 2: New Domain + +Follow same process but use new domain name. Make sure DNS points to your server. + +## Caddyfile Features + +### Basic Site +```caddy +example.com { + root * /srv/example + file_server +} +``` + +### With Compression +```caddy +example.com { + root * /srv/example + file_server + encode gzip zstd brotli +} +``` + +### With Caching +```caddy +example.com { + root * /srv/example + file_server + + @static { + path *.css *.js *.jpg *.png *.gif *.ico + } + header @static Cache-Control "public, max-age=604800" +} +``` + +### With Redirects +```caddy +www.example.com { + redir https://example.com{uri} permanent +} + +example.com { + root * /srv/example + file_server +} +``` + +### With Custom 404 +```caddy +example.com { + root * /srv/example + file_server + handle_errors { + rewrite * /404.html + file_server + } +} +``` + +### With Basic Auth (alternative to SSO) +```caddy +example.com { + root * /srv/example + basicauth { + user $2a$14$hashedpassword + } + file_server +} +``` + +Generate hashed password: +```bash +docker exec caddy-static caddy hash-password --plaintext "mypassword" +``` + +## Traefik Integration + +All sites route through Traefik: + +``` +Internet → DNS (*.edfig.dev, *.figgy.foo) + ↓ + Traefik (SSL termination) + ↓ + Tinyauth (SSO check for figgy.foo only) + ↓ + Caddy (static file serving) +``` + +**SSL certificates:** +- Traefik handles Let's Encrypt +- Caddy receives plain HTTP on port 80 +- Users see HTTPS + +**SSO protection:** +- `edfig.dev` & `blog.edfig.dev`: No SSO (public) +- `figgy.foo`: SSO protected (private) + +## Performance + +### Caching + +Static assets automatically cached: + +```caddy +@static { + path *.css *.js *.jpg *.jpeg *.png *.gif *.ico *.svg +} +header @static Cache-Control "public, max-age=604800, immutable" +``` + +- 7 days cache for images, CSS, JS +- Browsers won't re-request until expired + +### Compression + +All responses auto-compressed with gzip: + +```caddy +encode gzip +``` + +- 70-90% size reduction for HTML/CSS/JS +- Faster page loads +- Lower bandwidth usage + +### Performance Tips + +1. **Optimize images**: Use WebP format, compress before uploading +2. **Minify CSS/JS**: Use build tools (optional) +3. **Use CDN**: For high-traffic sites (optional) +4. **Enable HTTP/2**: Traefik handles this automatically + +## Monitoring + +### Check Service Status + +```bash +# Container status +docker ps | grep caddy-static + +# Logs +docker logs caddy-static -f + +# Resource usage +docker stats caddy-static +``` + +### Check Specific Site + +```bash +# Test site is reachable +curl -I https://edfig.dev + +# Test with timing +curl -w "@curl-format.txt" -o /dev/null -s https://edfig.dev + +# Check SSL certificate +echo | openssl s_client -connect edfig.dev:443 -servername edfig.dev 2>/dev/null | openssl x509 -noout -dates +``` + +### Access Logs + +Caddy logs to stdout (captured by Docker): + +```bash +# View logs +docker logs caddy-static + +# Follow logs +docker logs caddy-static -f + +# Last 100 lines +docker logs caddy-static --tail 100 +``` + +### Grafana Logs + +All logs forwarded to Loki automatically: + +**Query in Grafana** (https://logs.fig.systems): +```logql +{container="caddy-static"} +``` + +Filter by status code: +```logql +{container="caddy-static"} |= "404" +``` + +## Troubleshooting + +### Site not loading + +**Check container:** +```bash +docker ps | grep caddy-static +# If not running: +docker compose up -d +``` + +**Check logs:** +```bash +docker logs caddy-static +# Look for errors in Caddyfile or file not found +``` + +**Check DNS:** +```bash +dig +short edfig.dev +# Should point to your server IP +``` + +**Check Traefik:** +```bash +# See if Traefik sees the route +docker logs traefik | grep edfig +``` + +### 404 Not Found + +**Check file exists:** +```bash +ls -la sites/edfig.dev/index.html +``` + +**Check path in Caddyfile:** +```bash +grep "root" Caddyfile +# Should show: root * /srv/edfig.dev +``` + +**Check permissions:** +```bash +# Files should be readable +chmod -R 755 sites/ +``` + +### Changes not appearing + +**Caddy auto-reloads**, but double-check: + +```bash +# Check file modification time +ls -lh sites/edfig.dev/index.html + +# Force reload (shouldn't be needed) +docker exec caddy-static caddy reload --config /etc/caddy/Caddyfile +``` + +**Browser cache:** +```bash +# Force refresh in browser: Ctrl+Shift+R (Linux/Win) or Cmd+Shift+R (Mac) +# Or open in incognito/private window +``` + +### Markdown not rendering + +**Check templates enabled:** +```caddy +# In Caddyfile for blog.edfig.dev +blog.edfig.dev { + templates # <-- This must be present! + # ... +} +``` + +**Check file extension:** +```bash +# Must be .md +mv post.txt post.md +``` + +**Test rendering:** +```bash +curl https://blog.edfig.dev/posts/example-post.md +# Should return HTML, not raw markdown +``` + +### SSO not working on figgy.foo + +**Check middleware:** +```yaml +# In compose.yaml +traefik.http.routers.figgy-main.middlewares: tinyauth +``` + +**Check Tinyauth is running:** +```bash +docker ps | grep tinyauth +``` + +**Test without SSO:** +```bash +# Temporarily remove SSO to isolate issue +# Comment out middleware line in compose.yaml +# docker compose up -d +``` + +## Backup + +### Backup Site Content + +```bash +# Backup all sites +cd ~/homelab/compose/services/static-sites +tar czf sites-backup-$(date +%Y%m%d).tar.gz sites/ + +# Backup to external storage +scp sites-backup-*.tar.gz user@backup-server:/backups/ +``` + +### Version Control (Optional) + +Consider using Git for your sites: + +```bash +cd sites/ +git init +git add . +git commit -m "Initial site content" + +# Add remote +git remote add origin git@github.com:efigueroa/sites.git +git push -u origin main +``` + +## Security + +### Public vs Private + +**Public sites** (`edfig.dev`, `blog.edfig.dev`): +- No SSO middleware +- Accessible to everyone +- Use for portfolio, blog, public content + +**Private sites** (`figgy.foo`): +- SSO middleware enabled +- Requires LLDAP authentication +- Use for experiments, private content + +### Content Security + +**Don't commit:** +- API keys +- Passwords +- Private information +- Sensitive data + +**Do commit:** +- HTML, CSS, JS +- Images, assets +- Markdown blog posts +- Public content + +### File Permissions + +```bash +# Sites should be read-only to Caddy +chmod -R 755 sites/ +chown -R $USER:$USER sites/ +``` + +## Resources + +- [Caddy Documentation](https://caddyserver.com/docs/) +- [Caddyfile Tutorial](https://caddyserver.com/docs/caddyfile-tutorial) +- [Templates Documentation](https://caddyserver.com/docs/caddyfile/directives/templates) +- [Markdown Rendering](https://caddyserver.com/docs/caddyfile/directives/templates#markdown) + +## Next Steps + +1. ✅ Deploy Caddy static sites +2. ✅ Access edfig.dev, blog.edfig.dev, figgy.foo +3. ⬜ Customize edfig.dev with your content +4. ⬜ Write first blog post in Markdown +5. ⬜ Add experiments to figgy.foo +6. ⬜ Set up Git version control for sites +7. ⬜ Configure automated backups + +--- + +**Serve static content, simply and securely!** 🌐 diff --git a/compose/services/static-sites/compose.yaml b/compose/services/static-sites/compose.yaml new file mode 100644 index 0000000..88cce29 --- /dev/null +++ b/compose/services/static-sites/compose.yaml @@ -0,0 +1,63 @@ +# Caddy - Static Sites Server +# Docs: https://caddyserver.com/docs/ + +services: + caddy: + container_name: caddy-static + image: caddy:2-alpine + restart: unless-stopped + + env_file: + - .env + + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ./sites:/srv:ro + - caddy_data:/data + - caddy_config:/config + + networks: + - homelab + + labels: + # Traefik + traefik.enable: true + traefik.docker.network: homelab + + # edfig.dev (personal/professional site) + traefik.http.routers.edfig-www.rule: Host(`www.edfig.dev`) || Host(`edfig.dev`) + traefik.http.routers.edfig-www.entrypoints: websecure + traefik.http.routers.edfig-www.tls.certresolver: letsencrypt + traefik.http.routers.edfig-www.service: caddy-static + # No SSO - public personal site + + # blog.edfig.dev (blog) + traefik.http.routers.edfig-blog.rule: Host(`blog.edfig.dev`) + traefik.http.routers.edfig-blog.entrypoints: websecure + traefik.http.routers.edfig-blog.tls.certresolver: letsencrypt + traefik.http.routers.edfig-blog.service: caddy-static + # No SSO - public blog + + # figgy.foo (experimental/private) + traefik.http.routers.figgy-main.rule: Host(`figgy.foo`) || Host(`www.figgy.foo`) + traefik.http.routers.figgy-main.entrypoints: websecure + traefik.http.routers.figgy-main.tls.certresolver: letsencrypt + traefik.http.routers.figgy-main.service: caddy-static + traefik.http.routers.figgy-main.middlewares: tinyauth + # SSO protected - experimental/private content + + # Service definition (single backend for all routes) + traefik.http.services.caddy-static.loadbalancer.server.port: 80 + + # Homarr Discovery + homarr.name: Static Sites (Caddy) + homarr.group: Services + homarr.icon: mdi:web + +volumes: + caddy_data: + caddy_config: + +networks: + homelab: + external: true diff --git a/compose/services/static-sites/sites/blog.edfig.dev/index.html b/compose/services/static-sites/sites/blog.edfig.dev/index.html new file mode 100644 index 0000000..fcad278 --- /dev/null +++ b/compose/services/static-sites/sites/blog.edfig.dev/index.html @@ -0,0 +1,160 @@ + + + + + + Blog | Eduardo Figueroa + + + +
+

Blog

+

Thoughts on technology, systems, and automation

+
+ +
+ +
+

Coming Soon

+

Blog posts will appear here. Stay tuned!

+

+ In the meantime, you can write posts as:
+ + /srv/blog.edfig.dev/posts/my-post.md + +

+

+ Markdown files (.md) will automatically render as HTML! +

+
+ + + + + +
+ + diff --git a/compose/services/static-sites/sites/blog.edfig.dev/posts/example-post.md b/compose/services/static-sites/sites/blog.edfig.dev/posts/example-post.md new file mode 100644 index 0000000..14b1335 --- /dev/null +++ b/compose/services/static-sites/sites/blog.edfig.dev/posts/example-post.md @@ -0,0 +1,68 @@ +# Example Blog Post + +**Published:** January 10, 2025 +**Tags:** #homelab #docker #traefik + +--- + +## Introduction + +This is an example blog post written in Markdown. Caddy automatically renders `.md` files as HTML! + +## Why Markdown? + +Markdown is perfect for writing blog posts because: + +1. **Simple syntax** - Easy to write and read +2. **Fast** - No build step required +3. **Portable** - Works everywhere +4. **Clean** - Focus on content, not formatting + +## Code Examples + +Here's some example code: + +```bash +# Deploy a service +cd ~/homelab/compose/services/example +docker compose up -d + +# Check logs +docker logs example-service -f +``` + +## Features + +### Supported Elements + +- **Bold text** +- *Italic text* +- `Code snippets` +- [Links](https://edfig.dev) +- Lists (ordered and unordered) +- Code blocks with syntax highlighting +- Blockquotes +- Tables + +### Example Table + +| Service | URL | Purpose | +|---------|-----|---------| +| Traefik | traefik.fig.systems | Reverse Proxy | +| Sonarr | sonarr.fig.systems | TV Automation | +| Radarr | radarr.fig.systems | Movie Automation | + +## Blockquote Example + +> "The best way to predict the future is to invent it." +> — Alan Kay + +## Conclusion + +This is just an example post. Delete this file and create your own posts in the `posts/` directory! + +Each `.md` file will be automatically rendered when accessed via the browser. + +--- + +[← Back to Blog](/) diff --git a/compose/services/static-sites/sites/edfig.dev/index.html b/compose/services/static-sites/sites/edfig.dev/index.html new file mode 100644 index 0000000..dc47464 --- /dev/null +++ b/compose/services/static-sites/sites/edfig.dev/index.html @@ -0,0 +1,121 @@ + + + + + + Eduardo Figueroa + + + +
+

Eduardo Figueroa

+

Software Engineer & DevOps Enthusiast

+ +

+ Welcome to my personal site. I build scalable systems, automate infrastructure, + and explore the intersection of technology and efficiency. +

+ + + + +
+ + diff --git a/compose/services/static-sites/sites/figgy.foo/index.html b/compose/services/static-sites/sites/figgy.foo/index.html new file mode 100644 index 0000000..4cac6c9 --- /dev/null +++ b/compose/services/static-sites/sites/figgy.foo/index.html @@ -0,0 +1,191 @@ + + + + + + figgy.foo | Experimental Lab + + + +
+
+
+
+
+
+
+ figgy.foo — Terminal +
+
+
+ ______ _ ______ + | ____(_) | ____| + | |__ _ __ _ __ _ _ _| |__ ___ ___ + | __| | |/ _` |/ _` | | | | __| / _ \ / _ \ + | | | | (_| | (_| | |_| | | | (_) | (_) | + |_| |_|\__, |\__, |\__, |_| \___/ \___/ + __/ | __/ | __/ | + |___/ |___/ |___/ +
+ +
+ root@figgy:~$ whoami +
+
experimental lab user
+ +
+ root@figgy:~$ ls -la +
+
+drwxr-xr-x 3 root root 4096 Jan 10 2025 . +drwxr-xr-x 24 root root 4096 Jan 10 2025 .. +drwxr-xr-x 2 root root 4096 Jan 10 2025 experiments +-rw-r--r-- 1 root root 420 Jan 10 2025 README.txt +
+ +
+ root@figgy:~$ cat README.txt +
+
+╔════════════════════════════════════════════╗ +║ FIGGY.FOO EXPERIMENTAL LAB ║ +╚════════════════════════════════════════════╝ + +WARNING: This is a private experimental environment. + Access is restricted to authorized users only. + +Purpose: + • Test new services before production + • Develop and debug configurations + • Experiment with new technologies + • Break things safely + +Directory Browsing: ENABLED +Authentication: REQUIRED (SSO) + +Access Dashboard: home.fig.systems +Status Monitor: status.fig.systems +
+ +
+ root@figgy:~$ ./list-services.sh +
+
+Production Homelab: home.fig.systems +Professional Site: edfig.dev +Blog: blog.edfig.dev +
+ +
+ root@figgy:~$ +
+
+
+ + diff --git a/compose/services/vikunja/compose.yaml b/compose/services/vikunja/compose.yaml index e6c69d5..e15aef2 100644 --- a/compose/services/vikunja/compose.yaml +++ b/compose/services/vikunja/compose.yaml @@ -19,7 +19,7 @@ services: labels: traefik.enable: true traefik.docker.network: homelab - traefik.http.routers.vikunja.rule: Host(`tasks.fig.systems`) || Host(`tasks.edfig.dev`) + traefik.http.routers.vikunja.rule: Host(`tasks.fig.systems`) traefik.http.routers.vikunja.entrypoints: websecure traefik.http.routers.vikunja.tls.certresolver: letsencrypt traefik.http.services.vikunja.loadbalancer.server.port: 3456