diff --git a/README.md b/README.md index 6db6978..7b1d002 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ compose/ │ ├── sabnzbd/ # Usenet downloader │ └── qbittorrent/# Torrent client └── services/ # Utility services + ├── homarr/ # Dashboard (home.fig.systems) + ├── backrest/ # Backup manager (backup.fig.systems) ├── linkwarden/ # Bookmark manager (links.fig.systems) ├── vikunja/ # Task management (tasks.fig.systems) ├── lubelogger/ # Vehicle tracker (garage.fig.systems) @@ -56,6 +58,8 @@ All services are accessible via: | Traefik Dashboard | traefik.fig.systems | ✅ | | LLDAP | lldap.fig.systems | ✅ | | Tinyauth | auth.fig.systems | ❌ | +| Homarr | home.fig.systems | ✅ | +| Backrest | backup.fig.systems | ✅ | | Jellyfin | flix.fig.systems | ❌* | | Jellyseerr | requests.fig.systems | ✅ | | Immich | photos.fig.systems | ❌* | @@ -143,6 +147,8 @@ cd compose/media/automation/qbittorrent && docker compose up -d # Utility services cd compose/services/linkwarden && 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/calibre-web && docker compose up -d cd compose/services/booklore && docker compose up -d diff --git a/compose/services/backrest/.env b/compose/services/backrest/.env new file mode 100644 index 0000000..3faebec --- /dev/null +++ b/compose/services/backrest/.env @@ -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 diff --git a/compose/services/backrest/.gitignore b/compose/services/backrest/.gitignore new file mode 100644 index 0000000..a46c6d2 --- /dev/null +++ b/compose/services/backrest/.gitignore @@ -0,0 +1,7 @@ +# Backrest data and cache +data/ +config/ +cache/ + +# Keep .env.example if created +!.env.example diff --git a/compose/services/backrest/README.md b/compose/services/backrest/README.md new file mode 100644 index 0000000..0acf378 --- /dev/null +++ b/compose/services/backrest/README.md @@ -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 diff --git a/compose/services/backrest/compose.yaml b/compose/services/backrest/compose.yaml new file mode 100644 index 0000000..5d2dbf4 --- /dev/null +++ b/compose/services/backrest/compose.yaml @@ -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 diff --git a/compose/services/homarr/README.md b/compose/services/homarr/README.md new file mode 100644 index 0000000..bf75dbb --- /dev/null +++ b/compose/services/homarr/README.md @@ -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 diff --git a/compose/services/homarr/compose.yaml b/compose/services/homarr/compose.yaml new file mode 100644 index 0000000..bfb9500 --- /dev/null +++ b/compose/services/homarr/compose.yaml @@ -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 diff --git a/templates/service-template/.env.example b/templates/service-template/.env.example new file mode 100644 index 0000000..78e8edf --- /dev/null +++ b/templates/service-template/.env.example @@ -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 + +# 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 diff --git a/templates/service-template/README.md b/templates/service-template/README.md new file mode 100644 index 0000000..b41404c --- /dev/null +++ b/templates/service-template/README.md @@ -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) diff --git a/templates/service-template/compose.yaml b/templates/service-template/compose.yaml new file mode 100644 index 0000000..3f196af --- /dev/null +++ b/templates/service-template/compose.yaml @@ -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: diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..8b3ed10 --- /dev/null +++ b/terraform/README.md @@ -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! 🚀 diff --git a/terraform/proxmox-examples/docker-host/README.md b/terraform/proxmox-examples/docker-host/README.md new file mode 100644 index 0000000..5193f5e --- /dev/null +++ b/terraform/proxmox-examples/docker-host/README.md @@ -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@ + +# 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@ + +# 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@ +sudo cloud-init status +sudo cat /var/log/cloud-init-output.log +``` + +### Docker Not Installed + +**Manual installation:** +```bash +ssh ubuntu@ +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 +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/) diff --git a/terraform/proxmox-examples/docker-host/main.tf b/terraform/proxmox-examples/docker-host/main.tf new file mode 100644 index 0000000..b9c2f3c --- /dev/null +++ b/terraform/proxmox-examples/docker-host/main.tf @@ -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" + } +} diff --git a/terraform/proxmox-examples/docker-host/outputs.tf b/terraform/proxmox-examples/docker-host/outputs.tf new file mode 100644 index 0000000..3e0f190 --- /dev/null +++ b/terraform/proxmox-examples/docker-host/outputs.tf @@ -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'" +} diff --git a/terraform/proxmox-examples/docker-host/terraform.tfvars.example b/terraform/proxmox-examples/docker-host/terraform.tfvars.example new file mode 100644 index 0000000..bc686d3 --- /dev/null +++ b/terraform/proxmox-examples/docker-host/terraform.tfvars.example @@ -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" diff --git a/terraform/proxmox-examples/docker-host/variables.tf b/terraform/proxmox-examples/docker-host/variables.tf new file mode 100644 index 0000000..1920343 --- /dev/null +++ b/terraform/proxmox-examples/docker-host/variables.tf @@ -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" +}