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
This commit is contained in:
parent
07a8154fea
commit
953a9d52af
31 changed files with 1324 additions and 25 deletions
24
README.md
24
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
7
compose/services/static-sites/.env
Normal file
7
compose/services/static-sites/.env
Normal file
|
|
@ -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
|
||||
10
compose/services/static-sites/.gitignore
vendored
Normal file
10
compose/services/static-sites/.gitignore
vendored
Normal file
|
|
@ -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
|
||||
57
compose/services/static-sites/Caddyfile
Normal file
57
compose/services/static-sites/Caddyfile
Normal file
|
|
@ -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
|
||||
}
|
||||
604
compose/services/static-sites/README.md
Normal file
604
compose/services/static-sites/README.md
Normal file
|
|
@ -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 "<h1>About Me</h1>" > 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
|
||||
<!-- In any .html file under blog.edfig.dev -->
|
||||
<p>Page generated at: {{.Now.Format "2006-01-02 15:04:05"}}</p>
|
||||
```
|
||||
|
||||
**Example - Include header:**
|
||||
```html
|
||||
{{include "header.html"}}
|
||||
<main>
|
||||
<h1>My Page</h1>
|
||||
</main>
|
||||
{{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 "<h1>Test Site</h1>" > 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!** 🌐
|
||||
63
compose/services/static-sites/compose.yaml
Normal file
63
compose/services/static-sites/compose.yaml
Normal file
|
|
@ -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
|
||||
160
compose/services/static-sites/sites/blog.edfig.dev/index.html
Normal file
160
compose/services/static-sites/sites/blog.edfig.dev/index.html
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blog | Eduardo Figueroa</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 2em 0;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 1.1em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.post {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.post:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.post h2 {
|
||||
color: #667eea;
|
||||
margin-bottom: 0.5em;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
color: #999;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.post p {
|
||||
color: #555;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.read-more {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.read-more:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.no-posts {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.no-posts h2 {
|
||||
font-size: 2em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: center;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Blog</h1>
|
||||
<p>Thoughts on technology, systems, and automation</p>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<!-- Example post structure - replace with real posts -->
|
||||
<div class="no-posts">
|
||||
<h2>Coming Soon</h2>
|
||||
<p>Blog posts will appear here. Stay tuned!</p>
|
||||
<p style="margin-top: 2em;">
|
||||
In the meantime, you can write posts as:<br>
|
||||
<code style="background: #f5f5f5; padding: 5px 10px; border-radius: 5px;">
|
||||
/srv/blog.edfig.dev/posts/my-post.md
|
||||
</code>
|
||||
</p>
|
||||
<p style="margin-top: 1em; font-size: 0.9em; color: #666;">
|
||||
Markdown files (.md) will automatically render as HTML!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Example of how posts would look -->
|
||||
<!--
|
||||
<article class="post">
|
||||
<h2>Setting Up a Homelab with Docker and Traefik</h2>
|
||||
<div class="post-meta">January 10, 2025 • 5 min read</div>
|
||||
<p>
|
||||
Learn how to set up a complete homelab infrastructure using Docker Compose,
|
||||
Traefik for reverse proxy, and automated SSL certificates...
|
||||
</p>
|
||||
<a href="/posts/homelab-setup.html" class="read-more">
|
||||
Read more →
|
||||
</a>
|
||||
</article>
|
||||
-->
|
||||
|
||||
<nav>
|
||||
<a href="https://edfig.dev">← Back to Home</a>
|
||||
</nav>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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](/)
|
||||
121
compose/services/static-sites/sites/edfig.dev/index.html
Normal file
121
compose/services/static-sites/sites/edfig.dev/index.html
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Eduardo Figueroa</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
padding: 60px 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
margin-bottom: 0.5em;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.5em;
|
||||
color: #666;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.1em;
|
||||
color: #555;
|
||||
margin-bottom: 2em;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-block;
|
||||
padding: 12px 30px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 50px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.link.secondary {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border: 2px solid #667eea;
|
||||
}
|
||||
|
||||
.link.secondary:hover {
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 3em;
|
||||
padding-top: 2em;
|
||||
border-top: 1px solid #eee;
|
||||
color: #999;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Eduardo Figueroa</h1>
|
||||
<p class="subtitle">Software Engineer & DevOps Enthusiast</p>
|
||||
|
||||
<p class="description">
|
||||
Welcome to my personal site. I build scalable systems, automate infrastructure,
|
||||
and explore the intersection of technology and efficiency.
|
||||
</p>
|
||||
|
||||
<div class="links">
|
||||
<a href="https://blog.edfig.dev" class="link">Blog</a>
|
||||
<a href="https://github.com/efigueroa" class="link secondary" target="_blank">GitHub</a>
|
||||
<a href="https://home.fig.systems" class="link secondary">Homelab Dashboard</a>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Eduardo Figueroa | <a href="mailto:admin@edfig.dev" style="color: #667eea; text-decoration: none;">admin@edfig.dev</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
191
compose/services/static-sites/sites/figgy.foo/index.html
Normal file
191
compose/services/static-sites/sites/figgy.foo/index.html
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>figgy.foo | Experimental Lab</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Courier New', monospace;
|
||||
line-height: 1.6;
|
||||
color: #0f0;
|
||||
background: #000;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
background: #1a1a1a;
|
||||
border: 2px solid #0f0;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
background: #0f0;
|
||||
color: #000;
|
||||
padding: 10px 15px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.terminal-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.terminal-button {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.terminal-body {
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #0ff;
|
||||
}
|
||||
|
||||
.command {
|
||||
color: #ff0;
|
||||
}
|
||||
|
||||
.output {
|
||||
color: #0f0;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.line {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
background: #0f0;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #ff0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.ascii-art {
|
||||
color: #0f0;
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
white-space: pre;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="terminal">
|
||||
<div class="terminal-header">
|
||||
<div class="terminal-buttons">
|
||||
<div class="terminal-button"></div>
|
||||
<div class="terminal-button"></div>
|
||||
<div class="terminal-button"></div>
|
||||
</div>
|
||||
<span>figgy.foo — Terminal</span>
|
||||
</div>
|
||||
<div class="terminal-body">
|
||||
<div class="ascii-art">
|
||||
______ _ ______
|
||||
| ____(_) | ____|
|
||||
| |__ _ __ _ __ _ _ _| |__ ___ ___
|
||||
| __| | |/ _` |/ _` | | | | __| / _ \ / _ \
|
||||
| | | | (_| | (_| | |_| | | | (_) | (_) |
|
||||
|_| |_|\__, |\__, |\__, |_| \___/ \___/
|
||||
__/ | __/ | __/ |
|
||||
|___/ |___/ |___/
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
<span class="prompt">root@figgy:~$</span> <span class="command">whoami</span>
|
||||
</div>
|
||||
<div class="output line">experimental lab user</div>
|
||||
|
||||
<div class="line">
|
||||
<span class="prompt">root@figgy:~$</span> <span class="command">ls -la</span>
|
||||
</div>
|
||||
<div class="output line">
|
||||
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
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
<span class="prompt">root@figgy:~$</span> <span class="command">cat README.txt</span>
|
||||
</div>
|
||||
<div class="output line">
|
||||
╔════════════════════════════════════════════╗
|
||||
║ FIGGY.FOO EXPERIMENTAL LAB ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
<span class="warning">WARNING:</span> 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: <a href="https://home.fig.systems">home.fig.systems</a>
|
||||
Status Monitor: <a href="https://status.fig.systems">status.fig.systems</a>
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
<span class="prompt">root@figgy:~$</span> <span class="command">./list-services.sh</span>
|
||||
</div>
|
||||
<div class="output line">
|
||||
Production Homelab: <a href="https://home.fig.systems">home.fig.systems</a>
|
||||
Professional Site: <a href="https://edfig.dev">edfig.dev</a>
|
||||
Blog: <a href="https://blog.edfig.dev">blog.edfig.dev</a>
|
||||
</div>
|
||||
|
||||
<div class="line">
|
||||
<span class="prompt">root@figgy:~$</span> <span class="cursor"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue