Compare commits

...

55 Commits

Author SHA1 Message Date
ea210c73dd fix(core): mount backrest config directory instead of file
Backrest requires atomic file operations for config updates,
which fail with single-file bind mounts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 17:25:50 +01:00
c4fd23855b feat(media): add Immich photo/video management service
- Add immich_server, immich_ml, and immich_postgres services
- Use dedicated PostgreSQL with vector extensions (vectorchord + pgvectors)
- Connect to core Redis for job queues
- Configure Traefik routing for immich.media.pivoine.art
- Add backup volumes and plan for Backrest (daily at 12:00)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 17:23:25 +01:00
3cc9db6632 fix(core): remove filestash backup volume reference
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 17:08:28 +01:00
4851ca10f0 fix(media): remove filestash service
- Remove filestash service and volume from media/compose.yaml
- Remove filestash-backup plan from backrest config
- Remove MEDIA_FILESTASH_* environment variables from arty.yml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 17:01:07 +01:00
c55f41408a fix(ai): litellm config 2025-11-30 23:03:32 +01:00
7bca766247 fix(ai): litellm config 2025-11-30 22:32:49 +01:00
120bf7c385 feat(ai): bge over litellm 2025-11-30 20:12:07 +01:00
35e0f232f9 revert: remove GPU_TAILSCALE_HOST from arty.yml
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 09:58:44 +01:00
cd9256f09c fix: use Tailscale IP for GPU_TAILSCALE_HOST (MagicDNS doesn't work from Docker)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 09:53:14 +01:00
c9e3a5cc4f fix: add resolver for runtime DNS resolution in nginx
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 09:48:25 +01:00
b2b444fb98 fix: add Tailscale DNS to GPU proxy containers
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 09:47:06 +01:00
dcc29d20f0 fix: traefik labels 2025-11-28 09:39:39 +01:00
19ad30e8c4 revert: tailscale docker sidecar 2025-11-28 09:31:21 +01:00
99e39ee6e6 fix: tailscale sidecar dns 2025-11-28 09:12:52 +01:00
ed83e64727 fix: tailscale sidecar dns 2025-11-28 09:09:10 +01:00
18e6741596 fix: tailscale sidecar dns 2025-11-28 09:07:12 +01:00
c83b77ebdb feat: tailscale sidecar 2025-11-28 08:59:42 +01:00
6568dd10b5 feat: tailscale sidecar 2025-11-28 08:42:40 +01:00
a6e4540e84 feat: tailscale sidecar 2025-11-28 08:41:30 +01:00
dbdf33e78e feat: tailscale sidecar 2025-11-28 08:40:30 +01:00
74f618bcbb feat: tailscale sidecar 2025-11-28 08:36:50 +01:00
0c7fe219f7 feat: tailscale sidecar 2025-11-28 08:32:26 +01:00
6d0a15a969 fix: ai compose tailscale dns 2025-11-28 08:21:23 +01:00
f4dd7c7d9d fix: litellm compose 2025-11-28 08:14:13 +01:00
608b5ba793 fix: nginx audio mime types 2025-11-27 16:45:14 +01:00
2e45252793 fix: nginx proxy timeouts 2025-11-27 15:24:38 +01:00
20ba9952a1 feat: upscale service 2025-11-27 12:13:57 +01:00
69869ec3fb fix: remove vllm embedding 2025-11-27 01:11:43 +01:00
cc270c8539 fix: vllm model ids 2025-11-27 00:49:53 +01:00
8bdcde4b90 fix: supervisor env 2025-11-26 22:58:16 +01:00
2ab43e8fd3 fix: authelia for audiocraft 2025-11-26 22:56:30 +01:00
5d232c7d9b feat: audiocraft 2025-11-26 22:54:10 +01:00
cef233b678 chore: remove qwen 2025-11-26 21:03:43 +01:00
b63ddbffbd fix(ai): correct bge embedding model name to hosted_vllm/openai prefix
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 06:44:33 +01:00
d57a1241d2 feat(ai): add bge-large-en-v1.5 embedding model to litellm
- Add BGE embedding model config (port 8002) to litellm-config.yaml
- Add GPU_VLLM_EMBED_URL env var to compose and .env

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 06:40:36 +01:00
ef0309838c refactor(ai): remove crawl4ai service, add backrest config to repo
- Remove crawl4ai service from ai/compose.yaml (will use local MCP instead)
- Remove crawl4ai backup volume from core/compose.yaml
- Add core/backrest/config.json (infrastructure as code)
- Change backrest from volume to bind-mounted config
- Update CLAUDE.md and README.md documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 06:20:22 +01:00
071a74a996 revert(ai): remove SUPERVISOR_LOGFILE env var from supervisor-ui
Supervisor XML-RPC API v3.0 (Supervisor 4.3.0) only supports 2-parameter
readLog(offset, length) calls, not 3-parameter calls with filename.
The SUPERVISOR_LOGFILE environment variable is not used by the API.

Testing showed:
- Working: server.supervisor.readLog(-4096, 0)
- Failing: server.supervisor.readLog(-4096, 4096, '/path/to/log')

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 23:01:10 +01:00
74b3748b23 feat(ai): add SUPERVISOR_LOGFILE env var to supervisor-ui for RunPod logs
Configure supervisor-ui to use correct logfile path (/workspace/logs/supervisord.log)
for RunPod Supervisor instance. Fixes logs page error on https://supervisor.ai.pivoine.art/logs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 22:49:33 +01:00
87216ab26a fix: remove healthcheck from supervisor-ui service 2025-11-23 20:38:37 +01:00
9e2b19e7f6 feat: replace nginx supervisor proxy with modern supervisor-ui
- Replaced nginx:alpine proxy with dev.pivoine.art/valknar/supervisor-ui:latest
- Modern Next.js UI with real-time SSE updates, batch operations, and charts
- Changed service port from 80 (nginx) to 3000 (Next.js)
- Removed supervisor-nginx.conf (no longer needed)
- Kept same URL (supervisor.ai.pivoine.art) and Authelia SSO protection
- Added health check for /api/health endpoint
- Service connects to RunPod Supervisor via Tailscale (SUPERVISOR_HOST/PORT)
2025-11-23 20:18:29 +01:00
a80c6b931b fix: update compose.yaml to use new GPU_VLLM URLs 2025-11-23 16:22:54 +01:00
64c02228d8 fix: use EMPTY api_key for vLLM servers 2025-11-23 16:17:27 +01:00
55d9bef18a fix: remove api_key from vLLM config to fix authentication error
vLLM servers don't validate API keys, so LiteLLM shouldn't pass them

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 16:16:37 +01:00
7fc945e179 fix: update LiteLLM config for direct vLLM server access
- Replace orchestrator routing with direct vLLM server connections
- Qwen 2.5 7B on port 8000 (GPU_VLLM_QWEN_URL)
- Llama 3.1 8B on port 8001 (GPU_VLLM_LLAMA_URL)
- Simplify architecture by removing orchestrator proxy layer

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 16:10:20 +01:00
94ab4ae6dd feat: enable system message support for qwen-2.5-7b 2025-11-23 14:36:34 +01:00
779e76974d fix: use complete URL env var for vLLM API base
- Replace GPU_TAILSCALE_IP interpolation with GPU_VLLM_API_URL
- LiteLLM requires full URL in api_base with os.environ/ syntax

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 13:17:37 +01:00
f3f32c163f feat: consolidate GPU IP with single GPU_TAILSCALE_IP variable
- Replace COMFYUI_BACKEND_HOST and SUPERVISOR_BACKEND_HOST with GPU_TAILSCALE_IP
- Update LiteLLM config to use os.environ/GPU_TAILSCALE_IP for vLLM models
- Add GPU_TAILSCALE_IP env var to LiteLLM service
- Configure qwen-2.5-7b and llama-3.1-8b to route through orchestrator

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 13:05:33 +01:00
e00e959543 Update backend IPs for ComfyUI and Supervisor proxies
- Remove hardcoded default values from compose.yaml
- Backend IPs now managed via environment variables only

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 02:11:19 +01:00
0fd2eacad1 feat: add Supervisor proxy with Authelia SSO
Add nginx reverse proxy service for Supervisor web UI at supervisor.ai.pivoine.art with Authelia authentication. Proxies to RunPod GPU instance via Tailscale (100.121.199.88:9001).

Changes:
- Create supervisor-nginx.conf for nginx proxy configuration
- Add supervisor service to docker-compose with Traefik labels
- Add supervisor.ai.pivoine.art to Authelia protected domains
- Remove deprecated Flux-related files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 13:19:02 +01:00
bf402adb25 Add Llama 3.1 8B model to LiteLLM configuration 2025-11-21 21:30:18 +01:00
ae1c349b55 feat: make ComfyUI backend IP/port configurable via environment variables
- Replace hardcoded IP in comfyui-nginx.conf with env vars
- Add COMFYUI_BACKEND_HOST and COMFYUI_BACKEND_PORT to compose.yaml
- Use envsubst to substitute variables at container startup
- Defaults: 100.121.199.88:8188 (current RunPod Tailscale IP)
2025-11-21 21:24:51 +01:00
66d8c82e47 Remove Flux and MusicGen models from LiteLLM config
ComfyUI now handles Flux image generation directly.
MusicGen is not being used and has been removed.
2025-11-21 21:11:29 +01:00
ea81634ef3 feat: add ComfyUI to Authelia protected domains
- Add comfy.ai.pivoine.art to one_factor authentication policy
- Enables SSO protection for ComfyUI image generation service
2025-11-21 21:05:24 +01:00
25bd020b93 docs: document ComfyUI setup and integration
- Add ComfyUI service to AI stack service list
- Document ComfyUI proxy architecture and configuration
- Include deployment instructions via Ansible
- Explain network topology and access flow
- Add proxy configuration details (nginx, Tailscale, Authelia)
- Document RunPod setup process and model integration
2025-11-21 21:03:35 +01:00
904f7d3c2e feat(ai): add ComfyUI proxy service with Authelia SSO
- Add ComfyUI service to AI stack using nginx:alpine as reverse proxy
- Proxy to RunPod ComfyUI via Tailscale (100.121.199.88:8188)
- Configure Traefik routing for comfy.ai.pivoine.art
- Enable Authelia SSO middleware (net-authelia)
- Support WebSocket connections for real-time updates
- Set appropriate timeouts for image generation (300s)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 20:56:20 +01:00
13 changed files with 978 additions and 611 deletions

View File

@@ -25,7 +25,7 @@ Root `compose.yaml` uses Docker Compose's `include` directive to orchestrate mul
- **kit**: Unified toolkit with Vert file converter and miniPaint image editor (path-routed) - **kit**: Unified toolkit with Vert file converter and miniPaint image editor (path-routed)
- **jelly**: Jellyfin media server with hardware transcoding - **jelly**: Jellyfin media server with hardware transcoding
- **drop**: PairDrop peer-to-peer file sharing - **drop**: PairDrop peer-to-peer file sharing
- **ai**: AI infrastructure with Open WebUI, Crawl4AI, and pgvector (PostgreSQL) - **ai**: AI infrastructure with Open WebUI, ComfyUI proxy, Crawl4AI, and pgvector (PostgreSQL)
- **asciinema**: Terminal recording and sharing platform (PostgreSQL) - **asciinema**: Terminal recording and sharing platform (PostgreSQL)
- **restic**: Backrest backup system with restic backend - **restic**: Backrest backup system with restic backend
- **netdata**: Real-time infrastructure monitoring - **netdata**: Real-time infrastructure monitoring
@@ -451,11 +451,13 @@ AI infrastructure with Open WebUI, Crawl4AI, and dedicated PostgreSQL with pgvec
- User signup enabled - User signup enabled
- Data persisted in `ai_webui_data` volume - Data persisted in `ai_webui_data` volume
- **crawl4ai**: Crawl4AI web scraping service (internal API, no public access) - **comfyui**: ComfyUI reverse proxy exposed at `comfy.ai.pivoine.art:80`
- Optimized web scraper for LLM content preparation - Nginx-based proxy to ComfyUI running on RunPod GPU server
- Internal API on port 11235 (not exposed via Traefik) - Node-based UI for Flux.1 Schnell image generation workflows
- Designed for integration with Open WebUI and n8n workflows - Proxies to RunPod via Tailscale VPN (100.121.199.88:8188)
- Data persisted in `ai_crawl4ai_data` volume - Protected by Authelia SSO authentication
- WebSocket support for real-time updates
- Stateless architecture (no data persistence on VPS)
**Configuration**: **Configuration**:
- **Claude Integration**: Uses Anthropic API with OpenAI-compatible endpoint - **Claude Integration**: Uses Anthropic API with OpenAI-compatible endpoint
@@ -492,12 +494,55 @@ Open WebUI function for generating images via Flux.1 Schnell on RunPod GPU:
See `ai/FLUX_SETUP.md` for detailed setup instructions and troubleshooting. See `ai/FLUX_SETUP.md` for detailed setup instructions and troubleshooting.
**ComfyUI Image Generation**:
ComfyUI provides a professional node-based interface for creating Flux image generation workflows:
**Architecture**:
```
User → Traefik (VPS) → Authelia SSO → ComfyUI Proxy (nginx) → Tailscale → ComfyUI (RunPod:8188) → Flux Model (GPU)
```
**Access**:
1. Navigate to https://comfy.ai.pivoine.art
2. Authenticate via Authelia SSO
3. Create node-based workflows in ComfyUI interface
4. Use Flux.1 Schnell model from HuggingFace cache at `/workspace/ComfyUI/models/huggingface_cache`
**RunPod Setup** (via Ansible):
ComfyUI is installed on RunPod using the Ansible playbook at `/home/valknar/Projects/runpod/playbook.yml`:
- Clone ComfyUI from https://github.com/comfyanonymous/ComfyUI
- Install dependencies from `models/comfyui/requirements.txt`
- Create model directory structure (checkpoints, unet, vae, loras, clip, controlnet)
- Symlink Flux model from HuggingFace cache
- Start service via `models/comfyui/start.sh` on port 8188
**To deploy ComfyUI on RunPod**:
```bash
# Run Ansible playbook with comfyui tag
ssh -p 16186 root@213.173.110.150
cd /workspace/ai
ansible-playbook playbook.yml --tags comfyui --skip-tags always
# Start ComfyUI service
bash models/comfyui/start.sh &
```
**Proxy Configuration**:
The VPS runs an nginx proxy (`ai/comfyui-nginx.conf`) that:
- Listens on port 80 inside container
- Forwards to RunPod via Tailscale (100.121.199.88:8188)
- Supports WebSocket upgrades for real-time updates
- Handles large file uploads (100M limit)
- Uses extended timeouts for long-running generations (300s)
**Note**: ComfyUI runs directly on RunPod GPU server, not in a container. All data is stored on RunPod's `/workspace` volume.
**Integration Points**: **Integration Points**:
- **n8n**: Workflow automation with AI tasks (scraping, RAG ingestion, webhooks) - **n8n**: Workflow automation with AI tasks (scraping, RAG ingestion, webhooks)
- **Mattermost**: Can send AI-generated notifications via webhooks - **Mattermost**: Can send AI-generated notifications via webhooks
- **Crawl4AI**: Internal API for advanced web scraping - **Crawl4AI**: Internal API for advanced web scraping
- **Claude API**: Primary LLM provider via Anthropic - **Claude API**: Primary LLM provider via Anthropic
- **Flux via RunPod**: Image generation through orchestrator (GPU server) - **Flux via RunPod**: Image generation through orchestrator (GPU server) or ComfyUI
**Future Enhancements**: **Future Enhancements**:
- GPU server integration (IONOS A10 planned) - GPU server integration (IONOS A10 planned)
@@ -676,7 +721,7 @@ Backrest backup system with restic backend:
- Retention: 7 daily, 4 weekly, 3 monthly - Retention: 7 daily, 4 weekly, 3 monthly
16. **ai-backup** (3 AM daily) 16. **ai-backup** (3 AM daily)
- Paths: `/volumes/ai_postgres_data`, `/volumes/ai_webui_data`, `/volumes/ai_crawl4ai_data` - Paths: `/volumes/ai_postgres_data`, `/volumes/ai_webui_data`
- Retention: 7 daily, 4 weekly, 6 monthly, 2 yearly - Retention: 7 daily, 4 weekly, 6 monthly, 2 yearly
17. **asciinema-backup** (11 AM daily) 17. **asciinema-backup** (11 AM daily)
@@ -687,8 +732,7 @@ Backrest backup system with restic backend:
All Docker volumes are mounted read-only to `/volumes/` with prefixed names (e.g., `backup_core_postgres_data`) to avoid naming conflicts with other compose stacks. All Docker volumes are mounted read-only to `/volumes/` with prefixed names (e.g., `backup_core_postgres_data`) to avoid naming conflicts with other compose stacks.
**Configuration Management**: **Configuration Management**:
- `config.json` template in repository defines all backup plans - `core/backrest/config.json` in repository defines all backup plans (bind-mounted to container)
- On first run, copy config into volume: `docker cp restic/config.json restic_app:/config/config.json`
- Config version must be `4` for Backrest 1.10.1 compatibility - Config version must be `4` for Backrest 1.10.1 compatibility
- Backrest manages auth automatically (username: `valknar`, password set via web UI on first access) - Backrest manages auth automatically (username: `valknar`, password set via web UI on first access)
@@ -726,7 +770,7 @@ Each service uses named volumes prefixed with project name:
- `vault_data`: Vaultwarden password vault (SQLite database) - `vault_data`: Vaultwarden password vault (SQLite database)
- `joplin_data`: Joplin note-taking data - `joplin_data`: Joplin note-taking data
- `jelly_config`: Jellyfin media server configuration - `jelly_config`: Jellyfin media server configuration
- `ai_postgres_data`, `ai_webui_data`, `ai_crawl4ai_data`: AI stack databases and application data - `ai_postgres_data`, `ai_webui_data`: AI stack databases and application data
- `netdata_config`: Netdata monitoring configuration - `netdata_config`: Netdata monitoring configuration
- `restic_data`, `restic_config`, `restic_cache`, `restic_tmp`: Backrest backup system - `restic_data`, `restic_config`, `restic_cache`, `restic_tmp`: Backrest backup system
- `proxy_letsencrypt_data`: SSL certificates - `proxy_letsencrypt_data`: SSL certificates

View File

@@ -406,11 +406,10 @@ THE FALCON (falcon_network)
│ ├─ vaultwarden [vault.pivoine.art] → Password Manager │ ├─ vaultwarden [vault.pivoine.art] → Password Manager
│ └─ tandoor [tandoor.pivoine.art] → Recipe Manager │ └─ tandoor [tandoor.pivoine.art] → Recipe Manager
├─ 🤖 AI STACK (5 services) ├─ 🤖 AI STACK (4 services)
│ ├─ ai_postgres [Internal] → pgvector Database │ ├─ ai_postgres [Internal] → pgvector Database
│ ├─ webui [ai.pivoine.art] → Open WebUI (Claude) │ ├─ webui [ai.pivoine.art] → Open WebUI (Claude)
│ ├─ litellm [llm.ai.pivoine.art] → API Proxy │ ├─ litellm [llm.ai.pivoine.art] → API Proxy
│ ├─ crawl4ai [Internal:11235] → Web Scraper
│ └─ facefusion [facefusion.ai.pivoine.art] → Face AI │ └─ facefusion [facefusion.ai.pivoine.art] → Face AI
├─ 🛡️ NET STACK (4 services) ├─ 🛡️ NET STACK (4 services)
@@ -435,7 +434,7 @@ THE FALCON (falcon_network)
├─ Core: postgres_data, redis_data, backrest_* ├─ Core: postgres_data, redis_data, backrest_*
├─ Sexy: directus_uploads, directus_bundle ├─ Sexy: directus_uploads, directus_bundle
├─ Util: pairdrop_*, joplin_data, linkwarden_*, mattermost_*, vaultwarden_data, tandoor_* ├─ Util: pairdrop_*, joplin_data, linkwarden_*, mattermost_*, vaultwarden_data, tandoor_*
├─ AI: ai_postgres_data, ai_webui_data, ai_crawl4ai_data, facefusion_* ├─ AI: ai_postgres_data, ai_webui_data, facefusion_*
├─ Net: letsencrypt_data, netdata_* ├─ Net: letsencrypt_data, netdata_*
├─ Media: jelly_config, jelly_cache, filestash_data ├─ Media: jelly_config, jelly_cache, filestash_data
└─ Dev: gitea_*, coolify_data, n8n_data, asciinema_data └─ Dev: gitea_*, coolify_data, n8n_data, asciinema_data

View File

@@ -1,181 +0,0 @@
# Flux Image Generation Setup for Open WebUI
This guide explains how to add Flux.1 Schnell image generation to your Open WebUI installation.
## Architecture
```
Open WebUI → flux_image_gen.py Function → LiteLLM (port 4000) → Orchestrator (RunPod port 9000) → Flux Model
```
## Installation
### Automatic (via Docker Compose)
The Flux function is **automatically loaded** via Docker volume mount. No manual upload needed!
**How it works:**
- Function file: `ai/functions/flux_image_gen.py`
- Mounted to: `/app/backend/data/functions/` in the container (read-only)
- Open WebUI automatically discovers and loads functions from this directory on startup
**To deploy:**
```bash
cd ~/Projects/docker-compose
pnpm arty up -d ai_webui # Restart Open WebUI to load function
```
### Verify Installation
After restarting Open WebUI, the function should automatically appear in:
1. **Admin Settings → Functions**: Listed as "Flux Image Generator"
2. **Model dropdown**: "Flux.1 Schnell (4-5s)" available for selection
If you don't see it:
```bash
# Check if function is mounted correctly
docker exec ai_webui ls -la /app/backend/data/functions/
# Check logs for any loading errors
docker logs ai_webui | grep -i flux
```
## Usage
### Basic Image Generation
1. **Select the Flux model:**
- In Open WebUI chat, select "Flux.1 Schnell (4-5s)" from the model dropdown
2. **Send your prompt:**
```
A serene mountain landscape at sunset with vibrant colors
```
3. **Wait for generation:**
- The function will call LiteLLM → Orchestrator → RunPod Flux
- Image appears in 4-5 seconds
### Advanced Options
The function supports custom sizes (configure in Valves):
- `1024x1024` (default, square)
- `1024x768` (landscape)
- `768x1024` (portrait)
## Configuration
### Valves (Customization)
To customize function behavior:
1. **Access Open WebUI**:
- Go to https://ai.pivoine.art
- Profile → Settings → Admin Settings → Functions
2. **Find Flux Image Generator**:
- Click on "Flux Image Generator" in the functions list
- Go to "Valves" tab
3. **Available Settings:**
- `LITELLM_API_BASE`: LiteLLM endpoint (default: `http://litellm:4000/v1`)
- `LITELLM_API_KEY`: API key (default: `dummy` - not needed for internal use)
- `DEFAULT_MODEL`: Model name (default: `flux-schnell`)
- `DEFAULT_SIZE`: Image dimensions (default: `1024x1024`)
- `TIMEOUT`: Request timeout in seconds (default: `120`)
## Troubleshooting
### Function not appearing in model list
**Check:**
1. Function is enabled in Admin Settings → Functions
2. Function has no syntax errors (check logs)
3. Refresh browser cache (Ctrl+Shift+R)
### Image generation fails
**Check:**
1. LiteLLM is running: `docker ps | grep litellm`
2. LiteLLM can reach orchestrator: Check `docker logs ai_litellm`
3. Orchestrator is running on RunPod
4. Flux model is loaded: Check orchestrator logs
**Test LiteLLM directly:**
```bash
curl -X POST http://localhost:4000/v1/images/generations \
-H 'Content-Type: application/json' \
-d '{
"model": "flux-schnell",
"prompt": "A test image",
"size": "1024x1024"
}'
```
### Timeout errors
The default timeout is 120 seconds. If you're getting timeouts:
1. **Increase timeout in Valves:**
- Set `TIMEOUT` to `180` or higher
2. **Check Orchestrator status:**
- Flux model may still be loading (takes ~1 minute on first request)
## Technical Details
### How it Works
1. **User sends prompt** in Open WebUI chat interface
2. **Function extracts prompt** from messages array
3. **Function calls LiteLLM** `/v1/images/generations` endpoint
4. **LiteLLM routes to Orchestrator** via config (`http://100.121.199.88:9000/v1`)
5. **Orchestrator loads Flux** on RunPod GPU (if not already running)
6. **Flux generates image** in 4-5 seconds
7. **Image returns as base64** through the chain
8. **Function displays image** as markdown in chat
### Request Flow
```json
// Function sends to LiteLLM:
{
"model": "flux-schnell",
"prompt": "A serene mountain landscape",
"size": "1024x1024",
"n": 1,
"response_format": "b64_json"
}
// LiteLLM response:
{
"data": [{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAA..."
}]
}
// Function converts to markdown:
![Generated Image](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...)
```
## Limitations
- **Single model**: Currently only Flux.1 Schnell is available
- **Sequential generation**: One image at a time (n=1)
- **Fixed format**: PNG format only
- **Orchestrator dependency**: Requires RunPod GPU server to be running
## Future Enhancements
Potential improvements:
- Multiple size presets in model dropdown
- Support for other Flux variants (Dev, Pro)
- Batch generation (n > 1)
- Image-to-image support
- Custom aspect ratios
## Support
- **Documentation**: `/home/valknar/Projects/docker-compose/CLAUDE.md`
- **RunPod README**: `/home/valknar/Projects/runpod/README.md`
- **LiteLLM Config**: `/home/valknar/Projects/docker-compose/ai/litellm-config.yaml`

View File

@@ -15,7 +15,7 @@ services:
- ai_postgres_data:/var/lib/postgresql/data - ai_postgres_data:/var/lib/postgresql/data
- ./postgres/init:/docker-entrypoint-initdb.d - ./postgres/init:/docker-entrypoint-initdb.d
healthcheck: healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${AI_DB_USER}'] test: ["CMD-SHELL", "pg_isready -U ${AI_DB_USER}"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -73,95 +73,80 @@ services:
networks: networks:
- compose_network - compose_network
labels: labels:
- 'traefik.enable=${AI_TRAEFIK_ENABLED}' - "traefik.enable=${AI_TRAEFIK_ENABLED}"
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
- 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https' - "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-redirect-web-secure' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-redirect-web-secure"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.rule=Host(`${AI_TRAEFIK_HOST}`)' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.rule=Host(`${AI_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.entrypoints=web' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web.entrypoints=web"
# HTTPS router # HTTPS router
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${AI_TRAEFIK_HOST}`)' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${AI_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure"
- 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true' - "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-web-secure-compress,security-headers@file' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-web-secure-compress,security-headers@file"
# Service # Service
- 'traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=8080' - "traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=8080"
- 'traefik.docker.network=${NETWORK_NAME}' - "traefik.docker.network=${NETWORK_NAME}"
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
# LiteLLM - Proxy to convert Anthropic API to OpenAI-compatible format # LiteLLM - Proxy to convert Anthropic API to OpenAI-compatible format
litellm: litellm:
image: ghcr.io/berriai/litellm:main-latest image: ghcr.io/berriai/litellm:main-latest
container_name: ${AI_COMPOSE_PROJECT_NAME}_litellm container_name: ${AI_COMPOSE_PROJECT_NAME}_litellm
restart: unless-stopped restart: unless-stopped
dns:
- 100.100.100.100
- 8.8.8.8
environment: environment:
TZ: ${TIMEZONE:-Europe/Berlin} TZ: ${TIMEZONE:-Europe/Berlin}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
LITELLM_MASTER_KEY: ${AI_LITELLM_API_KEY} LITELLM_MASTER_KEY: ${AI_LITELLM_API_KEY}
DATABASE_URL: postgresql://${AI_DB_USER}:${AI_DB_PASSWORD}@ai_postgres:5432/litellm DATABASE_URL: postgresql://${AI_DB_USER}:${AI_DB_PASSWORD}@ai_postgres:5432/litellm
GPU_VLLM_LLAMA_URL: ${GPU_VLLM_LLAMA_URL}
GPU_VLLM_BGE_URL: ${GPU_VLLM_BGE_URL}
# LITELLM_DROP_PARAMS: 'true' # DISABLED: Was breaking streaming # LITELLM_DROP_PARAMS: 'true' # DISABLED: Was breaking streaming
NO_DOCS: 'true' NO_DOCS: "true"
NO_REDOC: 'true' NO_REDOC: "true"
# Performance optimizations # Performance optimizations
LITELLM_LOG: 'DEBUG' # Enable detailed logging for debugging streaming issues LITELLM_LOG: "DEBUG" # Enable detailed logging for debugging streaming issues
LITELLM_MODE: 'PRODUCTION' # Production mode for better performance LITELLM_MODE: "PRODUCTION" # Production mode for better performance
volumes: volumes:
- ./litellm-config.yaml:/app/litellm-config.yaml:ro - ./litellm-config.yaml:/app/litellm-config.yaml:ro
command: command:
[ [
'--config', "--config",
'/app/litellm-config.yaml', "/app/litellm-config.yaml",
'--host', "--host",
'0.0.0.0', "0.0.0.0",
'--port', "--port",
'4000' "4000",
] ]
depends_on: depends_on:
- ai_postgres - ai_postgres
networks:
- compose_network
healthcheck: healthcheck:
disable: true disable: true
labels:
- 'traefik.enable=${AI_TRAEFIK_ENABLED}'
# HTTP to HTTPS redirect
- 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-litellm-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-litellm-redirect-web-secure'
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web.rule=Host(`${AI_LITELLM_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web.entrypoints=web'
# HTTPS router
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.rule=Host(`${AI_LITELLM_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.entrypoints=web-secure'
- 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure-compress.compress=true'
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure-compress,security-headers@file'
# Service
- 'traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.loadbalancer.server.port=4000'
- 'traefik.docker.network=${NETWORK_NAME}'
# Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
# Crawl4AI - Web scraping for LLMs (internal API, no public access)
crawl4ai:
image: ${AI_CRAWL4AI_IMAGE:-unclecode/crawl4ai:latest}
container_name: ${AI_COMPOSE_PROJECT_NAME}_crawl4ai
restart: unless-stopped
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
# API configuration
PORT: ${AI_CRAWL4AI_PORT:-11235}
volumes:
- ai_crawl4ai_data:/app/.crawl4ai
networks: networks:
- compose_network - compose_network
labels: labels:
# No Traefik exposure - internal only - "traefik.enable=${AI_TRAEFIK_ENABLED}"
- 'traefik.enable=false' # HTTP to HTTPS redirect
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-litellm-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-litellm-redirect-web-secure"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web.rule=Host(`${AI_LITELLM_TRAEFIK_HOST}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web.entrypoints=web"
# HTTPS router
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.rule=Host(`${AI_LITELLM_TRAEFIK_HOST}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.entrypoints=web-secure"
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure-compress.compress=true"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure-compress,security-headers@file"
# Service
- "traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-litellm-web-secure.loadbalancer.server.port=4000"
- "traefik.docker.network=${NETWORK_NAME}"
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
# Facefusion - AI face swapping and enhancement # Facefusion - AI face swapping and enhancement
facefusion: facefusion:
build: build:
@@ -171,7 +156,7 @@ services:
container_name: ${AI_COMPOSE_PROJECT_NAME}_facefusion container_name: ${AI_COMPOSE_PROJECT_NAME}_facefusion
restart: unless-stopped restart: unless-stopped
tty: true tty: true
command: ['python', '-u', 'facefusion.py', 'run'] command: ["python", "-u", "facefusion.py", "run"]
environment: environment:
TZ: ${TIMEZONE:-Europe/Berlin} TZ: ${TIMEZONE:-Europe/Berlin}
GRADIO_SERVER_NAME: "0.0.0.0" GRADIO_SERVER_NAME: "0.0.0.0"
@@ -181,30 +166,175 @@ services:
networks: networks:
- compose_network - compose_network
labels: labels:
- 'traefik.enable=${AI_FACEFUSION_TRAEFIK_ENABLED}' - "traefik.enable=${AI_FACEFUSION_TRAEFIK_ENABLED}"
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
- 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-facefusion-redirect-web-secure.redirectscheme.scheme=https' - "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-facefusion-redirect-web-secure.redirectscheme.scheme=https"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-facefusion-redirect-web-secure' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-facefusion-redirect-web-secure"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web.rule=Host(`${AI_FACEFUSION_TRAEFIK_HOST}`)' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web.rule=Host(`${AI_FACEFUSION_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web.entrypoints=web' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web.entrypoints=web"
# HTTPS router with Authelia # HTTPS router with Authelia
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.rule=Host(`${AI_FACEFUSION_TRAEFIK_HOST}`)' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.rule=Host(`${AI_FACEFUSION_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.tls.certresolver=resolver' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.tls.certresolver=resolver"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.entrypoints=web-secure' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.entrypoints=web-secure"
- 'traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure-compress.compress=true' - "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure-compress.compress=true"
- 'traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure-compress,net-authelia,security-headers@file' - "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure-compress,net-authelia,security-headers@file"
# Service # Service
- 'traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.loadbalancer.server.port=7860' - "traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-facefusion-web-secure.loadbalancer.server.port=7860"
- 'traefik.docker.network=${NETWORK_NAME}' - "traefik.docker.network=${NETWORK_NAME}"
# Watchtower - disabled for custom local image # Watchtower - disabled for custom local image
- 'com.centurylinklabs.watchtower.enable=false' - "com.centurylinklabs.watchtower.enable=false"
# ComfyUI - Node-based UI for Flux image generation (proxies to RunPod GPU)
comfyui:
image: nginx:alpine
container_name: ${AI_COMPOSE_PROJECT_NAME}_comfyui
restart: unless-stopped
dns:
- 100.100.100.100
- 8.8.8.8
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
GPU_SERVICE_HOST: ${GPU_TAILSCALE_HOST:-runpod-ai-orchestrator}
GPU_SERVICE_PORT: ${COMFYUI_BACKEND_PORT:-8188}
volumes:
- ./nginx.conf.template:/etc/nginx/nginx.conf.template:ro
command: /bin/sh -c "envsubst '$${GPU_SERVICE_HOST},$${GPU_SERVICE_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && exec nginx -g 'daemon off;'"
networks:
- compose_network
labels:
- "traefik.enable=${AI_COMFYUI_TRAEFIK_ENABLED:-true}"
# HTTP to HTTPS redirect
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-comfyui-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-comfyui-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-comfyui-redirect-web-secure"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-comfyui-web.rule=Host(`${AI_COMFYUI_TRAEFIK_HOST:-comfy.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-comfyui-web.entrypoints=web"
# HTTPS router with Authelia SSO
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-comfyui-web-secure.rule=Host(`${AI_COMFYUI_TRAEFIK_HOST:-comfy.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-comfyui-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-comfyui-web-secure.entrypoints=web-secure"
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-comfyui-web-secure-compress.compress=true"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-comfyui-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-comfyui-web-secure-compress,net-authelia,security-headers@file"
# Service
- "traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-comfyui-web-secure.loadbalancer.server.port=80"
- "traefik.docker.network=${NETWORK_NAME}"
# Watchtower
- "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
audiocraft:
image: nginx:alpine
container_name: ${AI_COMPOSE_PROJECT_NAME}_audiocraft
restart: unless-stopped
dns:
- 100.100.100.100
- 8.8.8.8
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
GPU_SERVICE_HOST: ${GPU_TAILSCALE_HOST:-runpod-ai-orchestrator}
GPU_SERVICE_PORT: ${AUDIOCRAFT_BACKEND_PORT:-7860}
volumes:
- ./nginx.conf.template:/etc/nginx/nginx.conf.template:ro
command: /bin/sh -c "envsubst '$${GPU_SERVICE_HOST},$${GPU_SERVICE_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && exec nginx -g 'daemon off;'"
networks:
- compose_network
labels:
- "traefik.enable=${AI_AUDIOCRAFT_TRAEFIK_ENABLED:-true}"
# HTTP to HTTPS redirect
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-audiocraft-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-audiocraft-redirect-web-secure"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web.rule=Host(`${AI_AUDIOCRAFT_TRAEFIK_HOST:-audiocraft.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web.entrypoints=web"
# HTTPS router with Authelia SSO
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web-secure.rule=Host(`${AI_AUDIOCRAFT_TRAEFIK_HOST:-audiocraft.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web-secure.entrypoints=web-secure"
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web-secure-compress.compress=true"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-audiocraft-web-secure-compress,net-authelia,security-headers@file"
# Service
- "traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-audiocraft-web-secure.loadbalancer.server.port=80"
- "traefik.docker.network=${NETWORK_NAME}"
# Watchtower
- "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
upscale:
image: nginx:alpine
container_name: ${AI_COMPOSE_PROJECT_NAME}_upscale
restart: unless-stopped
dns:
- 100.100.100.100
- 8.8.8.8
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
GPU_SERVICE_HOST: ${GPU_TAILSCALE_HOST:-runpod-ai-orchestrator}
GPU_SERVICE_PORT: ${UPSCALE_BACKEND_PORT:-8080}
volumes:
- ./nginx.conf.template:/etc/nginx/nginx.conf.template:ro
command: /bin/sh -c "envsubst '$${GPU_SERVICE_HOST},$${GPU_SERVICE_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf && exec nginx -g 'daemon off;'"
networks:
- compose_network
labels:
- "traefik.enable=${AI_UPSCALE_TRAEFIK_ENABLED:-true}"
# HTTP to HTTPS redirect
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-upscale-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-upscale-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-upscale-redirect-web-secure"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-upscale-web.rule=Host(`${AI_UPSCALE_TRAEFIK_HOST:-upscale.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-upscale-web.entrypoints=web"
# HTTPS router with Authelia SSO
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-upscale-web-secure.rule=Host(`${AI_UPSCALE_TRAEFIK_HOST:-upscale.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-upscale-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-upscale-web-secure.entrypoints=web-secure"
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-upscale-web-secure-compress.compress=true"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-upscale-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-upscale-web-secure-compress,net-authelia,security-headers@file"
# Service
- "traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-upscale-web-secure.loadbalancer.server.port=80"
- "traefik.docker.network=${NETWORK_NAME}"
# Watchtower
- "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
# Supervisor UI - Modern web interface for RunPod process management
supervisor:
image: dev.pivoine.art/valknar/supervisor-ui:latest
container_name: ${AI_COMPOSE_PROJECT_NAME}_supervisor_ui
restart: unless-stopped
dns:
- 100.100.100.100
- 8.8.8.8
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
NODE_ENV: production
# Connect to RunPod Supervisor via Tailscale (host Tailscale provides DNS)
SUPERVISOR_HOST: ${GPU_TAILSCALE_HOST:-runpod-ai-orchestrator}
SUPERVISOR_PORT: ${SUPERVISOR_BACKEND_PORT:-9001}
# No auth needed - Supervisor has auth disabled (protected by Authelia)
networks:
- compose_network
labels:
- "traefik.enable=${AI_SUPERVISOR_TRAEFIK_ENABLED:-true}"
# HTTP to HTTPS redirect
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-supervisor-redirect-web-secure.redirectscheme.scheme=https"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-supervisor-web.middlewares=${AI_COMPOSE_PROJECT_NAME}-supervisor-redirect-web-secure"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-supervisor-web.rule=Host(`${AI_SUPERVISOR_TRAEFIK_HOST:-supervisor.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-supervisor-web.entrypoints=web"
# HTTPS router with Authelia SSO
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-supervisor-web-secure.rule=Host(`${AI_SUPERVISOR_TRAEFIK_HOST:-supervisor.ai.pivoine.art}`)"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-supervisor-web-secure.tls.certresolver=resolver"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-supervisor-web-secure.entrypoints=web-secure"
- "traefik.http.middlewares.${AI_COMPOSE_PROJECT_NAME}-supervisor-web-secure-compress.compress=true"
- "traefik.http.routers.${AI_COMPOSE_PROJECT_NAME}-supervisor-web-secure.middlewares=${AI_COMPOSE_PROJECT_NAME}-supervisor-web-secure-compress,net-authelia,security-headers@file"
# Service (port 3000 for Next.js app)
- "traefik.http.services.${AI_COMPOSE_PROJECT_NAME}-supervisor-web-secure.loadbalancer.server.port=3000"
- "traefik.docker.network=${NETWORK_NAME}"
# Watchtower
- "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
volumes: volumes:
ai_postgres_data: ai_postgres_data:
name: ${AI_COMPOSE_PROJECT_NAME}_postgres_data name: ${AI_COMPOSE_PROJECT_NAME}_postgres_data
ai_webui_data: ai_webui_data:
name: ${AI_COMPOSE_PROJECT_NAME}_webui_data name: ${AI_COMPOSE_PROJECT_NAME}_webui_data
ai_crawl4ai_data:
name: ${AI_COMPOSE_PROJECT_NAME}_crawl4ai_data
ai_facefusion_data: ai_facefusion_data:
name: ${AI_COMPOSE_PROJECT_NAME}_facefusion_data name: ${AI_COMPOSE_PROJECT_NAME}_facefusion_data
networks:
compose_network:
name: ${NETWORK_NAME}
external: true

View File

@@ -1,158 +0,0 @@
"""
title: Flux Image Generator
author: Valknar
version: 1.0.0
license: MIT
description: Generate images using Flux.1 Schnell via LiteLLM
requirements: requests, pydantic
"""
import os
import base64
import json
import requests
from typing import Generator
from pydantic import BaseModel, Field
class Pipe:
"""
Flux Image Generation Function for Open WebUI
Routes image generation requests to LiteLLM → Orchestrator → RunPod Flux
"""
class Valves(BaseModel):
"""Configuration valves for the image generation function"""
LITELLM_API_BASE: str = Field(
default="http://litellm:4000/v1",
description="LiteLLM API base URL"
)
LITELLM_API_KEY: str = Field(
default="dummy",
description="LiteLLM API key (not required for internal use)"
)
DEFAULT_MODEL: str = Field(
default="flux-schnell",
description="Default model to use for image generation"
)
DEFAULT_SIZE: str = Field(
default="1024x1024",
description="Default image size"
)
TIMEOUT: int = Field(
default=120,
description="Request timeout in seconds"
)
def __init__(self):
self.type = "manifold"
self.id = "flux_image_gen"
self.name = "Flux"
self.valves = self.Valves()
def pipes(self):
"""Return available models"""
return [
{
"id": "flux-schnell",
"name": "Flux.1 Schnell (4-5s)"
}
]
def pipe(self, body: dict) -> Generator[str, None, None]:
"""
Generate images via LiteLLM endpoint
Args:
body: Request body containing model, messages, etc.
Yields:
JSON chunks with generated image data
"""
try:
# Extract the prompt from messages
messages = body.get("messages", [])
if not messages:
yield self._error_response("No messages provided")
return
# Get the last user message as prompt
prompt = messages[-1].get("content", "")
if not prompt:
yield self._error_response("No prompt provided")
return
# Prepare image generation request
image_request = {
"model": body.get("model", self.valves.DEFAULT_MODEL),
"prompt": prompt,
"size": body.get("size", self.valves.DEFAULT_SIZE),
"n": 1,
"response_format": "b64_json"
}
# Call LiteLLM images endpoint
response = requests.post(
f"{self.valves.LITELLM_API_BASE}/images/generations",
json=image_request,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {self.valves.LITELLM_API_KEY}"
},
timeout=self.valves.TIMEOUT
)
if response.status_code != 200:
yield self._error_response(
f"Image generation failed: {response.status_code} - {response.text}"
)
return
# Parse response
result = response.json()
# Check if we got image data
if "data" not in result or len(result["data"]) == 0:
yield self._error_response("No image data in response")
return
# Get base64 image data
image_data = result["data"][0].get("b64_json")
if not image_data:
yield self._error_response("No base64 image data in response")
return
# Return image as markdown
image_markdown = f"![Generated Image](data:image/png;base64,{image_data})\n\n**Prompt:** {prompt}"
# Yield final response
yield json.dumps({
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": image_markdown
},
"finish_reason": "stop"
}]
})
except requests.Timeout:
yield self._error_response(f"Request timed out after {self.valves.TIMEOUT}s")
except requests.RequestException as e:
yield self._error_response(f"Request failed: {str(e)}")
except Exception as e:
yield self._error_response(f"Unexpected error: {str(e)}")
def _error_response(self, error_message: str) -> str:
"""Generate error response in OpenAI format"""
return json.dumps({
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": f"Error: {error_message}"
},
"finish_reason": "stop"
}]
})

View File

@@ -25,40 +25,31 @@ model_list:
api_key: os.environ/ANTHROPIC_API_KEY api_key: os.environ/ANTHROPIC_API_KEY
# =========================================================================== # ===========================================================================
# SELF-HOSTED MODELS VIA ORCHESTRATOR (GPU Server via Tailscale VPN) # SELF-HOSTED MODELS - DIRECT vLLM SERVERS (GPU Server via Tailscale VPN)
# =========================================================================== # ===========================================================================
# All requests route through orchestrator (port 9000) which manages model loading # Direct connections to dedicated vLLM servers (no orchestrator)
# Text Generation # Text Generation - Llama 3.1 8B (Port 8001)
- model_name: qwen-2.5-7b - model_name: llama-3.1-8b
litellm_params: litellm_params:
model: hosted_vllm/openai/qwen-2.5-7b # hosted_vllm/openai/ for vLLM via orchestrator model: hosted_vllm/meta-llama/Llama-3.1-8B-Instruct # hosted_vllm/openai/ prefix for proper streaming
api_base: http://100.121.199.88:9000/v1 # RunPod GPU via Tailscale api_base: os.environ/GPU_VLLM_LLAMA_URL # Direct to vLLM Llama server
api_key: dummy api_key: "EMPTY" # vLLM doesn't validate API keys
rpm: 1000 rpm: 1000
tpm: 100000 tpm: 100000
timeout: 600 # 10 minutes for generation timeout: 600 # 10 minutes for generation
stream_timeout: 600 stream_timeout: 600
supports_system_messages: false # vLLM handles system messages differently supports_system_messages: true # Llama supports system messages
stream: true # Enable streaming by default stream: true # Enable streaming by default
# Image Generation # Embeddings - BGE Large (Port 8002)
- model_name: flux-schnell - model_name: bge-large-en
litellm_params: litellm_params:
model: openai/dall-e-3 # OpenAI-compatible mapping model: openai/BAAI/bge-large-en-v1.5
api_base: http://100.121.199.88:9000/v1 # RunPod GPU via Tailscale api_base: os.environ/GPU_VLLM_BGE_URL
api_key: dummy api_key: "EMPTY"
rpm: 100 rpm: 1000
max_parallel_requests: 3 tpm: 500000
# Music Generation
- model_name: musicgen-medium
litellm_params:
model: openai/musicgen-medium
api_base: http://100.121.199.88:9000/v1 # RunPod GPU via Tailscale
api_key: dummy
rpm: 50
max_parallel_requests: 1
litellm_settings: litellm_settings:
drop_params: false # DISABLED: Was breaking streaming drop_params: false # DISABLED: Was breaking streaming

60
ai/nginx.conf.template Normal file
View File

@@ -0,0 +1,60 @@
events {
worker_connections 1024;
}
http {
# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;
# DNS resolver for Tailscale MagicDNS
resolver 100.100.100.100 8.8.8.8 valid=30s;
resolver_timeout 5s;
# Proxy settings
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts for long-running audio/image generation
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
server {
listen 80;
server_name _;
# Increase client body size for image uploads
client_max_body_size 100M;
location / {
# Proxy to service on RunPod via Tailscale
# Use variable to force runtime DNS resolution (not startup)
set $backend http://${GPU_SERVICE_HOST}:${GPU_SERVICE_PORT};
proxy_pass $backend;
# WebSocket upgrade
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Disable buffering for real-time updates
proxy_buffering off;
}
}
}

View File

@@ -78,16 +78,20 @@ envs:
UTIL_JOPLIN_DB_NAME: joplin UTIL_JOPLIN_DB_NAME: joplin
# PairDrop # PairDrop
UTIL_DROP_TRAEFIK_HOST: drop.pivoine.art UTIL_DROP_TRAEFIK_HOST: drop.pivoine.art
# Media Stack (Jellyfin, Filestash, Pinchflat) # Media Stack (Jellyfin, Pinchflat)
MEDIA_TRAEFIK_ENABLED: true MEDIA_TRAEFIK_ENABLED: true
MEDIA_COMPOSE_PROJECT_NAME: media MEDIA_COMPOSE_PROJECT_NAME: media
MEDIA_JELLYFIN_IMAGE: jellyfin/jellyfin:latest MEDIA_JELLYFIN_IMAGE: jellyfin/jellyfin:latest
MEDIA_JELLYFIN_TRAEFIK_HOST: jellyfin.media.pivoine.art MEDIA_JELLYFIN_TRAEFIK_HOST: jellyfin.media.pivoine.art
MEDIA_FILESTASH_IMAGE: machines/filestash:latest
MEDIA_FILESTASH_TRAEFIK_HOST: filestash.media.pivoine.art
MEDIA_FILESTASH_CANARY: true
MEDIA_PINCHFLAT_IMAGE: ghcr.io/kieraneglin/pinchflat:latest MEDIA_PINCHFLAT_IMAGE: ghcr.io/kieraneglin/pinchflat:latest
MEDIA_PINCHFLAT_TRAEFIK_HOST: pinchflat.media.pivoine.art MEDIA_PINCHFLAT_TRAEFIK_HOST: pinchflat.media.pivoine.art
# Immich - Photo and video management
MEDIA_IMMICH_SERVER_IMAGE: ghcr.io/immich-app/immich-server:release
MEDIA_IMMICH_ML_IMAGE: ghcr.io/immich-app/immich-machine-learning:release
MEDIA_IMMICH_POSTGRES_IMAGE: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
MEDIA_IMMICH_TRAEFIK_HOST: immich.media.pivoine.art
MEDIA_IMMICH_DB_NAME: immich
MEDIA_IMMICH_DB_USER: immich
# Dev (Gitea + Coolify) # Dev (Gitea + Coolify)
DEV_TRAEFIK_ENABLED: true DEV_TRAEFIK_ENABLED: true
DEV_COMPOSE_PROJECT_NAME: dev DEV_COMPOSE_PROJECT_NAME: dev
@@ -135,7 +139,6 @@ envs:
AI_COMPOSE_PROJECT_NAME: ai AI_COMPOSE_PROJECT_NAME: ai
AI_POSTGRES_IMAGE: pgvector/pgvector:pg16 AI_POSTGRES_IMAGE: pgvector/pgvector:pg16
AI_WEBUI_IMAGE: ghcr.io/open-webui/open-webui:main AI_WEBUI_IMAGE: ghcr.io/open-webui/open-webui:main
AI_CRAWL4AI_IMAGE: unclecode/crawl4ai:latest
AI_FACEFUSION_IMAGE: facefusion/facefusion:3.5.0-cpu AI_FACEFUSION_IMAGE: facefusion/facefusion:3.5.0-cpu
AI_FACEFUSION_TRAEFIK_ENABLED: true AI_FACEFUSION_TRAEFIK_ENABLED: true
AI_FACEFUSION_TRAEFIK_HOST: facefusion.ai.pivoine.art AI_FACEFUSION_TRAEFIK_HOST: facefusion.ai.pivoine.art
@@ -263,3 +266,57 @@ scripts:
docker restart sexy_api && docker restart sexy_api &&
echo "✓ Directus API restarted" echo "✓ Directus API restarted"
net/create: docker network create "$NETWORK_NAME" net/create: docker network create "$NETWORK_NAME"
# Setup iptables NAT for Docker containers to reach Tailscale network
# Requires Tailscale installed on host: curl -fsSL https://tailscale.com/install.sh | sh
tailscale/setup: |
echo "Setting up iptables for Docker-to-Tailscale routing..."
# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1
grep -q "net.ipv4.ip_forward=1" /etc/sysctl.conf || echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
# Get Docker network CIDR
DOCKER_CIDR=$(docker network inspect ${NETWORK_NAME} --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null || echo "172.18.0.0/16")
echo "Docker network CIDR: $DOCKER_CIDR"
# Add NAT rule (check if already exists)
if ! sudo iptables -t nat -C POSTROUTING -s "$DOCKER_CIDR" -o tailscale0 -j MASQUERADE 2>/dev/null; then
sudo iptables -t nat -A POSTROUTING -s "$DOCKER_CIDR" -o tailscale0 -j MASQUERADE
echo "✓ iptables NAT rule added"
else
echo "✓ iptables NAT rule already exists"
fi
# Persist rules
sudo netfilter-persistent save 2>/dev/null || echo "Install iptables-persistent to persist rules: sudo apt install iptables-persistent"
echo "✓ Tailscale routing configured"
# Install and configure Tailscale on host with persistent state
tailscale/install: |
echo "Installing Tailscale..."
# Install Tailscale if not present
if ! command -v tailscale &> /dev/null; then
curl -fsSL https://tailscale.com/install.sh | sh
else
echo "✓ Tailscale already installed"
fi
# Create state directory for persistence
TAILSCALE_STATE="/var/lib/tailscale"
sudo mkdir -p "$TAILSCALE_STATE"
# Start and enable tailscaled service
sudo systemctl enable --now tailscaled
# Connect to Tailscale network
echo "Connecting to Tailscale..."
sudo tailscale up --authkey="$TAILSCALE_AUTHKEY" --hostname=vps
# Show status
echo ""
tailscale status
echo ""
echo "✓ Tailscale installed and connected"
echo " Run 'arty tailscale/setup' to configure iptables routing for Docker"

370
core/backrest/config.json Normal file
View File

@@ -0,0 +1,370 @@
{
"modno": 1,
"version": 4,
"instance": "falcon",
"repos": [
{
"id": "hidrive-backup",
"uri": "/repos",
"guid": "df03886ea215b0a3ff9730190d906d7034032bf0f1906ed4ad00f2c4f1748215",
"password": "falcon-backup-2025",
"prunePolicy": {
"schedule": {
"cron": "0 2 * * 0"
}
},
"checkPolicy": {
"schedule": {
"cron": "0 3 * * 0"
}
},
"autoUnlock": true
}
],
"plans": [
{
"id": "ai-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/ai_postgres_data",
"/volumes/ai_webui_data"
],
"schedule": {
"cron": "0 3 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "asciinema-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/asciinema_data"
],
"schedule": {
"cron": "0 11 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "coolify-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/dev_coolify_data"
],
"schedule": {
"cron": "0 0 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "directus-bundle-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/directus_bundle"
],
"schedule": {
"cron": "0 4 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 3
}
}
},
{
"id": "directus-uploads-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/directus_uploads"
],
"schedule": {
"cron": "0 4 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "gitea-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/dev_gitea_config",
"/volumes/dev_gitea_data",
"/volumes/dev_gitea_runner_data"
],
"schedule": {
"cron": "0 11 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "jellyfin-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/jelly_config"
],
"schedule": {
"cron": "0 9 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "joplin-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/joplin_data"
],
"schedule": {
"cron": "0 2 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "letsencrypt-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/letsencrypt_data"
],
"schedule": {
"cron": "0 8 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 12,
"yearly": 3
}
}
},
{
"id": "linkwarden-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/linkwarden_data",
"/volumes/linkwarden_meili_data"
],
"schedule": {
"cron": "0 7 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6
}
}
},
{
"id": "mattermost-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/mattermost_config",
"/volumes/mattermost_data",
"/volumes/mattermost_plugins"
],
"schedule": {
"cron": "0 5 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "n8n-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/n8n_data"
],
"schedule": {
"cron": "0 6 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6
}
}
},
{
"id": "netdata-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/netdata_config"
],
"schedule": {
"cron": "0 10 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 3
}
}
},
{
"id": "postgres-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/core_postgres_data"
],
"schedule": {
"cron": "0 2 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
},
{
"id": "redis-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/core_redis_data"
],
"schedule": {
"cron": "0 3 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 3
}
}
},
{
"id": "scrapy-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/scrapy_code",
"/volumes/scrapyd_data"
],
"schedule": {
"cron": "0 6 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 3
}
}
},
{
"id": "tandoor-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/tandoor_mediafiles",
"/volumes/tandoor_staticfiles"
],
"schedule": {
"cron": "0 5 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6
}
}
},
{
"id": "vaultwarden-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/vaultwarden_data"
],
"schedule": {
"cron": "0 8 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 12,
"yearly": 3
}
}
},
{
"id": "immich-backup",
"repo": "hidrive-backup",
"paths": [
"/volumes/immich_postgres_data",
"/volumes/immich_upload"
],
"schedule": {
"cron": "0 12 * * *"
},
"retention": {
"policyTimeBucketed": {
"daily": 7,
"weekly": 4,
"monthly": 6,
"yearly": 2
}
}
}
]
}

View File

@@ -56,7 +56,7 @@ services:
volumes: volumes:
# Backrest application data # Backrest application data
- backrest_data:/data - backrest_data:/data
- backrest_config:/config - ./backrest:/config
- backrest_cache:/cache - backrest_cache:/cache
- backrest_tmp:/tmp - backrest_tmp:/tmp
@@ -74,7 +74,6 @@ services:
- backup_util_tandoor_staticfiles:/volumes/tandoor_staticfiles:ro - backup_util_tandoor_staticfiles:/volumes/tandoor_staticfiles:ro
- backup_util_tandoor_mediafiles:/volumes/tandoor_mediafiles:ro - backup_util_tandoor_mediafiles:/volumes/tandoor_mediafiles:ro
- backup_n8n_data:/volumes/n8n_data:ro - backup_n8n_data:/volumes/n8n_data:ro
- backup_filestash_data:/volumes/filestash_data:ro
- backup_util_linkwarden_data:/volumes/linkwarden_data:ro - backup_util_linkwarden_data:/volumes/linkwarden_data:ro
- backup_util_linkwarden_meili_data:/volumes/linkwarden_meili_data:ro - backup_util_linkwarden_meili_data:/volumes/linkwarden_meili_data:ro
- backup_letsencrypt_data:/volumes/letsencrypt_data:ro - backup_letsencrypt_data:/volumes/letsencrypt_data:ro
@@ -84,12 +83,13 @@ services:
- backup_netdata_config:/volumes/netdata_config:ro - backup_netdata_config:/volumes/netdata_config:ro
- backup_ai_postgres_data:/volumes/ai_postgres_data:ro - backup_ai_postgres_data:/volumes/ai_postgres_data:ro
- backup_ai_webui_data:/volumes/ai_webui_data:ro - backup_ai_webui_data:/volumes/ai_webui_data:ro
- backup_ai_crawl4ai_data:/volumes/ai_crawl4ai_data:ro
- backup_asciinema_data:/volumes/asciinema_data:ro - backup_asciinema_data:/volumes/asciinema_data:ro
- backup_dev_gitea_data:/volumes/dev_gitea_data:ro - backup_dev_gitea_data:/volumes/dev_gitea_data:ro
- backup_dev_gitea_config:/volumes/dev_gitea_config:ro - backup_dev_gitea_config:/volumes/dev_gitea_config:ro
- backup_dev_gitea_runner_data:/volumes/dev_gitea_runner_data:ro - backup_dev_gitea_runner_data:/volumes/dev_gitea_runner_data:ro
- backup_dev_coolify_data:/volumes/dev_coolify_data:ro - backup_dev_coolify_data:/volumes/dev_coolify_data:ro
- backup_media_immich_postgres_data:/volumes/immich_postgres_data:ro
- backup_media_immich_upload:/volumes/immich_upload:ro
environment: environment:
TZ: ${TIMEZONE:-Europe/Berlin} TZ: ${TIMEZONE:-Europe/Berlin}
@@ -124,8 +124,6 @@ volumes:
name: ${CORE_COMPOSE_PROJECT_NAME}_redis_data name: ${CORE_COMPOSE_PROJECT_NAME}_redis_data
backrest_data: backrest_data:
name: ${CORE_COMPOSE_PROJECT_NAME}_backrest_data name: ${CORE_COMPOSE_PROJECT_NAME}_backrest_data
backrest_config:
name: ${CORE_COMPOSE_PROJECT_NAME}_backrest_config
backrest_cache: backrest_cache:
name: ${CORE_COMPOSE_PROJECT_NAME}_backrest_cache name: ${CORE_COMPOSE_PROJECT_NAME}_backrest_cache
backrest_tmp: backrest_tmp:
@@ -162,9 +160,6 @@ volumes:
backup_n8n_data: backup_n8n_data:
name: dev_n8n_data name: dev_n8n_data
external: true external: true
backup_filestash_data:
name: stash_filestash_data
external: true
backup_util_linkwarden_data: backup_util_linkwarden_data:
name: util_linkwarden_data name: util_linkwarden_data
external: true external: true
@@ -192,9 +187,6 @@ volumes:
backup_ai_webui_data: backup_ai_webui_data:
name: ai_webui_data name: ai_webui_data
external: true external: true
backup_ai_crawl4ai_data:
name: ai_crawl4ai_data
external: true
backup_asciinema_data: backup_asciinema_data:
name: dev_asciinema_data name: dev_asciinema_data
external: true external: true
@@ -210,3 +202,9 @@ volumes:
backup_dev_coolify_data: backup_dev_coolify_data:
name: dev_coolify_data name: dev_coolify_data
external: true external: true
backup_media_immich_postgres_data:
name: media_immich_postgres_data
external: true
backup_media_immich_upload:
name: media_immich_upload
external: true

View File

@@ -33,36 +33,6 @@ services:
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
# Filestash - Web-based file manager
filestash:
image: ${MEDIA_FILESTASH_IMAGE:-machines/filestash:latest}
container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_filestash
restart: unless-stopped
volumes:
- filestash_data:/app/data/state/
tmpfs:
- /tmp:exec
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
APPLICATION_URL: ${MEDIA_FILESTASH_TRAEFIK_HOST}
CANARY: ${MEDIA_FILESTASH_CANARY:-true}
networks:
- compose_network
labels:
- 'traefik.enable=${MEDIA_TRAEFIK_ENABLED}'
- 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-filestash-redirect-web-secure'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web.rule=Host(`${MEDIA_FILESTASH_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web.entrypoints=web'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.rule=Host(`${MEDIA_FILESTASH_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.entrypoints=web-secure'
- 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure-compress.compress=true'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure-compress'
- 'traefik.http.services.${MEDIA_COMPOSE_PROJECT_NAME}-filestash-web-secure.loadbalancer.server.port=8334'
- 'traefik.docker.network=${NETWORK_NAME}'
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
# Pinchflat - YouTube download manager # Pinchflat - YouTube download manager
pinchflat: pinchflat:
image: ${MEDIA_PINCHFLAT_IMAGE:-ghcr.io/kieraneglin/pinchflat:latest} image: ${MEDIA_PINCHFLAT_IMAGE:-ghcr.io/kieraneglin/pinchflat:latest}
@@ -95,15 +65,98 @@ services:
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
# Immich PostgreSQL - Dedicated database with vector extensions
immich_postgres:
image: ${MEDIA_IMMICH_POSTGRES_IMAGE:-ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0}
container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_postgres
restart: unless-stopped
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
POSTGRES_USER: ${MEDIA_IMMICH_DB_USER:-immich}
POSTGRES_PASSWORD: ${MEDIA_IMMICH_DB_PASSWORD}
POSTGRES_DB: ${MEDIA_IMMICH_DB_NAME:-immich}
POSTGRES_INITDB_ARGS: --data-checksums
volumes:
- immich_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${MEDIA_IMMICH_DB_USER:-immich} -d ${MEDIA_IMMICH_DB_NAME:-immich}"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- compose_network
# Immich Server - Main application
immich_server:
image: ${MEDIA_IMMICH_SERVER_IMAGE:-ghcr.io/immich-app/immich-server:release}
container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_server
restart: unless-stopped
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
DB_HOSTNAME: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_postgres
DB_PORT: 5432
DB_USERNAME: ${MEDIA_IMMICH_DB_USER:-immich}
DB_PASSWORD: ${MEDIA_IMMICH_DB_PASSWORD}
DB_DATABASE_NAME: ${MEDIA_IMMICH_DB_NAME:-immich}
REDIS_HOSTNAME: ${CORE_COMPOSE_PROJECT_NAME}_redis
REDIS_PORT: ${CORE_REDIS_PORT:-6379}
IMMICH_MACHINE_LEARNING_URL: http://${MEDIA_COMPOSE_PROJECT_NAME}_immich_ml:3003
volumes:
- immich_upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
depends_on:
immich_postgres:
condition: service_healthy
networks:
- compose_network
labels:
- 'traefik.enable=${MEDIA_TRAEFIK_ENABLED}'
# HTTP to HTTPS redirect
- 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-immich-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-immich-redirect-web-secure'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web.rule=Host(`${MEDIA_IMMICH_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web.entrypoints=web'
# HTTPS router
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.rule=Host(`${MEDIA_IMMICH_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.entrypoints=web-secure'
- 'traefik.http.middlewares.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure-compress.compress=true'
- 'traefik.http.routers.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.middlewares=${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure-compress,security-headers@file'
# Service
- 'traefik.http.services.${MEDIA_COMPOSE_PROJECT_NAME}-immich-web-secure.loadbalancer.server.port=2283'
- 'traefik.docker.network=${NETWORK_NAME}'
# Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
# Immich Machine Learning - AI inference for faces, search, etc.
immich_ml:
image: ${MEDIA_IMMICH_ML_IMAGE:-ghcr.io/immich-app/immich-machine-learning:release}
container_name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_ml
restart: unless-stopped
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
volumes:
- immich_model_cache:/cache
networks:
- compose_network
labels:
# Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
volumes: volumes:
jellyfin_config: jellyfin_config:
name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_config name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_config
jellyfin_cache: jellyfin_cache:
name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_cache name: ${MEDIA_COMPOSE_PROJECT_NAME}_jellyfin_cache
filestash_data:
name: ${MEDIA_COMPOSE_PROJECT_NAME}_filestash_data
pinchflat_config: pinchflat_config:
name: ${MEDIA_COMPOSE_PROJECT_NAME}_pinchflat_config name: ${MEDIA_COMPOSE_PROJECT_NAME}_pinchflat_config
immich_postgres_data:
name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_postgres_data
immich_upload:
name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_upload
immich_model_cache:
name: ${MEDIA_COMPOSE_PROJECT_NAME}_immich_model_cache
networks: networks:
compose_network: compose_network:

View File

@@ -74,23 +74,26 @@ access_control:
- "admin.asciinema.dev.pivoine.art" - "admin.asciinema.dev.pivoine.art"
- "facefusion.ai.pivoine.art" - "facefusion.ai.pivoine.art"
- "pinchflat.media.pivoine.art" - "pinchflat.media.pivoine.art"
- "comfy.ai.pivoine.art"
- "supervisor.ai.pivoine.art"
- "audiocraft.ai.pivoine.art"
- "upscale.ai.pivoine.art"
policy: one_factor policy: one_factor
# session secret set via environment variable: AUTHELIA_SESSION_SECRET # session secret set via environment variable: AUTHELIA_SESSION_SECRET
session: session:
name: 'authelia_session' name: "authelia_session"
same_site: 'lax' same_site: "lax"
expiration: '1h' expiration: "1h"
inactivity: '5m' inactivity: "15m"
remember_me: '1M' remember_me: "1M"
cookies: cookies:
- domain: 'pivoine.art' - domain: "pivoine.art"
authelia_url: 'https://auth.pivoine.art' authelia_url: "https://auth.pivoine.art"
same_site: 'lax' same_site: "lax"
expiration: '1h' expiration: "1h"
inactivity: '5m' inactivity: "5m"
remember_me: '1M' remember_me: "1M"
regulation: regulation:
max_retries: 3 max_retries: 3

View File

@@ -6,49 +6,49 @@ services:
restart: unless-stopped restart: unless-stopped
command: command:
# API & Dashboard # API & Dashboard
- '--api.dashboard=true' - "--api.dashboard=true"
- '--api.insecure=false' - "--api.insecure=false"
# Ping endpoint for healthcheck # Ping endpoint for healthcheck
- '--ping=true' - "--ping=true"
# Experimental plugins # Experimental plugins
- '--experimental.plugins.sablier.modulename=github.com/acouvreur/sablier' - "--experimental.plugins.sablier.modulename=github.com/acouvreur/sablier"
- '--experimental.plugins.sablier.version=v1.8.0' - "--experimental.plugins.sablier.version=v1.8.0"
# Logging # Logging
- '--log.level=${NET_PROXY_LOG_LEVEL:-INFO}' - "--log.level=${NET_PROXY_LOG_LEVEL:-INFO}"
- '--accesslog=true' - "--accesslog=true"
# Global # Global
- '--global.sendAnonymousUsage=false' - "--global.sendAnonymousUsage=false"
- '--global.checkNewVersion=true' - "--global.checkNewVersion=true"
# Docker Provider # Docker Provider
- '--providers.docker=true' - "--providers.docker=true"
- '--providers.docker.exposedbydefault=false' - "--providers.docker.exposedbydefault=false"
- '--providers.docker.network=${NETWORK_NAME}' - "--providers.docker.network=${NETWORK_NAME}"
# File Provider for dynamic configuration # File Provider for dynamic configuration
- '--providers.file.directory=/etc/traefik/dynamic' - "--providers.file.directory=/etc/traefik/dynamic"
- '--providers.file.watch=true' - "--providers.file.watch=true"
# Entrypoints # Entrypoints
- '--entrypoints.web.address=:${NET_PROXY_PORT_HTTP:-80}' - "--entrypoints.web.address=:${NET_PROXY_PORT_HTTP:-80}"
- '--entrypoints.web-secure.address=:${NET_PROXY_PORT_HTTPS:-443}' - "--entrypoints.web-secure.address=:${NET_PROXY_PORT_HTTPS:-443}"
# Global HTTP to HTTPS redirect # Global HTTP to HTTPS redirect
- '--entrypoints.web.http.redirections.entryPoint.to=web-secure' - "--entrypoints.web.http.redirections.entryPoint.to=web-secure"
- '--entrypoints.web.http.redirections.entryPoint.scheme=https' - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- '--entrypoints.web.http.redirections.entryPoint.permanent=true' - "--entrypoints.web.http.redirections.entryPoint.permanent=true"
# Security Headers (applied globally) # Security Headers (applied globally)
- '--entrypoints.web-secure.http.middlewares=security-headers@file' - "--entrypoints.web-secure.http.middlewares=security-headers@file"
# Let's Encrypt # Let's Encrypt
- '--certificatesresolvers.resolver.acme.tlschallenge=true' - "--certificatesresolvers.resolver.acme.tlschallenge=true"
- '--certificatesresolvers.resolver.acme.email=${ADMIN_EMAIL}' - "--certificatesresolvers.resolver.acme.email=${ADMIN_EMAIL}"
- '--certificatesresolvers.resolver.acme.storage=/letsencrypt/acme.json' - "--certificatesresolvers.resolver.acme.storage=/letsencrypt/acme.json"
healthcheck: healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"] test: ["CMD", "traefik", "healthcheck", "--ping"]
@@ -74,20 +74,20 @@ services:
- ./dynamic:/etc/traefik/dynamic:ro - ./dynamic:/etc/traefik/dynamic:ro
labels: labels:
- 'traefik.enable=true' - "traefik.enable=true"
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure.redirectscheme.scheme=https' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure.redirectscheme.scheme=https"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-traefik-redirect-web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.entrypoints=web' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web.entrypoints=web"
# HTTPS router with auth # HTTPS router with auth
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.rule=Host(`${NET_PROXY_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.tls.certresolver=resolver' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.tls.certresolver=resolver"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.entrypoints=web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.entrypoints=web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.service=api@internal' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.service=api@internal"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-authelia,security-headers@file' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-authelia,security-headers@file"
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.loadbalancer.server.port=8080' - "traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-traefik-web-secure.loadbalancer.server.port=8080"
- 'traefik.docker.network=${NETWORK_NAME}' - "traefik.docker.network=${NETWORK_NAME}"
# Netdata - Real-time monitoring # Netdata - Real-time monitoring
netdata: netdata:
@@ -128,23 +128,23 @@ services:
networks: networks:
- compose_network - compose_network
labels: labels:
- 'traefik.enable=${NET_TRAEFIK_ENABLED}' - "traefik.enable=${NET_TRAEFIK_ENABLED}"
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure.redirectscheme.scheme=https' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure.redirectscheme.scheme=https"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-redirect-web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.entrypoints=web' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web.entrypoints=web"
# HTTPS router # HTTPS router
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.rule=Host(`${NET_NETDATA_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.tls.certresolver=resolver' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.tls.certresolver=resolver"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.entrypoints=web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.entrypoints=web-secure"
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-compress.compress=true' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-netdata-compress.compress=true"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-compress,${NET_COMPOSE_PROJECT_NAME}-authelia,security-headers@file' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-netdata-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-netdata-compress,${NET_COMPOSE_PROJECT_NAME}-authelia,security-headers@file"
# Service # Service
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-netdata.loadbalancer.server.port=19999' - "traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-netdata.loadbalancer.server.port=19999"
- 'traefik.docker.network=${NETWORK_NAME}' - "traefik.docker.network=${NETWORK_NAME}"
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
# Watchtower - Automatic container updates # Watchtower - Automatic container updates
watchtower: watchtower:
@@ -202,7 +202,8 @@ services:
- compose_network - compose_network
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"] test:
["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 5 retries: 5
@@ -210,21 +211,21 @@ services:
labels: labels:
# Traefik Configuration # Traefik Configuration
- 'traefik.enable=${NET_TRAEFIK_ENABLED}' - "traefik.enable=${NET_TRAEFIK_ENABLED}"
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure.redirectscheme.scheme=https' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure.redirectscheme.scheme=https"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-umami-redirect-web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.entrypoints=web' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web.entrypoints=web"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.rule=Host(`${NET_TRACK_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.tls.certresolver=resolver' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.tls.certresolver=resolver"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.entrypoints=web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.entrypoints=web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.middlewares=security-headers@file' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.middlewares=security-headers@file"
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.loadbalancer.server.port=3000' - "traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-umami-web-secure.loadbalancer.server.port=3000"
- 'traefik.docker.network=${NETWORK_NAME}' - "traefik.docker.network=${NETWORK_NAME}"
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
# Mailpit - SMTP server with web UI # Mailpit - SMTP server with web UI
mailpit: mailpit:
@@ -250,22 +251,22 @@ services:
networks: networks:
- compose_network - compose_network
labels: labels:
- 'traefik.enable=${NET_TRAEFIK_ENABLED}' - "traefik.enable=${NET_TRAEFIK_ENABLED}"
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-mailpit-redirect-web-secure.redirectscheme.scheme=https' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-mailpit-redirect-web-secure.redirectscheme.scheme=https"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-mailpit-redirect-web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-mailpit-redirect-web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web.rule=Host(`${NET_MAILPIT_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web.rule=Host(`${NET_MAILPIT_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web.entrypoints=web' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web.entrypoints=web"
# HTTPS router with auth # HTTPS router with auth
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.rule=Host(`${NET_MAILPIT_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.rule=Host(`${NET_MAILPIT_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.tls.certresolver=resolver' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.tls.certresolver=resolver"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.entrypoints=web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.entrypoints=web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-authelia,security-headers@file' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.middlewares=${NET_COMPOSE_PROJECT_NAME}-authelia,security-headers@file"
# Service # Service
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.loadbalancer.server.port=8025' - "traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-mailpit-web-secure.loadbalancer.server.port=8025"
- 'traefik.docker.network=${NETWORK_NAME}' - "traefik.docker.network=${NETWORK_NAME}"
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
# Authelia - SSO and authentication portal # Authelia - SSO and authentication portal
authelia: authelia:
@@ -285,27 +286,27 @@ services:
networks: networks:
- compose_network - compose_network
labels: labels:
- 'traefik.enable=${NET_TRAEFIK_ENABLED}' - "traefik.enable=${NET_TRAEFIK_ENABLED}"
# HTTP to HTTPS redirect # HTTP to HTTPS redirect
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia-redirect-web-secure.redirectscheme.scheme=https' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia-redirect-web-secure.redirectscheme.scheme=https"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-authelia-redirect-web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web.middlewares=${NET_COMPOSE_PROJECT_NAME}-authelia-redirect-web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web.rule=Host(`${NET_AUTHELIA_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web.rule=Host(`${NET_AUTHELIA_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web.entrypoints=web' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web.entrypoints=web"
# HTTPS router # HTTPS router
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.rule=Host(`${NET_AUTHELIA_TRAEFIK_HOST}`)' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.rule=Host(`${NET_AUTHELIA_TRAEFIK_HOST}`)"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.tls.certresolver=resolver' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.tls.certresolver=resolver"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.entrypoints=web-secure' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.entrypoints=web-secure"
- 'traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.middlewares=security-headers@file' - "traefik.http.routers.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.middlewares=security-headers@file"
# Service # Service
- 'traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.loadbalancer.server.port=9091' - "traefik.http.services.${NET_COMPOSE_PROJECT_NAME}-authelia-web-secure.loadbalancer.server.port=9091"
- 'traefik.docker.network=${NETWORK_NAME}' - "traefik.docker.network=${NETWORK_NAME}"
# ForwardAuth middleware for other services # ForwardAuth middleware for other services
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.address=http://net_authelia:9091/api/authz/forward-auth' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.address=http://net_authelia:9091/api/authz/forward-auth"
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.trustForwardHeader=true' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.trustForwardHeader=true"
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
- 'traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.authResponseHeadersRegex=^Remote-' - "traefik.http.middlewares.${NET_COMPOSE_PROJECT_NAME}-authelia.forwardAuth.authResponseHeadersRegex=^Remote-"
# Watchtower # Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}' - "com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}"
volumes: volumes:
letsencrypt_data: letsencrypt_data: