Merge pull request #2 from efigueroa/claude/gitops-home-services-011CUqEzDETA2BqAzYUcXtjt

feat: Add service template, backup solution, dashboard, and IaC tooling
This commit is contained in:
Eduardo Figueroa 2025-11-05 14:06:11 -08:00 committed by GitHub
commit 312144f37e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 2480 additions and 0 deletions

View file

@ -32,6 +32,8 @@ compose/
│ ├── sabnzbd/ # Usenet downloader │ ├── sabnzbd/ # Usenet downloader
│ └── qbittorrent/# Torrent client │ └── qbittorrent/# Torrent client
└── services/ # Utility services └── services/ # Utility services
├── homarr/ # Dashboard (home.fig.systems)
├── backrest/ # Backup manager (backup.fig.systems)
├── linkwarden/ # Bookmark manager (links.fig.systems) ├── linkwarden/ # Bookmark manager (links.fig.systems)
├── vikunja/ # Task management (tasks.fig.systems) ├── vikunja/ # Task management (tasks.fig.systems)
├── lubelogger/ # Vehicle tracker (garage.fig.systems) ├── lubelogger/ # Vehicle tracker (garage.fig.systems)
@ -56,6 +58,8 @@ All services are accessible via:
| Traefik Dashboard | traefik.fig.systems | ✅ | | Traefik Dashboard | traefik.fig.systems | ✅ |
| LLDAP | lldap.fig.systems | ✅ | | LLDAP | lldap.fig.systems | ✅ |
| Tinyauth | auth.fig.systems | ❌ | | Tinyauth | auth.fig.systems | ❌ |
| Homarr | home.fig.systems | ✅ |
| Backrest | backup.fig.systems | ✅ |
| Jellyfin | flix.fig.systems | ❌* | | Jellyfin | flix.fig.systems | ❌* |
| Jellyseerr | requests.fig.systems | ✅ | | Jellyseerr | requests.fig.systems | ✅ |
| Immich | photos.fig.systems | ❌* | | Immich | photos.fig.systems | ❌* |
@ -143,6 +147,8 @@ cd compose/media/automation/qbittorrent && docker compose up -d
# Utility services # Utility services
cd compose/services/linkwarden && docker compose up -d cd compose/services/linkwarden && docker compose up -d
cd compose/services/vikunja && docker compose up -d cd compose/services/vikunja && docker compose up -d
cd compose/services/homarr && docker compose up -d
cd compose/services/backrest && docker compose up -d
cd compose/services/lubelogger && docker compose up -d cd compose/services/lubelogger && docker compose up -d
cd compose/services/calibre-web && docker compose up -d cd compose/services/calibre-web && docker compose up -d
cd compose/services/booklore && docker compose up -d cd compose/services/booklore && docker compose up -d

View file

@ -0,0 +1,16 @@
# Backrest Configuration
# Backrest provides a web UI for managing Restic backups
# Timezone
TZ=America/Los_Angeles
# Note: Backup repositories and credentials are configured through the web UI
# Access the UI at https://backup.fig.systems to set up your Backblaze B2 repository
# Example B2 repository format (configured in web UI):
# Repository Type: S3 (S3-compatible storage)
# Endpoint: s3.us-west-002.backblazeb2.com
# Bucket: your-bucket-name
# Access Key ID: your-b2-key-id
# Secret Access Key: your-b2-application-key
# Path: /backrest-backups

7
compose/services/backrest/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# Backrest data and cache
data/
config/
cache/
# Keep .env.example if created
!.env.example

View file

@ -0,0 +1,346 @@
# Backrest - Web UI for Restic Backups
Backrest provides a modern web interface for managing Restic backups with support for Backblaze B2 (S3-compatible) storage.
## Features
- **Web UI**: User-friendly interface for managing backups
- **Encrypted Backups**: Uses Restic for encrypted, deduplicated backups
- **Scheduling**: Built-in scheduler for automatic backups
- **Monitoring**: View backup status, logs, and statistics
- **Multiple Repositories**: Support for multiple backup destinations
- **Retention Policies**: Automatic cleanup of old backups
- **Notifications**: Email and webhook notifications for backup events
## Quick Start
### 1. Start the Service
```bash
cd compose/services/backrest
docker compose up -d
```
### 2. Access Web UI
Open your browser and navigate to:
- https://backup.fig.systems
- Or: https://backup.edfig.dev
Login with your SSO credentials (tinyauth).
### 3. Configure Backblaze B2 Repository
1. **Create B2 Bucket** (if not already done):
- Go to https://secure.backblaze.com/b2_buckets.htm
- Click "Create a Bucket"
- Name: `homelab-backups` (or your choice)
- Files: Private
- Encryption: Server-Side (or Disabled - Backrest encrypts client-side)
2. **Create Application Key**:
- Go to https://secure.backblaze.com/app_keys.htm
- Click "Add a New Application Key"
- Name: `backrest-homelab`
- Access: Read and Write
- Bucket: Select your backup bucket
- Save the `keyID` and `applicationKey`
3. **Add Repository in Backrest**:
- Click "Add Repository"
- Repository Name: `B2 Immich Photos`
- Storage Type: `S3-compatible storage`
- Configuration:
```
Endpoint: s3.us-west-002.backblazeb2.com
Region: us-west-002
Bucket: homelab-backups
Path: /immich-photos
Access Key ID: [your B2 keyID]
Secret Access Key: [your B2 applicationKey]
```
- Encryption Password: Set a strong password (SAVE THIS!)
- Click "Initialize Repository"
### 4. Create Backup Plan
1. **Add Plan**:
- Click "Add Plan"
- Plan Name: `Immich Daily Backup`
- Repository: Select your B2 repository
- Paths to Backup:
- `/backups/immich`
- Exclude Patterns (optional):
- `*.tmp`
- `*.log`
2. **Schedule**:
- Backup Schedule: `0 3 * * *` (3 AM daily)
- Enable "Automatic Backups"
3. **Retention Policy**:
- Keep Last: 7 daily backups
- Keep Weekly: 4 weekly backups
- Keep Monthly: 6 monthly backups
- Keep Yearly: 2 yearly backups
4. **Notifications** (optional):
- Configure email or webhook for backup status
- Alert on failures
### 5. Run First Backup
Click "Run Now" to start your first backup immediately.
## Backup Locations
The service has access to these directories:
- `/backups/immich` - Immich photos (read-only)
- `/backups/homelab-config` - All compose configurations (read-only)
You can add more volumes in `compose.yaml` as needed.
## Monitoring
### View Backup Status
In the Backrest web UI:
- Dashboard shows all backup plans and their status
- Click on a plan to see backup history
- View logs for detailed information
### Check Repository Size
```bash
# Via web UI: Repository → Statistics
# Shows: Total size, deduplicated size, number of snapshots
```
### Verify Backups
Backrest has built-in verification:
1. Go to Repository → Verify
2. Click "Run Verification"
3. Check results for any errors
## Restore Files
### Via Web UI
1. Go to Plan → Snapshots
2. Select snapshot to restore
3. Click "Browse Files"
4. Select files/folders to restore
5. Choose restore location
6. Click "Restore"
### Via CLI (Advanced)
```bash
# List snapshots
docker exec backrest restic -r [repository] snapshots
# Restore specific snapshot
docker exec backrest restic -r [repository] restore [snapshot-id] --target /restore
# Restore specific file
docker exec backrest restic -r [repository] restore [snapshot-id] --target /restore --include /path/to/file
```
## Configuration Backup
### Backup Backrest Config
Your Backrest configuration (plans, schedules, repositories) is stored in:
- `./config/config.json`
**Important**: Backup this file! It contains your repository credentials (encrypted).
```bash
# Create backup
cp config/config.json config/config.json.backup
# Restore backup
cp config/config.json.backup config/config.json
docker compose restart
```
### Export Configuration
In Web UI:
1. Settings → Export Configuration
2. Save JSON file securely
3. Store encryption passwords separately
## Troubleshooting
### Cannot Access Web UI
Check container status:
```bash
docker compose logs backrest
docker compose ps
```
Verify Traefik routing:
```bash
docker logs traefik | grep backrest
```
### Backup Fails
1. **Check Logs**:
- Web UI: Plan → View Logs
- Or: `docker compose logs -f backrest`
2. **Verify B2 Credentials**:
- Test connection in Repository settings
- Ensure application key has read/write access
3. **Check Disk Space**:
```bash
df -h
docker exec backrest df -h /cache
```
### Repository Locked
If a backup is interrupted, the repository may be locked:
```bash
# Via Web UI: Repository → Unlock
# Or via CLI:
docker exec backrest restic -r [repository] unlock
```
### Slow Backups
1. **Enable Caching**: Already configured via `XDG_CACHE_HOME`
2. **Increase Upload Speed**: Check B2 endpoint is geographically close
3. **Exclude Unnecessary Files**: Add patterns to exclude list
## Security Considerations
### Encryption
- **Client-side**: All data encrypted before upload
- **Repository Password**: Required to access backups
- **Storage**: Store repository passwords in password manager
### Access Control
- **SSO Protected**: Web UI requires authentication via tinyauth
- **API Keys**: B2 application keys scoped to specific bucket
- **Read-Only Mounts**: Backup sources mounted read-only
### Best Practices
1. **Test Restores**: Regularly test restoring files
2. **Monitor Backups**: Check backup status weekly
3. **Verify Integrity**: Run verification monthly
4. **Secure Passwords**: Use strong, unique repository passwords
5. **Document Recovery**: Keep recovery procedures documented
6. **Offsite Storage**: B2 provides geographic redundancy
## Advanced Configuration
### Add More Backup Sources
Edit `compose.yaml` to add more volumes:
```yaml
volumes:
- /path/to/backup:/backups/name:ro
```
Then create a new backup plan in the web UI.
### Multiple Repositories
Configure multiple destinations:
1. Primary: Backblaze B2
2. Secondary: Local NAS/USB drive
3. Archive: Another cloud provider
### Webhooks
Configure webhooks for monitoring:
1. Settings → Notifications
2. Add Webhook URL (e.g., Discord, Slack, Uptime Kuma)
3. Select events: Backup Success, Backup Failure
### Custom Retention
Fine-tune retention policies:
```
--keep-within 7d
--keep-within-daily 30d
--keep-within-weekly 90d
--keep-within-monthly 1y
--keep-within-yearly 5y
```
## Resource Usage
**Typical Usage:**
- CPU: Low (spikes during backup)
- Memory: ~200-500MB
- Disk: Cache grows over time (monitor)
- Network: Depends on backup size
**Monitoring Cache Size:**
```bash
du -sh compose/services/backrest/cache
```
Clean cache if needed (safe to delete - will rebuild):
```bash
rm -rf compose/services/backrest/cache/*
docker compose restart
```
## Backrest vs Duplicati
We chose Backrest over Duplicati because:
- **Modern**: Built on Restic (actively developed)
- **Performance**: Better deduplication and compression
- **Reliability**: Restic is battle-tested
- **Features**: More advanced scheduling and monitoring
- **UI**: Clean, responsive interface
## Cost Estimation
**Backblaze B2 Pricing (2024):**
- Storage: $0.006/GB/month
- Download: $0.01/GB (first 3x storage free)
- Upload: Free
**Example: 100GB Immich photos**
- Storage Cost: $0.60/month
- Download (3 restores/month): Free
- **Total: ~$0.60/month**
**With Deduplication:**
- First backup: 100GB
- Daily incrementals: ~1-5GB
- Monthly growth: ~20GB
- Avg monthly cost: ~$0.70
## Resources
- [Backrest Documentation](https://github.com/garethgeorge/backrest)
- [Restic Documentation](https://restic.readthedocs.io/)
- [Backblaze B2 Documentation](https://www.backblaze.com/b2/docs/)
- [S3-compatible API Guide](https://www.backblaze.com/b2/docs/s3_compatible_api.html)
## Next Steps
1. ✅ Configure B2 repository
2. ✅ Create backup plan for Immich
3. ⬜ Run initial backup
4. ⬜ Verify backup integrity
5. ⬜ Test restore procedure
6. ⬜ Set up notifications
7. ⬜ Add homelab-config backups
8. ⬜ Schedule monthly verification

View file

@ -0,0 +1,49 @@
services:
backrest:
image: garethgeorge/backrest:latest
container_name: backrest
hostname: backrest
restart: unless-stopped
networks:
- homelab
volumes:
- ./data:/data
- ./config:/config
- ./cache:/cache
- /home/user/homelab/compose/media/frontend/immich/upload:/backups/immich:ro
- /home/user/homelab/compose:/backups/homelab-config:ro
environment:
- BACKREST_DATA=/data
- BACKREST_CONFIG=/config/config.json
- XDG_CACHE_HOME=/cache
- TZ=${TZ:-America/Los_Angeles}
labels:
# Traefik
traefik.enable: true
traefik.http.routers.backrest.rule: Host(`backup.fig.systems`) || Host(`backup.edfig.dev`)
traefik.http.routers.backrest.entrypoints: websecure
traefik.http.routers.backrest.tls.certresolver: letsencrypt
traefik.http.services.backrest.loadbalancer.server.port: 9898
# Require authentication
traefik.http.routers.backrest.middlewares: tinyauth
# Homarr Discovery
homarr.name: Backrest Backup
homarr.group: Services
homarr.icon: mdi:backup-restore
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:9898/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
homelab:
external: true

View file

@ -0,0 +1,332 @@
# Homarr Dashboard
Modern, customizable dashboard with automatic Docker service discovery.
## Features
- 🎨 **Modern UI** - Beautiful, responsive design
- 🔍 **Auto-Discovery** - Automatically finds Docker services
- 📊 **Widgets** - System stats, weather, calendar, RSS, etc.
- 🏷️ **Labels** - Organize services by category
- 🔗 **Integration** - Connects to *arr apps, Jellyfin, etc.
- 🎯 **Customizable** - Drag-and-drop layout
- 🌙 **Dark Mode** - Built-in dark theme
- 📱 **Mobile Friendly** - Works on all devices
## Access
- **URL:** https://home.fig.systems or https://home.edfig.dev
- **Port:** 7575 (if accessing directly)
## First-Time Setup
### 1. Deploy Homarr
```bash
cd compose/services/homarr
docker compose up -d
```
### 2. Access Dashboard
Open https://home.fig.systems in your browser.
### 3. Auto-Discovery
Homarr will automatically detect services with these labels:
```yaml
labels:
homarr.name: "Service Name"
homarr.group: "Category"
homarr.icon: "/icons/service.png"
homarr.href: "https://service.fig.systems"
```
## Adding Services to Dashboard
### Automatic (Recommended)
Add labels to your service's `compose.yaml`:
```yaml
labels:
# Traefik labels...
traefik.enable: true
# ... etc
# Homarr labels
homarr.name: Jellyfin
homarr.group: Media
homarr.icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/jellyfin.png
homarr.href: https://flix.fig.systems
```
Redeploy the service:
```bash
docker compose up -d
```
Homarr will automatically add it to the dashboard!
### Manual
1. Click the "+" button in Homarr
2. Select "Add Service"
3. Fill in:
- **Name:** Service name
- **URL:** https://service.fig.systems
- **Icon:** Choose from library or custom URL
- **Category:** Group services (Media, Services, etc.)
## Integration with Services
### Jellyfin
Add to Jellyfin's `compose.yaml`:
```yaml
labels:
homarr.name: Jellyfin
homarr.group: Media
homarr.icon: /icons/jellyfin.png
homarr.widget.type: jellyfin
homarr.widget.url: http://jellyfin:8096
homarr.widget.key: ${JELLYFIN_API_KEY}
```
Shows: Currently playing, library stats
### Sonarr/Radarr
```yaml
labels:
homarr.name: Sonarr
homarr.group: Media Automation
homarr.icon: /icons/sonarr.png
homarr.widget.type: sonarr
homarr.widget.url: http://sonarr:8989
homarr.widget.key: ${SONARR_API_KEY}
```
Shows: Queue, calendar, missing episodes
### qBittorrent
```yaml
labels:
homarr.name: qBittorrent
homarr.group: Downloads
homarr.icon: /icons/qbittorrent.png
homarr.widget.type: qbittorrent
homarr.widget.url: http://qbittorrent:8080
homarr.widget.username: ${QBIT_USERNAME}
homarr.widget.password: ${QBIT_PASSWORD}
```
Shows: Active torrents, download speed
## Available Widgets
### System Monitoring
- **CPU Usage** - Real-time CPU stats
- **Memory Usage** - RAM usage
- **Disk Space** - Storage capacity
- **Network** - Upload/download speeds
### Services
- **Jellyfin** - Media server stats
- **Sonarr** - TV show automation
- **Radarr** - Movie automation
- **Lidarr** - Music automation
- **Readarr** - Book automation
- **Prowlarr** - Indexer management
- **SABnzbd** - Usenet downloads
- **qBittorrent** - Torrent downloads
- **Overseerr/Jellyseerr** - Media requests
### Utilities
- **Weather** - Local weather forecast
- **Calendar** - Events and tasks
- **RSS Feeds** - News aggregator
- **Docker** - Container status
- **Speed Test** - Internet speed
- **Notes** - Sticky notes
- **Iframe** - Embed any website
## Customization
### Change Theme
1. Click settings icon (⚙️)
2. Go to "Appearance"
3. Choose color scheme
4. Save
### Reorganize Layout
1. Click edit mode (✏️)
2. Drag and drop services
3. Resize widgets
4. Click save
### Add Categories
1. Click "Add Category"
2. Name it (e.g., "Media", "Tools", "Infrastructure")
3. Drag services into categories
4. Collapse/expand as needed
### Custom Icons
**Option 1: Use Icon Library**
- Homarr includes icons from [Dashboard Icons](https://github.com/walkxcode/dashboard-icons)
- Search by service name
**Option 2: Custom URL**
```
https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/service.png
```
**Option 3: Local Icons**
- Place in `./icons/` directory
- Reference as `/icons/service.png`
## Recommended Dashboard Layout
```
┌─────────────────────────────────────────┐
│ 🏠 Homelab Dashboard │
├─────────────────────────────────────────┤
│ [System Stats] [Weather] [Calendar] │
├─────────────────────────────────────────┤
│ 📺 Media │
│ [Jellyfin] [Jellyseerr] [Immich] │
├─────────────────────────────────────────┤
│ 🤖 Media Automation │
│ [Sonarr] [Radarr] [qBittorrent] │
├─────────────────────────────────────────┤
│ 🛠️ Services │
│ [Linkwarden] [Vikunja] [FreshRSS] │
├─────────────────────────────────────────┤
│ 🔧 Infrastructure │
│ [Traefik] [LLDAP] [Tinyauth] │
└─────────────────────────────────────────┘
```
## Add to All Services
To make all your services auto-discoverable, add these labels:
### Jellyfin
```yaml
homarr.name: Jellyfin
homarr.group: Media
homarr.icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/jellyfin.png
```
### Jellyseerr
```yaml
homarr.name: Jellyseerr
homarr.group: Media
homarr.icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/jellyseerr.png
```
### Immich
```yaml
homarr.name: Immich Photos
homarr.group: Media
homarr.icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/immich.png
```
### Sonarr/Radarr/SABnzbd/qBittorrent
```yaml
homarr.name: [Service]
homarr.group: Automation
homarr.icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/[service].png
```
### Linkwarden/Vikunja/etc.
```yaml
homarr.name: [Service]
homarr.group: Utilities
homarr.icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/[service].png
```
## Mobile Access
Homarr is fully responsive. For best mobile experience:
1. Add to home screen (iOS/Android)
2. Works as PWA (Progressive Web App)
3. Touch-optimized interface
## Backup Configuration
### Backup
```bash
cd compose/services/homarr
tar -czf homarr-backup-$(date +%Y%m%d).tar.gz config/ data/
```
### Restore
```bash
cd compose/services/homarr
tar -xzf homarr-backup-YYYYMMDD.tar.gz
docker compose restart
```
## Troubleshooting
### Services not auto-discovered
Check Docker socket permission:
```bash
docker logs homarr
```
Verify labels on service:
```bash
docker inspect service-name | grep homarr
```
### Can't connect to services
Services must be on same Docker network or accessible via hostname.
Use container names, not `localhost`:
- ✅ `http://jellyfin:8096`
- ❌ `http://localhost:8096`
### Widgets not working
1. Check API keys are correct
2. Verify service URLs (use container names)
3. Check service is running: `docker ps`
## Alternatives Considered
| Dashboard | Auto-Discovery | Widgets | Complexity |
|-----------|---------------|---------|------------|
| **Homarr** | ✅ Excellent | ✅ Many | Low |
| Homepage | ✅ Good | ✅ Many | Low |
| Heimdall | ❌ Manual | ❌ Few | Very Low |
| Dashy | ⚠️ Limited | ✅ Some | Medium |
| Homer | ❌ Manual | ❌ None | Very Low |
| Organizr | ⚠️ Limited | ✅ Many | High |
**Homarr chosen for:** Best balance of features, auto-discovery, and ease of use.
## Resources
- [Official Docs](https://homarr.dev/docs)
- [GitHub](https://github.com/ajnart/homarr)
- [Discord Community](https://discord.gg/aCsmEV5RgA)
- [Icon Library](https://github.com/walkxcode/dashboard-icons)
## Tips
1. **Start Simple** - Add core services first, expand later
2. **Use Categories** - Group related services
3. **Enable Widgets** - Make dashboard informative
4. **Mobile First** - Test on phone/tablet
5. **Backup Config** - Save your layout regularly

View file

@ -0,0 +1,57 @@
# Homarr - Modern dashboard with Docker auto-discovery
# Docs: https://homarr.dev/docs/getting-started/installation
# GitHub: https://github.com/ajnart/homarr
services:
homarr:
container_name: homarr
image: ghcr.io/ajnart/homarr:latest
environment:
# Timezone
- TZ=America/Los_Angeles
# Base path (if behind reverse proxy with path)
# - BASE_URL=/dashboard
# Port (default: 7575)
- PORT=7575
# Authentication
# - AUTH_PROVIDER=oidc # For SSO integration
# - DEFAULT_COLOR_SCHEME=dark
volumes:
# Configuration and data
- ./config:/app/data/configs
- ./data:/data
- ./icons:/app/public/icons
# Docker socket for auto-discovery
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
# Web UI routing
traefik.http.routers.homarr.rule: Host(`home.fig.systems`) || Host(`home.edfig.dev`)
traefik.http.routers.homarr.entrypoints: websecure
traefik.http.routers.homarr.tls.certresolver: letsencrypt
traefik.http.services.homarr.loadbalancer.server.port: 7575
# SSO Protection (optional - dashboard may be public)
# traefik.http.routers.homarr.middlewares: tinyauth
# Homarr Labels for custom configuration
homarr.name: Homarr Dashboard
homarr.group: Infrastructure
homarr.icon: /icons/homarr.png
networks:
homelab:
external: true

View file

@ -0,0 +1,45 @@
# Service Configuration
# Copy this file to .env and update with your actual values
# Timezone
TZ=America/Los_Angeles
# User/Group IDs (match your host user)
PUID=1000
PGID=1000
# Service-specific configuration
# Uncomment and configure as needed:
# Database Configuration (if using database)
# DB_PASSWORD=changeme_please_set_secure_password
# DB_USERNAME=serviceuser
# DB_DATABASE_NAME=servicedb
# Redis Configuration (if using Redis)
# REDIS_PASSWORD=changeme_please_set_secure_password
# Application Settings
# APP_URL=https://service.fig.systems
# APP_SECRET=changeme_please_set_random_secret
# API_KEY=changeme_your_api_key_here
# Email/SMTP Configuration (if needed)
# SMTP_HOST=smtp.gmail.com
# SMTP_PORT=587
# SMTP_USER=your-email@gmail.com
# SMTP_PASSWORD=changeme_your_app_password
# SMTP_FROM=Service <noreply@fig.systems>
# Storage Configuration
# STORAGE_PATH=/data
# MAX_UPLOAD_SIZE=100M
# Security
# ADMIN_EMAIL=admin@edfig.dev
# ADMIN_PASSWORD=changeme_please_set_secure_password
# Feature Flags (example)
# ENABLE_REGISTRATION=false
# ENABLE_API=true
# LOG_LEVEL=info

View file

@ -0,0 +1,250 @@
# Service Template
This template provides a starting point for adding new services to your homelab.
## Quick Start
1. **Copy this template:**
```bash
cp -r templates/service-template compose/[category]/[service-name]
cd compose/[category]/[service-name]
```
2. **Choose the correct category:**
- `compose/core/` - Infrastructure services (reverse proxy, auth, etc.)
- `compose/media/` - Media-related services (streaming, automation)
- `compose/services/` - Utility services (bookmarks, tasks, etc.)
3. **Update compose.yaml:**
- Replace `service-name` with actual service name
- Update `image:` with the correct Docker image
- Configure environment variables
- Update port numbers
- Update Traefik domain (replace `service.fig.systems`)
- Add any required volumes or dependencies
4. **Create .env file (if needed):**
```bash
cp .env.example .env
# Edit .env and set real values
```
5. **Update README.md:**
- Add service to main README.md service table
- Include service URL and description
- Document any special configuration
6. **Test deployment:**
```bash
docker compose config # Validate syntax
docker compose up -d # Deploy
docker compose logs -f # Check logs
```
## Template Features
### Included by Default
- ✅ Traefik integration with SSL/TLS
- ✅ Dual domain support (fig.systems + edfig.dev)
- ✅ SSO middleware support (commented)
- ✅ Network configuration (homelab external)
- ✅ Standard environment variables (PUID, PGID, TZ)
- ✅ Restart policy
- ✅ Health check template
### Optional Components (Commented)
- Database service (PostgreSQL example)
- Redis cache
- Internal network for multi-container setups
- Port exposure (prefer Traefik)
- Named volumes
- Health checks
## Common Patterns
### Simple Single-Container Service
```yaml
services:
app:
image: app:latest
volumes:
- ./config:/config
networks:
- homelab
labels:
# Traefik config
```
### Multi-Container with Database
```yaml
services:
app:
image: app:latest
depends_on:
- database
networks:
- homelab
- app_internal
database:
image: postgres:16-alpine
networks:
- app_internal
networks:
homelab:
external: true
app_internal:
driver: bridge
```
### Service with Media Access
```yaml
services:
app:
image: app:latest
volumes:
- ./config:/config
- /media/movies:/movies:ro
- /media/books:/books:ro
```
## Checklist
Before submitting a PR with a new service:
- [ ] Service name is descriptive and lowercase with hyphens
- [ ] Docker image is from a trusted source
- [ ] All placeholder passwords use `changeme_*` format
- [ ] Traefik labels are complete (router, entrypoint, tls, rule)
- [ ] Both domains configured (fig.systems + edfig.dev)
- [ ] SSO middleware decision made (enabled/disabled with comment)
- [ ] Networks properly configured (external: true)
- [ ] Health check added (if applicable)
- [ ] Service added to README.md
- [ ] Documentation header in compose.yaml
- [ ] .env.example provided (if using env_file)
- [ ] Tested locally before committing
## Domain Selection
Choose a subdomain that makes sense:
**Common Patterns:**
- Service name: `servicename.fig.systems` (most common)
- Function-based: `monitor.fig.systems`, `backup.fig.systems`
- Alternative names: `flix.fig.systems` (Jellyfin), `requests.fig.systems` (Jellyseerr)
**Reserved Domains:**
- `auth.fig.systems` - Tinyauth
- `lldap.fig.systems` - LLDAP
- `traefik.fig.systems` - Traefik dashboard
- See README.md for complete list
## Network Configuration
### Single Container
```yaml
networks:
homelab:
external: true
```
### Multi-Container (with internal network)
```yaml
networks:
homelab:
external: true # For Traefik access
service_internal:
name: service_internal
driver: bridge # For inter-container communication
```
### Traefik Network Selection
If using multiple networks, specify which Traefik should use:
```yaml
labels:
traefik.docker.network: homelab
```
## Volume Patterns
### Configuration Only
```yaml
volumes:
- ./config:/config
```
### With Data Storage
```yaml
volumes:
- ./config:/config
- ./data:/data
```
### With Media Access (read-only recommended)
```yaml
volumes:
- ./config:/config
- /media/movies:/movies:ro
- /media/tv:/tv:ro
```
### With Database
```yaml
volumes:
- ./db:/var/lib/postgresql/data
```
## Troubleshooting
### Service won't start
```bash
# Check logs
docker compose logs app
# Validate compose syntax
docker compose config
# Check network exists
docker network ls | grep homelab
```
### Can't access via domain
```bash
# Check Traefik is running
docker ps | grep traefik
# Check Traefik logs
docker logs traefik
# Verify DNS points to server
dig service.fig.systems
# Check SSL certificate
curl -I https://service.fig.systems
```
### Permission errors
```bash
# Check PUID/PGID match your user
id
# Fix ownership
sudo chown -R 1000:1000 ./config ./data
```
## Examples
See these services for reference:
- **Simple:** `compose/services/filebrowser/`
- **With database:** `compose/services/vikunja/`
- **Multi-container:** `compose/media/frontend/immich/`
- **Media service:** `compose/media/frontend/jellyfin/`
## Resources
- [Docker Compose Docs](https://docs.docker.com/compose/)
- [Traefik Docker Provider](https://doc.traefik.io/traefik/providers/docker/)
- [LinuxServer.io Images](https://fleet.linuxserver.io/)
- [Awesome Selfhosted](https://github.com/awesome-selfhosted/awesome-selfhosted)

View file

@ -0,0 +1,118 @@
# Service Name - Brief Description
# Official Docs: https://docs.example.com
# Docker Hub: https://hub.docker.com/r/example/service
services:
service-name:
container_name: service-name
image: example/service:latest
# Environment Variables
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
# Add service-specific variables here
# Optional: Use .env file for sensitive config
# env_file: .env
# Volumes - Persistent data storage
volumes:
- ./config:/config
- ./data:/data
# Add media folder mounts if needed:
# - /media/movies:/movies:ro
# - /media/books:/books:ro
# Ports (optional - prefer Traefik routing)
# ports:
# - "8080:8080"
# Restart Policy
restart: unless-stopped
# Networks
networks:
- homelab
# Add internal network if multi-container:
# - service_internal
# Health Check (optional but recommended)
# healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
# interval: 30s
# timeout: 10s
# retries: 3
# start_period: 40s
# Dependencies (if needed)
# depends_on:
# - database
# - redis
# Traefik Labels - Web Access with SSL
labels:
traefik.enable: true
# Routing - Replace 'service' with actual service name
traefik.http.routers.service-name.rule: Host(`service.fig.systems`) || Host(`service.edfig.dev`)
traefik.http.routers.service-name.entrypoints: websecure
traefik.http.routers.service-name.tls.certresolver: letsencrypt
# Service port - Change to actual port
traefik.http.services.service-name.loadbalancer.server.port: 8080
# SSO Protection (optional - uncomment if needed)
traefik.http.routers.service-name.middlewares: tinyauth
# Network selection (if using multiple networks)
# traefik.docker.network: homelab
# Optional: Database service (PostgreSQL example)
# database:
# container_name: service-name-db
# image: postgres:16-alpine
# environment:
# POSTGRES_DB: servicedb
# POSTGRES_USER: serviceuser
# POSTGRES_PASSWORD: ${DB_PASSWORD}
# TZ: America/Los_Angeles
# volumes:
# - ./db:/var/lib/postgresql/data
# restart: unless-stopped
# networks:
# - service_internal
# healthcheck:
# test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER"]
# interval: 10s
# timeout: 5s
# retries: 5
# Optional: Redis cache
# redis:
# container_name: service-name-redis
# image: redis:alpine
# restart: unless-stopped
# networks:
# - service_internal
# healthcheck:
# test: ["CMD", "redis-cli", "ping"]
# interval: 30s
# timeout: 10s
# retries: 5
# Networks
networks:
homelab:
external: true
# Internal network (if multi-container service)
# service_internal:
# name: service_internal
# driver: bridge
# Named Volumes (optional)
# volumes:
# data:
# cache:

506
terraform/README.md Normal file
View file

@ -0,0 +1,506 @@
# OpenTofu Infrastructure as Code for Proxmox
This directory contains OpenTofu (Terraform) configurations for managing Proxmox infrastructure.
## What is OpenTofu?
OpenTofu is an open-source fork of Terraform, providing Infrastructure as Code (IaC) capabilities. It allows you to:
- 📝 **Define infrastructure as code** - Version control your infrastructure
- 🔄 **Automate provisioning** - Create VMs/containers with one command
- 🎯 **Consistency** - Same config = same result every time
- 🔍 **Plan changes** - Preview changes before applying
- 🗑️ **Easy cleanup** - Destroy infrastructure when done
## Why OpenTofu over Terraform?
- ✅ **Truly Open Source** - MPL 2.0 license (vs. Terraform's BSL)
- ✅ **Community Driven** - Not controlled by single company
- ✅ **Terraform Compatible** - Drop-in replacement
- ✅ **Active Development** - Regular updates and features
## Quick Start
### 1. Install OpenTofu
**Linux/macOS:**
```bash
# Install via package manager
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh | sh
# Or via Homebrew (macOS/Linux)
brew install opentofu
```
**Verify installation:**
```bash
tofu version
```
### 2. Configure Proxmox API
**Create API Token in Proxmox:**
1. Login to Proxmox web UI
2. Datacenter → Permissions → API Tokens
3. Add new token:
- User: `root@pam`
- Token ID: `terraform`
- Privilege Separation: Unchecked (for full access)
4. Save the token ID and secret!
**Set environment variables:**
```bash
export PM_API_URL="https://proxmox.local:8006/api2/json"
export PM_API_TOKEN_ID="root@pam!terraform"
export PM_API_TOKEN_SECRET="your-secret-here"
# Verify SSL (optional, set to false for self-signed certs)
export PM_TLS_INSECURE=true
```
### 3. Choose Your Use Case
We provide examples for common scenarios:
| Example | Description | Best For |
|---------|-------------|----------|
| [single-vm](./proxmox-examples/single-vm/) | Simple Ubuntu VM | Learning, testing |
| [docker-host](./proxmox-examples/docker-host/) | VM for Docker containers | Production homelab |
| [lxc-containers](./proxmox-examples/lxc-containers/) | Lightweight LXC containers | Resource efficiency |
| [multi-node](./proxmox-examples/multi-node/) | Multiple VMs/services | Complex deployments |
| [cloud-init](./proxmox-examples/cloud-init/) | Cloud-init automation | Production VMs |
## Directory Structure
```
terraform/
├── README.md # This file
├── proxmox-examples/
│ ├── single-vm/ # Simple VM example
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── docker-host/ # Docker host VM
│ ├── lxc-containers/ # LXC container examples
│ ├── multi-node/ # Multiple VM deployment
│ └── cloud-init/ # Cloud-init examples
└── modules/ # Reusable modules (future)
```
## Basic Workflow
### Initialize
```bash
cd proxmox-examples/single-vm
tofu init
```
### Plan
```bash
tofu plan
```
Preview changes before applying.
### Apply
```bash
tofu apply
```
Review plan and type `yes` to proceed.
### Destroy
```bash
tofu destroy
```
Removes all managed resources.
## Common Commands
```bash
# Initialize and download providers
tofu init
# Validate configuration syntax
tofu validate
# Format code to standard style
tofu fmt
# Preview changes
tofu plan
# Apply changes
tofu apply
# Apply without confirmation (careful!)
tofu apply -auto-approve
# Show current state
tofu show
# List all resources
tofu state list
# Destroy specific resource
tofu destroy -target=proxmox_vm_qemu.vm
# Destroy everything
tofu destroy
```
## Provider Configuration
### Proxmox Provider
```hcl
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.50"
}
}
}
provider "proxmox" {
endpoint = var.pm_api_url
api_token = "${var.pm_token_id}!${var.pm_token_secret}"
insecure = true # For self-signed certs
ssh {
agent = true
}
}
```
## Best Practices
### 1. Use Variables
Don't hardcode values:
```hcl
# Bad
target_node = "pve"
# Good
target_node = var.proxmox_node
```
### 2. Use terraform.tfvars
Store configuration separately:
```hcl
# terraform.tfvars
proxmox_node = "pve"
vm_name = "docker-host"
vm_cores = 4
vm_memory = 8192
```
### 3. Version Control
**Commit:**
- ✅ `*.tf` files
- ✅ `*.tfvars` (if no secrets)
- ✅ `.terraform.lock.hcl`
**DO NOT commit:**
- ❌ `terraform.tfstate`
- ❌ `terraform.tfstate.backup`
- ❌ `.terraform/` directory
- ❌ Secrets/passwords
Use `.gitignore`:
```
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars # If contains secrets
```
### 4. Use Modules
For reusable components:
```hcl
module "docker_vm" {
source = "./modules/docker-host"
vm_name = "docker-01"
cores = 4
memory = 8192
}
```
### 5. State Management
**Local State (default):**
- Simple, single-user
- State in `terraform.tfstate`
**Remote State (recommended for teams):**
```hcl
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "proxmox/terraform.tfstate"
region = "us-east-1"
}
}
```
## Example Use Cases
### Homelab Docker Host
Provision a VM optimized for Docker:
- 4-8 CPU cores
- 8-16GB RAM
- 50GB+ disk
- Ubuntu Server 24.04
- Docker pre-installed via cloud-init
See: `proxmox-examples/docker-host/`
### Development Environment
Multiple VMs for testing:
- Web server VM
- Database VM
- Application VM
- All networked together
See: `proxmox-examples/multi-node/`
### LXC Containers
Lightweight containers for services:
- Lower overhead than VMs
- Fast startup
- Resource efficient
See: `proxmox-examples/lxc-containers/`
## Proxmox Provider Resources
### Virtual Machines (QEMU)
```hcl
resource "proxmox_vm_qemu" "vm" {
name = "my-vm"
target_node = "pve"
clone = "ubuntu-cloud-template" # Template to clone
cores = 2
memory = 2048
disk {
size = "20G"
storage = "local-lvm"
}
network {
model = "virtio"
bridge = "vmbr0"
}
}
```
### LXC Containers
```hcl
resource "proxmox_lxc" "container" {
hostname = "my-container"
target_node = "pve"
ostemplate = "local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.gz"
cores = 1
memory = 512
rootfs {
storage = "local-lvm"
size = "8G"
}
network {
name = "eth0"
bridge = "vmbr0"
ip = "dhcp"
}
}
```
### Cloud-Init
```hcl
resource "proxmox_vm_qemu" "cloudinit_vm" {
# ... basic config ...
ciuser = "ubuntu"
cipassword = var.vm_password
sshkeys = file("~/.ssh/id_rsa.pub")
ipconfig0 = "ip=dhcp"
}
```
## Troubleshooting
### SSL Certificate Errors
```bash
export PM_TLS_INSECURE=true
```
Or add to provider:
```hcl
provider "proxmox" {
insecure = true
}
```
### API Permission Errors
Ensure API token has necessary permissions:
```bash
# In Proxmox shell
pveum acl modify / -token 'root@pam!terraform' -role Administrator
```
### VM Clone Errors
Ensure template exists:
```bash
# List VMs
qm list
# Check template flag
qm config 9000
```
### Timeout Errors
Increase timeout:
```hcl
resource "proxmox_vm_qemu" "vm" {
# ...
timeout_create = "30m"
timeout_clone = "30m"
}
```
## Migration from Terraform
OpenTofu is a drop-in replacement:
```bash
# Rename binary
alias tofu=terraform
# Or replace commands
terraform → tofu
```
State files are compatible - no conversion needed!
## Advanced Topics
### Custom Cloud Images
1. Download cloud image
2. Create VM template
3. Use cloud-init for customization
See: `proxmox-examples/cloud-init/`
### Network Configuration
```hcl
# VLAN tagging
network {
model = "virtio"
bridge = "vmbr0"
tag = 100 # VLAN 100
}
# Multiple NICs
network {
model = "virtio"
bridge = "vmbr0"
}
network {
model = "virtio"
bridge = "vmbr1"
}
```
### Storage Options
```hcl
# Local LVM
disk {
storage = "local-lvm"
size = "50G"
type = "scsi"
}
# NFS/CIFS
disk {
storage = "nfs-storage"
size = "100G"
}
# Multiple disks
disk {
slot = 0
size = "50G"
storage = "local-lvm"
}
disk {
slot = 1
size = "100G"
storage = "data"
}
```
## Recommended Resources
### Providers
- **[bpg/proxmox](https://registry.terraform.io/providers/bpg/proxmox)** - Most feature-complete (recommended)
- **[Telmate/proxmox](https://registry.terraform.io/providers/Telmate/proxmox)** - Legacy, still works
### Learning
- [OpenTofu Docs](https://opentofu.org/docs/)
- [Proxmox Provider Docs](https://registry.terraform.io/providers/bpg/proxmox/latest/docs)
- [Terraform/OpenTofu Tutorial](https://developer.hashicorp.com/terraform/tutorials)
### Tools
- **[tflint](https://github.com/terraform-linters/tflint)** - Linting
- **[terraform-docs](https://github.com/terraform-docs/terraform-docs)** - Generate docs
- **[infracost](https://www.infracost.io/)** - Cost estimation
- **[terragrunt](https://terragrunt.gruntwork.io/)** - Wrapper for DRY configs
## Next Steps
1. **Start Simple:** Try `proxmox-examples/single-vm/`
2. **Learn Basics:** Get familiar with plan/apply/destroy
3. **Expand:** Try docker-host or multi-node
4. **Customize:** Adapt examples to your needs
5. **Automate:** Integrate with CI/CD
## Getting Help
- Check example READMEs in each directory
- Review Proxmox provider docs
- OpenTofu community Discord
- Ask in r/Proxmox or r/selfhosted
Happy Infrastructure as Code! 🚀

View file

@ -0,0 +1,405 @@
# Docker Host VM with OpenTofu
This configuration creates a VM optimized for running Docker containers in your homelab.
## What This Creates
- ✅ Ubuntu VM (from cloud template)
- ✅ Docker & Docker Compose installed
- ✅ Homelab network created
- ✅ /media directories structure
- ✅ SSH key authentication
- ✅ Automatic updates enabled
## Prerequisites
### 1. Create Ubuntu Cloud Template
First, create a cloud-init enabled template in Proxmox:
```bash
# SSH to Proxmox server
ssh root@proxmox.local
# Download Ubuntu cloud image
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
# Create VM
qm create 9000 --name ubuntu-cloud-template --memory 2048 --net0 virtio,bridge=vmbr0
# Import disk
qm importdisk 9000 jammy-server-cloudimg-amd64.img local-lvm
# Attach disk
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-9000-disk-0
# Add cloud-init drive
qm set 9000 --ide2 local-lvm:cloudinit
# Set boot disk
qm set 9000 --boot c --bootdisk scsi0
# Add serial console
qm set 9000 --serial0 socket --vga serial0
# Convert to template
qm template 9000
# Cleanup
rm jammy-server-cloudimg-amd64.img
```
### 2. Create API Token
In Proxmox UI:
1. Datacenter → Permissions → API Tokens
2. Add → User: `root@pam`, Token ID: `terraform`
3. Uncheck "Privilege Separation"
4. Save the secret!
### 3. Install OpenTofu
```bash
# Linux/macOS
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh | sh
# Verify
tofu version
```
## Quick Start
### 1. Configure Variables
```bash
cd terraform/proxmox-examples/docker-host
# Copy example config
cp terraform.tfvars.example terraform.tfvars
# Edit with your values
nano terraform.tfvars
```
**Required changes:**
- `pm_api_token_secret` - Your Proxmox API secret
- `vm_ssh_keys` - Your SSH public key
- `vm_password` - Set a secure password
**Optional changes:**
- `vm_name` - Change VM name
- `vm_cores` / `vm_memory` - Adjust resources
- `vm_ip_address` - Set static IP (or keep DHCP)
### 2. Initialize
```bash
tofu init
```
### 3. Plan
```bash
tofu plan
```
Review what will be created.
### 4. Apply
```bash
tofu apply
```
Type `yes` to confirm.
### 5. Connect
```bash
# Get SSH command from output
tofu output ssh_command
# Or manually
ssh ubuntu@<VM-IP>
# Verify Docker
docker --version
docker ps
docker network ls | grep homelab
```
## Configuration Options
### Resource Sizing
**Light workload (1-5 containers):**
```hcl
vm_cores = 2
vm_memory = 4096
disk_size = "30"
```
**Medium workload (5-15 containers):**
```hcl
vm_cores = 4
vm_memory = 8192
disk_size = "50"
```
**Heavy workload (15+ containers):**
```hcl
vm_cores = 8
vm_memory = 16384
disk_size = "100"
```
### Network Configuration
**DHCP (easiest):**
```hcl
vm_ip_address = "dhcp"
```
**Static IP:**
```hcl
vm_ip_address = "192.168.1.100"
vm_ip_netmask = 24
vm_gateway = "192.168.1.1"
```
### Multiple SSH Keys
```hcl
vm_ssh_keys = [
"ssh-rsa AAAAB3... user1@laptop",
"ssh-rsa AAAAB3... user2@desktop"
]
```
## Post-Deployment
### Deploy Homelab Services
```bash
# SSH to VM
ssh ubuntu@<VM-IP>
# Clone homelab repo (if not auto-cloned)
git clone https://github.com/efigueroa/homelab.git
cd homelab
# Deploy services
cd compose/core/traefik
docker compose up -d
cd ../lldap
docker compose up -d
# Continue with other services...
```
### Verify Setup
```bash
# Check Docker
docker --version
docker compose version
# Check network
docker network ls | grep homelab
# Check media directories
ls -la /media
# Check system resources
htop
df -h
```
## Managing the VM
### View State
```bash
tofu show
tofu state list
```
### Update VM
1. Edit `terraform.tfvars`:
```hcl
vm_cores = 8 # Increase from 4
vm_memory = 16384 # Increase from 8192
```
2. Apply changes:
```bash
tofu plan
tofu apply
```
**Note:** Some changes require VM restart.
### Destroy VM
```bash
# Backup any data first!
tofu destroy
```
Type `yes` to confirm deletion.
## Troubleshooting
### Template Not Found
Error: `template with ID 9000 not found`
**Solution:** Create cloud template (see Prerequisites)
### API Permission Error
Error: `permission denied`
**Solution:** Check API token permissions:
```bash
pveum acl modify / -token 'root@pam!terraform' -role Administrator
```
### Cloud-Init Not Working
**Check cloud-init status:**
```bash
ssh ubuntu@<VM-IP>
sudo cloud-init status
sudo cat /var/log/cloud-init-output.log
```
### Docker Not Installed
**Manual installation:**
```bash
ssh ubuntu@<VM-IP>
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker ubuntu
```
### VM Won't Start
**Check Proxmox logs:**
```bash
# On Proxmox server
qm status <VM-ID>
tail -f /var/log/pve/tasks/active
```
## Advanced Usage
### Multiple VMs
Create `docker-host-02.tfvars`:
```hcl
vm_name = "docker-host-02"
vm_ip_address = "192.168.1.101"
```
Deploy:
```bash
tofu apply -var-file="docker-host-02.tfvars"
```
### Custom Cloud-Init
Edit `main.tf` to add custom cloud-init sections:
```yaml
users:
- name: myuser
groups: sudo, docker
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
packages:
- zsh
- tmux
- neovim
runcmd:
- sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
```
### Attach Additional Disk
Add to `main.tf`:
```hcl
disk {
datastore_id = var.storage
size = 200
interface = "scsi1"
}
```
Mount in cloud-init:
```yaml
mounts:
- ["/dev/sdb", "/mnt/data", "ext4", "defaults", "0", "0"]
```
## Cost Analysis
**Resource Usage:**
- 4 cores, 8GB RAM, 50GB disk
- Running 24/7
**Homelab Cost:** $0 (uses existing hardware)
**If in cloud (comparison):**
- AWS: ~$50-100/month
- DigitalOcean: ~$40/month
- Linode: ~$40/month
**Homelab ROI:** Pays for itself in ~2-3 months!
## Security Hardening
### Enable Firewall
Add to cloud-init:
```yaml
runcmd:
- ufw default deny incoming
- ufw default allow outgoing
- ufw allow ssh
- ufw allow 80/tcp
- ufw allow 443/tcp
- ufw --force enable
```
### Disable Password Authentication
After SSH key setup:
```yaml
ssh_pwauth: false
```
### Automatic Updates
Already enabled in cloud-init. Verify:
```bash
sudo systemctl status unattended-upgrades
```
## Next Steps
1. ✅ Deploy core services (Traefik, LLDAP, Tinyauth)
2. ✅ Configure SSL certificates
3. ✅ Deploy media services
4. ✅ Set up backups (Restic)
5. ✅ Add monitoring (Prometheus/Grafana)
## Resources
- [OpenTofu Docs](https://opentofu.org/docs/)
- [Proxmox Provider](https://registry.terraform.io/providers/bpg/proxmox/latest/docs)
- [Cloud-Init Docs](https://cloudinit.readthedocs.io/)
- [Docker Docs](https://docs.docker.com/)

View file

@ -0,0 +1,155 @@
terraform {
required_version = ">= 1.6"
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~> 0.50"
}
}
}
provider "proxmox" {
endpoint = var.pm_api_url
api_token = var.pm_api_token_secret != "" ? "${var.pm_api_token_id}=${var.pm_api_token_secret}" : null
# For self-signed certificates
insecure = var.pm_tls_insecure
ssh {
agent = true
}
}
resource "proxmox_virtual_environment_vm" "docker_host" {
name = var.vm_name
description = "Docker host for homelab services - Managed by OpenTofu"
node_name = var.proxmox_node
# Clone from template (must exist in Proxmox)
clone {
vm_id = var.template_vm_id
full = true
}
# CPU configuration
cpu {
cores = var.vm_cores
type = "host" # Use host CPU type for best performance
}
# Memory configuration
memory {
dedicated = var.vm_memory
}
# Network interface
network_device {
bridge = var.network_bridge
model = "virtio"
}
# Disk configuration
disk {
datastore_id = var.storage
size = var.disk_size
interface = "scsi0"
discard = "on" # Enable TRIM for SSDs
iothread = true
}
# Cloud-init configuration
initialization {
ip_config {
ipv4 {
address = var.vm_ip_address == "dhcp" ? "dhcp" : "${var.vm_ip_address}/${var.vm_ip_netmask}"
gateway = var.vm_gateway
}
}
user_account {
username = var.vm_username
keys = var.vm_ssh_keys
password = var.vm_password
}
user_data_file_id = proxmox_virtual_environment_file.cloud_init_user_data.id
}
# Start VM on boot
on_boot = true
# Tags for organization
tags = ["terraform", "docker", "homelab"]
}
# Cloud-init user data for Docker installation
resource "proxmox_virtual_environment_file" "cloud_init_user_data" {
content_type = "snippets"
datastore_id = "local"
node_name = var.proxmox_node
source_raw {
data = <<-EOF
#cloud-config
hostname: ${var.vm_name}
manage_etc_hosts: true
# Install Docker and dependencies
package_update: true
package_upgrade: true
packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- git
- vim
- htop
- net-tools
# Add Docker's official GPG key and repository
runcmd:
- mkdir -p /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
- chmod a+r /etc/apt/keyrings/docker.gpg
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- apt-get update
- apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- systemctl enable docker
- systemctl start docker
- usermod -aG docker ${var.vm_username}
- docker network create homelab || true
# Create media directories
write_files:
- path: /usr/local/bin/setup-media-dirs
permissions: '0755'
content: |
#!/bin/bash
mkdir -p /media/{audiobooks,books,comics,complete,downloads,homemovies,incomplete,movies,music,photos,tv}
chown -R ${var.vm_username}:${var.vm_username} /media
chmod -R 755 /media
# Run setup script
runcmd:
- /usr/local/bin/setup-media-dirs
# Optional: Clone homelab repo
${var.clone_homelab_repo ? "- su - ${var.vm_username} -c 'cd ~ && git clone https://github.com/${var.github_username}/homelab.git'" : "# Homelab repo cloning disabled"}
# Set timezone
timezone: ${var.vm_timezone}
# Reboot after setup
power_state:
mode: reboot
condition: true
EOF
file_name = "cloud-init-docker-${var.vm_name}.yaml"
}
}

View file

@ -0,0 +1,29 @@
output "vm_id" {
description = "VM ID"
value = proxmox_virtual_environment_vm.docker_host.vm_id
}
output "vm_name" {
description = "VM name"
value = proxmox_virtual_environment_vm.docker_host.name
}
output "vm_ipv4_address" {
description = "VM IPv4 address"
value = try(proxmox_virtual_environment_vm.docker_host.ipv4_addresses[1][0], "DHCP - check Proxmox UI")
}
output "vm_mac_address" {
description = "VM MAC address"
value = proxmox_virtual_environment_vm.docker_host.mac_addresses[0]
}
output "ssh_command" {
description = "SSH command to connect to VM"
value = "ssh ${var.vm_username}@${try(proxmox_virtual_environment_vm.docker_host.ipv4_addresses[1][0], "DHCP-ADDRESS")}"
}
output "docker_status_command" {
description = "Command to check Docker status"
value = "ssh ${var.vm_username}@${try(proxmox_virtual_environment_vm.docker_host.ipv4_addresses[1][0], "DHCP-ADDRESS")} 'docker ps'"
}

View file

@ -0,0 +1,34 @@
# Proxmox Connection
pm_api_url = "https://proxmox.local:8006"
pm_api_token_id = "root@pam!terraform"
pm_api_token_secret = "your-secret-here"
pm_tls_insecure = true
# Proxmox Configuration
proxmox_node = "pve"
template_vm_id = 9000
storage = "local-lvm"
network_bridge = "vmbr0"
# VM Configuration
vm_name = "docker-host"
vm_cores = 4
vm_memory = 8192
disk_size = "50"
# Network Configuration
vm_ip_address = "dhcp" # Or "192.168.1.100" for static
vm_ip_netmask = 24
vm_gateway = "192.168.1.1"
# User Configuration
vm_username = "ubuntu"
vm_password = "changeme_please_set_secure_password"
vm_ssh_keys = [
"ssh-rsa AAAAB3NzaC1yc2E... your-key-here"
]
# Optional
vm_timezone = "America/Los_Angeles"
clone_homelab_repo = true
github_username = "efigueroa"

View file

@ -0,0 +1,125 @@
variable "pm_api_url" {
description = "Proxmox API URL"
type = string
default = "https://proxmox.local:8006"
}
variable "pm_api_token_id" {
description = "Proxmox API token ID (format: user@realm!tokenid)"
type = string
default = "root@pam!terraform"
}
variable "pm_api_token_secret" {
description = "Proxmox API token secret"
type = string
sensitive = true
}
variable "pm_tls_insecure" {
description = "Disable TLS verification for self-signed certificates"
type = bool
default = true
}
variable "proxmox_node" {
description = "Proxmox node name"
type = string
default = "pve"
}
variable "vm_name" {
description = "VM name"
type = string
default = "docker-host"
}
variable "template_vm_id" {
description = "Template VM ID to clone from"
type = number
default = 9000
}
variable "vm_cores" {
description = "Number of CPU cores"
type = number
default = 4
}
variable "vm_memory" {
description = "Memory in MB"
type = number
default = 8192
}
variable "disk_size" {
description = "Disk size (e.g., 50G, 100G)"
type = string
default = "50"
}
variable "storage" {
description = "Storage pool name"
type = string
default = "local-lvm"
}
variable "network_bridge" {
description = "Network bridge"
type = string
default = "vmbr0"
}
variable "vm_ip_address" {
description = "Static IP address or 'dhcp'"
type = string
default = "dhcp"
}
variable "vm_ip_netmask" {
description = "Network netmask (CIDR notation, e.g., 24)"
type = number
default = 24
}
variable "vm_gateway" {
description = "Network gateway"
type = string
default = "192.168.1.1"
}
variable "vm_username" {
description = "VM username"
type = string
default = "ubuntu"
}
variable "vm_password" {
description = "VM user password"
type = string
sensitive = true
}
variable "vm_ssh_keys" {
description = "List of SSH public keys"
type = list(string)
default = []
}
variable "vm_timezone" {
description = "VM timezone"
type = string
default = "America/Los_Angeles"
}
variable "clone_homelab_repo" {
description = "Clone homelab repository on first boot"
type = bool
default = false
}
variable "github_username" {
description = "GitHub username for homelab repo"
type = string
default = "efigueroa"
}