Compare commits

...
12 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.6 cfb6cea452 docs: add project handover notes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:35:09 +01:00
jpmschweitzerandClaude Opus 4.6 d0f06918ea docs(containers): add Stable Audio and TRELLIS, update service counts
- Add full profiles for Stable Audio and TRELLIS services
- Update quick reference table, GPU services, and storage distribution
- Add new stacks to README port allocation and GPU section
- Update total services to 37 across 23 stacks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:49 +01:00
jpmschweitzerandClaude Opus 4.6 bc1574b36e feat(stack): add Stable Audio and TRELLIS GPU service stacks
- Stable Audio Open: AI audio generation on port 11500 (~6GB VRAM)
- TRELLIS: 3D model generation on port 11510 (~6-8GB VRAM, low-VRAM fork)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:34 +01:00
jpmschweitzerandClaude Opus 4.6 827dffe164 fix(penpot): update assets storage config for Penpot 2.11+
Rename PENPOT_ASSETS_STORAGE_* to PENPOT_OBJECTS_STORAGE_* per upstream changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:27 +01:00
jpmschweitzerandClaude Opus 4.6 71cd913d05 fix(core-api): use IP address for Home Assistant URL
Container name resolution doesn't work for host-networked services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:19 +01:00
jpmschweitzerandClaude Opus 4.6 aa54af8a87 config(agents): add Anthropic API config for Tatlock and Webber
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:14 +01:00
jpmschweitzerandClaude Opus 4.6 dd141099e1 config(ollama): reduce VRAM usage for GPU sharing with other services
Change keep-alive from infinite to 5m and max loaded models from 2 to 1,
freeing VRAM for Stable Audio, TRELLIS, and other GPU services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:34:09 +01:00
jpmschweitzerandClaude Opus 4.6 ec8c3f5925 feat(home-assistant): switch to host networking and add Matter Server
- Switch HA from bridge to host networking for better device discovery
- Add python-matter-server container for Matter protocol support
- Both services share host network for mDNS/IPv6 multicast
- Update NPM forward hostname to use IP address
- Add Matter Server setup instructions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:33:49 +01:00
jpmschweitzerandClaude Opus 4.6 24a70d9527 fix(agents): correct RAM spec from 16GB to 64GB
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:33:42 +01:00
jpmschweitzerandClaude Opus 4.6 9ac817bb6e chore(gitignore): ignore local Claude settings and Jupyter checkpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:33:35 +01:00
jpmschweitzerandClaude Opus 4.5 cd3cdad11c docs(containers): add Shell In A Box host service
Add shellinabox web-based terminal emulator running on port 4200,
accessible via https://shell.schweitz.net through NPM reverse proxy.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 17:55:10 +01:00
jpmschweitzerandClaude Opus 4.5 27e7d9aff0 docs(home-assistant): add MCP server setup guide for Claude integration
Documents the configuration steps for connecting Claude.ai and Claude Desktop
to Home Assistant via the Model Context Protocol (MCP) server endpoint.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 09:12:35 +01:00
15 changed files with 791 additions and 39 deletions
-17
View File
@@ -1,17 +0,0 @@
{
"permissions": {
"allow": [
"WebSearch",
"Bash(docker logs:*)",
"Bash(docker ps:*)",
"Bash(docker inspect:*)",
"Bash(tree:*)",
"Bash(nvidia-smi:*)",
"Bash(find:*)",
"Bash(grep:*)",
"Bash(cat:*)"
],
"deny": [],
"ask": []
}
}
+4
View File
@@ -70,6 +70,10 @@ docker-compose.override.yml
# Local configuration (machine-specific)
local.yml
local.config
.claude/settings.local.json
# Jupyter notebook checkpoints
.ipynb_checkpoints/
# Archive files
*.zip
+1 -1
View File
@@ -17,7 +17,7 @@ This is the `tower-of-joy` project - a containerized home server infrastructure
**Purpose:** Self-hosted services platform with GPU-accelerated ML model serving, media streaming, cloud storage, and secure remote access.
**System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 16GB RAM, Zorin OS 16.3 (Ubuntu 20.04 based)
**System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3 (Ubuntu 20.04 based)
**Storage Architecture:**
- **SSD (489GB):** Container configs, databases, Docker images - `/home/jpmschweitzer/docker-data/`
+101 -3
View File
@@ -1,7 +1,7 @@
# Container Reference - tower-of-joy Infrastructure
> **Last Updated:** 2026-01-31
> **Total Services:** 34 containers across 21 stacks
> **Last Updated:** 2026-02-20
> **Total Services:** 37 containers across 23 stacks
> **System:** Intel i7-6700, RTX 2080 Ti (11GB VRAM), 64GB RAM, Zorin OS 16.3
---
@@ -40,8 +40,11 @@
| **Paperless-ngx** | 8091 | https://documents.schweitz.net | Internet | No | 8 | ✅ Running |
| **Penpot** | 9001 | https://penpot.schweitz.net | Internet (SSO) | No | 10 | ✅ Running |
| **ClamAV** | 3310 | N/A (host service) | No | No | - | ✅ Running |
| **Shell In A Box** | 4200 | https://shell.schweitz.net | Internet (host service) | No | - | ✅ Running |
| **AdGuard Home** | 53, 3053 | http://dns.schweitz.internal | LAN (DNS) | No | - | ✅ Running |
| **Tatlock UI** | 9999 | https://home.schweitz.net | Internet | No | - | ✅ Running |
| **Stable Audio** | 11500 | http://192.168.86.149:11500 | LAN | Yes (RTX 2080 Ti) | - | ⏸️ Not Deployed |
| **TRELLIS** | 11510 | http://192.168.86.149:11510 | LAN | Yes (RTX 2080 Ti) | - | ✅ Running |
### External Domains (SSL via Let's Encrypt)
- **home.schweitz.net** → Tatlock UI
@@ -51,6 +54,7 @@
- **auth.schweitz.net** → Authentik SSO
- **api.schweitz.net** → Core API
- **code.schweitz.net** → Code-Server (host service)
- **shell.schweitz.net** → Shell In A Box (host service)
- **amp.schweitz.net** → AMP Game Server
- **housekeeping.schweitz.net** → Home Assistant
- **documents.schweitz.net** → Paperless-ngx
@@ -287,6 +291,31 @@ Code-Server provides a browser-based Visual Studio Code IDE running directly on
---
### Shell In A Box
Shell In A Box is a web-based terminal emulator that provides secure shell access to the host system through a browser, enabling command-line administration from any device without requiring an SSH client. It runs as a lightweight systemd service on the host, exposing a terminal interface via HTTP that is secured externally through Nginx Proxy Manager with SSL encryption. The service provides a quick way to access the host shell for system administration, troubleshooting, and maintenance tasks when a full IDE like Code-Server is not needed.
| Property | Value |
|----------|-------|
| **Deployment Type** | Host-based systemd service (NOT containerized) |
| **Binary Location** | `/usr/bin/shellinaboxd` |
| **Service Name** | `shellinabox.service` |
| **Access URL (LAN)** | http://192.168.86.149:4200 |
| **Access URL (Public)** | https://shell.schweitz.net |
| **External Access** | Yes (via NPM reverse proxy with SSL) |
| **Port Binding** | 0.0.0.0:4200 (HTTP) |
| **User/Group** | `shellinabox:shellinabox` (daemon), login as any system user |
| **Restart Policy** | `on-failure` (systemd) |
| **Config Location** | `/etc/default/shellinabox` |
| **Resource Limits** | None (native host process) |
| **GPU Required** | No |
| **Dependencies** | NPM (reverse proxy) |
| **Authentication** | System login (PAM), SSL via NPM |
| **Typical Memory** | ~5-10MB |
| **Features** | Web-based terminal, color themes, multiple sessions, copy/paste support |
---
## Networking Layer
### Headscale
@@ -739,6 +768,68 @@ Penpot is an open-source design and prototyping platform, serving as a self-host
---
### Stable Audio
Stable Audio Open is an AI audio generation model from Stability AI that creates music and sound effects from text prompts. It generates up to 47 seconds of audio using diffusion techniques, with a Gradio web interface for easy interaction. The service uses GPU acceleration for inference and runs as a locally-built container image, storing model weights in a HuggingFace cache on the SSD.
| Property | Value |
|----------|-------|
| **Image** | `stable-audio-open:local` (locally built) |
| **Container Name** | `stable-audio` |
| **Access URL (LAN)** | http://192.168.86.149:11500 |
| **External Access** | LAN only |
| **Port Mapping** | 11500:8000 (Gradio Web UI) |
| **Network Mode** | Bridge (docker-dataplane) |
| **Restart Policy** | `unless-stopped` |
| **Volume Mounts** | `~/docker-data/stable-audio/hf-cache:/root/.cache/huggingface` (SSD - model cache ~6GB) |
| **Environment** | `NVIDIA_VISIBLE_DEVICES=all`, `NVIDIA_DRIVER_CAPABILITIES=compute,utility`, `HF_TOKEN=<required>`, `TZ=Europe/Amsterdam` |
| **Resource Limits** | Memory: 16GB limit, 8GB reservation |
| **GPU Required** | Yes (RTX 2080 Ti - ~6GB VRAM) |
| **Dependencies** | NVIDIA Container Toolkit, HuggingFace token |
| **Health Check** | `curl -fSs http://localhost:8000/` (60s interval, 300s start_period) |
| **Build Location** | `/home/jpmschweitzer/docker-data/stable-audio-open/` |
| **Source** | https://github.com/SaladTechnologies/stable-audio-open |
| **Features** | Text-to-audio, up to 47s generation, configurable diffusion steps, CFG scale |
**Prerequisites:**
1. Accept model license: https://huggingface.co/stabilityai/stable-audio-open-1.0
2. Create HuggingFace token with read access
3. Build image: `docker build -t stable-audio-open:local .`
---
### TRELLIS
TRELLIS is Microsoft's 3D model generation system that creates 3D assets from text or image inputs. This deployment uses the low-VRAM fork (0lento/TRELLIS) optimized for 11GB GPUs, using 6-8GB VRAM instead of the standard 16GB requirement. It outputs GLB meshes with UV mappings, suitable for game asset pipelines. The Gradio web interface allows interactive generation with configurable parameters.
| Property | Value |
|----------|-------|
| **Image** | `trellis:local` (locally built) |
| **Container Name** | `trellis` |
| **Access URL (LAN)** | http://192.168.86.149:11510 |
| **External Access** | LAN only |
| **Port Mapping** | 11510:7860 (Gradio Web UI) |
| **Network Mode** | Bridge (docker-dataplane) |
| **Restart Policy** | `unless-stopped` |
| **Volume Mounts** | `~/docker-data/trellis/hf-cache:/root/.cache/huggingface` (SSD - model cache ~5GB), `/mnt/media/trellis/outputs:/app/outputs` (HDD - generated GLB files) |
| **Environment** | `NVIDIA_VISIBLE_DEVICES=all`, `NVIDIA_DRIVER_CAPABILITIES=compute,utility`, `ATTN_BACKEND=xformers`, `SPCONV_ALGO=native`, `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`, `HF_TOKEN=<optional>`, `TZ=Europe/Amsterdam` |
| **Resource Limits** | Memory: 16GB limit, 4GB reservation |
| **GPU Required** | Yes (RTX 2080 Ti - 6-8GB VRAM with low-VRAM fork) |
| **Dependencies** | NVIDIA Container Toolkit |
| **Health Check** | `curl -fSs http://localhost:7860/` (60s interval, 300s start_period) |
| **Build Location** | `/home/jpmschweitzer/docker-data/trellis/` |
| **Source** | https://github.com/0lento/TRELLIS (low-vram branch) |
| **Output Format** | GLB with mesh geometry, UV mappings, and textures |
| **Features** | Text-to-3D, image-to-3D, GLB export with UVs, Blender-compatible output |
**Use Case:** Game asset pipeline - generate 3D meshes with UVs, import into Blender, apply custom textures, render isometric sprites.
**Prerequisites:**
1. Create directories: `mkdir -p ~/docker-data/trellis/{hf-cache,config} /mnt/media/trellis/outputs`
2. Build image: `cd ~/docker-data/trellis && docker build -t trellis:local .` (~20-30 min)
---
### ClamAV
ClamAV is an open-source antivirus engine running on the host OS, providing virus scanning capabilities for the entire server and accessible via TCP socket for container-based services like Paperless-ngx. It includes automatic virus definition updates via freshclam and can perform both on-demand and real-time scanning. Running on the host provides better security isolation than containerized scanning and allows scanning of the host filesystem directly.
@@ -834,6 +925,7 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| **Redis Shared** | redis-shared:6379 | No (internal) | Shared cache/session store |
| **NPM** | http://192.168.86.149:81 | Yes (admin) | Reverse proxy admin |
| **Code-Server** | https://code.schweitz.net | Yes | Browser-based IDE |
| **Shell In A Box** | https://shell.schweitz.net | Yes | Web-based terminal |
| **Ollama** | http://192.168.86.149:11434 | No | ML model API |
| **Headscale** | http://192.168.86.149:8085 | No | VPN control server |
| **Open WebUI** | https://webui.schweitz.net | Yes (SSO) | LLM chat interface |
@@ -862,9 +954,13 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
|---------|-----------|-------------------|---------|
| **Ollama** | Compute, Utility | 2-10GB (model dependent) | LLM inference |
| **Jellyfin** | Video Encode/Decode | ~1-2GB (during transcode) | Media transcoding |
| **Stable Audio** | Compute | ~6GB | AI audio generation |
| **TRELLIS** | Compute | 6-8GB (low-VRAM fork) | 3D model generation |
**Total VRAM Available:** 11GB (RTX 2080 Ti)
**Note:** Stable Audio, TRELLIS, and Ollama share GPU. For best results, stop competing services during heavy generation tasks.
---
### Storage Distribution
@@ -894,6 +990,8 @@ Gitea is a lightweight, self-hosted Git service providing repository hosting, is
| **Webber** | `~/docker-data/webber/logs/`, `~/docker-data/webber/sandbox/` | N/A | Logs: ~10MB, Sandbox: variable |
| **Penpot** | `~/docker-data/penpot/assets/` | N/A | Assets: variable (uploads, exports) |
| **Tatlock UI** | None (stateless) | N/A | ~0MB (static files in container) |
| **Stable Audio** | `~/docker-data/stable-audio/hf-cache/` | N/A | Model cache: ~6GB |
| **TRELLIS** | `~/docker-data/trellis/hf-cache/` | `/mnt/media/trellis/outputs/` | Model cache: ~5GB, Outputs: variable |
**SSD Usage (docker-data):** ~6-11GB (configs, caches, databases)
**HDD Usage (/mnt/media):** ~2.1TB / 3.6TB (58% used)
@@ -943,4 +1041,4 @@ redis-cli -h redis-shared # Redis connection
---
*Last Updated: 2026-01-31*
*Last Updated: 2026-02-20*
+119
View File
@@ -0,0 +1,119 @@
# Handover: Add Swap Limits to Containers
## Problem
System swap is at 99% (2043/2047 MB) after 60 days uptime. All containers have `mem_swappiness` and `memswap_limit` unset, meaning when they hit their memory ceiling the kernel pushes pages to swap indefinitely instead of applying back-pressure. Over time this exhausts swap.
## Root Cause Analysis (2026-02-03)
Per-process swap audit identified these containers as the top offenders:
| Container | Swap Used | Mem Limit | File |
|-----------|-----------|-----------|------|
| authentik-worker | ~865 MB (across 5 worker procs) | 1G | `stacks/authentik.yml` |
| neo4j | 384 MB | 4G | `stacks/neo4j.yml` |
| penpot-backend | 372 MB | 1G | `stacks/penpot.yml` |
| authentik-server | 248 MB (gunicorn master+worker) | 512M | `stacks/authentik.yml` |
| open-webui | 129 MB | 1G | `stacks/open-webui.yml` |
| library-desk | 130 MB | 768M | `stacks/library-desk.yml` |
Additionally, host-level `clamd` (ClamAV) uses ~410 MB swap but is not managed by Portainer.
## Required Changes
For each container listed above, add `memswap_limit` equal to the `memory` limit and set `mem_swappiness: 0` under the top-level service config (not under `deploy`). This is Docker Compose v2 compatible syntax that works alongside v3 deploy blocks.
### stacks/authentik.yml
**authentik-server** — add to the service block (sibling to `deploy`, not nested inside it):
```yaml
server:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 512M
```
**authentik-worker** — same pattern:
```yaml
worker:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/neo4j.yml
```yaml
neo4j:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 4G
```
### stacks/penpot.yml
**penpot-backend** only (frontend and exporter are not swap offenders):
```yaml
penpot-backend:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/open-webui.yml
```yaml
open-webui:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 1G
```
### stacks/library-desk.yml
```yaml
library-desk:
# ... existing config ...
mem_swappiness: 0
memswap_limit: 768M
```
## What These Settings Do
- `memswap_limit: X` — total memory+swap budget. Setting it equal to the memory limit means zero swap allowed.
- `mem_swappiness: 0` — tells the kernel to avoid swapping for this container unless absolutely necessary.
Together, these ensure containers are constrained to their RAM allocation. If they exceed it, the OOM killer handles it properly rather than silently filling swap.
## Deployment Notes
- These are runtime container settings — each stack needs to be redeployed in Portainer (or via `docker compose up -d`) for changes to take effect.
- After deploying, clear current swap: `sudo swapoff -a && sudo swapon -a`
- Monitor with: `cd /mnt/media/Projects/system-management && ./sysmon check memory`
- If any container starts OOM-killing after this change, its memory limit may need bumping. The most likely candidate is `authentik-worker` (was using 865 MB swap on top of its 1G RAM limit — may need 1.5G or 2G).
## ClamAV (Host Service)
ClamAV (`clamd`) is the single largest swap consumer at 410 MB. It runs as a host service, not a container. To limit it, edit `/etc/clamav/clamd.conf` or the systemd unit to set a memory ceiling. This is outside the scope of portainer-core but noted for completeness.
## Verification
After all stacks are redeployed and swap is cleared:
```bash
# Confirm swap is back to healthy levels
free -h
# Run system check
cd /mnt/media/Projects/system-management && ./sysmon check memory
# Verify no container is using swap
for cid in $(docker ps -q); do
name=$(docker inspect --format '{{.Name}}' "$cid")
swap=$(docker stats --no-stream --format '{{.MemUsage}}' "$cid")
echo "$name: $swap"
done
```
+191
View File
@@ -0,0 +1,191 @@
# Home Assistant MCP Server Setup Guide
> **Created:** 2026-02-06
> **Status:** Infrastructure Ready - Manual Configuration Required
This guide documents the setup of the Home Assistant MCP (Model Context Protocol) Server integration for use with Claude.ai and Claude Desktop.
---
## Current State
| Component | Status | Details |
|-----------|--------|---------|
| Home Assistant | Running | https://housekeeping.schweitz.net |
| MCP Endpoint | Active | https://housekeeping.schweitz.net/api/mcp |
| NPM Proxy | Configured | WebSocket support enabled, SSL via Let's Encrypt |
| Authentik | Not in path | Good - allows Home Assistant's built-in IndieAuth |
### Verified Configuration
**MCP Endpoint Test Results:**
- `GET /api/mcp` → `401 Unauthorized` (expected - requires OAuth)
- `OPTIONS /api/mcp` → CORS preflight working
- Allowed methods: GET, OPTIONS, POST
**NPM Configuration (proxy host ID 15):**
- Domain: `housekeeping.schweitz.net`
- Forward: `localhost:8123`
- WebSocket upgrade: Enabled
- SSL: Let's Encrypt (certificate ID 18)
- Advanced config includes WebSocket headers
---
## Step 1: Home Assistant Configuration
### 1.1 Verify MCP Server Integration is Enabled
The MCP endpoint is already responding, which suggests the integration may already be installed. Verify in Home Assistant:
1. Go to **Settings** → **Devices & Services**
2. Look for "Model Context Protocol Server" or "MCP Server"
3. If not present, click **Add Integration** and search for "MCP"
### 1.2 Expose Entities to Voice Assistants (Required)
The MCP server only exposes entities that are enabled for voice assistants. You must configure which entities Claude can access:
1. Go to **Settings** → **Voice assistants**
2. Click on **Assist** (the default assistant)
3. Go to the **Expose** tab
4. Enable the entities you want Claude to control:
- **Lights** - for lighting control
- **Switches** - for on/off devices
- **Climate** - for thermostats and HVAC
- **Covers** - for blinds, garage doors, etc.
- **Scenes** - for activating predefined scenes
- **Scripts** - for running custom automations
- **Media players** - for controlling media devices
- **Sensors** - for reading sensor values (read-only)
**Recommendation:** Start with a few entities to test, then expand once connectivity is verified.
### 1.3 (Optional) Generate Long-Lived Access Token
Only needed if using clients that don't support OAuth:
1. Click your user profile (your name in the sidebar)
2. Scroll down to **Long-lived access tokens**
3. Click **Create Token**
4. Name it (e.g., "Claude MCP Token")
5. Copy and save the token securely - it won't be shown again
---
## Step 2: Claude.ai Configuration
### For Claude.ai (Web Interface)
1. Go to [claude.ai](https://claude.ai) and sign in
2. Navigate to **Settings** → **Integrations** or **MCP Servers**
3. Click **Add Remote MCP Server**
4. Enter the following configuration:
- **Server URL:** `https://housekeeping.schweitz.net/api/mcp`
- **Name:** Home Assistant (or your preferred name)
5. Click **Connect** or **Add**
6. You'll be redirected to Home Assistant for OAuth authentication
7. Log in to Home Assistant and authorize Claude
8. Return to Claude.ai - the connection should now be active
### For Claude Desktop
1. Open Claude Desktop application
2. Go to **Settings** or **Preferences**
3. Navigate to **MCP Servers** or **Integrations**
4. Click **Add Remote Server**
5. Enter:
- **URL:** `https://housekeeping.schweitz.net/api/mcp`
6. Click **Connect**
7. A browser window will open for Home Assistant authentication
8. Log in and authorize the connection
9. Return to Claude Desktop - the server should appear as connected
---
## Step 3: Verification
### Test MCP Endpoint Accessibility
From command line:
```bash
# Should return 401 Unauthorized (expected without auth)
curl -I https://housekeeping.schweitz.net/api/mcp
# More detailed test
curl -v https://housekeeping.schweitz.net/api/mcp
```
### Test Claude Integration
After connecting Claude to Home Assistant, try these commands:
1. **List devices:** "What Home Assistant devices are available?"
2. **Check state:** "What's the current state of the living room lights?"
3. **Control device:** "Turn on the living room lights"
4. **Get sensor value:** "What's the current temperature inside?"
### Troubleshooting
| Issue | Possible Cause | Solution |
|-------|---------------|----------|
| Connection timeout | Network/proxy issue | Check NPM logs, verify WebSocket support |
| 401 Unauthorized | OAuth not completed | Re-authenticate through Claude settings |
| No devices visible | Entities not exposed | Enable entities in Voice assistants → Assist → Expose |
| Commands fail | Entity not exposed for control | Check entity exposure settings in Home Assistant |
---
## Technical Details
### How MCP Authentication Works
Home Assistant uses IndieAuth (an OAuth 2.0-based protocol) for MCP authentication:
1. Claude identifies as client ID `https://claude.ai`
2. Home Assistant validates the request and presents login
3. User authenticates and authorizes Claude
4. Home Assistant issues an access token
5. Claude uses the token for subsequent API calls
### Protocol
The MCP Server uses "Streamable HTTP" protocol:
- Requests: POST with JSON-RPC 2.0 payload
- Responses: Server-Sent Events (SSE) or direct JSON
### NPM Configuration Details
Current advanced config for `housekeeping.schweitz.net`:
```nginx
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```
If you experience SSE streaming issues, add:
```nginx
proxy_buffering off;
proxy_cache off;
```
---
## Security Notes
1. **Authentik is NOT in front of Home Assistant** - This is intentional and required for IndieAuth OAuth to work directly with Claude
2. **Entity exposure is granular** - Only expose entities you want Claude to access
3. **OAuth tokens are temporary** - Re-authentication may be required periodically
4. **All traffic is encrypted** - SSL/TLS via Let's Encrypt
---
## References
- [Home Assistant MCP Documentation](https://www.home-assistant.io/integrations/mcp_server/)
- [Model Context Protocol Specification](https://spec.modelcontextprotocol.io/)
- [Claude MCP Integration](https://docs.anthropic.com/claude/docs/mcp)
---
*Last Updated: 2026-02-06*
+102
View File
@@ -0,0 +1,102 @@
# Tatlock Claudification Handover - portainer-core
## Context
Tatlock (the butler) is being upgraded to use Claude as its primary LLM backend instead of Ollama. This requires infrastructure changes to support the new configuration.
**Parent Issue:** See `/mnt/media/Projects/tatlock/PROJECT_CLAUDIFICATION.md`
## Impact on portainer-core
Portainer-core manages Docker Swarm stacks. The Tatlock stack needs updated configuration.
## Required Changes
### Priority: High (Required for Claude backend)
- [ ] **Update `stacks/agents.yml`** (or wherever Tatlock is defined)
Add new environment variables:
```yaml
services:
tatlock:
environment:
# Anthropic Configuration (Claude - preferred backend)
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=claude-sonnet-4-20250514
- PREFER_CLOUD_BACKEND=true
# Existing Ollama config remains as fallback
- OLLAMA_HOST=http://ollama:11434
- OLLAMA_DEFAULT_MODEL=mistral-nemo:latest
```
- [ ] **Configure secrets management**
Options:
1. **Docker secrets** (recommended for Swarm):
```yaml
secrets:
anthropic_api_key:
external: true
services:
tatlock:
secrets:
- anthropic_api_key
environment:
- ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key
```
2. **Environment file** (simpler but less secure):
```yaml
services:
tatlock:
env_file:
- ./secrets/tatlock.env
```
- [ ] **Update `CONTAINERS.md`**
- Document new environment variables
- Note Claude as preferred backend with Ollama fallback
- Update any API documentation
### Priority: Medium (Phase 2 - MCP Server)
- [ ] **Add `tatlock-mcp` service definition** (when Phase 2 is ready)
```yaml
tatlock-mcp:
image: git.schweitz.net/jpmschweitzer/tatlock:latest
command: ["python", "-m", "src.mcp.server"]
ports:
- "8778:8778"
environment:
- MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN}
# ... other config
```
### Priority: Low (Optimization)
- [ ] **Review resource limits**
- Claude backend may have different resource profile than Ollama
- Monitor memory/CPU after deployment
- Adjust limits if needed
## Testing
After stack update:
```bash
# Check Tatlock logs for backend selection
docker service logs tatlock_tatlock 2>&1 | grep -E "claude|backend"
# Should see:
# model_backend_configured backend=claude model=claude-sonnet-4-20250514
# OR (if no API key):
# claude_health_check_skipped reason=no_api_key
# model_backend_configured backend=ollama
```
## Timeline
- **Blocking:** Yes - required for production Claude deployment
- **When to implement:** When ANTHROPIC_API_KEY is available
- **Effort:** ~1 hour for stack updates, ~30 min for secrets setup
+6
View File
@@ -38,6 +38,8 @@ review the http://core-api/docs openapi documentation for infrastructure managem
| **Gitea** | `gitea.yml` | 3002, 2222 | No | Git repository hosting (includes PostgreSQL) |
| **Samba** | `samba.yml` | 139, 445 | No | Network file sharing |
| **Home Assistant** | `home-assistant.yml` | 8123 | No | Smart home automation platform |
| **Stable Audio** | `stable-audio.yml` | 11500 | **Yes** | AI audio generation (local build) |
| **TRELLIS** | `trellis.yml` | 11510 | **Yes** | 3D model generation (local build) |
## Port Allocation
@@ -61,6 +63,8 @@ review the http://core-api/docs openapi documentation for infrastructure managem
### ML/API Services (11000+)
- 11434: Ollama
- 11500: Stable Audio Open
- 11510: TRELLIS (3D model generation)
### Network Services
- 80: HTTP (NPM reverse proxy)
@@ -87,6 +91,8 @@ All stacks follow the dual-disk strategy:
Stacks requiring GPU access (marked with **Yes** above):
- `ollama.yml` - ML model inference
- `jellyfin.yml` - Hardware transcoding
- `stable-audio.yml` - AI audio generation (~6GB VRAM)
- `trellis.yml` - 3D model generation (~6-8GB VRAM)
**Prerequisites:**
- NVIDIA Container Toolkit installed
+8
View File
@@ -25,6 +25,10 @@ services:
- API_HOST=0.0.0.0
- API_PORT=8000
# Ollama (shared service)
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=claude-sonnet-4-20250514
- PREFER_CLOUD_BACKEND=truec
# Ollama (shared service)
- OLLAMA_HOST=http://ollama:11434
- OLLAMA_DEFAULT_MODEL=mistral-nemo:latest
- OLLAMA_TIMEOUT=120
@@ -97,6 +101,10 @@ services:
- CORS_ORIGINS=["*"]
- CORS_METHODS=["*"]
- CORS_HEADERS=["*"]
# Ollama (shared service)
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- ANTHROPIC_MODEL=claude-sonnet-4-20250514
- PREFER_CLOUD_BACKEND=truec
# Ollama (shared service) - LLM Models hot in VRAM
- OLLAMA_URL=http://ollama:11434
- OLLAMA_AGENT_MODEL=mistral-nemo:latest
+1 -1
View File
@@ -50,7 +50,7 @@ services:
- AUTHENTIK_CORE_API_TOKEN=${AUTHENTIK_CORE_API_TOKEN}
# HOME ASSITANT VARIABLES
- HOMEASSISTANT_URL=http://home-assistant:8123
- HOMEASSISTANT_URL=http://192.168.86.149:8123
- HOMEASSISTANT_TOKEN=${HOMEASSISTANT_TOKEN}
# AI SYSTEM VARIABLES
+45 -10
View File
@@ -1,7 +1,7 @@
version: '3.8'
# Home Assistant - Smart Home Automation Platform
# Port: 8123 (Home Assistant default)
# Ports: 8123 (Home Assistant), 5580 (Matter Server WebSocket)
# GPU: No
# Storage: SSD (config and database)
@@ -10,8 +10,7 @@ services:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: home-assistant
restart: unless-stopped
ports:
- "8123:8123"
network_mode: host
volumes:
# Config on SSD (includes database, automations, scripts)
- /home/jpmschweitzer/docker-data/home-assistant/config:/config
@@ -19,8 +18,6 @@ services:
- TZ=Europe/Amsterdam
# Privileged mode for USB device access (Z-Wave, Zigbee dongles)
privileged: true
networks:
- docker-dataplane
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8123/manifest.json || exit 1"]
interval: 30s
@@ -32,10 +29,34 @@ services:
limits:
memory: 1G
networks:
docker-dataplane:
external: true
name: docker-dataplane
matter-server:
image: ghcr.io/matter-js/python-matter-server:stable
container_name: matter-server
restart: unless-stopped
network_mode: host
security_opt:
- apparmor:unconfined
volumes:
# Matter fabric data on SSD (BACK UP before upgrades - contains device credentials)
- /home/jpmschweitzer/docker-data/matter-server/data:/data
# D-Bus socket for Bluetooth commissioning
- /run/dbus:/run/dbus:ro
environment:
- TZ=Europe/Amsterdam
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:5580 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 512M
# Note: Both services use network_mode: host for LAN device discovery (mDNS, SSDP, Matter)
# Home Assistant reaches Matter Server at ws://localhost:5580/ws
# Other containers should reach HA via http://192.168.86.149:8123
# Setup Instructions:
# 1. Create config directory:
@@ -64,7 +85,7 @@ networks:
# NPM Configuration:
# Domain: housekeeping.schweitz.net
# Scheme: http
# Forward Hostname/IP: home-assistant
# Forward Hostname/IP: 192.168.86.149
# Forward Port: 8123
# Websockets Support: ENABLED (required for HA)
# SSL: Let's Encrypt
@@ -77,3 +98,17 @@ networks:
# - 172.16.0.0/12
# - 192.168.0.0/16
# - 10.0.0.0/8
#
# Matter Server Setup:
# 1. Create data directory:
# mkdir -p /home/jpmschweitzer/docker-data/matter-server/data
#
# 2. After deploying, connect from Home Assistant:
# Settings > Devices & Services > Add Integration > Matter
# Deselect "Install the official Matter Server app"
# Enter URL: ws://localhost:5580/ws
#
# 3. Commission devices via the HA Companion app (uses phone Bluetooth)
# or add --bluetooth-adapter 0 to the matter-server command for local BT
#
# Source: https://github.com/matter-js/python-matter-server
+4 -4
View File
@@ -24,10 +24,10 @@ services:
- NVIDIA_DRIVER_CAPABILITIES=all
# Process requests sequentially to avoid batch overflow panics
- OLLAMA_NUM_PARALLEL=1
# Keep models loaded in VRAM (don't unload after idle)
- OLLAMA_KEEP_ALIVE=-1
# Load multiple models concurrently (mistral-nemo + nomic-embed-text)
- OLLAMA_MAX_LOADED_MODELS=2
# Unload LLMs after 5 minutes idle (keeps VRAM free for other services)
- OLLAMA_KEEP_ALIVE=5m
# Only keep one model loaded at a time (embedding model stays, LLMs swap)
- OLLAMA_MAX_LOADED_MODELS=1
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:11434/api/tags || exit 1"]
interval: 30s
+3 -3
View File
@@ -78,9 +78,9 @@ services:
# Redis (shared, DB 10)
PENPOT_REDIS_URI: redis://redis-shared:6379/10
# Assets storage
PENPOT_ASSETS_STORAGE_BACKEND: assets-fs
PENPOT_STORAGE_ASSETS_FS_DIRECTORY: /opt/data/assets
# Assets storage (updated for Penpot 2.11+)
PENPOT_OBJECTS_STORAGE_BACKEND: fs
PENPOT_OBJECTS_STORAGE_FS_DIRECTORY: /opt/data/assets
# Secret key (512-bit base64)
PENPOT_SECRET_KEY: +g+fSbAzBWVA2gE8ZnJUcpgq9L1BCqtuB+h9w73iF9qcgxhjMF1QU23XUjQ5ZdxpPmwoyuuQEm/frtKUV2M6pA==
+90
View File
@@ -0,0 +1,90 @@
version: '3.8'
# Stable Audio Open - AI Audio Generation
# Phase: ML Infrastructure
# Ports: 8000 (Gradio Web UI)
# GPU: YES - Requires NVIDIA Container Toolkit (8GB+ VRAM)
# Storage: SSD recommended for model cache
# Image: Built locally from https://github.com/SaladTechnologies/stable-audio-open
services:
stable-audio:
image: stable-audio-open:local
container_name: stable-audio
restart: unless-stopped
ports:
- "11500:8000" # Gradio Web UI (internal only)
volumes:
# HuggingFace cache for model weights (~6GB)
- /home/jpmschweitzer/docker-data/stable-audio/hf-cache:/root/.cache/huggingface
environment:
- TZ=Europe/Amsterdam
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
- HF_TOKEN=${HF_TOKEN}
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8000/ || exit 1"]
interval: 60s
timeout: 30s
retries: 3
start_period: 300s # Model download + load takes time
deploy:
resources:
limits:
memory: 16G
reservations:
memory: 8G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# GPU Requirements:
# - RTX 2080 Ti (11GB VRAM) - minimum viable, may struggle with long generations
# - Recommended: 16GB+ VRAM for reliable 47s audio generation
# - NVIDIA Container Toolkit must be installed
#
# IMPORTANT: GPU Contention
# - This service shares GPU with Ollama and Jellyfin
# - Consider stopping Ollama when generating audio for better VRAM availability
# - Monitor with: watch -n 1 nvidia-smi
#
# Prerequisites:
# 1. Build image: cd /home/jpmschweitzer/docker-data/stable-audio-open && docker build -t stable-audio-open:local .
# 2. Create HuggingFace token: https://huggingface.co/settings/tokens (read access)
# 3. Accept model license: https://huggingface.co/stabilityai/stable-audio-open-1.0
# 4. Create data directories:
# mkdir -p /home/jpmschweitzer/docker-data/stable-audio/{hf-cache,output}
#
# After Deployment:
# 1. Set HF_TOKEN in Portainer environment variables
# 2. Deploy stack via Portainer
# 3. First run downloads model weights (~6GB) - be patient
# 4. Verify GPU access: docker exec stable-audio nvidia-smi
# 5. Access Web UI: http://tower-of-joy:11500 or http://audio.schweitz.internal (via NPM)
#
# NPM Configuration (audio.schweitz.internal):
# - Domain: audio.schweitz.internal
# - Scheme: http
# - Forward Hostname: stable-audio (or tower-of-joy)
# - Forward Port: 11500
# - Block Common Exploits: Yes
# - Websockets Support: Yes (required for Gradio)
#
# API Usage (Gradio API):
# curl -X POST http://audio.schweitz.internal/api/predict \
# -H "Content-Type: application/json" \
# -d '{"data": ["epic orchestral music, cinematic", 30, 100, 7]}'
# Parameters: [prompt, duration_seconds, diffusion_steps, cfg_scale]
#
# Rebuilding Image:
# cd /home/jpmschweitzer/docker-data/stable-audio-open
# git pull
# docker build -t stable-audio-open:local .
+116
View File
@@ -0,0 +1,116 @@
version: '3.8'
# TRELLIS 1 - 3D Model Generation (Low-VRAM Fork)
# Purpose: Generate 3D meshes with UV mappings for game asset pipeline
# Ports: 11510 (Gradio Web UI)
# GPU: YES - Optimized for 11GB VRAM (RTX 2080 Ti)
# Storage: SSD for model cache, HDD for GLB outputs
# Image: Built locally from /home/jpmschweitzer/docker-data/trellis/
services:
trellis:
image: trellis:local
container_name: trellis
restart: unless-stopped
ports:
- "11510:7860" # Gradio Web UI
volumes:
# SSD: HuggingFace model cache (~5GB)
- /home/jpmschweitzer/docker-data/trellis/hf-cache:/root/.cache/huggingface
# HDD: Generated GLB files
- /mnt/media/trellis/outputs:/app/outputs
# Entrypoint script (patches Gradio bugs without rebuilding image)
- /home/jpmschweitzer/docker-data/trellis/entrypoint.sh:/app/entrypoint.sh:ro
command: ["bash", "/app/entrypoint.sh"]
environment:
- TZ=Europe/Amsterdam
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
# VRAM Optimization (for 11GB card)
- ATTN_BACKEND=xformers
- SPCONV_ALGO=native
- PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
# Gradio must bind to 0.0.0.0 inside Docker
- GRADIO_SERVER_NAME=0.0.0.0
# HuggingFace token for model downloads (set in Portainer)
- HF_TOKEN=${HF_TOKEN:-}
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:7860/ || exit 1"]
interval: 60s
timeout: 30s
retries: 3
start_period: 300s # Model download + load time
memswap_limit: 20G
deploy:
resources:
limits:
memory: 20G
reservations:
memory: 4G
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
labels:
- "com.centurylinklabs.watchtower.enable=false" # Manual updates for local builds
networks:
- docker-dataplane
networks:
docker-dataplane:
external: true
name: docker-dataplane
# =============================================================================
# PREREQUISITES
# =============================================================================
#
# 1. Build the Docker image first:
# cd /home/jpmschweitzer/docker-data/trellis
# docker build -t trellis:local .
# (Takes ~20-30 minutes for CUDA compilation)
#
# 2. Directories are already created:
# /home/jpmschweitzer/docker-data/trellis/hf-cache (SSD - model cache)
# /mnt/media/trellis/outputs (HDD - generated GLB files)
#
# =============================================================================
# AFTER DEPLOYMENT
# =============================================================================
#
# 1. Verify GPU access:
# docker exec trellis nvidia-smi
#
# 2. Check logs:
# docker logs trellis
#
# 3. Access Web UI:
# http://tower-of-joy:11510
# http://192.168.86.149:11510
#
# 4. Monitor VRAM during generation:
# watch -n 1 nvidia-smi
#
# =============================================================================
# USAGE - GAME ASSET PIPELINE
# =============================================================================
#
# 1. Open Gradio UI at http://tower-of-joy:11510
# 2. Upload reference image or enter text prompt
# 3. Generate 3D model (uses 6-8GB VRAM)
# 4. Download GLB file from outputs
# 5. Import GLB into Blender (File > Import > glTF)
# 6. Apply custom textures, set up isometric camera
# 7. Render at 1024x1024, downsample to 64x64 tile sprites
#
# =============================================================================
# GPU CONTENTION NOTE
# =============================================================================
#
# TRELLIS shares the RTX 2080 Ti with Ollama and Jellyfin.
# For best results during complex generations:
# docker stop ollama
# # Run TRELLIS generation
# docker start ollama
#
# =============================================================================