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:
commit
312144f37e
16 changed files with 2480 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
16
compose/services/backrest/.env
Normal file
16
compose/services/backrest/.env
Normal 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
7
compose/services/backrest/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Backrest data and cache
|
||||
data/
|
||||
config/
|
||||
cache/
|
||||
|
||||
# Keep .env.example if created
|
||||
!.env.example
|
||||
346
compose/services/backrest/README.md
Normal file
346
compose/services/backrest/README.md
Normal 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
|
||||
49
compose/services/backrest/compose.yaml
Normal file
49
compose/services/backrest/compose.yaml
Normal 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
|
||||
332
compose/services/homarr/README.md
Normal file
332
compose/services/homarr/README.md
Normal 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
|
||||
57
compose/services/homarr/compose.yaml
Normal file
57
compose/services/homarr/compose.yaml
Normal 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
|
||||
45
templates/service-template/.env.example
Normal file
45
templates/service-template/.env.example
Normal 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
|
||||
250
templates/service-template/README.md
Normal file
250
templates/service-template/README.md
Normal 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)
|
||||
118
templates/service-template/compose.yaml
Normal file
118
templates/service-template/compose.yaml
Normal 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
506
terraform/README.md
Normal 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! 🚀
|
||||
405
terraform/proxmox-examples/docker-host/README.md
Normal file
405
terraform/proxmox-examples/docker-host/README.md
Normal 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/)
|
||||
155
terraform/proxmox-examples/docker-host/main.tf
Normal file
155
terraform/proxmox-examples/docker-host/main.tf
Normal 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"
|
||||
}
|
||||
}
|
||||
29
terraform/proxmox-examples/docker-host/outputs.tf
Normal file
29
terraform/proxmox-examples/docker-host/outputs.tf
Normal 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'"
|
||||
}
|
||||
|
|
@ -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"
|
||||
125
terraform/proxmox-examples/docker-host/variables.tf
Normal file
125
terraform/proxmox-examples/docker-host/variables.tf
Normal 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"
|
||||
}
|
||||
Loading…
Reference in a new issue