Compare commits
10 Commits
c8dcf2b5ee
...
1a0bc3dee4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a0bc3dee4 | ||
|
|
b4defab163 | ||
|
|
3112b3d6fe | ||
|
|
a900f9c744 | ||
|
|
282a2f8a9c | ||
|
|
cd614e753a | ||
|
|
b8b4b13130 | ||
|
|
117788f216 | ||
|
|
11b96bce2c | ||
|
|
66d2a4bda7 |
@ -1,122 +1,462 @@
|
||||
# Home Assistant Deployment Architecture
|
||||
# Home Assistant Deployment Guide
|
||||
|
||||
## Recommended Deployment: Podman Container on Proxmox
|
||||
**Last Updated:** January 9, 2026
|
||||
**Deployment Method:** Home Assistant OS (HAOS) VM on Proxmox
|
||||
**Status:** ✅ Deployed and Operational
|
||||
|
||||
### Why Podman Container vs Home Assistant OS?
|
||||
- **Flexibility:** Full control over host system, easier customization
|
||||
- **Integration:** Better integration with existing Proxmox infrastructure
|
||||
- **Backup:** Standard container backup/restore workflows
|
||||
- **Resources:** More efficient resource usage
|
||||
- **Updates:** Granular update control
|
||||
- **GPU Support:** Already proven working with your Tdarr setup
|
||||
## Current Deployment
|
||||
|
||||
### Container Architecture
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| **VM ID** | 109 |
|
||||
| **VM Name** | homeassistant |
|
||||
| **Primary IP** | 10.0.0.28 (home network - for Matter/IoT) |
|
||||
| **Secondary IP** | 10.10.0.215 (server network - management) |
|
||||
| **Web UI** | http://10.0.0.28:8123 |
|
||||
| **USB Device** | Nabu Casa ZBT-2 (Thread/Zigbee coordinator) |
|
||||
|
||||
## Deployment Overview
|
||||
|
||||
### Why HAOS VM (Not Container)?
|
||||
|
||||
| Factor | HAOS VM | Container (HA Core) |
|
||||
|--------|---------|---------------------|
|
||||
| **Add-on Marketplace** | ✅ Full access | ❌ Not available |
|
||||
| **Supervisor** | ✅ Included | ❌ Not included |
|
||||
| **USB Passthrough** | ✅ Reliable | ⚠️ Problematic |
|
||||
| **Official Support** | ✅ Yes | ⚠️ Limited |
|
||||
| **Backup/Restore** | ✅ Built-in | Manual |
|
||||
| **Updates** | ✅ One-click | Manual |
|
||||
|
||||
The Add-on marketplace provides one-click installation of:
|
||||
- **Mosquitto** (MQTT broker)
|
||||
- **Node-RED** (visual automation)
|
||||
- **Zigbee2MQTT** / **ZHA** (Zigbee coordination)
|
||||
- **ESPHome** (DIY sensors)
|
||||
- **Piper** / **Whisper** (local voice)
|
||||
- **File Editor**, **Terminal**, **Samba**, and 1000+ more
|
||||
|
||||
---
|
||||
|
||||
## Pre-Deployment: Decommission Old HA VM
|
||||
|
||||
An old, unmaintained Home Assistant VM exists and should be removed before deploying the new instance.
|
||||
|
||||
### Decommissioning Steps
|
||||
|
||||
1. **Identify the old VM** in Proxmox web UI
|
||||
```bash
|
||||
# List all VMs to find the old HA instance
|
||||
qm list | grep -i home
|
||||
```
|
||||
|
||||
2. **Check if it has any valuable config** (likely not if never fully implemented)
|
||||
```bash
|
||||
# If VM is running, you could backup config first
|
||||
# But if it was never completed, skip this
|
||||
```
|
||||
|
||||
3. **Stop and remove the old VM**
|
||||
```bash
|
||||
# Stop the VM (replace VMID with actual ID)
|
||||
qm stop <VMID>
|
||||
|
||||
# Remove the VM and its disks
|
||||
qm destroy <VMID> --purge
|
||||
```
|
||||
|
||||
4. **Free up resources** - Note the resources being reclaimed for the new VM
|
||||
|
||||
---
|
||||
|
||||
## VM Specifications
|
||||
|
||||
### Recommended Configuration
|
||||
|
||||
| Resource | Value | Rationale |
|
||||
|----------|-------|-----------|
|
||||
| **CPU** | 4 cores | Headroom for add-ons and integrations |
|
||||
| **RAM** | 8 GB | Comfortable for MQTT, Node-RED, history |
|
||||
| **Disk** | 64 GB | Add-ons, recorder database, backups |
|
||||
| **Network** | Bridge (vmbr0) | Same network as IoT devices |
|
||||
| **Machine Type** | q35 | Modern, better device support |
|
||||
| **BIOS** | OVMF (UEFI) | Required for HAOS |
|
||||
|
||||
### Network Considerations
|
||||
|
||||
- Place on same VLAN/subnet as IoT devices for discovery
|
||||
- Static IP recommended (or DHCP reservation)
|
||||
- Suggested IP: Document in your network scheme
|
||||
- Ports: 8123 (web UI), 1883 (MQTT if using add-on)
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Method: Community Helper Script (Recommended)
|
||||
|
||||
The community-maintained scripts (originally by tteck) automate the entire process:
|
||||
|
||||
```bash
|
||||
# Core Home Assistant container
|
||||
podman run -d --name homeassistant \
|
||||
--restart=unless-stopped \
|
||||
-p 8123:8123 \
|
||||
-v /path/to/config:/config \
|
||||
-v /etc/localtime:/etc/localtime:ro \
|
||||
--device /dev/ttyUSB0:/dev/ttyUSB0 \ # If you add Zigbee/Z-Wave
|
||||
--network=host \
|
||||
ghcr.io/home-assistant/home-assistant:stable
|
||||
# Run from Proxmox shell
|
||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/haos-vm.sh)"
|
||||
```
|
||||
|
||||
### Supporting Services Architecture
|
||||
### Script Prompts
|
||||
|
||||
When prompted, select **Advanced** to customize specs:
|
||||
|
||||
| Prompt | Recommended Value |
|
||||
|--------|-------------------|
|
||||
| Use default settings? | No (select Advanced) |
|
||||
| HAOS Version | Latest (default) |
|
||||
| VM ID | Next available or choose |
|
||||
| Machine Type | q35 |
|
||||
| Disk Size | 64G |
|
||||
| CPU Cores | 4 |
|
||||
| RAM | 8192 |
|
||||
| Bridge | vmbr0 (or your IoT bridge) |
|
||||
| MAC Address | Auto or specify |
|
||||
| VLAN | Your IoT VLAN if applicable |
|
||||
| Start after creation | Yes |
|
||||
|
||||
### Post-Installation
|
||||
|
||||
1. **Wait for HAOS to boot** (2-3 minutes first boot)
|
||||
2. **Access web UI** at `http://<VM-IP>:8123`
|
||||
3. **Create admin account**
|
||||
4. **Complete onboarding wizard**
|
||||
|
||||
---
|
||||
|
||||
## Initial Configuration
|
||||
|
||||
### Step 1: Onboarding
|
||||
|
||||
1. Set location (for sun-based automations, weather)
|
||||
2. Create admin user account
|
||||
3. Skip or configure any auto-discovered devices
|
||||
4. Install HA mobile app for presence detection
|
||||
|
||||
### Step 2: Essential Add-ons
|
||||
|
||||
Navigate to **Settings → Add-ons → Add-on Store**
|
||||
|
||||
| Add-on | Purpose | Priority |
|
||||
|--------|---------|----------|
|
||||
| **File Editor** | Edit YAML configs in browser | Install first |
|
||||
| **Terminal & SSH** | Shell access | Install first |
|
||||
| **Samba Share** | Access config from desktop | Recommended |
|
||||
| **Mosquitto** | MQTT broker for devices | When needed |
|
||||
| **HACS** | Community integrations | Recommended |
|
||||
|
||||
### Step 3: Philips Hue Integration
|
||||
|
||||
1. Go to **Settings → Devices & Services**
|
||||
2. Hue Bridge should auto-discover
|
||||
3. Click **Configure** → Press button on Hue Bridge
|
||||
4. All Hue devices appear immediately
|
||||
5. Full local control established
|
||||
|
||||
### Step 4: Matter/Thread Setup
|
||||
|
||||
For Eve and other Matter devices:
|
||||
|
||||
1. Go to **Settings → Devices & Services → Add Integration**
|
||||
2. Search for **Matter**
|
||||
3. Follow pairing instructions for each device
|
||||
4. Use existing Thread network (Apple TV/HomePod as border routers)
|
||||
|
||||
---
|
||||
|
||||
## USB Passthrough: Nabu Casa ZBT-2
|
||||
|
||||
The ZBT-2 is a Thread/Zigbee coordinator that provides:
|
||||
- **Thread Border Router** functionality (for Matter over Thread devices)
|
||||
- **Zigbee coordinator** capability (for ZHA integration)
|
||||
|
||||
### Current Configuration
|
||||
|
||||
```bash
|
||||
# USB device info
|
||||
Bus 002 Device 003: ID 303a:831a Nabu Casa ZBT-2
|
||||
|
||||
# VM config line
|
||||
usb0: host=303a:831a
|
||||
```
|
||||
|
||||
### Setup Steps
|
||||
|
||||
1. **Plug ZBT-2 into Proxmox host** (USB 2.0 port preferred to avoid USB 3.0 interference)
|
||||
|
||||
2. **Identify device:**
|
||||
```bash
|
||||
ssh proxmox "lsusb | grep -i nabu"
|
||||
# Expected: 303a:831a Nabu Casa ZBT-2
|
||||
```
|
||||
|
||||
3. **Stop VM and add USB passthrough:**
|
||||
```bash
|
||||
qm stop 109
|
||||
qm set 109 -usb0 host=303a:831a
|
||||
qm start 109
|
||||
```
|
||||
|
||||
4. **Verify in HA:** Settings → System → Hardware
|
||||
- Should show `/dev/ttyACM0` or similar
|
||||
- Full path: `/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_<SERIAL>-if00`
|
||||
|
||||
### Flash Thread Firmware
|
||||
|
||||
If the ZBT-2 needs Thread firmware:
|
||||
1. Settings → System → Hardware → Find ZBT-2
|
||||
2. Click three dots → Configure → Flash Thread firmware
|
||||
3. Wait for flash to complete
|
||||
4. Restart OpenThread Border Router addon
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Network Configuration for Matter
|
||||
|
||||
**Matter devices REQUIRE Home Assistant to be on the same subnet as the IoT devices.** This is the most common cause of Matter commissioning failures.
|
||||
|
||||
### The Problem
|
||||
|
||||
If HA is on a server VLAN (10.10.0.x) but IoT devices are on the home network (10.0.0.x):
|
||||
- mDNS discovery fails across subnets
|
||||
- Matter commissioning times out with "Discovery timed out" errors
|
||||
- PASE session establishment fails
|
||||
|
||||
### The Solution: Dual-NIC Configuration
|
||||
|
||||
VM 109 has two network interfaces:
|
||||
|
||||
| Interface | Bridge | Network | Purpose |
|
||||
|-----------|--------|---------|---------|
|
||||
| net0 (enp6s18) | vmbr1 | 10.0.0.x (Home) | **Primary** - Matter/IoT communication |
|
||||
| net1 (enp6s19) | vmbr0 | 10.10.0.x (Server) | Secondary - Management access |
|
||||
|
||||
### Proxmox VM Network Config
|
||||
|
||||
```bash
|
||||
# Current configuration (qm config 109 | grep net)
|
||||
net0: virtio=0E:E0:41:39:CC:95,bridge=vmbr1
|
||||
net1: virtio=6E:16:AB:69:63:89,bridge=vmbr0
|
||||
```
|
||||
|
||||
### Why This Matters
|
||||
|
||||
- **net0 on vmbr1 (home network)**: Matter Server auto-selects the first interface. By putting the home network on net0, Matter commissioning works.
|
||||
- **net1 on vmbr0 (server network)**: Provides management access from the server VLAN.
|
||||
|
||||
### HAOS Network Settings
|
||||
|
||||
| Interface | IP | Gateway | Purpose |
|
||||
|-----------|-----|---------|---------|
|
||||
| enp6s18 | 10.0.0.28 (DHCP) | 10.0.0.1 | Matter/mDNS/IoT |
|
||||
| enp6s19 | 10.10.0.215 (DHCP) | 10.10.0.1 | Server management |
|
||||
|
||||
**Important:** Set a DHCP reservation for 10.0.0.28 in your router to prevent IP changes.
|
||||
|
||||
### Unifi mDNS Configuration
|
||||
|
||||
If you have VLANs, enable mDNS reflection:
|
||||
1. Unifi Controller → Settings → Services
|
||||
2. **Gateway mDNS Proxy**: Custom
|
||||
3. **VLAN Scope**: Select all VLANs that need mDNS (Home Network, Servers)
|
||||
4. **Service Scope**: All
|
||||
|
||||
---
|
||||
|
||||
## Thread Network Configuration
|
||||
|
||||
### Network Topology
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Home Assistant VM │
|
||||
│ (10.0.0.28 on home network) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────┐ ┌─────────────────────────────────┐ │
|
||||
│ │ Matter Server │ │ OpenThread Border Router │ │
|
||||
│ │ (addon) │ │ (ZBT-2 @ /dev/ttyACM0) │ │
|
||||
│ └────────┬────────┘ └────────────┬────────────────────┘ │
|
||||
└───────────┼──────────────────────────┼──────────────────────┘
|
||||
│ │
|
||||
│ IPv6/mDNS │ 802.15.4 Radio
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐
|
||||
│ Apple Thread │◄────────►│ Thread │
|
||||
│ Border Routers│ Thread │ Devices │
|
||||
│ (8x HomePods) │ Mesh │ (Eve, etc.) │
|
||||
└───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
### Thread Networks
|
||||
|
||||
Two Thread networks exist in this environment:
|
||||
|
||||
| Network | PAN ID | Channel | Border Routers |
|
||||
|---------|--------|---------|----------------|
|
||||
| MyHome (Apple) | 0xF672 | 25 | 8 (HomePods/Apple TVs) |
|
||||
| ha-thread-b072 | 0xB072 | 25 | 1 (ZBT-2) |
|
||||
|
||||
**Current setup:** Apple network is set as "Preferred" in HA → Settings → Thread. This means:
|
||||
- HA uses Apple's Thread credentials for commissioning
|
||||
- Devices join Apple's strong mesh (8 border routers)
|
||||
- HA can still control devices via Matter
|
||||
|
||||
### OTBR Addon Configuration
|
||||
|
||||
Settings → Add-ons → OpenThread Border Router:
|
||||
- **Device**: `/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_DCB4D90E794C-if00`
|
||||
- **Baudrate**: 460800
|
||||
- **Hardware flow control**: Enabled
|
||||
- **NAT64**: Enabled
|
||||
- **OTBR firewall**: Enabled
|
||||
|
||||
### OTBR REST API
|
||||
|
||||
Accessible at `http://10.0.0.28:8081/`:
|
||||
- `/node/state` - Current state (leader, router, etc.)
|
||||
- `/node/dataset/active` - Active Thread dataset
|
||||
- `/diagnostics` - Network diagnostics
|
||||
|
||||
---
|
||||
|
||||
## Matter Commissioning
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. HA on same subnet as IoT devices (10.0.0.x)
|
||||
2. Matter Server addon running
|
||||
3. OpenThread Border Router addon running
|
||||
4. Thread network configured (Apple or HA)
|
||||
5. HA Companion app on phone with Bluetooth permission
|
||||
|
||||
### Commissioning Steps
|
||||
|
||||
1. **Factory reset the device** (hold button 10+ seconds)
|
||||
2. Open **HA Companion app** on phone
|
||||
3. Go to **Settings → Devices & Services → Add Integration → Matter**
|
||||
4. Scan the Matter QR code on the device
|
||||
5. Phone uses Bluetooth for initial PASE session
|
||||
6. Device receives Thread credentials and joins network
|
||||
7. Device appears in HA
|
||||
|
||||
### Adding HA as Secondary Controller
|
||||
|
||||
If device is already in Apple Home:
|
||||
1. In Apple Home app, find device → Settings
|
||||
2. Look for **"Turn On Pairing Mode"**
|
||||
3. In HA Companion app → Add Matter Device
|
||||
4. Enter the temporary pairing code
|
||||
5. Device now controlled by both Apple Home and HA
|
||||
|
||||
---
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Built-in Backups
|
||||
|
||||
HAOS includes automated backup to:
|
||||
- Local storage
|
||||
- Network shares (Samba/NFS)
|
||||
- Google Drive (via add-on)
|
||||
|
||||
### Recommended Schedule
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml for full stack
|
||||
version: '3.8'
|
||||
services:
|
||||
homeassistant:
|
||||
container_name: homeassistant
|
||||
image: ghcr.io/home-assistant/home-assistant:stable
|
||||
volumes:
|
||||
- ./config:/config
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
restart: unless-stopped
|
||||
privileged: true
|
||||
network_mode: host
|
||||
|
||||
# Optional: Local database instead of SQLite
|
||||
postgres:
|
||||
container_name: ha-postgres
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_DB: homeassistant
|
||||
POSTGRES_USER: homeassistant
|
||||
POSTGRES_PASSWORD: your-secure-password
|
||||
volumes:
|
||||
- ./postgres-data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
# Optional: MQTT broker for device communication
|
||||
mosquitto:
|
||||
container_name: ha-mosquitto
|
||||
image: eclipse-mosquitto:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "1883:1883"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- ./mosquitto/config:/mosquitto/config
|
||||
- ./mosquitto/data:/mosquitto/data
|
||||
- ./mosquitto/log:/mosquitto/log
|
||||
|
||||
# Optional: Node-RED for visual automation development
|
||||
node-red:
|
||||
container_name: ha-node-red
|
||||
image: nodered/node-red:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "1880:1880"
|
||||
volumes:
|
||||
- ./node-red-data:/data
|
||||
# Example: Weekly full backup
|
||||
# Settings → System → Backups → Create Backup
|
||||
```
|
||||
|
||||
### Proxmox-Level Backup
|
||||
|
||||
Additionally, back up the entire VM via Proxmox:
|
||||
```bash
|
||||
# Manual backup
|
||||
vzdump <VMID> --storage local --mode snapshot
|
||||
|
||||
# Or configure in Proxmox Backup Server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Parallel Setup (Recommended)
|
||||
1. **Deploy HA alongside Apple Home** (don't disturb current setup)
|
||||
2. **Start with 1-2 test devices** (re-pair a couple of sensors)
|
||||
3. **Build basic automations** to validate functionality
|
||||
4. **Test for 1-2 weeks** to ensure stability
|
||||
### Phase 1: Foundation (Week 1)
|
||||
|
||||
### Phase 2: Gradual Migration
|
||||
1. **Re-pair devices in groups** (sensors first, then bulbs, etc.)
|
||||
2. **Migrate automations one by one**
|
||||
3. **Keep Apple Home as backup** until confident
|
||||
1. ✅ Decommission old HA VM
|
||||
2. ✅ Deploy new HAOS VM with recommended specs
|
||||
3. Connect Philips Hue Bridge (native integration)
|
||||
4. Test Hue control from HA
|
||||
5. Install essential add-ons
|
||||
|
||||
### Phase 3: Full Cutover
|
||||
1. **Remove devices from Apple Home**
|
||||
2. **Decommission Apple TV hub role** (keep as media device)
|
||||
3. **Full automation implementation**
|
||||
### Phase 2: Parallel Operation (Weeks 2-3)
|
||||
|
||||
## Device Integration Strategy
|
||||
1. Re-pair 2-3 Matter devices (Eve switches) to HA
|
||||
2. Keep Apple Home running for remaining devices
|
||||
3. Build basic automations in HA
|
||||
4. Test reliability and family usability
|
||||
5. Install HA mobile app for all household members
|
||||
|
||||
### Matter Devices (Your Current Setup)
|
||||
- **Direct Integration:** HA's Matter support is mature as of 2024
|
||||
- **No Hub Required:** HA can be the Matter controller
|
||||
- **Thread Network:** Can share Thread network between platforms during migration
|
||||
### Phase 3: Full Migration (Weeks 4-5)
|
||||
|
||||
### Philips Hue Bridge
|
||||
- **Option 1:** Keep bridge, integrate via Hue integration (easier)
|
||||
- **Option 2:** Direct Zigbee control (requires Zigbee coordinator)
|
||||
- **Recommendation:** Keep bridge initially, migrate later if needed
|
||||
1. Re-pair remaining Matter devices to HA
|
||||
2. Migrate all automations from Apple Home
|
||||
3. Optional: Set up HomeKit Controller integration
|
||||
- Exposes HA devices back to Apple Home
|
||||
- Maintains Siri voice control for family
|
||||
4. Decommission Apple Home as primary controller
|
||||
|
||||
### Future Expansion Options
|
||||
- **Zigbee:** Add Zigbee coordinator for non-Matter devices
|
||||
- **Z-Wave:** Add Z-Wave stick for legacy devices
|
||||
- **WiFi Devices:** Direct integration via HA's vast library
|
||||
- **Custom Integrations:** HACS (Home Assistant Community Store)
|
||||
### Phase 4: Enhancement (Ongoing)
|
||||
|
||||
## Automation Examples You Can Build
|
||||
1. Add HACS for community integrations
|
||||
2. Build advanced automations
|
||||
3. Consider voice integration (Whisper/Piper add-ons)
|
||||
4. Evaluate Zigbee coordinator if expanding beyond Matter
|
||||
|
||||
---
|
||||
|
||||
## Device Integration Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Home Assistant VM │
|
||||
│ (HAOS on Proxmox) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ Hue Native │ │ Matter │ │ Future: Zigbee │ │
|
||||
│ │ Integration │ │ Integration │ │ (ZBT-2 + Z2M) │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
|
||||
│ │ │ │ │
|
||||
└─────────┼────────────────┼─────────────────────┼─────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌───────────┐ ┌───────────┐
|
||||
│ Hue Bridge │ │ Thread │ │ USB │
|
||||
│ (Zigbee) │ │ Border │ │ Coord. │
|
||||
└──────┬───────┘ │ Router │ └─────┬─────┘
|
||||
│ │(Apple TV) │ │
|
||||
▼ └─────┬─────┘ ▼
|
||||
┌──────────────┐ │ ┌───────────┐
|
||||
│ Hue Bulbs │ ▼ │ Zigbee │
|
||||
│ Hue Sensors │ ┌───────────┐ │ Devices │
|
||||
└──────────────┘ │Eve, etc. │ └───────────┘
|
||||
│(Matter/ │
|
||||
│ Thread) │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation Examples
|
||||
|
||||
### Complex Scheduling
|
||||
|
||||
```yaml
|
||||
# Advanced morning routine with conditions
|
||||
automation:
|
||||
@ -129,57 +469,212 @@ automation:
|
||||
entity_id: binary_sensor.workday
|
||||
state: 'on'
|
||||
- condition: state
|
||||
entity_id: person.your_name
|
||||
entity_id: person.cal
|
||||
state: 'home'
|
||||
action:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: light.bedroom_lights
|
||||
entity_id: light.bedroom
|
||||
data:
|
||||
brightness_pct: 30
|
||||
color_temp: 400
|
||||
- delay: "00:15:00"
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: light.bedroom
|
||||
data:
|
||||
brightness_pct: 80
|
||||
```
|
||||
|
||||
### Presence Detection
|
||||
### Presence-Based Automation
|
||||
|
||||
```yaml
|
||||
# Multi-factor presence with phone + door sensors
|
||||
# Welcome home scene
|
||||
automation:
|
||||
- alias: "Arrival Detection"
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: person.your_name
|
||||
entity_id: person.cal
|
||||
to: 'home'
|
||||
- platform: state
|
||||
entity_id: binary_sensor.front_door
|
||||
to: 'on'
|
||||
condition:
|
||||
- condition: state
|
||||
entity_id: person.your_name
|
||||
state: 'home'
|
||||
- condition: sun
|
||||
after: sunset
|
||||
action:
|
||||
- service: scene.turn_on
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: scene.arrival_lights
|
||||
- service: climate.set_temperature
|
||||
target:
|
||||
entity_id: climate.main_thermostat
|
||||
entity_id: light.living_room
|
||||
data:
|
||||
temperature: 72
|
||||
brightness_pct: 80
|
||||
- service: notify.mobile_app
|
||||
data:
|
||||
message: "Welcome home!"
|
||||
```
|
||||
|
||||
## Resource Requirements
|
||||
---
|
||||
|
||||
### Minimum Specs
|
||||
- **CPU:** 2 cores
|
||||
- **RAM:** 2GB
|
||||
- **Storage:** 20GB
|
||||
- **Network:** Gigabit recommended for media streaming integration
|
||||
## Troubleshooting
|
||||
|
||||
### Your Proxmox Environment
|
||||
- Should handle HA easily alongside existing containers
|
||||
- Consider dedicating specific resources if running many integrations
|
||||
- Network mode: host recommended for device discovery
|
||||
### VM Won't Start
|
||||
|
||||
```bash
|
||||
# Check VM status
|
||||
qm status 109
|
||||
|
||||
# Check for errors
|
||||
journalctl -u pve-guests | grep 109
|
||||
|
||||
# Verify UEFI boot
|
||||
qm config 109 | grep bios
|
||||
```
|
||||
|
||||
### Can't Access Web UI
|
||||
|
||||
1. Check VM console in Proxmox for IP address
|
||||
2. Verify network bridge configuration
|
||||
3. Ensure firewall allows port 8123
|
||||
4. Wait 5 minutes on first boot for full initialization
|
||||
5. Try both IPs: http://10.0.0.28:8123 and http://10.10.0.215:8123
|
||||
|
||||
### Hue Bridge Not Discovered
|
||||
|
||||
1. Verify same subnet as HA VM
|
||||
2. Check mDNS/Avahi is working
|
||||
3. Manually add: Settings → Integrations → Add → Philips Hue
|
||||
4. Enter bridge IP manually if needed
|
||||
|
||||
### Matter Commissioning Fails
|
||||
|
||||
**Symptom:** "Pairing Failed" or endless spinning in Companion app
|
||||
|
||||
**Check Matter Server logs** (Settings → Add-ons → Matter Server → Logs):
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| `Discovery timed out` | HA can't find device on network | Verify same subnet |
|
||||
| `PASESession timed out` | Can't establish secure session | Check Bluetooth permissions on phone |
|
||||
| `Thread credentials don't match` | Wrong Thread network | Set correct preferred network in HA |
|
||||
| `Msg Retransmission failure` | Network communication issue | Check mDNS reflection, firewall |
|
||||
|
||||
**Most common fix:** Ensure HA is on the same subnet as IoT devices (see Network Configuration section).
|
||||
|
||||
### Thread/OTBR Issues
|
||||
|
||||
**Check OTBR status:**
|
||||
```bash
|
||||
curl -s http://10.0.0.28:8081/node/state
|
||||
# Should return: "leader" or "router"
|
||||
```
|
||||
|
||||
**Check Thread diagnostics:**
|
||||
```bash
|
||||
curl -s http://10.0.0.28:8081/diagnostics | jq '.[] | {ActiveRouters, ChildTable, IfInUcastPkts}'
|
||||
```
|
||||
|
||||
**Common issues:**
|
||||
|
||||
| Symptom | Cause | Solution |
|
||||
|---------|-------|----------|
|
||||
| `NoAck` errors in OTBR logs | Radio can't communicate | Check USB passthrough, restart OTBR |
|
||||
| `IfInUcastPkts: 0` | Not receiving any traffic | Verify channel matches other devices |
|
||||
| Empty `ChildTable` | No devices joined | Device joining different network |
|
||||
| Commissioner disabled | Matter not enabling it | Restart Matter Server |
|
||||
|
||||
### ZBT-2 Radio Issues
|
||||
|
||||
**Verify USB passthrough:**
|
||||
```bash
|
||||
ssh proxmox "lsusb | grep 303a"
|
||||
# Should show: 303a:831a Nabu Casa ZBT-2
|
||||
```
|
||||
|
||||
**Check if device visible in HA:**
|
||||
- Settings → System → Hardware → Search "usb"
|
||||
- Should show `/dev/ttyACM0`
|
||||
|
||||
**Flash firmware if needed:**
|
||||
- Settings → System → Hardware → ZBT-2 → Configure → Flash
|
||||
|
||||
### Network Interface Issues
|
||||
|
||||
**Matter Server using wrong interface:**
|
||||
|
||||
Check logs for: `Using 'enp6s18' as primary network interface`
|
||||
|
||||
If using wrong interface:
|
||||
1. The primary interface (net0) must be on the IoT network
|
||||
2. Swap interfaces in Proxmox if needed:
|
||||
```bash
|
||||
ssh proxmox "qm set 109 -net0 virtio=<MAC>,bridge=vmbr1"
|
||||
ssh proxmox "qm set 109 -net1 virtio=<MAC>,bridge=vmbr0"
|
||||
ssh proxmox "qm reboot 109"
|
||||
```
|
||||
3. Reconfigure IPs in HAOS after reboot
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Home Assistant Documentation](https://www.home-assistant.io/docs/)
|
||||
- [Community Scripts - HAOS VM](https://community-scripts.github.io/ProxmoxVE/scripts?id=haos-vm)
|
||||
- [Home Assistant Community Forums](https://community.home-assistant.io/)
|
||||
- [HACS - Community Store](https://hacs.xyz/)
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
### Pre-Deployment
|
||||
- [x] Identify and document old HA VM ID
|
||||
- [x] Plan IP address for new VM
|
||||
- [x] Verify Proxmox storage has 64GB+ free
|
||||
|
||||
### Deployment
|
||||
- [x] Decommission old HA VM
|
||||
- [x] Run community helper script
|
||||
- [x] Configure with recommended specs (4 cores, 8GB, 64GB)
|
||||
- [x] Complete onboarding wizard
|
||||
|
||||
### Post-Deployment
|
||||
- [x] Install File Editor and Terminal add-ons
|
||||
- [ ] Connect Philips Hue Bridge
|
||||
- [ ] Test light control from HA
|
||||
- [x] Install HA mobile app
|
||||
- [ ] Set up first automation
|
||||
|
||||
### USB Passthrough (ZBT-2)
|
||||
- [x] Plug ZBT-2 into Proxmox host
|
||||
- [x] Add USB passthrough to VM (303a:831a)
|
||||
- [x] Verify device visible in HA
|
||||
- [x] Flash Thread firmware
|
||||
- [x] Configure OpenThread Border Router addon
|
||||
|
||||
### Network Configuration
|
||||
- [x] Add second NIC to VM (net1 on vmbr0)
|
||||
- [x] Swap interfaces (net0 on home network vmbr1)
|
||||
- [x] Configure IPs in HAOS
|
||||
- [ ] Set DHCP reservation for 10.0.0.28
|
||||
- [x] Enable mDNS reflection in Unifi
|
||||
|
||||
### Matter/Thread Setup
|
||||
- [x] Install Matter Server addon
|
||||
- [x] Install OpenThread Border Router addon
|
||||
- [x] Set Apple network as preferred Thread network
|
||||
- [x] Commission first Matter device (Eve outlet)
|
||||
- [ ] Commission remaining Matter devices
|
||||
|
||||
---
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. **Matter requires same-subnet connectivity** - The biggest blocker was HA being on a different subnet than IoT devices. mDNS doesn't cross subnets without explicit reflection.
|
||||
|
||||
2. **Matter Server selects the first network interface** - No configuration option exists to change this. The solution is to ensure net0 is on the correct network at the Proxmox level.
|
||||
|
||||
3. **Thread channel matters** - If HA's Thread network is on a different channel than existing devices, they can't communicate. Match channels or use the existing network's credentials.
|
||||
|
||||
4. **Apple Thread network is robust** - With 8 HomePods/Apple TVs as border routers, the Apple Thread mesh is strong. Using it as the preferred network is practical.
|
||||
|
||||
5. **ZBT-2 USB passthrough works reliably** - Using vendor:product ID (303a:831a) passthrough is reliable and survives USB port changes.
|
||||
|
||||
---
|
||||
*Last updated: January 9, 2026*
|
||||
|
||||
@ -63,6 +63,13 @@ When working with specific technologies, automatically load their dedicated cont
|
||||
- Note: Ko-fi → Paper Dynasty integration for automated pack distribution
|
||||
- Location: Task manager at `~/.claude/tools/task-manager/`, n8n at LXC 210 (10.10.0.210)
|
||||
|
||||
**OpenClaw Keywords**
|
||||
- "openclaw", "ai assistant", "personal assistant", "minimax", "autonomous agent", "agent skills"
|
||||
- Load: `productivity/openclaw/CONTEXT.md` (technology overview and patterns)
|
||||
- Load: `productivity/openclaw/troubleshooting.md` (error handling and debugging)
|
||||
- Note: Personal AI assistant on LXC 224 with MiniMax M2.1 and Discord integration
|
||||
- Location: LXC 224 (10.10.0.224), gateway at http://10.10.0.224:18789
|
||||
|
||||
**Media Server Keywords**
|
||||
- "jellyfin", "plex", "emby", "media server", "streaming", "watchstate", "watch history"
|
||||
- Load: `media-servers/CONTEXT.md` (technology overview and patterns)
|
||||
|
||||
297
baldurs-gate-3-coop-friend-setup.md
Normal file
297
baldurs-gate-3-coop-friend-setup.md
Normal file
@ -0,0 +1,297 @@
|
||||
# BG3 Coop Friend Setup (Linux)
|
||||
|
||||
Quick setup guide for joining a modded BG3 coop game. Uses pre-packaged mod archive for identical setup.
|
||||
|
||||
**You need:** The `bg3-mods-clean.zip` file from your coop partner.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Prerequisites
|
||||
|
||||
### Install GE-Proton
|
||||
|
||||
Required for running BG3 Mod Manager on Linux.
|
||||
|
||||
1. Download latest from: https://github.com/GloriousEggroll/proton-ge-custom/releases
|
||||
2. Extract to: `~/.local/share/Steam/compatibilitytools.d/`
|
||||
|
||||
Example:
|
||||
```bash
|
||||
mkdir -p ~/.local/share/Steam/compatibilitytools.d
|
||||
cd ~/.local/share/Steam/compatibilitytools.d
|
||||
tar -xf ~/Downloads/GE-Proton9-27.tar.gz
|
||||
```
|
||||
|
||||
### Run BG3 Once
|
||||
|
||||
Launch BG3 via Steam at least once to create the wine prefix. You can quit after the main menu loads.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Find Your Paths
|
||||
|
||||
You need two paths. Run this to find them:
|
||||
|
||||
```bash
|
||||
# Find your BG3 install
|
||||
find ~/.steam /mnt -type d -name "Baldurs Gate 3" -path "*/common/*" 2>/dev/null
|
||||
|
||||
# Find your wine prefix
|
||||
find ~/.steam /mnt -path "*/compatdata/1086940/pfx" 2>/dev/null
|
||||
```
|
||||
|
||||
Write these down:
|
||||
- **BG3_INSTALL:** (e.g., `/mnt/games/steam/steamapps/common/Baldurs Gate 3`)
|
||||
- **WINEPREFIX:** (e.g., `/mnt/games/steam/steamapps/compatdata/1086940/pfx`)
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Install Script Extender
|
||||
|
||||
```bash
|
||||
# Set your BG3 install path from Step 2
|
||||
BG3_INSTALL="/path/to/Baldurs Gate 3"
|
||||
|
||||
# Download and extract
|
||||
curl -sL $(curl -s https://api.github.com/repos/Norbyte/bg3se/releases/latest | grep browser_download_url | head -1 | cut -d'"' -f4) -o /tmp/se.zip
|
||||
unzip -o /tmp/se.zip -d "$BG3_INSTALL/bin"
|
||||
```
|
||||
|
||||
Verify: `ls "$BG3_INSTALL/bin/DWrite.dll"` should exist.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Install BG3 Mod Manager
|
||||
|
||||
```bash
|
||||
mkdir -p ~/Applications/BG3ModManager
|
||||
cd ~/Applications/BG3ModManager
|
||||
|
||||
# Download latest release
|
||||
curl -sL $(curl -s https://api.github.com/repos/LaughingLeader/BG3ModManager/releases/latest | grep "browser_download_url.*zip" | head -1 | cut -d'"' -f4) -o BG3MM.zip
|
||||
unzip BG3MM.zip && rm BG3MM.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Create Launcher Script
|
||||
|
||||
Save this to `~/bin/launch-bg3mm.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# EDIT THESE to match your system (from Step 2)
|
||||
WINEPREFIX="/path/to/compatdata/1086940/pfx"
|
||||
GE_PROTON="GE-Proton9-27"
|
||||
|
||||
BG3MM_DIR="$HOME/Applications/BG3ModManager"
|
||||
WINE64="$HOME/.local/share/Steam/compatibilitytools.d/$GE_PROTON/files/bin/wine64"
|
||||
|
||||
cd "$BG3MM_DIR"
|
||||
WINEPREFIX="$WINEPREFIX" WINEFSYNC=1 "$WINE64" BG3ModManager.exe "$@"
|
||||
```
|
||||
|
||||
Make executable:
|
||||
```bash
|
||||
chmod +x ~/bin/launch-bg3mm.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Import Mods
|
||||
|
||||
1. Launch BG3MM:
|
||||
```bash
|
||||
~/bin/launch-bg3mm.sh
|
||||
```
|
||||
|
||||
2. If it asks for BG3 folder, navigate to your BG3_INSTALL path from Step 2
|
||||
|
||||
3. **File → Import Order & Mods from Archive** → select `bg3-mods-clean.zip`
|
||||
|
||||
4. **File → Export Order to Game**
|
||||
|
||||
5. Close BG3MM
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Configure Steam Launch Options
|
||||
|
||||
Right-click BG3 in Steam → Properties → Launch Options:
|
||||
|
||||
```
|
||||
gamemoderun mangohud %command% --skip-launcher --dx11
|
||||
```
|
||||
|
||||
**The `--skip-launcher` is required** - the Larian Launcher doesn't work properly in Proton.
|
||||
|
||||
---
|
||||
|
||||
## Step 7b: SteamTinkerLaunch Config (Optional)
|
||||
|
||||
If you use STL, create `~/.config/steamtinkerlaunch/gamecfgs/id/1086940.conf`:
|
||||
|
||||
```ini
|
||||
# Baldur's Gate 3 - Modded Coop Configuration
|
||||
|
||||
##########################
|
||||
## PROTON
|
||||
##########################
|
||||
USEPROTON="GE-Proton9-27"
|
||||
GAMEARGS="--skip-launcher"
|
||||
|
||||
##########################
|
||||
## NVIDIA RTX
|
||||
##########################
|
||||
PROTON_ENABLE_NVAPI="1"
|
||||
PROTON_HIDE_NVIDIA_GPU="0"
|
||||
VKD3D_CONFIG="dxr"
|
||||
DXVK_ASYNC="1"
|
||||
DXVK_HDR="1"
|
||||
DXVK_LOG_LEVEL="none"
|
||||
|
||||
##########################
|
||||
## SCRIPT EXTENDER
|
||||
##########################
|
||||
USE_WINEDLLOVERRIDE="1"
|
||||
WINEDLLOVERRIDE="DWrite.dll=n,b"
|
||||
|
||||
##########################
|
||||
## PERFORMANCE
|
||||
##########################
|
||||
USEGAMEMODERUN="1"
|
||||
USEMANGOHUD="1"
|
||||
PROTON_NO_FSYNC="0"
|
||||
PROTON_NO_ESYNC="0"
|
||||
PROTON_FORCE_LARGE_ADDRESS_AWARE="1"
|
||||
|
||||
##########################
|
||||
## DISPLAY
|
||||
##########################
|
||||
PROTON_ENABLE_HDR="1"
|
||||
ENABLE_HDR_WSI="1"
|
||||
USEGAMESCOPE="0"
|
||||
|
||||
##########################
|
||||
## DEBUGGING (disabled)
|
||||
##########################
|
||||
PROTON_LOG="0"
|
||||
STLWINEDEBUG="-all"
|
||||
```
|
||||
|
||||
Key settings explained:
|
||||
- `WINEDLLOVERRIDE="DWrite.dll=n,b"` - Loads Script Extender (native, then builtin)
|
||||
- `PROTON_ENABLE_NVAPI="1"` - Enables NVIDIA driver passthrough for RTX/DLSS
|
||||
- `PROTON_HIDE_NVIDIA_GPU="0"` - Don't hide GPU from game (needed for RTX)
|
||||
- `VKD3D_CONFIG="dxr"` - Enable ray tracing support
|
||||
- `GAMEARGS="--skip-launcher"` - Bypass broken Larian Launcher
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Verify
|
||||
|
||||
1. Launch BG3 via Steam
|
||||
2. Check main menu bottom-left shows Script Extender version
|
||||
3. Start a new game - mods should be active
|
||||
|
||||
---
|
||||
|
||||
## Included Mods (16 .pak mods via BG3MM)
|
||||
|
||||
| Category | Mod | Purpose |
|
||||
|----------|-----|---------|
|
||||
| Dependencies | VolitionCabinet | Framework for auto-send mods |
|
||||
| | Mod Configuration Menu | Settings UI |
|
||||
| | Mark Books as Read | Required by auto-send books |
|
||||
| UI | ImpUI | UI improvements |
|
||||
| | Better Inventory UI | Color-coded items |
|
||||
| | Better Containers | Improved container UI |
|
||||
| | Better Hotbar 2 | More hotbar slots |
|
||||
| | BagsBagsBagsReforged | Auto-sort containers |
|
||||
| QoL | Weightless Consumables | No potion/scroll weight |
|
||||
| | Carry Weight Increased | x10 carry capacity |
|
||||
| | Auto Send Food To Camp | Food auto-transfers |
|
||||
| | Auto Send Read Books | Books auto-transfer |
|
||||
| | Wifi Potions | Share potions across party |
|
||||
| | Fix Stragglers | Auto-teleport stuck companions |
|
||||
| | MoveFaster | 2x movement speed |
|
||||
| | TransmogEnhanced | Change gear appearance |
|
||||
|
||||
---
|
||||
|
||||
## Optional: Native Mods (Client-Side Only)
|
||||
|
||||
These are DLL mods that go in the `bin/` folder, NOT managed by BG3MM. They don't need to be synced for coop - each player can choose to use them independently.
|
||||
|
||||
| Mod | Purpose |
|
||||
|-----|---------|
|
||||
| Native Mod Loader | Framework for native DLL mods |
|
||||
| WASD Character Movement | Direct keyboard movement instead of click-to-move |
|
||||
| Native Camera Tweaks | Unlocked camera controls (zoom, rotation, etc.) |
|
||||
|
||||
### Installing Native Mods
|
||||
|
||||
```bash
|
||||
BG3_BIN="/path/to/Baldurs Gate 3/bin"
|
||||
|
||||
# 1. Download from NexusMods:
|
||||
# - Native Mod Loader: https://www.nexusmods.com/baldursgate3/mods/944
|
||||
# - WASD: https://www.nexusmods.com/baldursgate3/mods/781
|
||||
# - Native Camera Tweaks: https://www.nexusmods.com/baldursgate3/mods/945
|
||||
|
||||
# 2. Backup original bink2w64.dll
|
||||
cp "$BG3_BIN/bink2w64.dll" "$BG3_BIN/bink2w64_original.dll"
|
||||
|
||||
# 3. Extract NativeModLoader's bink2w64.dll to bin/
|
||||
unzip NativeModLoader*.zip -d /tmp/nml
|
||||
cp /tmp/nml/bin/bink2w64.dll "$BG3_BIN/"
|
||||
|
||||
# 4. Create NativeMods folder and extract WASD/Camera
|
||||
mkdir -p "$BG3_BIN/NativeMods"
|
||||
unzip WASD*.zip -d /tmp/wasd
|
||||
unzip "Native Camera*.zip" -d /tmp/camera
|
||||
cp /tmp/wasd/bin/NativeMods/* "$BG3_BIN/NativeMods/"
|
||||
cp /tmp/camera/bin/NativeMods/* "$BG3_BIN/NativeMods/"
|
||||
```
|
||||
|
||||
After installation, `bin/NativeMods/` should contain:
|
||||
- `BG3WASD.dll` + `BG3WASD.toml`
|
||||
- `BG3NativeCameraTweaks.dll` + `BG3NativeCameraTweaks.toml`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### BG3MM won't launch
|
||||
|
||||
**Wineserver mismatch:** Kill existing wineserver and retry:
|
||||
```bash
|
||||
wineserver -k
|
||||
~/bin/launch-bg3mm.sh
|
||||
```
|
||||
|
||||
**Wrong wine:** Make sure you're using GE-Proton's wine64, not system wine.
|
||||
|
||||
### Game won't start / stuck on "Running"
|
||||
|
||||
Make sure `--skip-launcher` is in your Steam launch options.
|
||||
|
||||
### "Version mismatch" with coop partner
|
||||
|
||||
Both players must have:
|
||||
- Same game version (check Steam)
|
||||
- Same mods (use the same archive)
|
||||
- Script Extender showing in main menu
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Item | Path |
|
||||
|------|------|
|
||||
| Script Extender | `<BG3_INSTALL>/bin/DWrite.dll` |
|
||||
| Mods folder | `<WINEPREFIX>/drive_c/users/steamuser/AppData/Local/Larian Studios/Baldur's Gate 3/Mods/` |
|
||||
| BG3MM | `~/Applications/BG3ModManager/` |
|
||||
| GE-Proton | `~/.local/share/Steam/compatibilitytools.d/GE-Proton9-27/` |
|
||||
@ -109,6 +109,27 @@ volumes:
|
||||
- NVDEC: H.264, HEVC, VP8, VP9, AV1
|
||||
- Sessions: 3+ concurrent
|
||||
|
||||
## GPU Health Monitoring
|
||||
|
||||
### Jellyfin GPU Monitor
|
||||
**Location**: `ubuntu-manticore:~/scripts/jellyfin_gpu_monitor.py`
|
||||
**Schedule**: Every 5 minutes via cron
|
||||
**Logs**: `~/logs/jellyfin-gpu-monitor.log`
|
||||
|
||||
The monitor detects when the Jellyfin container loses GPU access (common after
|
||||
driver updates or Docker restarts) and automatically:
|
||||
1. Sends Discord alert
|
||||
2. Restarts the container to restore GPU access
|
||||
3. Confirms GPU is restored
|
||||
|
||||
**Manual check:**
|
||||
```bash
|
||||
ssh ubuntu-manticore "python3 ~/scripts/jellyfin_gpu_monitor.py --check"
|
||||
```
|
||||
|
||||
**FFmpeg exit code 187**: Indicates NVENC failure due to lost GPU access.
|
||||
The monitor catches this condition before users report playback failures.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
@ -116,6 +137,7 @@ volumes:
|
||||
2. **Transcoding failures**: Verify codec support for your GPU generation
|
||||
3. **Slow playback start**: Check network mount performance
|
||||
4. **Cache filling up**: Monitor trickplay/thumbnail generation
|
||||
5. **FFmpeg exit code 187**: GPU access lost - monitor should auto-restart
|
||||
|
||||
### Diagnostic Commands
|
||||
```bash
|
||||
|
||||
395
monitoring/scripts/jellyfin_gpu_monitor.py
Normal file
395
monitoring/scripts/jellyfin_gpu_monitor.py
Normal file
@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Jellyfin GPU Health Monitor with Discord Alerts
|
||||
|
||||
Monitors Jellyfin container's GPU access and sends Discord notifications
|
||||
when GPU access is lost. Optionally auto-restarts the container.
|
||||
|
||||
The GTX 1070 in ubuntu-manticore is shared between Jellyfin and Tdarr.
|
||||
GPU access can be lost after driver updates, Docker restarts, or other
|
||||
runtime issues - this monitor detects that condition before users report
|
||||
playback failures.
|
||||
|
||||
Usage:
|
||||
# Basic health check
|
||||
python3 jellyfin_gpu_monitor.py --check
|
||||
|
||||
# Check with Discord alerts
|
||||
python3 jellyfin_gpu_monitor.py --check --discord-alerts
|
||||
|
||||
# Check and auto-restart if GPU lost
|
||||
python3 jellyfin_gpu_monitor.py --check --auto-restart
|
||||
|
||||
# Full monitoring with alerts and auto-restart
|
||||
python3 jellyfin_gpu_monitor.py --check --discord-alerts --auto-restart
|
||||
|
||||
# Test Discord integration
|
||||
python3 jellyfin_gpu_monitor.py --discord-test
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
import requests
|
||||
|
||||
|
||||
@dataclass
|
||||
class GPUStatus:
|
||||
timestamp: str
|
||||
container_name: str
|
||||
gpu_accessible: bool
|
||||
gpu_name: Optional[str] = None
|
||||
gpu_temp: Optional[int] = None
|
||||
driver_version: Optional[str] = None
|
||||
cuda_version: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContainerStatus:
|
||||
running: bool
|
||||
status: str
|
||||
uptime: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class HealthStatus:
|
||||
timestamp: str
|
||||
overall_healthy: bool
|
||||
gpu_status: GPUStatus
|
||||
container_status: ContainerStatus
|
||||
action_taken: Optional[str] = None
|
||||
|
||||
|
||||
class DiscordNotifier:
|
||||
def __init__(self, webhook_url: str, timeout: int = 10):
|
||||
self.webhook_url = webhook_url
|
||||
self.timeout = timeout
|
||||
self.logger = logging.getLogger(f"{__name__}.DiscordNotifier")
|
||||
|
||||
def send_alert(self, title: str, description: str, color: int = 0xff6b6b,
|
||||
fields: list = None) -> bool:
|
||||
"""Send embed alert to Discord."""
|
||||
embed = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"color": color,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"fields": fields or []
|
||||
}
|
||||
|
||||
payload = {
|
||||
"username": "Jellyfin GPU Monitor",
|
||||
"embeds": [embed]
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
self.webhook_url,
|
||||
json=payload,
|
||||
timeout=self.timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
self.logger.info("Discord notification sent successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to send Discord notification: {e}")
|
||||
return False
|
||||
|
||||
def send_gpu_lost_alert(self, gpu_status: GPUStatus, auto_restart: bool) -> bool:
|
||||
"""Send alert when GPU access is lost."""
|
||||
action = "Container will be automatically restarted" if auto_restart else "Manual intervention required"
|
||||
|
||||
fields = [
|
||||
{"name": "Container", "value": gpu_status.container_name, "inline": True},
|
||||
{"name": "Error", "value": gpu_status.error or "Unknown", "inline": True},
|
||||
{"name": "Action", "value": action, "inline": False}
|
||||
]
|
||||
|
||||
return self.send_alert(
|
||||
title="Jellyfin GPU Access Lost",
|
||||
description="The Jellyfin container has lost access to the NVIDIA GPU. Transcoding will fail until resolved.",
|
||||
color=0xff6b6b, # Red
|
||||
fields=fields
|
||||
)
|
||||
|
||||
def send_gpu_restored_alert(self, gpu_status: GPUStatus) -> bool:
|
||||
"""Send alert when GPU access is restored."""
|
||||
fields = [
|
||||
{"name": "Container", "value": gpu_status.container_name, "inline": True},
|
||||
{"name": "GPU", "value": gpu_status.gpu_name or "Unknown", "inline": True},
|
||||
{"name": "Driver", "value": gpu_status.driver_version or "Unknown", "inline": True}
|
||||
]
|
||||
|
||||
return self.send_alert(
|
||||
title="Jellyfin GPU Access Restored",
|
||||
description="GPU access has been restored. Hardware transcoding should now work.",
|
||||
color=0x28a745, # Green
|
||||
fields=fields
|
||||
)
|
||||
|
||||
|
||||
class JellyfinGPUMonitor:
|
||||
def __init__(self, container_name: str = "jellyfin",
|
||||
discord_webhook: str = None,
|
||||
enable_discord: bool = False,
|
||||
auto_restart: bool = False,
|
||||
ssh_host: str = None):
|
||||
self.container_name = container_name
|
||||
self.auto_restart = auto_restart
|
||||
self.ssh_host = ssh_host
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
self.discord = None
|
||||
if enable_discord and discord_webhook:
|
||||
self.discord = DiscordNotifier(discord_webhook)
|
||||
|
||||
def _run_command(self, cmd: list, timeout: int = 30) -> tuple:
|
||||
"""Run command locally or via SSH."""
|
||||
if self.ssh_host:
|
||||
cmd = ["ssh", self.ssh_host] + [" ".join(cmd)]
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
shell=isinstance(cmd[-1], str) and self.ssh_host is not None
|
||||
)
|
||||
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
||||
except subprocess.TimeoutExpired:
|
||||
return -1, "", "Command timed out"
|
||||
except Exception as e:
|
||||
return -1, "", str(e)
|
||||
|
||||
def check_container_status(self) -> ContainerStatus:
|
||||
"""Check if Jellyfin container is running."""
|
||||
cmd = ["docker", "inspect", "--format",
|
||||
"{{.State.Running}}|{{.State.Status}}|{{.State.StartedAt}}",
|
||||
self.container_name]
|
||||
|
||||
code, stdout, stderr = self._run_command(cmd)
|
||||
|
||||
if code != 0:
|
||||
return ContainerStatus(
|
||||
running=False,
|
||||
status="not_found",
|
||||
error=stderr or "Container not found"
|
||||
)
|
||||
|
||||
parts = stdout.split("|")
|
||||
running = parts[0].lower() == "true"
|
||||
status = parts[1] if len(parts) > 1 else "unknown"
|
||||
started_at = parts[2] if len(parts) > 2 else None
|
||||
|
||||
return ContainerStatus(
|
||||
running=running,
|
||||
status=status,
|
||||
uptime=started_at
|
||||
)
|
||||
|
||||
def check_gpu_access(self) -> GPUStatus:
|
||||
"""Check if container has GPU access via nvidia-smi."""
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
# Run nvidia-smi inside the container
|
||||
cmd = ["docker", "exec", self.container_name, "nvidia-smi",
|
||||
"--query-gpu=name,temperature.gpu,driver_version",
|
||||
"--format=csv,noheader,nounits"]
|
||||
|
||||
code, stdout, stderr = self._run_command(cmd)
|
||||
|
||||
if code != 0:
|
||||
# Try basic nvidia-smi to get more error info
|
||||
cmd_basic = ["docker", "exec", self.container_name, "nvidia-smi"]
|
||||
_, _, stderr_basic = self._run_command(cmd_basic)
|
||||
|
||||
return GPUStatus(
|
||||
timestamp=timestamp,
|
||||
container_name=self.container_name,
|
||||
gpu_accessible=False,
|
||||
error=stderr_basic or stderr or "nvidia-smi failed"
|
||||
)
|
||||
|
||||
# Parse nvidia-smi output
|
||||
try:
|
||||
parts = [p.strip() for p in stdout.split(",")]
|
||||
gpu_name = parts[0] if len(parts) > 0 else None
|
||||
gpu_temp = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else None
|
||||
driver_version = parts[2] if len(parts) > 2 else None
|
||||
|
||||
return GPUStatus(
|
||||
timestamp=timestamp,
|
||||
container_name=self.container_name,
|
||||
gpu_accessible=True,
|
||||
gpu_name=gpu_name,
|
||||
gpu_temp=gpu_temp,
|
||||
driver_version=driver_version
|
||||
)
|
||||
except Exception as e:
|
||||
return GPUStatus(
|
||||
timestamp=timestamp,
|
||||
container_name=self.container_name,
|
||||
gpu_accessible=True, # nvidia-smi worked, just parsing failed
|
||||
error=f"Parse error: {e}"
|
||||
)
|
||||
|
||||
def restart_container(self) -> bool:
|
||||
"""Restart the Jellyfin container."""
|
||||
self.logger.info(f"Restarting container: {self.container_name}")
|
||||
|
||||
cmd = ["docker", "restart", self.container_name]
|
||||
code, stdout, stderr = self._run_command(cmd, timeout=120)
|
||||
|
||||
if code == 0:
|
||||
self.logger.info("Container restarted successfully")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Failed to restart container: {stderr}")
|
||||
return False
|
||||
|
||||
def health_check(self) -> HealthStatus:
|
||||
"""Perform full health check."""
|
||||
timestamp = datetime.now().isoformat()
|
||||
action_taken = None
|
||||
|
||||
# Check container first
|
||||
container_status = self.check_container_status()
|
||||
|
||||
if not container_status.running:
|
||||
gpu_status = GPUStatus(
|
||||
timestamp=timestamp,
|
||||
container_name=self.container_name,
|
||||
gpu_accessible=False,
|
||||
error="Container not running"
|
||||
)
|
||||
return HealthStatus(
|
||||
timestamp=timestamp,
|
||||
overall_healthy=False,
|
||||
gpu_status=gpu_status,
|
||||
container_status=container_status
|
||||
)
|
||||
|
||||
# Check GPU access
|
||||
gpu_status = self.check_gpu_access()
|
||||
|
||||
# Handle GPU access lost
|
||||
if not gpu_status.gpu_accessible:
|
||||
self.logger.warning(f"GPU access lost: {gpu_status.error}")
|
||||
|
||||
# Send Discord alert
|
||||
if self.discord:
|
||||
self.discord.send_gpu_lost_alert(gpu_status, self.auto_restart)
|
||||
|
||||
# Auto-restart if enabled
|
||||
if self.auto_restart:
|
||||
if self.restart_container():
|
||||
action_taken = "Container restarted"
|
||||
|
||||
# Re-check GPU after restart
|
||||
import time
|
||||
time.sleep(5) # Wait for container to initialize
|
||||
gpu_status = self.check_gpu_access()
|
||||
container_status = self.check_container_status()
|
||||
|
||||
if gpu_status.gpu_accessible and self.discord:
|
||||
self.discord.send_gpu_restored_alert(gpu_status)
|
||||
else:
|
||||
action_taken = "Restart failed"
|
||||
|
||||
overall_healthy = (
|
||||
container_status.running and
|
||||
gpu_status.gpu_accessible
|
||||
)
|
||||
|
||||
return HealthStatus(
|
||||
timestamp=timestamp,
|
||||
overall_healthy=overall_healthy,
|
||||
gpu_status=gpu_status,
|
||||
container_status=container_status,
|
||||
action_taken=action_taken
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Monitor Jellyfin GPU health')
|
||||
parser.add_argument('--container', default='jellyfin', help='Container name')
|
||||
parser.add_argument('--check', action='store_true', help='Perform health check')
|
||||
parser.add_argument('--discord-webhook',
|
||||
default='https://discord.com/api/webhooks/1404105821549498398/y2Ud1RK9rzFjv58xbypUfQNe3jrL7ZUq1FkQHa4_dfOHm2ylp93z0f4tY0O8Z-vQgKhD',
|
||||
help='Discord webhook URL')
|
||||
parser.add_argument('--discord-alerts', action='store_true', help='Enable Discord alerts')
|
||||
parser.add_argument('--discord-test', action='store_true', help='Test Discord integration')
|
||||
parser.add_argument('--auto-restart', action='store_true', help='Auto-restart on GPU loss')
|
||||
parser.add_argument('--ssh-host', default=None, help='SSH host for remote monitoring')
|
||||
parser.add_argument('--output', choices=['json', 'pretty'], default='pretty')
|
||||
parser.add_argument('--verbose', action='store_true', help='Verbose logging')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Configure logging
|
||||
level = logging.DEBUG if args.verbose else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Discord test
|
||||
if args.discord_test:
|
||||
notifier = DiscordNotifier(args.discord_webhook)
|
||||
success = notifier.send_alert(
|
||||
title="Jellyfin GPU Monitor Test",
|
||||
description="Discord integration is working correctly.",
|
||||
color=0x00ff00,
|
||||
fields=[
|
||||
{"name": "Container", "value": args.container, "inline": True},
|
||||
{"name": "Status", "value": "Test successful", "inline": True}
|
||||
]
|
||||
)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
# Health check
|
||||
if args.check:
|
||||
monitor = JellyfinGPUMonitor(
|
||||
container_name=args.container,
|
||||
discord_webhook=args.discord_webhook,
|
||||
enable_discord=args.discord_alerts,
|
||||
auto_restart=args.auto_restart,
|
||||
ssh_host=args.ssh_host
|
||||
)
|
||||
|
||||
result = monitor.health_check()
|
||||
|
||||
if args.output == 'json':
|
||||
print(json.dumps(asdict(result), indent=2))
|
||||
else:
|
||||
print(f"=== Jellyfin GPU Health Check - {result.timestamp} ===")
|
||||
status_icon = "" if result.overall_healthy else ""
|
||||
print(f"Overall: {status_icon} {'Healthy' if result.overall_healthy else 'UNHEALTHY'}")
|
||||
print(f"\nContainer: {result.container_status.status}")
|
||||
print(f"GPU Access: {'Yes' if result.gpu_status.gpu_accessible else 'NO'}")
|
||||
|
||||
if result.gpu_status.gpu_accessible:
|
||||
print(f"GPU: {result.gpu_status.gpu_name}")
|
||||
print(f"Temperature: {result.gpu_status.gpu_temp}C")
|
||||
print(f"Driver: {result.gpu_status.driver_version}")
|
||||
else:
|
||||
print(f"Error: {result.gpu_status.error}")
|
||||
|
||||
if result.action_taken:
|
||||
print(f"\nAction: {result.action_taken}")
|
||||
|
||||
sys.exit(0 if result.overall_healthy else 1)
|
||||
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
194
productivity/openclaw/CONTEXT.md
Normal file
194
productivity/openclaw/CONTEXT.md
Normal file
@ -0,0 +1,194 @@
|
||||
# OpenClaw Personal AI Assistant - Technology Context
|
||||
|
||||
## Overview
|
||||
|
||||
OpenClaw is an open-source personal AI assistant that runs locally with autonomous agent capabilities. Originally created as "Clawdbot" by Peter Steinberger, now community-maintained as OpenClaw.
|
||||
|
||||
**Deployment:** LXC 224 (10.10.0.224)
|
||||
**Status:** Deployment in progress
|
||||
**Primary Use:** Personal automation, task management, Discord integration
|
||||
|
||||
## Architecture
|
||||
|
||||
### Gateway-Centric Design
|
||||
- **Gateway Daemon:** Single control plane for all operations
|
||||
- **Messaging:** Handles chat platform integrations (Discord, Telegram, etc.)
|
||||
- **Tool Execution:** Sandboxed code execution via nested Docker containers
|
||||
- **Client Connections:** WebSocket and HTTP API for external integrations
|
||||
|
||||
### Key Components
|
||||
1. **OpenClaw Gateway** - Node.js application, port 18789
|
||||
2. **Docker-in-Docker Sandbox** - Isolated execution environment
|
||||
3. **Persistent Workspace** - `/workspace` for file operations and memory
|
||||
4. **Configuration Layer** - `openclaw.json` + environment variables
|
||||
|
||||
## AI Provider Integration
|
||||
|
||||
**Current Provider:** MiniMax M2.1
|
||||
- **Model:** MiniMax-M2.1 (200K context window)
|
||||
- **API Type:** Anthropic-compatible messages API
|
||||
- **Endpoint:** https://api.minimax.io/anthropic
|
||||
- **Authentication:** Bearer token via `MINIMAX_API_KEY`
|
||||
|
||||
**Model Selection:**
|
||||
- Primary: `minimax/MiniMax-M2.1` (standard, balanced performance)
|
||||
- Fast variant: `minimax/MiniMax-M2.1-lightning` (lower latency, lower cost)
|
||||
|
||||
**Pricing (per 1M tokens):**
|
||||
- M2.1: $0.50 input / $1.50 output
|
||||
- M2.1 Lightning: $0.30 input / $0.90 output
|
||||
|
||||
## Discord Integration
|
||||
|
||||
**Bot Configuration:**
|
||||
- **Policy:** DM pairing (secure by default)
|
||||
- **Intents Required:** Message Content + Server Members
|
||||
- **Permissions:** View/Send/History + Embeds + Files + Reactions
|
||||
|
||||
**Access Control:**
|
||||
- DMs: Pairing code required (1-hour expiry)
|
||||
- Guild channels: Can be enabled per-server with allowlists
|
||||
- Mention requirement: Optional gating for shared channels
|
||||
|
||||
**Message Handling:**
|
||||
- History context: 20 messages (configurable)
|
||||
- File uploads: 8MB max (configurable)
|
||||
- Response format: Markdown with embeds
|
||||
|
||||
## Security Model
|
||||
|
||||
### Sandboxing
|
||||
- **Docker-in-Docker:** Code execution isolated in nested containers
|
||||
- **AppArmor:** Unconfined profile required for container nesting
|
||||
- **UID:** Runs as non-root `node` user (UID 1000) inside container
|
||||
|
||||
### Secrets Management
|
||||
- **Storage:** Environment variables via `.env` file
|
||||
- **Interpolation:** `${VAR_NAME}` syntax in openclaw.json
|
||||
- **Scope:** Secrets only accessible to gateway process
|
||||
|
||||
### Network Isolation
|
||||
- **Bridge Network:** `openclaw-net` isolates from other services
|
||||
- **Exposed Ports:** Only 18789 (gateway) accessible on host network
|
||||
- **Outbound:** Requires internet access for AI API calls
|
||||
|
||||
## Operational Patterns
|
||||
|
||||
### Standard Operations
|
||||
```bash
|
||||
# Start OpenClaw
|
||||
docker compose up -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f openclaw-gateway
|
||||
|
||||
# Restart after config changes
|
||||
docker compose restart openclaw-gateway
|
||||
|
||||
# Stop service
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Pairing Management
|
||||
```bash
|
||||
# List pending pairing requests
|
||||
docker compose exec openclaw-gateway openclaw pairing list discord
|
||||
|
||||
# Approve pairing
|
||||
docker compose exec openclaw-gateway openclaw pairing approve discord <code>
|
||||
|
||||
# Revoke access
|
||||
docker compose exec openclaw-gateway openclaw pairing revoke discord <user_id>
|
||||
```
|
||||
|
||||
### Diagnostics
|
||||
```bash
|
||||
# Health check
|
||||
docker compose exec openclaw-gateway openclaw doctor
|
||||
|
||||
# Channel status
|
||||
docker compose exec openclaw-gateway openclaw channels status --probe
|
||||
|
||||
# Model configuration
|
||||
docker compose exec openclaw-gateway openclaw models list
|
||||
```
|
||||
|
||||
### Configuration Updates
|
||||
```bash
|
||||
# Edit configuration
|
||||
nano openclaw.json
|
||||
|
||||
# Restart to apply changes
|
||||
docker compose restart openclaw-gateway
|
||||
|
||||
# Verify changes
|
||||
docker compose logs openclaw-gateway | grep "Configuration loaded"
|
||||
```
|
||||
|
||||
## Resource Usage Patterns
|
||||
|
||||
**Expected Baseline:**
|
||||
- Idle: ~200MB RAM, <5% CPU
|
||||
- Active chat: ~500MB RAM, 10-20% CPU
|
||||
- Browser automation: ~1GB RAM, 30-50% CPU
|
||||
- Concurrent operations: Up to 2GB RAM
|
||||
|
||||
**Disk Usage:**
|
||||
- Application: ~500MB
|
||||
- Workspace files: Variable (user-dependent)
|
||||
- Logs: ~50MB/week (with rotation)
|
||||
- Docker images: ~1GB
|
||||
|
||||
**Network:**
|
||||
- AI API calls: ~10-100KB per request
|
||||
- Discord: WebSocket connection (minimal bandwidth)
|
||||
- File uploads: Up to 8MB per message
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Current Integrations
|
||||
- **Discord:** DM-based personal assistant
|
||||
- **MiniMax API:** AI model provider
|
||||
- **Docker:** Sandboxed execution environment
|
||||
|
||||
### Potential Future Integrations
|
||||
- **n8n:** Workflow automation triggers (not currently configured)
|
||||
- **Home Assistant:** Smart home control via API
|
||||
- **Additional chat platforms:** Telegram, Signal, WhatsApp
|
||||
- **Browser automation skills:** Web scraping, form filling
|
||||
|
||||
## Troubleshooting Quick Reference
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Gateway won't start | Check `docker compose logs` for errors; verify .env secrets |
|
||||
| Discord not connecting | Verify `DISCORD_BOT_TOKEN` and intents enabled |
|
||||
| "Used disallowed intents" error | Enable Message Content Intent in Discord portal |
|
||||
| Pairing code not working | Check expiry (1 hour), regenerate if needed |
|
||||
| High memory usage | Check for stuck browser automation processes |
|
||||
| MiniMax API errors | Verify `MINIMAX_API_KEY`, check API quota |
|
||||
|
||||
## References
|
||||
|
||||
- **Official Docs:** https://docs.openclaw.ai/
|
||||
- **GitHub:** https://github.com/openclaw/openclaw
|
||||
- **Discord Setup:** https://docs.openclaw.ai/channels/discord
|
||||
- **MiniMax Provider:** https://docs.openclaw.ai/providers/minimax
|
||||
- **MiniMax Platform:** https://platform.minimax.io/
|
||||
|
||||
## Maintenance Notes
|
||||
|
||||
**Update Strategy:**
|
||||
- Auto-updates: Gateway pulls `:latest` tag on restart
|
||||
- Breaking changes: Check release notes before updating
|
||||
- Rollback: Pin specific version tag if needed
|
||||
|
||||
**Backup Strategy:**
|
||||
- Configuration: `openclaw.json` + `.env` (version controlled template)
|
||||
- Workspace: `/opt/openclaw/workspace` (contains agent memory/files)
|
||||
- Logs: Optional retention for debugging
|
||||
|
||||
**Monitoring:**
|
||||
- Health check: HTTP endpoint at http://10.10.0.224:18789/health
|
||||
- Discord connectivity: Verify bot status in server member list
|
||||
- Resource usage: Monitor via Proxmox dashboard
|
||||
254
productivity/openclaw/DEPLOYMENT_STATUS.md
Normal file
254
productivity/openclaw/DEPLOYMENT_STATUS.md
Normal file
@ -0,0 +1,254 @@
|
||||
# OpenClaw Deployment Status
|
||||
|
||||
## Infrastructure Setup - COMPLETE
|
||||
|
||||
### LXC Container (224)
|
||||
- **Status:** ✅ Created and running
|
||||
- **Hostname:** openclaw-lxc
|
||||
- **IP Address:** 10.10.0.224
|
||||
- **Resources:** 2 cores, 4GB RAM, 32GB disk
|
||||
- **OS:** Ubuntu 20.04
|
||||
- **Docker:** Installed (version 28.1.1)
|
||||
- **Docker Compose:** Installed (version 2.35.1)
|
||||
|
||||
### Configuration Files
|
||||
- **Status:** ✅ Deployed to /opt/openclaw/
|
||||
- **docker-compose.yml:** ✅ Created
|
||||
- **openclaw.json:** ✅ Created (MiniMax + Discord config)
|
||||
- **.env.example:** ✅ Created (template for secrets)
|
||||
- **Directory structure:** ✅ Created (workspace/, logs/)
|
||||
|
||||
### AppArmor & Features
|
||||
- **Status:** ✅ Configured
|
||||
- **AppArmor profile:** unconfined (required for Docker-in-Docker)
|
||||
- **Features:** nesting=1, keyctl=1 (enabled)
|
||||
- **Container devices:** Allowed for Docker functionality
|
||||
|
||||
## Documentation - COMPLETE
|
||||
|
||||
### Technology Documentation
|
||||
- **Status:** ✅ Created in /mnt/NV2/Development/claude-home/productivity/openclaw/
|
||||
- **CONTEXT.md:** ✅ Comprehensive technology overview (architecture, operations, integrations)
|
||||
- **troubleshooting.md:** ✅ Complete troubleshooting guide (startup, Discord, API, performance)
|
||||
- **README.md:** ✅ Quick reference guide
|
||||
|
||||
### Infrastructure Integration
|
||||
- **Status:** ✅ Updated
|
||||
- **hosts.yml:** ✅ Added openclaw host entry
|
||||
- **LXC config backup:** ✅ Saved to server-configs/proxmox/lxc/224.conf
|
||||
- **CLAUDE.md:** ✅ Added OpenClaw keyword triggers to auto-loading rules
|
||||
- **.env.example:** ✅ Created template in server-configs/openclaw/
|
||||
|
||||
## Next Steps (REQUIRES USER INPUT)
|
||||
|
||||
### 1. Obtain API Keys
|
||||
You need to provide the following secrets:
|
||||
|
||||
**MiniMax API Key:**
|
||||
- Sign up at: https://platform.minimax.io/
|
||||
- Create an API key
|
||||
- Key format: `sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
||||
|
||||
**Discord Bot Token:**
|
||||
- Go to: https://discord.com/developers/applications
|
||||
- Create new application: "OpenClaw - Cal's Assistant" (or preferred name)
|
||||
- Bot tab → Add Bot
|
||||
- Copy bot token
|
||||
|
||||
### 2. Configure Environment Variables
|
||||
Once you have the keys:
|
||||
|
||||
```bash
|
||||
# SSH to LXC
|
||||
ssh root@10.10.0.224
|
||||
|
||||
# Create .env file from template
|
||||
cd /opt/openclaw
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env file and add your actual keys
|
||||
nano .env
|
||||
```
|
||||
|
||||
Add your keys:
|
||||
```
|
||||
MINIMAX_API_KEY=sk-your_actual_key_here
|
||||
DISCORD_BOT_TOKEN=your_actual_discord_token_here
|
||||
```
|
||||
|
||||
### 3. Enable Discord Bot Intents
|
||||
**CRITICAL:** Must be done before starting the bot
|
||||
|
||||
1. Go to Discord Developer Portal
|
||||
2. Select your application
|
||||
3. Bot tab → Privileged Gateway Intents
|
||||
4. Enable these two intents:
|
||||
- ✅ **Message Content Intent** (REQUIRED - bot can't read messages without this)
|
||||
- ✅ **Server Members Intent** (recommended for @mentions)
|
||||
5. Save changes
|
||||
|
||||
### 4. Generate Discord Bot Invite URL
|
||||
1. In Discord Developer Portal
|
||||
2. OAuth2 → URL Generator
|
||||
3. Select scopes:
|
||||
- ✅ `bot`
|
||||
- ✅ `applications.commands`
|
||||
4. Select bot permissions:
|
||||
- ✅ View Channels
|
||||
- ✅ Send Messages
|
||||
- ✅ Read Message History
|
||||
- ✅ Embed Links
|
||||
- ✅ Attach Files
|
||||
- ✅ Add Reactions
|
||||
5. Copy generated URL
|
||||
|
||||
### 5. Invite Bot to Discord Server
|
||||
1. Open the generated invite URL
|
||||
2. Select your Discord server
|
||||
3. Authorize the bot
|
||||
|
||||
### 6. Start OpenClaw
|
||||
```bash
|
||||
ssh root@10.10.0.224
|
||||
cd /opt/openclaw
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 7. Verify Deployment
|
||||
```bash
|
||||
# Check container status
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs -f openclaw-gateway
|
||||
|
||||
# Check health
|
||||
docker compose exec openclaw-gateway openclaw doctor
|
||||
|
||||
# Verify Discord connection
|
||||
docker compose exec openclaw-gateway openclaw channels status --probe
|
||||
```
|
||||
|
||||
Expected in logs:
|
||||
- "Configuration loaded"
|
||||
- "Discord channel connected"
|
||||
- "MiniMax provider initialized"
|
||||
- NO errors about missing environment variables or intents
|
||||
|
||||
### 8. Test DM Pairing
|
||||
1. Send DM to OpenClaw bot on Discord: "Hello"
|
||||
2. Bot should respond with a pairing code
|
||||
3. Approve pairing:
|
||||
```bash
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose exec openclaw-gateway openclaw pairing approve discord <code>'
|
||||
```
|
||||
4. Pairing confirmed message
|
||||
5. Send another DM: "What's 2+2?"
|
||||
6. Bot should respond with answer (verifies MiniMax API working)
|
||||
|
||||
## Success Criteria Checklist
|
||||
|
||||
Infrastructure:
|
||||
- [x] LXC 224 created and running
|
||||
- [x] Docker installed and configured
|
||||
- [x] AppArmor profile set to unconfined
|
||||
- [x] Nesting and keyctl features enabled
|
||||
- [x] Directory structure created
|
||||
- [x] Configuration files deployed
|
||||
|
||||
Documentation:
|
||||
- [x] CONTEXT.md created
|
||||
- [x] troubleshooting.md created
|
||||
- [x] README.md created
|
||||
- [x] Infrastructure inventory updated
|
||||
- [x] CLAUDE.md loading rules updated
|
||||
- [x] LXC config backed up
|
||||
|
||||
User Actions Required:
|
||||
- [ ] MiniMax API key obtained
|
||||
- [ ] Discord bot created
|
||||
- [ ] Discord intents enabled (Message Content + Server Members)
|
||||
- [ ] Bot invited to Discord server
|
||||
- [ ] .env file created with actual secrets
|
||||
- [ ] OpenClaw started with `docker compose up -d`
|
||||
- [ ] Discord bot online in server
|
||||
- [ ] DM pairing completed
|
||||
- [ ] Test message successful
|
||||
|
||||
## Deployment Files Location
|
||||
|
||||
**On LXC 224 (10.10.0.224):**
|
||||
```
|
||||
/opt/openclaw/
|
||||
├── docker-compose.yml
|
||||
├── openclaw.json
|
||||
├── .env.example
|
||||
├── .env (YOU MUST CREATE THIS)
|
||||
├── workspace/
|
||||
└── logs/
|
||||
```
|
||||
|
||||
**In Version Control:**
|
||||
```
|
||||
/mnt/NV2/Development/claude-home/
|
||||
├── productivity/openclaw/
|
||||
│ ├── CONTEXT.md
|
||||
│ ├── troubleshooting.md
|
||||
│ ├── README.md
|
||||
│ └── DEPLOYMENT_STATUS.md (this file)
|
||||
├── server-configs/
|
||||
│ ├── hosts.yml (updated)
|
||||
│ ├── proxmox/lxc/224.conf (backed up)
|
||||
│ └── openclaw/docker-compose/openclaw/.env.example
|
||||
└── CLAUDE.md (updated with OpenClaw keywords)
|
||||
```
|
||||
|
||||
## Quick Reference Commands
|
||||
|
||||
```bash
|
||||
# Start OpenClaw
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose up -d'
|
||||
|
||||
# View logs
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose logs -f'
|
||||
|
||||
# Stop OpenClaw
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose down'
|
||||
|
||||
# Restart after config changes
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose restart'
|
||||
|
||||
# Approve pairing
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose exec openclaw-gateway openclaw pairing approve discord <code>'
|
||||
|
||||
# Health check
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose exec openclaw-gateway openclaw doctor'
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues during deployment:
|
||||
|
||||
1. **Container won't start:** Check `docker compose logs` for errors
|
||||
2. **Discord not connecting:** Verify bot token and intents enabled
|
||||
3. **MiniMax API errors:** Verify API key format and validity
|
||||
4. **Environment variables not working:** Ensure .env file exists in /opt/openclaw/
|
||||
|
||||
See [troubleshooting.md](./troubleshooting.md) for comprehensive solutions.
|
||||
|
||||
## Next Phase (After User Completes Setup)
|
||||
|
||||
Once you've completed the user actions and verified everything works:
|
||||
|
||||
1. Test various OpenClaw features (file operations, browser automation, etc.)
|
||||
2. Configure guild/server channels if desired (optional)
|
||||
3. Set up monitoring/alerts (optional)
|
||||
4. Consider n8n integration for workflow automation (future)
|
||||
5. Store learnings to MemoryGraph
|
||||
|
||||
---
|
||||
|
||||
**Deployment Date:** 2026-02-02
|
||||
**Deployed By:** Claude Code
|
||||
**LXC VMID:** 224
|
||||
**Status:** Infrastructure complete, awaiting user secrets configuration
|
||||
68
productivity/openclaw/README.md
Normal file
68
productivity/openclaw/README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# OpenClaw Personal AI Assistant
|
||||
|
||||
Personal AI assistant running on LXC 224 with MiniMax M2.1 integration and Discord connectivity.
|
||||
|
||||
## Quick Access
|
||||
|
||||
- **Gateway Web UI:** http://10.10.0.224:18789
|
||||
- **LXC IP:** 10.10.0.224
|
||||
- **SSH:** `ssh root@10.10.0.224`
|
||||
- **Config Location:** `/opt/openclaw/`
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Start/stop/restart
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose up -d'
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose down'
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose restart'
|
||||
|
||||
# View logs
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose logs -f'
|
||||
|
||||
# Approve Discord pairing
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose exec openclaw-gateway openclaw pairing approve discord <code>'
|
||||
|
||||
# Health check
|
||||
ssh root@10.10.0.224 'cd /opt/openclaw && docker compose exec openclaw-gateway openclaw doctor'
|
||||
```
|
||||
|
||||
## Current Configuration
|
||||
|
||||
- **AI Provider:** MiniMax M2.1
|
||||
- **Chat Platform:** Discord (DM pairing enabled)
|
||||
- **Resources:** 2 cores, 4GB RAM, 32GB disk
|
||||
- **Security:** Pairing code required for new DM conversations
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Technology Overview:** [CONTEXT.md](./CONTEXT.md)
|
||||
- **Troubleshooting:** [troubleshooting.md](./troubleshooting.md)
|
||||
- **Official Docs:** https://docs.openclaw.ai/
|
||||
|
||||
## Setup Checklist
|
||||
|
||||
- [ ] LXC 224 created with proper resources
|
||||
- [ ] Docker Compose stack deployed
|
||||
- [ ] MiniMax API key configured
|
||||
- [ ] Discord bot created and invited
|
||||
- [ ] Discord intents enabled (Message Content + Server Members)
|
||||
- [ ] DM pairing completed
|
||||
- [ ] Infrastructure inventory updated
|
||||
- [ ] Documentation created
|
||||
|
||||
## Resource Monitoring
|
||||
|
||||
```bash
|
||||
# Check LXC resource usage (from Proxmox host)
|
||||
ssh root@10.10.0.11 "pct exec 224 -- docker stats --no-stream"
|
||||
|
||||
# Check disk usage
|
||||
ssh root@10.10.0.224 "df -h /opt/openclaw && du -sh /opt/openclaw/workspace"
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
- **Updates:** Auto-updates on restart (`:latest` tag)
|
||||
- **Backups:** Configuration in version control, workspace in `/opt/openclaw/workspace`
|
||||
- **Monitoring:** Health check at http://10.10.0.224:18789/health
|
||||
416
productivity/openclaw/troubleshooting.md
Normal file
416
productivity/openclaw/troubleshooting.md
Normal file
@ -0,0 +1,416 @@
|
||||
# OpenClaw Troubleshooting Guide
|
||||
|
||||
## Gateway Startup Issues
|
||||
|
||||
### Container Won't Start
|
||||
**Symptoms:** `docker compose up` fails immediately
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
docker compose logs openclaw-gateway
|
||||
```
|
||||
|
||||
**Common Causes:**
|
||||
1. **Missing environment variables:**
|
||||
- Check `.env` file exists and contains required keys
|
||||
- Verify `MINIMAX_API_KEY` and `DISCORD_BOT_TOKEN` are set
|
||||
- Solution: Copy `.env.example` to `.env` and populate
|
||||
|
||||
2. **Port conflict (18789 already in use):**
|
||||
- Check: `netstat -tulpn | grep 18789`
|
||||
- Solution: Stop conflicting service or change port in docker-compose.yml
|
||||
|
||||
3. **Invalid openclaw.json syntax:**
|
||||
- JSON5 allows comments and trailing commas
|
||||
- Validate: `docker compose config` (checks interpolation)
|
||||
- Solution: Fix syntax errors, remove invalid characters
|
||||
|
||||
### Container Starts but Exits Immediately
|
||||
**Symptoms:** Container runs briefly then stops
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
docker compose logs --tail=50 openclaw-gateway
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
**Common Causes:**
|
||||
1. **Invalid MiniMax API key:**
|
||||
- Error: "Authentication failed" or "Invalid API key"
|
||||
- Solution: Verify key at https://platform.minimax.io/
|
||||
- Check key format: Should start with `sk-`
|
||||
|
||||
2. **Docker socket permission denied:**
|
||||
- Error: "Cannot connect to Docker daemon"
|
||||
- Check: `ls -l /var/run/docker.sock`
|
||||
- Solution: Already mounted in compose file; check LXC nesting enabled
|
||||
|
||||
3. **Configuration file not found:**
|
||||
- Error: "Cannot read openclaw.json"
|
||||
- Check: `ls -la /opt/openclaw/openclaw.json`
|
||||
- Solution: Create file from plan template
|
||||
|
||||
## Discord Integration Issues
|
||||
|
||||
### Bot Not Connecting to Discord
|
||||
**Symptoms:** Bot shows offline in Discord server
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
docker compose logs openclaw-gateway | grep -i discord
|
||||
docker compose exec openclaw-gateway openclaw channels status --probe
|
||||
```
|
||||
|
||||
**Common Causes:**
|
||||
1. **Invalid bot token:**
|
||||
- Error: "Incorrect login credentials"
|
||||
- Solution: Regenerate token in Discord Developer Portal
|
||||
- Update `.env` file and restart: `docker compose restart`
|
||||
|
||||
2. **Missing Message Content Intent:**
|
||||
- Error: "Used disallowed intents"
|
||||
- Solution:
|
||||
1. Go to Discord Developer Portal
|
||||
2. Bot → Privileged Gateway Intents
|
||||
3. Enable "Message Content Intent"
|
||||
4. Restart OpenClaw gateway
|
||||
|
||||
3. **Bot not invited to server:**
|
||||
- No error, just offline status
|
||||
- Solution: Generate invite URL from OAuth2 → URL Generator
|
||||
- Use scopes: `bot` + `applications.commands`
|
||||
- Invite with required permissions
|
||||
|
||||
### Bot Online but Not Responding to DMs
|
||||
**Symptoms:** Bot shows online but doesn't reply to messages
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check channel configuration
|
||||
docker compose exec openclaw-gateway openclaw channels status discord
|
||||
|
||||
# Check recent logs
|
||||
docker compose logs --tail=100 openclaw-gateway | grep -E "(DM|discord)"
|
||||
```
|
||||
|
||||
**Common Causes:**
|
||||
1. **Pairing required but not approved:**
|
||||
- Bot sends pairing code on first message
|
||||
- Code expires after 1 hour
|
||||
- Solution: Approve pairing:
|
||||
```bash
|
||||
docker compose exec openclaw-gateway openclaw pairing list discord
|
||||
docker compose exec openclaw-gateway openclaw pairing approve discord <code>
|
||||
```
|
||||
|
||||
2. **Policy set to disabled:**
|
||||
- Check `openclaw.json` → `channels.discord.dm.policy`
|
||||
- Should be `"pairing"` or `"open"`
|
||||
- Solution: Change policy and restart
|
||||
|
||||
3. **Message Content Intent disabled (again):**
|
||||
- Bot can't read message text
|
||||
- Solution: See "Missing Message Content Intent" above
|
||||
|
||||
### Pairing Code Not Working
|
||||
**Symptoms:** User receives pairing code but approval command fails
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# List all pending pairing requests
|
||||
docker compose exec openclaw-gateway openclaw pairing list discord
|
||||
|
||||
# Check logs for pairing errors
|
||||
docker compose logs openclaw-gateway | grep -i pairing
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. **Code expired (>1 hour old):**
|
||||
- Regenerate: Send new DM to bot
|
||||
- Approve new code within 1 hour
|
||||
|
||||
2. **Wrong pairing code format:**
|
||||
- Code format: Usually 6-digit alphanumeric
|
||||
- Case-sensitive: Use exact code from Discord message
|
||||
|
||||
3. **Multiple pending codes:**
|
||||
- List all codes: `openclaw pairing list discord`
|
||||
- Approve latest code (most recent timestamp)
|
||||
|
||||
## MiniMax API Issues
|
||||
|
||||
### API Authentication Errors
|
||||
**Symptoms:** "Authentication failed" or "Invalid API key" in logs
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check API key is set
|
||||
docker compose exec openclaw-gateway env | grep MINIMAX
|
||||
|
||||
# Test API key directly
|
||||
curl -X POST https://api.minimax.io/anthropic/v1/messages \
|
||||
-H "Authorization: Bearer $MINIMAX_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"MiniMax-M2.1","max_tokens":10,"messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. **Key not set or incorrect:**
|
||||
- Verify `.env` file contains: `MINIMAX_API_KEY=sk-...`
|
||||
- Restart after changes: `docker compose restart`
|
||||
|
||||
2. **Key revoked or expired:**
|
||||
- Log in to https://platform.minimax.io/
|
||||
- Check API key status
|
||||
- Generate new key if needed
|
||||
|
||||
3. **Interpolation not working:**
|
||||
- Check `openclaw.json` uses: `"apiKey": "${MINIMAX_API_KEY}"`
|
||||
- Verify `.env` file is in same directory as docker-compose.yml
|
||||
|
||||
### Rate Limiting or Quota Errors
|
||||
**Symptoms:** "Rate limit exceeded" or "Insufficient quota"
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check recent API errors
|
||||
docker compose logs openclaw-gateway | grep -i "rate\|quota"
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. **Rate limit (temporary):**
|
||||
- Wait 60 seconds and retry
|
||||
- Reduce message frequency
|
||||
- Consider upgrading MiniMax plan
|
||||
|
||||
2. **Quota exceeded (billing):**
|
||||
- Check account balance at https://platform.minimax.io/
|
||||
- Add credits or upgrade plan
|
||||
- Set usage alerts to prevent future issues
|
||||
|
||||
3. **Context window exceeded:**
|
||||
- Error: "Context length too long"
|
||||
- Reduce `historyLimit` in openclaw.json (default 20)
|
||||
- Current limit: 200,000 tokens (very high, unlikely to hit)
|
||||
|
||||
## Performance Issues
|
||||
|
||||
### High Memory Usage
|
||||
**Symptoms:** Container using >2GB RAM, system sluggish
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check container stats
|
||||
docker stats openclaw
|
||||
|
||||
# Check for stuck browser processes
|
||||
docker compose exec openclaw-gateway ps aux | grep -E "(chrome|firefox|playwright)"
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. **Browser automation processes not cleaned up:**
|
||||
- Restart gateway: `docker compose restart`
|
||||
- Consider disabling browser skills if not needed
|
||||
|
||||
2. **Large workspace files:**
|
||||
- Check workspace size: `du -sh /opt/openclaw/workspace`
|
||||
- Clean old files: Review and remove unnecessary workspace data
|
||||
|
||||
3. **Memory leak (rare):**
|
||||
- Update to latest version: `docker compose pull && docker compose up -d`
|
||||
- Report issue to OpenClaw GitHub
|
||||
|
||||
### Slow Response Times
|
||||
**Symptoms:** Bot takes >30 seconds to respond
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check API latency
|
||||
docker compose logs openclaw-gateway | grep -i "duration\|latency"
|
||||
|
||||
# Check network connectivity
|
||||
docker compose exec openclaw-gateway ping -c 3 api.minimax.io
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. **MiniMax API slow:**
|
||||
- Switch to lightning model: `minimax/MiniMax-M2.1-lightning`
|
||||
- Edit `openclaw.json` → `agents.defaults.model.primary`
|
||||
- Restart gateway
|
||||
|
||||
2. **Large context window:**
|
||||
- Reduce `historyLimit` in openclaw.json (e.g., 10 instead of 20)
|
||||
- Shorter history = faster responses
|
||||
|
||||
3. **Network latency:**
|
||||
- Check internet connection on LXC: `ping -c 5 8.8.8.8`
|
||||
- Verify DNS resolution: `nslookup api.minimax.io`
|
||||
|
||||
### High CPU Usage
|
||||
**Symptoms:** Constant high CPU (>50%) even when idle
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check process CPU usage
|
||||
docker compose exec openclaw-gateway top -b -n 1
|
||||
|
||||
# Check for infinite loops in logs
|
||||
docker compose logs --tail=200 openclaw-gateway
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. **Stuck skill execution:**
|
||||
- Identify stuck process in logs
|
||||
- Restart gateway: `docker compose restart`
|
||||
|
||||
2. **Discord WebSocket reconnect loop:**
|
||||
- Error: "WebSocket closed, reconnecting..."
|
||||
- Check Discord API status: https://discordstatus.com/
|
||||
- Verify bot token is valid
|
||||
|
||||
3. **Log spam:**
|
||||
- Reduce log level: Add `LOG_LEVEL=warn` to .env
|
||||
- Restart gateway
|
||||
|
||||
## Configuration Issues
|
||||
|
||||
### Changes Not Taking Effect
|
||||
**Symptoms:** Modified openclaw.json but behavior unchanged
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Restart gateway to reload configuration
|
||||
docker compose restart openclaw-gateway
|
||||
|
||||
# Verify configuration loaded
|
||||
docker compose logs openclaw-gateway | grep "Configuration loaded"
|
||||
|
||||
# Check for configuration errors
|
||||
docker compose logs openclaw-gateway | grep -i "config\|error"
|
||||
```
|
||||
|
||||
### Environment Variables Not Interpolating
|
||||
**Symptoms:** Literal `${VAR_NAME}` in logs instead of values
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check environment variables are set in container
|
||||
docker compose exec openclaw-gateway env | grep -E "(MINIMAX|DISCORD)"
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
1. **Docker Compose not loading .env:**
|
||||
- Verify `.env` file is in same directory as docker-compose.yml
|
||||
- Check file permissions: `ls -la .env` (should be readable)
|
||||
|
||||
2. **Variable name mismatch:**
|
||||
- Ensure `.env` uses exact name from openclaw.json
|
||||
- Case-sensitive: `MINIMAX_API_KEY` not `minimax_api_key`
|
||||
|
||||
3. **Quoting issues:**
|
||||
- Don't quote values in .env: `KEY=value` not `KEY="value"`
|
||||
- OpenClaw handles interpolation, Docker doesn't need quotes
|
||||
|
||||
## Emergency Recovery
|
||||
|
||||
### Complete Service Reset
|
||||
If all else fails, nuclear option:
|
||||
|
||||
```bash
|
||||
# Stop and remove all containers
|
||||
docker compose down -v
|
||||
|
||||
# Clear workspace (CAUTION: deletes all agent memory/files)
|
||||
rm -rf workspace/*
|
||||
rm -rf logs/*
|
||||
|
||||
# Reset configuration to defaults
|
||||
cp openclaw.json openclaw.json.backup
|
||||
# Re-create openclaw.json from plan template
|
||||
|
||||
# Recreate from scratch
|
||||
docker compose up -d
|
||||
|
||||
# Watch logs for errors
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
### LXC Container Issues
|
||||
If Docker itself is broken:
|
||||
|
||||
```bash
|
||||
# From Proxmox host
|
||||
ssh root@10.10.0.11
|
||||
|
||||
# Restart LXC
|
||||
pct restart 224
|
||||
|
||||
# If restart fails, stop and start
|
||||
pct stop 224
|
||||
pct start 224
|
||||
|
||||
# Check LXC status
|
||||
pct status 224
|
||||
|
||||
# Access LXC console directly
|
||||
pct enter 224
|
||||
```
|
||||
|
||||
### Rollback to Previous Version
|
||||
If update caused issues:
|
||||
|
||||
```bash
|
||||
# Pin to specific working version
|
||||
# Edit docker-compose.yml, change:
|
||||
# image: openclaw/gateway:latest
|
||||
# To:
|
||||
# image: openclaw/gateway:2026.1.24-1
|
||||
|
||||
# Recreate container
|
||||
docker compose down
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Diagnostic Commands Reference
|
||||
|
||||
```bash
|
||||
# Full system health check
|
||||
docker compose exec openclaw-gateway openclaw doctor
|
||||
|
||||
# Channel status (Discord, etc.)
|
||||
docker compose exec openclaw-gateway openclaw channels status --probe
|
||||
|
||||
# Model configuration
|
||||
docker compose exec openclaw-gateway openclaw models list
|
||||
docker compose exec openclaw-gateway openclaw models get minimax/MiniMax-M2.1
|
||||
|
||||
# Pairing management
|
||||
docker compose exec openclaw-gateway openclaw pairing list discord
|
||||
docker compose exec openclaw-gateway openclaw pairing approve discord <code>
|
||||
docker compose exec openclaw-gateway openclaw pairing revoke discord <user_id>
|
||||
|
||||
# Container health
|
||||
docker compose ps
|
||||
docker compose logs --tail=50 openclaw-gateway
|
||||
docker stats openclaw
|
||||
|
||||
# Network connectivity
|
||||
docker compose exec openclaw-gateway ping -c 3 api.minimax.io
|
||||
docker compose exec openclaw-gateway curl -I https://discord.com/api/v10/gateway
|
||||
|
||||
# File system check
|
||||
docker compose exec openclaw-gateway df -h
|
||||
docker compose exec openclaw-gateway du -sh /workspace/*
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
**Official Resources:**
|
||||
- Documentation: https://docs.openclaw.ai/
|
||||
- GitHub Issues: https://github.com/openclaw/openclaw/issues
|
||||
- Community Discord: [Check OpenClaw website for invite]
|
||||
|
||||
**Homelab-Specific:**
|
||||
- Check MemoryGraph: `python ~/.claude/skills/memorygraph/client.py recall "openclaw"`
|
||||
- Review CONTEXT.md: `/mnt/NV2/Development/claude-home/productivity/openclaw/CONTEXT.md`
|
||||
- Infrastructure inventory: `/mnt/NV2/Development/claude-home/server-configs/hosts.yml`
|
||||
27
server-configs/.gitignore
vendored
Normal file
27
server-configs/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Secrets - NEVER commit these
|
||||
.env
|
||||
*.env
|
||||
!.env.example
|
||||
|
||||
# Application data that may accidentally get pulled
|
||||
data/
|
||||
logs/
|
||||
*.log
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
cache/
|
||||
config/metadata/
|
||||
config/data/
|
||||
__pycache__/
|
||||
node_modules/
|
||||
*.pyc
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
*.zip
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
231
server-configs/README.md
Normal file
231
server-configs/README.md
Normal file
@ -0,0 +1,231 @@
|
||||
# Home Lab Server Configurations
|
||||
|
||||
Version-controlled configuration files for the home lab infrastructure. This system provides a centralized way to track, sync, and deploy Docker Compose files and VM/LXC configurations across multiple hosts.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Check status of all hosts
|
||||
./sync-configs.sh status
|
||||
|
||||
# Pull latest configs from all hosts
|
||||
./sync-configs.sh pull
|
||||
|
||||
# Pull from a specific host
|
||||
./sync-configs.sh pull ubuntu-manticore
|
||||
|
||||
# Show differences between local and remote
|
||||
./sync-configs.sh diff
|
||||
|
||||
# Push configs (no restart)
|
||||
./sync-configs.sh push
|
||||
|
||||
# Deploy and restart a specific service
|
||||
./sync-configs.sh deploy sba-bots paper-dynasty
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
server-configs/
|
||||
├── hosts.yml # Host inventory with connection details
|
||||
├── sync-configs.sh # Main sync script
|
||||
├── .gitignore # Prevents secrets from being committed
|
||||
├── README.md # This file
|
||||
│
|
||||
├── proxmox/ # Proxmox hypervisor configs
|
||||
│ ├── lxc/ # LXC container configurations
|
||||
│ └── qemu/ # VM configurations
|
||||
│
|
||||
├── ubuntu-manticore/ # Physical Ubuntu server
|
||||
│ └── docker-compose/
|
||||
│ ├── jellyfin/
|
||||
│ ├── tdarr/
|
||||
│ └── watchstate/
|
||||
│
|
||||
├── sba-bots/ # SBA Discord bots VM
|
||||
│ └── docker-compose/
|
||||
│ ├── paper-dynasty/
|
||||
│ ├── major-domo/
|
||||
│ └── ...
|
||||
│
|
||||
├── strat-database/ # Database services VM
|
||||
│ └── docker-compose/
|
||||
│ ├── pd-database/
|
||||
│ ├── sba-database/
|
||||
│ └── ...
|
||||
│
|
||||
├── arr-stack/ # Media automation LXC
|
||||
│ └── docker-compose/
|
||||
│ └── arr-stack/
|
||||
│
|
||||
├── n8n/ # Workflow automation LXC
|
||||
│ └── docker-compose/
|
||||
│ └── n8n/
|
||||
│
|
||||
├── akamai/ # Cloud server (Linode)
|
||||
│ └── docker-compose/
|
||||
│ ├── nginx-proxy-manager/
|
||||
│ ├── major-domo/
|
||||
│ └── ...
|
||||
│
|
||||
└── nobara-desktop/ # Local dev machine
|
||||
└── docker-compose/
|
||||
```
|
||||
|
||||
## Host Inventory
|
||||
|
||||
| Host | Type | IP | Description |
|
||||
|------|------|-----|-------------|
|
||||
| proxmox | Proxmox VE | 10.10.0.11 | Main hypervisor (VMs/LXCs) |
|
||||
| ubuntu-manticore | Docker | 10.10.0.226 | Physical server - media services |
|
||||
| discord-bots | Docker | 10.10.0.33 | Discord bots and game services |
|
||||
| sba-bots | Docker | 10.10.0.88 | SBA/Paper Dynasty production |
|
||||
| strat-database | Docker | 10.10.0.42 | Database services |
|
||||
| arr-stack | Docker | 10.10.0.221 | Sonarr/Radarr/etc. |
|
||||
| n8n | Docker | 10.10.0.210 | Workflow automation |
|
||||
| gitea | LXC | 10.10.0.225 | Self-hosted Git server + CI/CD |
|
||||
| akamai | Docker | 172.237.147.99 | Public-facing services |
|
||||
| nobara-desktop | Local | - | Development workstation |
|
||||
|
||||
## Commands Reference
|
||||
|
||||
### `./sync-configs.sh pull [host]`
|
||||
Pulls Docker Compose files from remote hosts to the local repository. Only syncs `docker-compose*.yml`, `compose.yml`, and `.env.example` files - not application data.
|
||||
|
||||
### `./sync-configs.sh push [host]`
|
||||
Pushes local configs to remote hosts. Does NOT restart services. Use this to stage changes before deployment.
|
||||
|
||||
### `./sync-configs.sh diff [host]`
|
||||
Shows differences between local repository and remote host configurations.
|
||||
|
||||
### `./sync-configs.sh deploy <host> <service>`
|
||||
Pushes config for a specific service and restarts it. Example:
|
||||
```bash
|
||||
./sync-configs.sh deploy sba-bots paper-dynasty
|
||||
```
|
||||
|
||||
### `./sync-configs.sh status`
|
||||
Shows connectivity status and config count for all hosts.
|
||||
|
||||
### `./sync-configs.sh list`
|
||||
Lists all configured hosts and their services.
|
||||
|
||||
## Secrets Management
|
||||
|
||||
**Secrets are NOT stored in this repository.**
|
||||
|
||||
All sensitive values (tokens, passwords, API keys) are referenced via environment variables (`${VAR_NAME}`) and stored in `.env` files on each host. The `.gitignore` prevents `.env` files from being committed.
|
||||
|
||||
For each service that requires secrets, a `.env.example` file is provided showing the required variables:
|
||||
|
||||
```bash
|
||||
# Example .env.example
|
||||
BOT_TOKEN=your_discord_bot_token_here
|
||||
API_TOKEN=your_api_token_here
|
||||
DB_PASSWORD=your_database_password_here
|
||||
```
|
||||
|
||||
When deploying a new service:
|
||||
1. Copy `.env.example` to `.env` on the target host
|
||||
2. Fill in the actual secret values
|
||||
3. Run `docker compose up -d`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Making Configuration Changes
|
||||
|
||||
1. **Pull latest** to ensure you have current configs:
|
||||
```bash
|
||||
./sync-configs.sh pull
|
||||
```
|
||||
|
||||
2. **Edit locally** in your preferred editor
|
||||
|
||||
3. **Review changes** before pushing:
|
||||
```bash
|
||||
./sync-configs.sh diff <host>
|
||||
```
|
||||
|
||||
4. **Push changes** to the host:
|
||||
```bash
|
||||
./sync-configs.sh push <host>
|
||||
```
|
||||
|
||||
5. **Deploy** when ready to restart:
|
||||
```bash
|
||||
./sync-configs.sh deploy <host> <service>
|
||||
```
|
||||
|
||||
6. **Commit** to git:
|
||||
```bash
|
||||
git add -A && git commit -m "Update <service> config"
|
||||
```
|
||||
|
||||
### Adding a New Host
|
||||
|
||||
1. Add host entry to `hosts.yml`:
|
||||
```yaml
|
||||
new-host:
|
||||
type: docker
|
||||
ssh_alias: new-host
|
||||
ip: 10.10.0.XXX
|
||||
user: cal
|
||||
description: "Description here"
|
||||
config_paths:
|
||||
docker-compose: /path/to/compose/files
|
||||
services:
|
||||
- service1
|
||||
- service2
|
||||
```
|
||||
|
||||
2. Add SSH alias to `~/.ssh/config` if needed
|
||||
|
||||
3. Create directory and pull:
|
||||
```bash
|
||||
mkdir -p server-configs/new-host/docker-compose
|
||||
./sync-configs.sh pull new-host
|
||||
```
|
||||
|
||||
### Adding a New Service
|
||||
|
||||
1. Create the service on the host with `docker-compose.yml`
|
||||
2. Pull the config:
|
||||
```bash
|
||||
./sync-configs.sh pull <host>
|
||||
```
|
||||
3. Sanitize any hardcoded secrets (replace with `${VAR}` references)
|
||||
4. Create `.env.example` if secrets are required
|
||||
5. Commit to git
|
||||
|
||||
## Proxmox Configs
|
||||
|
||||
Proxmox VM and LXC configurations are pulled for reference and backup purposes. The sync script does NOT push Proxmox configs back automatically due to the sensitive nature of hypervisor configuration.
|
||||
|
||||
To restore a VM/LXC config:
|
||||
1. Review the config file in `proxmox/qemu/` or `proxmox/lxc/`
|
||||
2. Manually copy to Proxmox if needed
|
||||
3. Use Proxmox web UI or CLI for actual restoration
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Host shows as offline
|
||||
- Check if the VM/LXC is running on Proxmox
|
||||
- Verify SSH connectivity: `ssh <host-alias>`
|
||||
- Check `hosts.yml` for correct IP/alias
|
||||
|
||||
### Changes not taking effect
|
||||
- Ensure you ran `push` after editing
|
||||
- Check if service needs restart: `deploy` command
|
||||
- Verify the remote file was updated: `diff` command
|
||||
|
||||
### Permission denied on push
|
||||
- Check SSH key is loaded: `ssh-add -l`
|
||||
- Verify user has write access to config directory
|
||||
- For root-owned paths (arr-stack, n8n), ensure SSH alias uses correct user
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Tdarr Setup](../tdarr/CONTEXT.md) - Media transcoding configuration
|
||||
- [Networking](../networking/CONTEXT.md) - NPM and network config
|
||||
- [Monitoring](../monitoring/CONTEXT.md) - System monitoring setup
|
||||
@ -0,0 +1,4 @@
|
||||
# Dev Paper Dynasty - Environment Variables
|
||||
BOT_TOKEN=your_discord_bot_token_here
|
||||
API_TOKEN=your_api_token_here
|
||||
DB_PASSWORD=your_database_password_here
|
||||
@ -0,0 +1,52 @@
|
||||
services:
|
||||
discord-app:
|
||||
image: manticorum67/paper-dynasty-discordapp:dev
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./dev-storage:/usr/src/app/storage
|
||||
- ./dev-logs:/usr/src/app/logs
|
||||
environment:
|
||||
- PYTHONBUFFERED=0
|
||||
- GUILD_ID=669356687294988350
|
||||
- BOT_TOKEN=${BOT_TOKEN}
|
||||
- LOG_LEVEL=INFO
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- SCOREBOARD_CHANNEL=1000521215703789609
|
||||
- TZ=America/Chicago
|
||||
- PYTHONHASHSEED=1749583062
|
||||
- DATABASE=Dev
|
||||
- DB_USERNAME=postgres
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- DB_URL=db
|
||||
- DB_NAME=postgres
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- pd_postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready"]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
# start_period: 30s
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 8008:8080
|
||||
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
pd_postgres:
|
||||
@ -0,0 +1,29 @@
|
||||
services:
|
||||
discord-app:
|
||||
# image: manticorum67/major-domo-discordapp:1.5
|
||||
image: manticorum67/major-domo-discordapp:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# - ./storage:/usr/src/app/storage
|
||||
# - ./logs:/usr/src/app/logs
|
||||
- ./logs:/app/logs
|
||||
- ./storage:/app/data
|
||||
environment:
|
||||
- PYTHONBUFFERED=0
|
||||
- BOT_TOKEN=${BOT_TOKEN}
|
||||
- GUILD_ID=${GUILD_ID}
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TESTING=${TESTING}
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
- TZ=${TZ}
|
||||
- DB_URL=${DB_URL}
|
||||
- HELP_EDITOR_ROLE_NAME=${HELP_EDITOR_ROLE_NAME}
|
||||
- ENVIRONMENT=${ENVIRONMENT}
|
||||
- OFFSEASON_FLAG=${OFFSEASON_FLAG}
|
||||
networks:
|
||||
- backend
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
networks:
|
||||
npm_network:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- npm_network
|
||||
ports:
|
||||
# These ports are in format <host-port>:<container-port>
|
||||
- '80:80' # Public HTTP Port
|
||||
- '443:443' # Public HTTPS Port
|
||||
- '81:81' # Admin Web Port
|
||||
# Add any other Stream port you want to expose
|
||||
# - '21:21' # FTP
|
||||
|
||||
environment:
|
||||
TZ: Americas/Chicago
|
||||
# Uncomment this if you want to change the location of
|
||||
# the SQLite DB file within the container
|
||||
# DB_SQLITE_FILE: "/data/database.sqlite"
|
||||
|
||||
# Uncomment this if IPv6 is not enabled on your host
|
||||
# DISABLE_IPV6: 'true'
|
||||
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./letsencrypt:/etc/letsencrypt
|
||||
@ -0,0 +1,80 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:17
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: ${TZ}
|
||||
POSTGRES_USER: ${PD_DB_USER}
|
||||
POSTGRES_PASSWORD: ${PD_DB_PASSWORD}
|
||||
POSTGRES_DB: ${PD_DB_DATABASE}
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
|
||||
apiproxy:
|
||||
image: manticorum67/paper-dynasty-apiproxy:dev
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: ${TZ}
|
||||
PRODUCTION: False
|
||||
JWT_SECRET: ${PD_JWT_SECRET}
|
||||
ports:
|
||||
- 8000:8000
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: always
|
||||
environment:
|
||||
TZ: ${TZ}
|
||||
ports:
|
||||
- 8088:8080
|
||||
|
||||
postgrest:
|
||||
image: postgrest/postgrest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
TZ: ${TZ}
|
||||
PGRST_DB_URI: "postgres://${PD_DB_USER}:${PD_DB_PASSWORD}@db:5432/${PD_DB_DATABASE}"
|
||||
PGRST_OPENAPI_SERVER_PROXY_URI: ${PD_API_URL}
|
||||
PGRST_DB_ANON_ROLE: web_anon
|
||||
PGRST_JWT_SECRET: ${PD_JWT_SECRET}
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
swagger:
|
||||
image: swaggerapi/swagger-ui
|
||||
ports:
|
||||
- "8080:8080"
|
||||
expose:
|
||||
- "8080"
|
||||
environment:
|
||||
TZ: ${TZ}
|
||||
PD_API_URL: ${PD_API_URL}
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
# pgadmin:
|
||||
# image: dpage/pgadmin4
|
||||
# ports:
|
||||
# - "8082:80"
|
||||
# environment:
|
||||
# TZ: ${TZ}
|
||||
# PGADMIN_DEFAULT_EMAIL: cal.corum@gmail.com
|
||||
# PGADMIN_DEFAULT_PASSWORD: ${PD_DB_PASSWORD}
|
||||
# POSTGRES_HOST: postgreshost
|
||||
# POSTGRES_USER: ${PD_DB_USER}
|
||||
# POSTGRES_PASSWORD: ${PD_DB_PASSWORD}
|
||||
# POSTGRES_DB: db
|
||||
# PGADMIN_CONFIG_SERVER_MODE: False
|
||||
# PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: False
|
||||
# volumes:
|
||||
# - pgadmin-data:/var/lib/pgadmin
|
||||
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
pgadmin-data:
|
||||
@ -0,0 +1,120 @@
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
nginx-proxy-manager_npm_network:
|
||||
external: true
|
||||
|
||||
services:
|
||||
api:
|
||||
# build: .
|
||||
image: manticorum67/major-domo-database:latest
|
||||
restart: unless-stopped
|
||||
container_name: sba_db_api
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
ports:
|
||||
- 801:80
|
||||
networks:
|
||||
- default
|
||||
- nginx-proxy-manager_npm_network
|
||||
environment:
|
||||
- TESTING=False
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TZ=${TZ}
|
||||
- WORKERS_PER_CORE=1.5
|
||||
- TIMEOUT=120
|
||||
- GRACEFUL_TIMEOUT=120
|
||||
- DATABASE_TYPE=postgresql
|
||||
- POSTGRES_HOST=sba_postgres
|
||||
- POSTGRES_DB=${SBA_DATABASE}
|
||||
- POSTGRES_USER=${SBA_DB_USER}
|
||||
- POSTGRES_PASSWORD=${SBA_DB_USER_PASSWORD}
|
||||
- REDIS_HOST=sba_redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_DB=0
|
||||
- CACHE_ENABLED=False
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
container_name: sba_postgres
|
||||
environment:
|
||||
- POSTGRES_DB=${SBA_DATABASE}
|
||||
- POSTGRES_USER=${SBA_DB_USER}
|
||||
- POSTGRES_PASSWORD=${SBA_DB_USER_PASSWORD}
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./logs:/var/log/postgresql
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${SBA_DB_USER} -d ${SBA_DATABASE}"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
container_name: sba_redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
command: redis-server --appendonly yes
|
||||
|
||||
adminer:
|
||||
image: adminer:latest
|
||||
restart: unless-stopped
|
||||
container_name: sba_adminer
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- ADMINER_DEFAULT_SERVER=sba_postgres
|
||||
- TZ=${TZ}
|
||||
# - ADMINER_DESIGN=pepa-linha-dark
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
sync-prod:
|
||||
image: alpine:latest
|
||||
container_name: sba_sync_prod
|
||||
volumes:
|
||||
- ./scripts:/scripts
|
||||
- /home/cal/.ssh:/tmp/ssh:ro
|
||||
environment:
|
||||
- SBA_DB_USER=${SBA_DB_USER}
|
||||
- SBA_DATABASE=${SBA_DATABASE}
|
||||
- SBA_DB_USER_PASSWORD=${SBA_DB_USER_PASSWORD}
|
||||
command: >
|
||||
sh -c "
|
||||
cp -r /tmp/ssh /root/.ssh &&
|
||||
chmod 700 /root/.ssh &&
|
||||
chmod 600 /root/.ssh/* &&
|
||||
chown -R root:root /root/.ssh &&
|
||||
/scripts/sync_from_prod.sh
|
||||
"
|
||||
profiles: ["sync"]
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- default
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
@ -0,0 +1,22 @@
|
||||
services:
|
||||
sba-web:
|
||||
image: manticorum67/sba-website:${VERSION:-latest}
|
||||
ports:
|
||||
- "803:80" # Use internal port since nginx proxy manager handles external routing
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./public:/usr/share/nginx/html/public
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
networks:
|
||||
- default
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
@ -0,0 +1,18 @@
|
||||
services:
|
||||
database:
|
||||
image: manticorum67/major-domo-database:latest
|
||||
restart: unless-stopped
|
||||
container_name: sba_database
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
ports:
|
||||
- 801:80
|
||||
environment:
|
||||
- TESTING=False
|
||||
- LOG_LEVEL=INFO
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TZ=America/Chicago
|
||||
- WORKERS_PER_CORE=1.5
|
||||
- TIMEOUT=120
|
||||
- GRACEFUL_TIMEOUT=120
|
||||
@ -0,0 +1,19 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:17
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: paperdynasty
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
@ -0,0 +1,99 @@
|
||||
# /opt/arr-stack/docker-compose.yml
|
||||
# Simplified *arr stack - Usenet only (no VPN needed)
|
||||
# Deployed: 2025-12-05
|
||||
|
||||
services:
|
||||
sonarr:
|
||||
image: linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- ./config/sonarr:/config
|
||||
- /mnt/media:/media
|
||||
ports:
|
||||
- 8989:8989
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
restart: unless-stopped
|
||||
|
||||
radarr:
|
||||
image: linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- ./config/radarr:/config
|
||||
- /mnt/media:/media
|
||||
ports:
|
||||
- 7878:7878
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
restart: unless-stopped
|
||||
|
||||
readarr:
|
||||
image: ghcr.io/hotio/readarr:latest
|
||||
container_name: readarr
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- ./config/readarr:/config
|
||||
- /mnt/media:/media
|
||||
ports:
|
||||
- 8787:8787
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
restart: unless-stopped
|
||||
|
||||
lidarr:
|
||||
image: linuxserver/lidarr:latest
|
||||
container_name: lidarr
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- ./config/lidarr:/config
|
||||
- /mnt/media:/media
|
||||
ports:
|
||||
- 8686:8686
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
restart: unless-stopped
|
||||
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr:latest
|
||||
container_name: jellyseerr
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
- LOG_LEVEL=debug
|
||||
volumes:
|
||||
- ./config/jellyseerr:/app/config
|
||||
ports:
|
||||
- 5055:5055
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
restart: unless-stopped
|
||||
|
||||
sabnzbd:
|
||||
image: linuxserver/sabnzbd:latest
|
||||
container_name: sabnzbd
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- ./config/sabnzbd:/config
|
||||
- /mnt/media/downloads:/downloads
|
||||
- /mnt/media:/media
|
||||
ports:
|
||||
- 8080:8080
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
restart: unless-stopped
|
||||
25
server-configs/gitea/.env.example
Normal file
25
server-configs/gitea/.env.example
Normal file
@ -0,0 +1,25 @@
|
||||
# Gitea Database Configuration
|
||||
# Copy this to .env and fill in actual values
|
||||
|
||||
# PostgreSQL Database
|
||||
DB_TYPE=postgres
|
||||
DB_HOST=127.0.0.1:5432
|
||||
DB_NAME=gitea
|
||||
DB_USER=gitea
|
||||
DB_PASSWORD=your_database_password_here
|
||||
|
||||
# Gitea Admin Account (initial setup)
|
||||
ADMIN_USERNAME=cal
|
||||
ADMIN_EMAIL=cal@manticorum.com
|
||||
ADMIN_PASSWORD=your_admin_password_here
|
||||
|
||||
# Server Configuration
|
||||
GITEA_DOMAIN=git.manticorum.com
|
||||
GITEA_ROOT_URL=https://git.manticorum.com/
|
||||
GITEA_SSH_DOMAIN=git.manticorum.com
|
||||
GITEA_SSH_PORT=22
|
||||
|
||||
# Security (auto-generated during setup, stored in /etc/gitea/app.ini)
|
||||
# SECRET_KEY=<generated>
|
||||
# INTERNAL_TOKEN=<generated>
|
||||
# JWT_SECRET=<generated>
|
||||
298
server-configs/gitea/README.md
Normal file
298
server-configs/gitea/README.md
Normal file
@ -0,0 +1,298 @@
|
||||
# Gitea - Self-Hosted Git Server
|
||||
|
||||
**LXC 225** | **10.10.0.225** | **git.manticorum.com**
|
||||
|
||||
Self-hosted Git server with web UI, Git LFS support, and Gitea Actions for CI/CD pipelines.
|
||||
|
||||
## Quick Info
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Type** | LXC Container (Proxmox) |
|
||||
| **OS** | Ubuntu 20.04 LTS |
|
||||
| **IP** | 10.10.0.225 |
|
||||
| **Public URL** | https://git.manticorum.com |
|
||||
| **Gitea Version** | 1.22.6 |
|
||||
| **Database** | PostgreSQL 12 |
|
||||
| **Reverse Proxy** | Nginx Proxy Manager (10.10.0.16) |
|
||||
|
||||
## Container Specs
|
||||
|
||||
- **VMID**: 225
|
||||
- **CPU**: 2 cores
|
||||
- **RAM**: 2GB
|
||||
- **Disk**: 20GB
|
||||
- **Features**: Nesting enabled (for future Docker runner support)
|
||||
|
||||
## Services
|
||||
|
||||
### Gitea Web
|
||||
- **Port**: 3000 (internal)
|
||||
- **Service**: `gitea.service`
|
||||
- **User**: `git`
|
||||
- **Work Dir**: `/var/lib/gitea`
|
||||
- **Config**: `/etc/gitea/app.ini`
|
||||
- **Data**: `/var/lib/gitea/data`
|
||||
- **Logs**: `/var/lib/gitea/log`
|
||||
|
||||
### PostgreSQL
|
||||
- **Version**: 12
|
||||
- **Port**: 5432 (localhost only)
|
||||
- **Database**: `gitea`
|
||||
- **User**: `gitea`
|
||||
- **Service**: `postgresql`
|
||||
|
||||
## Management
|
||||
|
||||
### Access Container
|
||||
```bash
|
||||
ssh root@10.10.0.225
|
||||
# or via Proxmox
|
||||
pct enter 225
|
||||
```
|
||||
|
||||
### Service Management
|
||||
```bash
|
||||
# Status
|
||||
systemctl status gitea
|
||||
systemctl status postgresql
|
||||
|
||||
# Restart
|
||||
systemctl restart gitea
|
||||
|
||||
# Logs
|
||||
journalctl -u gitea -f
|
||||
```
|
||||
|
||||
### Database Access
|
||||
```bash
|
||||
# As postgres user
|
||||
sudo -u postgres psql -d gitea
|
||||
|
||||
# As gitea user (from container)
|
||||
PGPASSWORD=gitea123 psql -U gitea -d gitea -h 127.0.0.1
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Main Config File
|
||||
`/etc/gitea/app.ini` contains all Gitea settings:
|
||||
- Database connection
|
||||
- Server domain and URLs
|
||||
- SSH settings
|
||||
- LFS configuration
|
||||
- OAuth2/JWT secrets
|
||||
- Actions enabled
|
||||
|
||||
**Permissions**:
|
||||
- Owner: `root:git`
|
||||
- Mode: `640`
|
||||
- Directory: `750` on `/etc/gitea`
|
||||
|
||||
### Admin Account
|
||||
- **Username**: `cal`
|
||||
- **Password**: Set during initial setup (change immediately!)
|
||||
- **Email**: `cal@manticorum.com`
|
||||
|
||||
### Features Enabled
|
||||
- ✅ **Gitea Actions** - Built-in CI/CD (GitHub Actions compatible)
|
||||
- ✅ **Git LFS** - Large file storage support
|
||||
- ✅ **SSH Access** - Git over SSH on port 22
|
||||
- ✅ **Web UI** - Repository browser and management
|
||||
- ✅ **Organizations** - Multi-user repository groups
|
||||
- ✅ **Webhooks** - Integration with external services
|
||||
|
||||
## Backup
|
||||
|
||||
### What to Backup
|
||||
1. **PostgreSQL database**: `gitea` database
|
||||
2. **Repository data**: `/var/lib/gitea/data/gitea-repositories`
|
||||
3. **Configuration**: `/etc/gitea/app.ini`
|
||||
4. **Custom files**: `/var/lib/gitea/custom` (if any)
|
||||
|
||||
### Backup Commands
|
||||
```bash
|
||||
# Database dump
|
||||
sudo -u postgres pg_dump gitea > gitea-backup-$(date +%Y%m%d).sql
|
||||
|
||||
# Full data directory
|
||||
tar -czf gitea-data-$(date +%Y%m%d).tar.gz /var/lib/gitea
|
||||
|
||||
# Config only
|
||||
cp /etc/gitea/app.ini gitea-app-$(date +%Y%m%d).ini
|
||||
```
|
||||
|
||||
### Restore
|
||||
```bash
|
||||
# Restore database
|
||||
sudo -u postgres psql -d gitea < gitea-backup.sql
|
||||
|
||||
# Restore data
|
||||
tar -xzf gitea-data.tar.gz -C /
|
||||
chown -R git:git /var/lib/gitea
|
||||
```
|
||||
|
||||
## Upgrades
|
||||
|
||||
### Upgrade Gitea
|
||||
```bash
|
||||
# Stop service
|
||||
systemctl stop gitea
|
||||
|
||||
# Backup current binary
|
||||
cp /usr/local/bin/gitea /usr/local/bin/gitea.backup
|
||||
|
||||
# Download new version
|
||||
wget -O /usr/local/bin/gitea https://dl.gitea.com/gitea/VERSION/gitea-VERSION-linux-amd64
|
||||
|
||||
# Set permissions
|
||||
chmod +x /usr/local/bin/gitea
|
||||
|
||||
# Start service (will auto-migrate database)
|
||||
systemctl start gitea
|
||||
|
||||
# Check logs
|
||||
journalctl -u gitea -f
|
||||
```
|
||||
|
||||
### Check Version
|
||||
```bash
|
||||
/usr/local/bin/gitea --version
|
||||
```
|
||||
|
||||
## Setting Up CI/CD with Gitea Actions
|
||||
|
||||
Gitea Actions are enabled and ready to use. To set up a runner:
|
||||
|
||||
### Option 1: Docker Runner (Recommended)
|
||||
Since the LXC has nesting enabled, you can run a Docker-based Actions runner:
|
||||
|
||||
```bash
|
||||
# Install Docker in the LXC
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# Run Gitea Actions runner
|
||||
docker run -d \
|
||||
--name gitea-runner \
|
||||
--restart unless-stopped \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-e GITEA_INSTANCE_URL=https://git.manticorum.com \
|
||||
-e GITEA_RUNNER_REGISTRATION_TOKEN=<token-from-gitea-admin> \
|
||||
gitea/act_runner:latest
|
||||
```
|
||||
|
||||
### Option 2: Separate Runner LXC
|
||||
Create a dedicated LXC for running Actions with more isolation.
|
||||
|
||||
### Using Actions
|
||||
Create `.gitea/workflows/main.yml` in your repository:
|
||||
|
||||
```yaml
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run tests
|
||||
run: |
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
## Adding Repositories
|
||||
|
||||
### Via Web UI
|
||||
1. Go to https://git.manticorum.com
|
||||
2. Click "+" → "New Repository"
|
||||
3. Fill in details and create
|
||||
|
||||
### Via Command Line
|
||||
```bash
|
||||
# Add remote
|
||||
git remote add homelab git@git.manticorum.com:cal/repo-name.git
|
||||
|
||||
# Or HTTPS
|
||||
git remote add homelab https://git.manticorum.com/cal/repo-name.git
|
||||
|
||||
# Push
|
||||
git push homelab main
|
||||
```
|
||||
|
||||
### Migrate from GitHub
|
||||
Gitea has built-in migration:
|
||||
1. New Repository → "Migrate from GitHub"
|
||||
2. Enter GitHub URL and token
|
||||
3. Gitea will clone all commits, branches, tags
|
||||
|
||||
## Integration with NPM
|
||||
|
||||
Reverse proxy is configured on NPM (10.10.0.16):
|
||||
- **Domain**: git.manticorum.com
|
||||
- **Forward to**: 10.10.0.225:3000
|
||||
- **SSL**: Let's Encrypt
|
||||
- **Websockets**: Enabled
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gitea won't start
|
||||
```bash
|
||||
# Check logs
|
||||
journalctl -u gitea -n 50
|
||||
|
||||
# Common issues:
|
||||
# - Permission on /etc/gitea/app.ini (should be 640, root:git)
|
||||
# - PostgreSQL not running
|
||||
# - Port 3000 already in use
|
||||
```
|
||||
|
||||
### Can't connect to database
|
||||
```bash
|
||||
# Check PostgreSQL is running
|
||||
systemctl status postgresql
|
||||
|
||||
# Test connection
|
||||
PGPASSWORD=gitea123 psql -U gitea -d gitea -h 127.0.0.1 -c "SELECT 1;"
|
||||
|
||||
# Check pg_hba.conf allows md5 auth
|
||||
cat /etc/postgresql/12/main/pg_hba.conf | grep md5
|
||||
```
|
||||
|
||||
### 502 Bad Gateway on web
|
||||
```bash
|
||||
# Check Gitea is listening
|
||||
ss -tlnp | grep 3000
|
||||
|
||||
# Check NPM can reach container
|
||||
curl http://10.10.0.225:3000
|
||||
|
||||
# Verify firewall rules (should allow from 10.10.0.0/24)
|
||||
```
|
||||
|
||||
### Actions runner not working
|
||||
- Ensure runner is registered in Gitea Admin → Actions → Runners
|
||||
- Check runner logs: `docker logs gitea-runner`
|
||||
- Verify GITEA_INSTANCE_URL is correct
|
||||
- Ensure runner has network access to Gitea
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Database password is stored in `/etc/gitea/app.ini` (secured with 640 permissions)
|
||||
- SSH keys for Git access are stored per-user in Gitea database
|
||||
- JWT secrets are auto-generated and stored in config
|
||||
- LXC is unprivileged for better isolation
|
||||
- PostgreSQL only listens on localhost
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Official Gitea Docs](https://docs.gitea.io/)
|
||||
- [Gitea Actions](https://docs.gitea.io/en-us/usage/actions/overview/)
|
||||
- [Proxmox LXC Config](../proxmox/lxc/225.conf)
|
||||
- [Networking Setup](../../networking/CONTEXT.md)
|
||||
|
||||
## Deployment Date
|
||||
|
||||
**Created**: 2026-02-03
|
||||
**By**: Claude Code (Proxmox Skill)
|
||||
**Initial Version**: Gitea 1.22.6 on Ubuntu 20.04
|
||||
11
server-configs/home-assistant/.env.example
Normal file
11
server-configs/home-assistant/.env.example
Normal file
@ -0,0 +1,11 @@
|
||||
# Home Assistant API Configuration
|
||||
# VM ID: 109 (Proxmox)
|
||||
#
|
||||
# Generate a Long-Lived Access Token:
|
||||
# 1. Go to http://10.10.0.174:8123
|
||||
# 2. Click profile icon (bottom-left)
|
||||
# 3. Scroll to "Long-Lived Access Tokens"
|
||||
# 4. Create token and paste below
|
||||
|
||||
HA_URL=http://10.10.0.174:8123
|
||||
HA_TOKEN=your_long_lived_access_token_here
|
||||
215
server-configs/hosts.yml
Normal file
215
server-configs/hosts.yml
Normal file
@ -0,0 +1,215 @@
|
||||
# Home Lab Host Inventory
|
||||
# Used by sync-configs.sh for configuration management
|
||||
|
||||
# Host Types:
|
||||
# - proxmox: Proxmox VE hypervisor (LXC/VM configs)
|
||||
# - docker: Hosts running Docker containers
|
||||
# - local: Local machine (no SSH needed)
|
||||
|
||||
hosts:
|
||||
# Proxmox Hypervisor
|
||||
proxmox:
|
||||
type: proxmox
|
||||
ssh_alias: proxmox
|
||||
ip: 10.10.0.11
|
||||
user: root
|
||||
description: "Main Proxmox VE hypervisor"
|
||||
config_paths:
|
||||
lxc: /etc/pve/nodes/proxmox/lxc
|
||||
qemu: /etc/pve/nodes/proxmox/qemu-server
|
||||
|
||||
# Ubuntu Server (Physical)
|
||||
ubuntu-manticore:
|
||||
type: docker
|
||||
ssh_alias: ubuntu-manticore
|
||||
ip: 10.10.0.226
|
||||
user: cal
|
||||
description: "Physical Ubuntu server - media services"
|
||||
config_paths:
|
||||
docker-compose: /home/cal/docker
|
||||
services:
|
||||
- jellyfin
|
||||
- tdarr
|
||||
- watchstate
|
||||
|
||||
# Discord Bots VM (Proxmox)
|
||||
discord-bots:
|
||||
type: docker
|
||||
ssh_alias: discord-bots
|
||||
ip: 10.10.0.33
|
||||
user: cal
|
||||
description: "Discord bots and game services"
|
||||
config_paths:
|
||||
docker-compose: /home/cal/container-data
|
||||
services:
|
||||
- mln-ghost-ball
|
||||
- postgres
|
||||
- home-run-derby
|
||||
- grizzlies-pa
|
||||
- foundry
|
||||
- major-domo
|
||||
- fallout-dice
|
||||
- forever-werewolf
|
||||
- sbadev-database
|
||||
- mln-central
|
||||
- sand-trap-scramble
|
||||
|
||||
# SBA Bots VM (Proxmox)
|
||||
sba-bots:
|
||||
type: docker
|
||||
ssh_alias: sba-bots
|
||||
ip: 10.10.0.88
|
||||
user: cal
|
||||
description: "SBA/Paper Dynasty production bots"
|
||||
config_paths:
|
||||
docker-compose: /home/cal/container-data
|
||||
services:
|
||||
- paper-dynasty
|
||||
- major-domo
|
||||
- sba-website
|
||||
- sba-ghost
|
||||
|
||||
# Database VM (Proxmox)
|
||||
strat-database:
|
||||
type: docker
|
||||
ssh_alias: strat-database
|
||||
ip: 10.10.0.42
|
||||
user: cal
|
||||
description: "Database services"
|
||||
config_paths:
|
||||
docker-compose: /home/cal/container-data
|
||||
services:
|
||||
- sba-cards
|
||||
- pd-database
|
||||
- postgres-database
|
||||
- sba-database
|
||||
- dev-pd-database
|
||||
- dev-sba-database
|
||||
|
||||
# Arr Stack LXC (Proxmox)
|
||||
arr-stack:
|
||||
type: docker
|
||||
ssh_alias: arr-stack
|
||||
ip: 10.10.0.221
|
||||
user: root
|
||||
description: "Media automation stack (Sonarr, Radarr, etc.)"
|
||||
config_paths:
|
||||
docker-compose: /opt/arr-stack
|
||||
services:
|
||||
- arr-stack
|
||||
|
||||
# n8n LXC (Proxmox)
|
||||
n8n:
|
||||
type: docker
|
||||
ssh_alias: n8n
|
||||
ip: 10.10.0.210
|
||||
user: root
|
||||
description: "n8n workflow automation"
|
||||
config_paths:
|
||||
docker-compose: /opt/n8n
|
||||
services:
|
||||
- n8n
|
||||
|
||||
# Foundry VTT LXC (Proxmox)
|
||||
foundry-lxc:
|
||||
type: docker
|
||||
ssh_alias: foundry-lxc
|
||||
ip: 10.10.0.223
|
||||
user: root
|
||||
description: "Foundry VTT tabletop gaming server"
|
||||
config_paths:
|
||||
docker-compose: /opt/foundry
|
||||
services:
|
||||
- foundry
|
||||
|
||||
# OpenClaw LXC (Proxmox)
|
||||
openclaw:
|
||||
type: docker
|
||||
ssh_alias: openclaw
|
||||
ip: 10.10.0.224
|
||||
user: root
|
||||
description: "OpenClaw personal AI assistant"
|
||||
config_paths:
|
||||
docker-compose: /opt/openclaw
|
||||
services:
|
||||
- openclaw
|
||||
|
||||
# Gitea LXC (Proxmox)
|
||||
gitea:
|
||||
type: lxc
|
||||
ssh_alias: gitea
|
||||
ip: 10.10.0.225
|
||||
user: root
|
||||
vmid: 225
|
||||
description: "Gitea self-hosted Git server with CI/CD"
|
||||
url: https://git.manticorum.com
|
||||
config_paths:
|
||||
gitea: /etc/gitea
|
||||
data: /var/lib/gitea
|
||||
services:
|
||||
- gitea
|
||||
database:
|
||||
type: postgresql
|
||||
version: "12"
|
||||
name: gitea
|
||||
user: gitea
|
||||
|
||||
# Home Assistant VM (Proxmox)
|
||||
home-assistant:
|
||||
type: homeassistant
|
||||
ip: 10.0.0.28
|
||||
vmid: 109
|
||||
user: root
|
||||
description: "Home Assistant OS - smart home automation"
|
||||
api:
|
||||
port: 8123
|
||||
env_file: server-configs/home-assistant/.env
|
||||
integrations:
|
||||
- matter
|
||||
- mobile_app
|
||||
- met_weather
|
||||
services:
|
||||
- home-assistant-core
|
||||
- matter-server
|
||||
|
||||
# Local Development Machine
|
||||
nobara-desktop:
|
||||
type: local
|
||||
ip: null
|
||||
user: cal
|
||||
description: "Local development workstation"
|
||||
config_paths:
|
||||
docker-compose:
|
||||
- /mnt/NV2/Development/major-domo/discord-app-v2
|
||||
- /mnt/NV2/Development/strat-gameplay-webapp
|
||||
services:
|
||||
- major-domo-dev
|
||||
- strat-gameplay-webapp
|
||||
|
||||
# Akamai Cloud Server
|
||||
akamai:
|
||||
type: docker
|
||||
ssh_alias: akamai
|
||||
ip: 172.237.147.99
|
||||
user: root
|
||||
description: "Akamai Linode - public-facing services"
|
||||
config_paths:
|
||||
docker-compose: /root/container-data
|
||||
services:
|
||||
- nginx-proxy-manager
|
||||
- major-domo
|
||||
- dev-paper-dynasty
|
||||
- sba-database
|
||||
- postgres
|
||||
- sba-website
|
||||
- sqlite-major-domo
|
||||
- temp-postgres
|
||||
|
||||
# Decommissioned hosts (kept for reference)
|
||||
# decommissioned:
|
||||
# tdarr-old:
|
||||
# ip: 10.10.0.43
|
||||
# note: "Replaced by ubuntu-manticore tdarr"
|
||||
# docker-home:
|
||||
# ip: 10.10.0.124
|
||||
# note: "Decommissioned"
|
||||
75
server-configs/n8n/docker-compose/n8n/docker-compose.yml
Normal file
75
server-configs/n8n/docker-compose/n8n/docker-compose.yml
Normal file
@ -0,0 +1,75 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: n8n-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks:
|
||||
- n8n-network
|
||||
|
||||
n8n:
|
||||
image: n8nio/n8n:latest
|
||||
container_name: n8n
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "5678:5678"
|
||||
environment:
|
||||
# Database
|
||||
- DB_TYPE=postgresdb
|
||||
- DB_POSTGRESDB_HOST=postgres
|
||||
- DB_POSTGRESDB_PORT=5432
|
||||
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
|
||||
- DB_POSTGRESDB_USER=${POSTGRES_USER}
|
||||
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
|
||||
|
||||
# n8n Configuration
|
||||
- N8N_HOST=${N8N_HOST}
|
||||
- N8N_PORT=5678
|
||||
- N8N_PROTOCOL=${N8N_PROTOCOL}
|
||||
- WEBHOOK_URL=${WEBHOOK_URL}
|
||||
- GENERIC_TIMEZONE=${TIMEZONE}
|
||||
|
||||
# Security
|
||||
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
|
||||
- N8N_BASIC_AUTH_ACTIVE=true
|
||||
- N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
|
||||
- N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
|
||||
|
||||
# Performance
|
||||
- NODE_ENV=production
|
||||
- EXECUTIONS_PROCESS=main
|
||||
- EXECUTIONS_MODE=regular
|
||||
|
||||
# Logging
|
||||
- N8N_LOG_LEVEL=info
|
||||
- N8N_LOG_OUTPUT=console
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
networks:
|
||||
- n8n-network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
name: n8n_postgres_data
|
||||
n8n_data:
|
||||
name: n8n_data
|
||||
|
||||
networks:
|
||||
n8n-network:
|
||||
name: n8n-network
|
||||
driver: bridge
|
||||
16
server-configs/openclaw/docker-compose/openclaw/.env.example
Normal file
16
server-configs/openclaw/docker-compose/openclaw/.env.example
Normal file
@ -0,0 +1,16 @@
|
||||
# OpenClaw Environment Variables
|
||||
# Copy this file to .env and fill in your actual values
|
||||
# NEVER commit the .env file to version control
|
||||
|
||||
# MiniMax AI Provider
|
||||
# Get your API key from: https://platform.minimax.io/
|
||||
MINIMAX_API_KEY=sk-your_minimax_api_key_here
|
||||
|
||||
# Discord Bot
|
||||
# Create bot at: https://discord.com/developers/applications
|
||||
# Enable intents: Message Content, Server Members
|
||||
DISCORD_BOT_TOKEN=your_discord_bot_token_here
|
||||
|
||||
# Optional Configuration
|
||||
# LOG_LEVEL=info
|
||||
# NODE_ENV=production
|
||||
12
server-configs/proxmox/lxc/108.conf
Normal file
12
server-configs/proxmox/lxc/108.conf
Normal file
@ -0,0 +1,12 @@
|
||||
arch: amd64
|
||||
cores: 4
|
||||
features: nesting=1
|
||||
hostname: ansible
|
||||
memory: 8096
|
||||
nameserver: 10.10.0.16 1.1.1.1
|
||||
net0: name=eth0,bridge=vmbr0,firewall=1,gw=10.10.0.1,hwaddr=F6:97:C3:23:0D:B2,ip=10.10.0.5/24,type=veth
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-108-disk-0,size=32G
|
||||
searchdomain: corumservers.com
|
||||
swap: 8096
|
||||
unprivileged: 1
|
||||
13
server-configs/proxmox/lxc/210.conf
Normal file
13
server-configs/proxmox/lxc/210.conf
Normal file
@ -0,0 +1,13 @@
|
||||
arch: amd64
|
||||
cores: 4
|
||||
hostname: docker-n8n-lxc
|
||||
memory: 8192
|
||||
nameserver: 8.8.8.8
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,hwaddr=32:67:BE:A4:7F:F4,ip=10.10.0.210/24,type=veth
|
||||
onboot: 1
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-210-disk-0,size=128G
|
||||
swap: 512
|
||||
lxc.apparmor.profile: unconfined
|
||||
lxc.cgroup2.devices.allow: a
|
||||
lxc.cap.drop:
|
||||
13
server-configs/proxmox/lxc/211.conf
Normal file
13
server-configs/proxmox/lxc/211.conf
Normal file
@ -0,0 +1,13 @@
|
||||
arch: amd64
|
||||
cores: 4
|
||||
features: nesting=1,keyctl=1
|
||||
hostname: docker-7days-lxc
|
||||
memory: 32768
|
||||
nameserver: 8.8.8.8
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,hwaddr=CE:7E:8F:B2:40:C2,ip=10.10.0.250/24,type=veth
|
||||
onboot: 1
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-211-disk-0,size=128G
|
||||
searchdomain: local
|
||||
swap: 2048
|
||||
lxc.apparmor.profile: unconfined
|
||||
10
server-configs/proxmox/lxc/221.conf
Normal file
10
server-configs/proxmox/lxc/221.conf
Normal file
@ -0,0 +1,10 @@
|
||||
arch: amd64
|
||||
cores: 2
|
||||
features: nesting=1,keyctl=1
|
||||
hostname: arr-stack
|
||||
memory: 4096
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,hwaddr=5A:28:7D:A8:13:65,ip=10.10.0.221/24,type=veth
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-221-disk-0,size=32G
|
||||
swap: 512
|
||||
lxc.apparmor.profile: unconfined
|
||||
13
server-configs/proxmox/lxc/222.conf
Normal file
13
server-configs/proxmox/lxc/222.conf
Normal file
@ -0,0 +1,13 @@
|
||||
arch: amd64
|
||||
cores: 1
|
||||
features: nesting=1,keyctl=1
|
||||
hostname: memos
|
||||
memory: 1024
|
||||
nameserver: 10.10.0.16
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,hwaddr=3E:9F:39:6F:B1:92,ip=10.10.0.222/24,type=veth
|
||||
onboot: 1
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-222-disk-0,size=8G
|
||||
searchdomain: local
|
||||
swap: 512
|
||||
lxc.apparmor.profile: unconfined
|
||||
14
server-configs/proxmox/lxc/224.conf
Normal file
14
server-configs/proxmox/lxc/224.conf
Normal file
@ -0,0 +1,14 @@
|
||||
arch: amd64
|
||||
cores: 2
|
||||
hostname: openclaw-lxc
|
||||
memory: 4096
|
||||
nameserver: 8.8.8.8
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,hwaddr=FA:43:92:76:1D:8C,ip=10.10.0.224/24,type=veth
|
||||
onboot: 1
|
||||
features: nesting=1,keyctl=1
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-224-disk-0,size=32G
|
||||
swap: 512
|
||||
lxc.apparmor.profile: unconfined
|
||||
lxc.cgroup2.devices.allow: a
|
||||
lxc.cap.drop:
|
||||
12
server-configs/proxmox/lxc/225.conf
Normal file
12
server-configs/proxmox/lxc/225.conf
Normal file
@ -0,0 +1,12 @@
|
||||
arch: amd64
|
||||
cores: 2
|
||||
features: nesting=1
|
||||
hostname: gitea
|
||||
memory: 2048
|
||||
nameserver: 10.10.0.1
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,hwaddr=96:85:25:C9:39:EA,ip=10.10.0.225/24,type=veth
|
||||
onboot: 1
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-225-disk-1,size=20G
|
||||
swap: 512
|
||||
unprivileged: 1
|
||||
20
server-configs/proxmox/qemu/100.conf
Normal file
20
server-configs/proxmox/qemu/100.conf
Normal file
@ -0,0 +1,20 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 2
|
||||
ide2: local:iso/ubuntu-20.04.4-desktop-amd64.iso,media=cdrom
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: ubuntu-template
|
||||
net0: virtio=0E:A6:F2:0D:50:69,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
ostype: l26
|
||||
scsi0: local-lvm:base-100-disk-0,size=288G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=52abe492-8d48-4e3d-bf39-2a8448d6dbec
|
||||
sockets: 2
|
||||
template: 1
|
||||
vmgenid: a9118d77-133a-49ad-af18-8b15420958dc
|
||||
|
||||
[PENDING]
|
||||
boot: order=scsi0;net0
|
||||
delete: ide2
|
||||
14
server-configs/proxmox/qemu/101.conf
Normal file
14
server-configs/proxmox/qemu/101.conf
Normal file
@ -0,0 +1,14 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 4
|
||||
memory: 32768
|
||||
meta: creation-qemu=6.1.0,ctime=1648393429
|
||||
name: 7d-solo
|
||||
net0: virtio=6A:40:D4:03:F1:23,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-101-disk-0,size=128G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=2f1c6c7d-8e39-4c44-9309-51c415a0e72f
|
||||
sockets: 1
|
||||
vmgenid: 8a9dd631-5875-4336-9113-ce9f3eff9f5f
|
||||
14
server-configs/proxmox/qemu/102.conf
Normal file
14
server-configs/proxmox/qemu/102.conf
Normal file
@ -0,0 +1,14 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 4
|
||||
memory: 32768
|
||||
meta: creation-qemu=6.1.0,ctime=1648412615
|
||||
name: 7d-staci
|
||||
net0: virtio=8E:F7:FC:6B:6D:DC,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-102-disk-0,size=128G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=f56221fd-246a-49b0-9cdb-a3458475d41e
|
||||
sockets: 1
|
||||
vmgenid: c09c50ab-93da-4fd8-8326-322c572c8bc3
|
||||
16
server-configs/proxmox/qemu/103.conf
Normal file
16
server-configs/proxmox/qemu/103.conf
Normal file
@ -0,0 +1,16 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 2
|
||||
ide2: local:iso/ubuntu-20.04.4-desktop-amd64.iso,media=cdrom
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: docker-template
|
||||
net0: virtio=8E:B7:FE:6D:D7:52,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
ostype: l26
|
||||
scsi0: local-lvm:base-103-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=793ca394-3e87-40f2-8823-5d908c5ee564
|
||||
sockets: 2
|
||||
template: 1
|
||||
vmgenid: 854bfe23-67cc-4dd7-a4d0-a93f10787e50
|
||||
14
server-configs/proxmox/qemu/104.conf
Normal file
14
server-configs/proxmox/qemu/104.conf
Normal file
@ -0,0 +1,14 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 4
|
||||
memory: 32768
|
||||
meta: creation-qemu=6.1.0,ctime=1648393429
|
||||
name: 7d-wotw
|
||||
net0: virtio=C2:B3:F6:FD:84:CF,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-104-disk-0,size=128G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=d4e80082-2aaf-4602-94a5-c256a0121657
|
||||
sockets: 1
|
||||
vmgenid: 3db3e3f4-62e0-429f-9322-95d0de797e77
|
||||
15
server-configs/proxmox/qemu/105.conf
Normal file
15
server-configs/proxmox/qemu/105.conf
Normal file
@ -0,0 +1,15 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 8
|
||||
memory: 16384
|
||||
meta: creation-qemu=6.1.0,ctime=1646688596
|
||||
name: docker-vpn
|
||||
net0: virtio=76:36:85:A7:6A:A3,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-105-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=55061264-b9b1-4ce4-8d44-9c187affcb1d
|
||||
sockets: 1
|
||||
vmgenid: 30878bdf-66f9-41bf-be34-c31b400340f9
|
||||
15
server-configs/proxmox/qemu/106.conf
Normal file
15
server-configs/proxmox/qemu/106.conf
Normal file
@ -0,0 +1,15 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 4
|
||||
memory: 16384
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: docker-home
|
||||
net0: virtio=BA:65:DF:88:85:4C,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-106-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=54ef12fc-edcc-4744-a109-dd2de9a6dc03
|
||||
sockets: 2
|
||||
vmgenid: a13c92a2-a955-485e-a80e-391e99b19fbd
|
||||
15
server-configs/proxmox/qemu/107.conf
Normal file
15
server-configs/proxmox/qemu/107.conf
Normal file
@ -0,0 +1,15 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 8
|
||||
memory: 16384
|
||||
meta: creation-qemu=6.1.0,ctime=1646716190
|
||||
name: plex
|
||||
net0: virtio=D2:82:86:25:B9:FC,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-107-disk-0,size=128G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=e4a21847-39a7-4fb8-b9db-2fb2cf46077f
|
||||
sockets: 2
|
||||
vmgenid: ca0840de-bcf2-4c38-bcad-fcb883cd6b1e
|
||||
27
server-configs/proxmox/qemu/109.conf
Normal file
27
server-configs/proxmox/qemu/109.conf
Normal file
@ -0,0 +1,27 @@
|
||||
# Home Assistant OS VM
|
||||
# Deployed: January 2026
|
||||
# Primary IP: 10.0.0.28 (home network - Matter/IoT)
|
||||
# Secondary IP: 10.10.0.215 (server network - management)
|
||||
# Web UI: http://10.0.0.28:8123
|
||||
#
|
||||
# CRITICAL: net0 MUST be on vmbr1 (home network) for Matter to work
|
||||
# Matter Server auto-selects the first interface for mDNS/commissioning
|
||||
|
||||
agent: 1
|
||||
bios: ovmf
|
||||
boot: order=scsi0
|
||||
cores: 4
|
||||
efidisk0: local-lvm:vm-109-disk-0,efitype=4m,pre-enrolled-keys=0,size=4M
|
||||
machine: q35
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1767762202
|
||||
name: homeassistant
|
||||
net0: virtio=0E:E0:41:39:CC:95,bridge=vmbr1
|
||||
net1: virtio=6E:16:AB:69:63:89,bridge=vmbr0
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-109-disk-1,discard=on,size=64G,ssd=1
|
||||
scsihw: virtio-scsi-pci
|
||||
serial0: socket
|
||||
smbios1: uuid=ace611b1-66f3-4d34-8727-9dd9ff29e84e
|
||||
usb0: host=303a:831a
|
||||
vmgenid: d2aa05f5-f01f-4944-9fc3-5c4662096d7e
|
||||
16
server-configs/proxmox/qemu/110.conf
Normal file
16
server-configs/proxmox/qemu/110.conf
Normal file
@ -0,0 +1,16 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 2
|
||||
ide2: local:iso/ubuntu-20.04.4-desktop-amd64.iso,media=cdrom
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: discord-bots
|
||||
net0: virtio=DA:1E:55:E6:64:8B,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-110-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=b3384b2f-d395-44de-986d-6a4d938ddbea
|
||||
sockets: 2
|
||||
vmgenid: acd1ecab-d817-4880-9341-2ca0afd46230
|
||||
15
server-configs/proxmox/qemu/111.conf
Normal file
15
server-configs/proxmox/qemu/111.conf
Normal file
@ -0,0 +1,15 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 4
|
||||
memory: 32768
|
||||
meta: creation-qemu=6.1.0,ctime=1667162709
|
||||
name: docker-7days
|
||||
net0: virtio=36:90:52:0E:42:B0,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: home-truenas:111/vm-111-disk-0.qcow2,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=9695bb1d-f840-44c7-8b6e-af03c7b559d7
|
||||
sockets: 1
|
||||
vmgenid: 337b527a-cbaa-44a5-ad69-3035853c001b
|
||||
16
server-configs/proxmox/qemu/112.conf
Normal file
16
server-configs/proxmox/qemu/112.conf
Normal file
@ -0,0 +1,16 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 2
|
||||
ide2: none,media=cdrom
|
||||
memory: 16384
|
||||
meta: creation-qemu=6.1.0,ctime=1694032461
|
||||
name: databases-bots
|
||||
net0: virtio=D6:98:76:F1:AD:70,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-112-disk-0,size=512G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=ee8889c4-2cac-4704-afb5-f98cde4efb90
|
||||
sockets: 4
|
||||
vmgenid: 577cf49c-7722-437c-83ee-b1d6f12ce3ee
|
||||
16
server-configs/proxmox/qemu/113.conf
Normal file
16
server-configs/proxmox/qemu/113.conf
Normal file
@ -0,0 +1,16 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 4
|
||||
ide2: local:iso/ubuntu-20.04.4-desktop-amd64.iso,media=cdrom
|
||||
memory: 16384
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: docker-tdarr
|
||||
net0: virtio=22:ED:56:82:D6:A7,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-113-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=2594a1a4-20ad-4861-99d5-4d6c58f69c46
|
||||
sockets: 2
|
||||
vmgenid: 59ad1317-11f0-42ab-9757-4a70a0ede22c
|
||||
15
server-configs/proxmox/qemu/114.conf
Normal file
15
server-configs/proxmox/qemu/114.conf
Normal file
@ -0,0 +1,15 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;net0
|
||||
cores: 2
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: docker-pittsburgh
|
||||
net0: virtio=5E:6D:4A:6C:D1:EE,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-114-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=8b24ecc0-ac58-441e-aa69-a3957073c7dc
|
||||
sockets: 2
|
||||
vmgenid: 89072f53-2238-4a53-8b20-ed7d1f47ac71
|
||||
16
server-configs/proxmox/qemu/115.conf
Normal file
16
server-configs/proxmox/qemu/115.conf
Normal file
@ -0,0 +1,16 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 8
|
||||
ide2: local:iso/ubuntu-20.04.4-desktop-amd64.iso,media=cdrom
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: docker-sba
|
||||
net0: virtio=2E:17:9E:D6:62:0E,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-115-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=19be98ee-f60d-473d-acd2-9164717fcd11
|
||||
sockets: 2
|
||||
vmgenid: 682dfeab-8c63-4f0b-8ed2-8828c2f808ef
|
||||
16
server-configs/proxmox/qemu/116.conf
Normal file
16
server-configs/proxmox/qemu/116.conf
Normal file
@ -0,0 +1,16 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 2
|
||||
ide2: local:iso/ubuntu-20.04.4-desktop-amd64.iso,media=cdrom
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: docker-home-servers
|
||||
net0: virtio=16:0C:5A:5E:D6:5D,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
onboot: 1
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-116-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=e7ab7046-504b-462e-b3b8-5c12825a1407
|
||||
sockets: 2
|
||||
vmgenid: 9171ab63-7961-4b9d-9bd4-4e036519ecdb
|
||||
15
server-configs/proxmox/qemu/117.conf
Normal file
15
server-configs/proxmox/qemu/117.conf
Normal file
@ -0,0 +1,15 @@
|
||||
agent: 1
|
||||
boot: order=scsi0;ide2;net0
|
||||
cores: 2
|
||||
ide2: local:iso/ubuntu-20.04.4-desktop-amd64.iso,media=cdrom
|
||||
memory: 8192
|
||||
meta: creation-qemu=6.1.0,ctime=1646083628
|
||||
name: docker-unused
|
||||
net0: virtio=D6:88:7E:E5:17:01,bridge=vmbr0,firewall=1
|
||||
numa: 0
|
||||
ostype: l26
|
||||
scsi0: local-lvm:vm-117-disk-0,size=256G
|
||||
scsihw: virtio-scsi-pci
|
||||
smbios1: uuid=51c1d801-a2c8-416c-a7f9-a47fcc5ca783
|
||||
sockets: 2
|
||||
vmgenid: 2110750f-9078-42d7-9360-70e0a642cbed
|
||||
@ -0,0 +1,3 @@
|
||||
# Major Domo Discord Bot - Environment Variables
|
||||
BOT_TOKEN=your_discord_bot_token_here
|
||||
API_TOKEN=your_api_token_here
|
||||
@ -0,0 +1,22 @@
|
||||
services:
|
||||
discord-app:
|
||||
# build: ./discord-app
|
||||
image: manticorum67/major-domo-discordapp:dev
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
networks:
|
||||
- backend
|
||||
environment:
|
||||
- PYTHONBUFFERED=0
|
||||
- BOT_TOKEN=${BOT_TOKEN}
|
||||
- GUILD_ID=613880856032968834
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TESTING=False
|
||||
- LOG_LEVEL=INFO
|
||||
- TZ=America/Chicago
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
@ -0,0 +1,4 @@
|
||||
# Paper Dynasty Discord Bot - Environment Variables
|
||||
BOT_TOKEN=your_discord_bot_token_here
|
||||
API_TOKEN=your_api_token_here
|
||||
DB_PASSWORD=your_database_password_here
|
||||
@ -0,0 +1,68 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
discord-app:
|
||||
image: manticorum67/paper-dynasty-discordapp:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
environment:
|
||||
- PYTHONBUFFERED=0
|
||||
- GUILD_ID=613880856032968834
|
||||
- BOT_TOKEN=${BOT_TOKEN}
|
||||
- LOG_LEVEL=INFO
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- SCOREBOARD_CHANNEL=1000623557191155804
|
||||
- TZ=America/Chicago
|
||||
- PYTHONHASHSEED=1749583062
|
||||
- DATABASE=Prod
|
||||
- DB_USERNAME=postgres
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- DB_URL=db
|
||||
- DB_NAME=postgres
|
||||
networks:
|
||||
- backend
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "python3 -c 'import sys; sys.exit(0)' || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
db:
|
||||
image: postgres:18
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- pd_postgres:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
# start_period: 30s
|
||||
networks:
|
||||
- backend
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
networks:
|
||||
- backend
|
||||
|
||||
networks:
|
||||
backend:
|
||||
driver: bridge
|
||||
|
||||
paper_dynasty:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
pd_postgres:
|
||||
@ -0,0 +1,16 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
sba-ghost:
|
||||
image: ghost:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 2368:2368
|
||||
volumes:
|
||||
- ./ghost:/var/lib/ghost/content
|
||||
environment:
|
||||
database__client: sqlite3
|
||||
database__connection__filename: /var/lib/ghost/content/data/ghost.db
|
||||
url: https://sbanews.manticorum.com
|
||||
NODE_ENV: production
|
||||
TZ: Americas/Chicago
|
||||
@ -0,0 +1,24 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
sba-web:
|
||||
image: manticorum67/sba-website:${VERSION:-latest}
|
||||
ports:
|
||||
- "803:80" # Use internal port since nginx proxy manager handles external routing
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./public:/usr/share/nginx/html/public
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
networks:
|
||||
- default
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
@ -0,0 +1,24 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
sba-web:
|
||||
image: manticorum67/sba-website:${VERSION:-latest}
|
||||
ports:
|
||||
- "803:80" # Use internal port since nginx proxy manager handles external routing
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./public:/usr/share/nginx/html/public
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
networks:
|
||||
- default
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
@ -0,0 +1,20 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
database:
|
||||
# build: ./database-v2
|
||||
image: manticorum67/paper-dynasty-database:dev
|
||||
restart: unless-stopped
|
||||
container_name: dev_pd_database
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
ports:
|
||||
- 813:80
|
||||
environment:
|
||||
- TESTING=True
|
||||
- LOG_LEVEL=INFO
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TZ=America/Chicago
|
||||
- WORKERS_PER_CORE=1.0
|
||||
- PRIVATE_IN_SCHEMA=TRUE
|
||||
@ -0,0 +1,119 @@
|
||||
version: '3'
|
||||
|
||||
#networks:
|
||||
# nginx-proxy-manager_npm_network:
|
||||
# external: true
|
||||
|
||||
services:
|
||||
api:
|
||||
# build: .
|
||||
image: manticorum67/major-domo-database:dev
|
||||
restart: unless-stopped
|
||||
container_name: sba_db_api
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
ports:
|
||||
- 814:80
|
||||
networks:
|
||||
- default
|
||||
# - nginx-proxy-manager_npm_network
|
||||
environment:
|
||||
- TESTING=False
|
||||
- LOG_LEVEL=${LOG_LEVEL}
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TZ=${TZ}
|
||||
- WORKERS_PER_CORE=1.5
|
||||
- TIMEOUT=120
|
||||
- GRACEFUL_TIMEOUT=120
|
||||
- DATABASE_TYPE=postgresql
|
||||
- POSTGRES_HOST=sba_postgres
|
||||
- POSTGRES_DB=${SBA_DATABASE}
|
||||
- POSTGRES_USER=${SBA_DB_USER}
|
||||
- POSTGRES_PASSWORD=${SBA_DB_USER_PASSWORD}
|
||||
- REDIS_HOST=sba_redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_DB=0
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
container_name: sba_postgres
|
||||
environment:
|
||||
- POSTGRES_DB=${SBA_DATABASE}
|
||||
- POSTGRES_USER=${SBA_DB_USER}
|
||||
- POSTGRES_PASSWORD=${SBA_DB_USER_PASSWORD}
|
||||
- TZ=${TZ}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./logs:/var/log/postgresql
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${SBA_DB_USER} -d ${SBA_DATABASE}"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
container_name: sba_redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
command: redis-server --appendonly yes
|
||||
|
||||
adminer:
|
||||
image: adminer:latest
|
||||
restart: unless-stopped
|
||||
container_name: sba_adminer
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- ADMINER_DEFAULT_SERVER=sba_postgres
|
||||
- TZ=${TZ}
|
||||
# - ADMINER_DESIGN=pepa-linha-dark
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
sync-prod:
|
||||
image: alpine:latest
|
||||
container_name: sba_sync_prod
|
||||
volumes:
|
||||
- ./scripts:/scripts
|
||||
- /home/cal/.ssh:/tmp/ssh:ro
|
||||
environment:
|
||||
- SBA_DB_USER=${SBA_DB_USER}
|
||||
- SBA_DATABASE=${SBA_DATABASE}
|
||||
- SBA_DB_USER_PASSWORD=${SBA_DB_USER_PASSWORD}
|
||||
command: >
|
||||
sh -c "
|
||||
cp -r /tmp/ssh /root/.ssh &&
|
||||
chmod 700 /root/.ssh &&
|
||||
chmod 600 /root/.ssh/* &&
|
||||
chown -R root:root /root/.ssh &&
|
||||
/scripts/sync_from_prod.sh
|
||||
"
|
||||
profiles: ["sync"]
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- default
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
@ -0,0 +1,38 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
# database:
|
||||
# build: ./database
|
||||
# restart: unless-stopped
|
||||
# container_name: pd_database
|
||||
# volumes:
|
||||
# - ./storage:/usr/src/app/storage
|
||||
# - ./logs:/usr/src/app/logs
|
||||
# ports:
|
||||
# - 811:80
|
||||
# environment:
|
||||
# - TESTING=False
|
||||
# - LOG_LEVEL=INFO
|
||||
# - API_TOKEN=${API_TOKEN}
|
||||
# - TZ=America/Chicago
|
||||
# - WORKERS_PER_CORE=1.5
|
||||
# # - PYTHONHASHSEED=1749583062
|
||||
|
||||
database-v2:
|
||||
# build: ./database-v2
|
||||
image: manticorum67/paper-dynasty-database:1.5
|
||||
restart: unless-stopped
|
||||
container_name: pd_database_v2
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
ports:
|
||||
- 815:80
|
||||
environment:
|
||||
- TESTING=False
|
||||
- LOG_LEVEL=INFO
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TZ=America/Chicago
|
||||
- WORKERS_PER_CORE=1.5
|
||||
- WORKER_TIMEOUT=180
|
||||
- TIMEOUT=180
|
||||
@ -0,0 +1,2 @@
|
||||
# PostgreSQL Database - Environment Variables
|
||||
POSTGRES_PASSWORD=your_postgres_password_here
|
||||
@ -0,0 +1,19 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
database:
|
||||
image: postgres
|
||||
restart: unless-stopped
|
||||
shm_size: 128mb
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- /home/cal/postgres-data:/var/lib/postgresql/data
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080
|
||||
@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
services:
|
||||
sba-cards:
|
||||
image: lipanski/docker-static-website:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "804:3000"
|
||||
volumes:
|
||||
- ./cards:/home/static/cards
|
||||
- ./images:/home/static/images
|
||||
- ./httpd.conf:/home/static/httpd.conf:ro
|
||||
@ -0,0 +1,21 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
database:
|
||||
# build: ./database
|
||||
image: manticorum67/major-domo-database:latest
|
||||
restart: unless-stopped
|
||||
container_name: sba_database
|
||||
volumes:
|
||||
- ./storage:/usr/src/app/storage
|
||||
- ./logs:/usr/src/app/logs
|
||||
ports:
|
||||
- 801:80
|
||||
environment:
|
||||
- TESTING=False
|
||||
- LOG_LEVEL=INFO
|
||||
- API_TOKEN=${API_TOKEN}
|
||||
- TZ=America/Chicago
|
||||
- WORKERS_PER_CORE=1.5
|
||||
- TIMEOUT=120
|
||||
- GRACEFUL_TIMEOUT=120
|
||||
451
server-configs/sync-configs.sh
Executable file
451
server-configs/sync-configs.sh
Executable file
@ -0,0 +1,451 @@
|
||||
#!/bin/bash
|
||||
# Home Lab Configuration Sync Script
|
||||
# Syncs Docker Compose and VM configs between local git repo and remote hosts
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
HOSTS_FILE="$SCRIPT_DIR/hosts.yml"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Helper functions
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") <command> [host] [service]
|
||||
|
||||
Commands:
|
||||
pull [host] Pull configs from remote hosts to local repo
|
||||
push [host] Push configs from local repo to remote hosts (no restart)
|
||||
diff [host] Show differences between local and remote configs
|
||||
deploy <host> <service> Push config and restart specific service
|
||||
status Show sync status for all hosts
|
||||
list List all configured hosts and services
|
||||
|
||||
Examples:
|
||||
$(basename "$0") pull # Pull from all hosts
|
||||
$(basename "$0") pull ubuntu-manticore # Pull from specific host
|
||||
$(basename "$0") diff discord-bots # Show diffs for host
|
||||
$(basename "$0") deploy sba-bots paper-dynasty # Deploy and restart service
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse hosts.yml using simple bash (no yq dependency)
|
||||
get_hosts() {
|
||||
grep -E "^ [a-z]" "$HOSTS_FILE" | grep -v "^ #" | sed 's/://g' | awk '{print $1}'
|
||||
}
|
||||
|
||||
get_host_property() {
|
||||
local host="$1"
|
||||
local property="$2"
|
||||
# Simple YAML parsing - works for our flat structure
|
||||
sed -n "/^ $host:/,/^ [a-z]/p" "$HOSTS_FILE" | grep " $property:" | head -1 | sed 's/.*: *//' | tr -d '"'
|
||||
}
|
||||
|
||||
get_host_type() {
|
||||
get_host_property "$1" "type"
|
||||
}
|
||||
|
||||
get_ssh_alias() {
|
||||
get_host_property "$1" "ssh_alias"
|
||||
}
|
||||
|
||||
get_docker_path() {
|
||||
local host="$1"
|
||||
sed -n "/^ $host:/,/^ [a-z]/p" "$HOSTS_FILE" | grep "docker-compose:" | head -1 | sed 's/.*: *//' | tr -d '"'
|
||||
}
|
||||
|
||||
# Check if host is reachable
|
||||
check_host() {
|
||||
local host="$1"
|
||||
local host_type
|
||||
host_type=$(get_host_type "$host")
|
||||
|
||||
if [[ "$host_type" == "local" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ssh_alias
|
||||
ssh_alias=$(get_ssh_alias "$host")
|
||||
|
||||
if ssh -o ConnectTimeout=3 -o BatchMode=yes "$ssh_alias" "echo ok" &>/dev/null; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Pull configs from a single host
|
||||
pull_host() {
|
||||
local host="$1"
|
||||
local host_type
|
||||
host_type=$(get_host_type "$host")
|
||||
local local_dir="$SCRIPT_DIR/$host"
|
||||
|
||||
log_info "Pulling configs from $host..."
|
||||
|
||||
if [[ "$host_type" == "local" ]]; then
|
||||
log_warn "Skipping local host $host (no pull needed)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! check_host "$host"; then
|
||||
log_error "Cannot connect to $host"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ssh_alias
|
||||
ssh_alias=$(get_ssh_alias "$host")
|
||||
|
||||
if [[ "$host_type" == "proxmox" ]]; then
|
||||
# Pull LXC configs
|
||||
mkdir -p "$local_dir/lxc"
|
||||
log_info " Pulling LXC configs..."
|
||||
rsync -av --delete "$ssh_alias:/etc/pve/nodes/proxmox/lxc/" "$local_dir/lxc/" 2>/dev/null || true
|
||||
|
||||
# Pull QEMU configs
|
||||
mkdir -p "$local_dir/qemu"
|
||||
log_info " Pulling QEMU/VM configs..."
|
||||
rsync -av --delete "$ssh_alias:/etc/pve/nodes/proxmox/qemu-server/" "$local_dir/qemu/" 2>/dev/null || true
|
||||
|
||||
elif [[ "$host_type" == "docker" ]]; then
|
||||
local remote_path
|
||||
remote_path=$(get_docker_path "$host")
|
||||
|
||||
if [[ -z "$remote_path" ]]; then
|
||||
log_warn " No docker-compose path configured for $host"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$local_dir/docker-compose"
|
||||
log_info " Pulling Docker Compose configs from $remote_path..."
|
||||
|
||||
# Find and sync ONLY docker-compose files (not application data)
|
||||
ssh "$ssh_alias" "find $remote_path -maxdepth 2 \( -name 'docker-compose*.yml' -o -name 'compose.yml' \) 2>/dev/null" | while read -r compose_file; do
|
||||
local service_dir
|
||||
service_dir=$(dirname "$compose_file")
|
||||
local service_name
|
||||
service_name=$(basename "$service_dir")
|
||||
|
||||
mkdir -p "$local_dir/docker-compose/$service_name"
|
||||
|
||||
# ONLY sync compose files and .env.example - nothing else!
|
||||
# Explicitly copy just the files we want, not directories
|
||||
scp "$ssh_alias:$service_dir/docker-compose*.yml" "$local_dir/docker-compose/$service_name/" 2>/dev/null || true
|
||||
scp "$ssh_alias:$service_dir/compose.yml" "$local_dir/docker-compose/$service_name/" 2>/dev/null || true
|
||||
scp "$ssh_alias:$service_dir/.env.example" "$local_dir/docker-compose/$service_name/" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
|
||||
log_success "Pulled configs from $host"
|
||||
}
|
||||
|
||||
# Push configs to a single host
|
||||
push_host() {
|
||||
local host="$1"
|
||||
local host_type
|
||||
host_type=$(get_host_type "$host")
|
||||
local local_dir="$SCRIPT_DIR/$host"
|
||||
|
||||
log_info "Pushing configs to $host..."
|
||||
|
||||
if [[ "$host_type" == "local" ]]; then
|
||||
log_warn "Skipping local host $host (no push needed)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -d "$local_dir" ]]; then
|
||||
log_error "No local configs found for $host"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! check_host "$host"; then
|
||||
log_error "Cannot connect to $host"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ssh_alias
|
||||
ssh_alias=$(get_ssh_alias "$host")
|
||||
|
||||
if [[ "$host_type" == "proxmox" ]]; then
|
||||
log_warn "Pushing Proxmox configs requires manual review - skipping for safety"
|
||||
log_info " Use 'diff $host' to review changes first"
|
||||
return 0
|
||||
|
||||
elif [[ "$host_type" == "docker" ]]; then
|
||||
local remote_path
|
||||
remote_path=$(get_docker_path "$host")
|
||||
|
||||
if [[ ! -d "$local_dir/docker-compose" ]]; then
|
||||
log_warn " No docker-compose configs to push for $host"
|
||||
return 0
|
||||
fi
|
||||
|
||||
for service_dir in "$local_dir/docker-compose"/*/; do
|
||||
local service_name
|
||||
service_name=$(basename "$service_dir")
|
||||
local remote_service_path="$remote_path/$service_name"
|
||||
|
||||
log_info " Pushing $service_name..."
|
||||
rsync -av --dry-run \
|
||||
--include='docker-compose*.yml' \
|
||||
--include='compose.yml' \
|
||||
--include='*.conf' \
|
||||
--exclude='*' \
|
||||
"$service_dir" "$ssh_alias:$remote_service_path/" 2>/dev/null
|
||||
|
||||
# Actual push (remove --dry-run for real push)
|
||||
rsync -av \
|
||||
--include='docker-compose*.yml' \
|
||||
--include='compose.yml' \
|
||||
--include='*.conf' \
|
||||
--exclude='*' \
|
||||
"$service_dir" "$ssh_alias:$remote_service_path/" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
|
||||
log_success "Pushed configs to $host (services NOT restarted)"
|
||||
}
|
||||
|
||||
# Show diff between local and remote
|
||||
diff_host() {
|
||||
local host="$1"
|
||||
local host_type
|
||||
host_type=$(get_host_type "$host")
|
||||
local local_dir="$SCRIPT_DIR/$host"
|
||||
|
||||
log_info "Comparing configs for $host..."
|
||||
|
||||
if [[ "$host_type" == "local" ]]; then
|
||||
log_warn "Skipping local host $host"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! check_host "$host"; then
|
||||
log_error "Cannot connect to $host"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ssh_alias
|
||||
ssh_alias=$(get_ssh_alias "$host")
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d)
|
||||
|
||||
# Pull current remote state to temp
|
||||
if [[ "$host_type" == "proxmox" ]]; then
|
||||
mkdir -p "$temp_dir/lxc" "$temp_dir/qemu"
|
||||
rsync -a "$ssh_alias:/etc/pve/nodes/proxmox/lxc/" "$temp_dir/lxc/" 2>/dev/null || true
|
||||
rsync -a "$ssh_alias:/etc/pve/nodes/proxmox/qemu-server/" "$temp_dir/qemu/" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=== LXC Config Differences ==="
|
||||
diff -rq "$local_dir/lxc" "$temp_dir/lxc" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=== QEMU Config Differences ==="
|
||||
diff -rq "$local_dir/qemu" "$temp_dir/qemu" 2>/dev/null || true
|
||||
|
||||
elif [[ "$host_type" == "docker" ]]; then
|
||||
local remote_path
|
||||
remote_path=$(get_docker_path "$host")
|
||||
|
||||
for service_dir in "$local_dir/docker-compose"/*/; do
|
||||
local service_name
|
||||
service_name=$(basename "$service_dir")
|
||||
local remote_service_path="$remote_path/$service_name"
|
||||
|
||||
mkdir -p "$temp_dir/$service_name"
|
||||
rsync -a \
|
||||
--include='docker-compose*.yml' \
|
||||
--include='compose.yml' \
|
||||
--exclude='*' \
|
||||
"$ssh_alias:$remote_service_path/" "$temp_dir/$service_name/" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=== $service_name ==="
|
||||
diff -u "$temp_dir/$service_name/docker-compose.yml" "$service_dir/docker-compose.yml" 2>/dev/null || echo "(no differences or file missing)"
|
||||
done
|
||||
fi
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
}
|
||||
|
||||
# Deploy a specific service (push + restart)
|
||||
deploy_service() {
|
||||
local host="$1"
|
||||
local service="$2"
|
||||
local host_type
|
||||
host_type=$(get_host_type "$host")
|
||||
|
||||
if [[ "$host_type" != "docker" ]]; then
|
||||
log_error "Deploy only works for docker hosts"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ssh_alias
|
||||
ssh_alias=$(get_ssh_alias "$host")
|
||||
local remote_path
|
||||
remote_path=$(get_docker_path "$host")
|
||||
local local_dir="$SCRIPT_DIR/$host/docker-compose/$service"
|
||||
local remote_service_path="$remote_path/$service"
|
||||
|
||||
if [[ ! -d "$local_dir" ]]; then
|
||||
log_error "No local config found for $service on $host"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! check_host "$host"; then
|
||||
log_error "Cannot connect to $host"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Deploying $service to $host..."
|
||||
|
||||
# Push the config
|
||||
rsync -av \
|
||||
--include='docker-compose*.yml' \
|
||||
--include='compose.yml' \
|
||||
--include='*.conf' \
|
||||
--exclude='*' \
|
||||
"$local_dir/" "$ssh_alias:$remote_service_path/"
|
||||
|
||||
# Restart the service
|
||||
log_info "Restarting $service..."
|
||||
ssh "$ssh_alias" "cd $remote_service_path && docker compose down && docker compose up -d"
|
||||
|
||||
log_success "Deployed and restarted $service on $host"
|
||||
}
|
||||
|
||||
# Show status of all hosts
|
||||
show_status() {
|
||||
echo ""
|
||||
echo "Home Lab Configuration Status"
|
||||
echo "=============================="
|
||||
echo ""
|
||||
|
||||
for host in $(get_hosts); do
|
||||
local host_type
|
||||
host_type=$(get_host_type "$host")
|
||||
local status_icon
|
||||
|
||||
if [[ "$host_type" == "local" ]]; then
|
||||
status_icon="${GREEN}●${NC}"
|
||||
status_text="local"
|
||||
elif check_host "$host"; then
|
||||
status_icon="${GREEN}●${NC}"
|
||||
status_text="online"
|
||||
else
|
||||
status_icon="${RED}●${NC}"
|
||||
status_text="offline"
|
||||
fi
|
||||
|
||||
local local_dir="$SCRIPT_DIR/$host"
|
||||
local config_count=0
|
||||
if [[ -d "$local_dir" ]]; then
|
||||
config_count=$(find "$local_dir" -name "*.yml" -o -name "*.conf" 2>/dev/null | wc -l)
|
||||
fi
|
||||
|
||||
printf " %b %-20s %-10s %-8s %d configs tracked\n" "$status_icon" "$host" "($host_type)" "$status_text" "$config_count"
|
||||
done
|
||||
echo ""
|
||||
}
|
||||
|
||||
# List all hosts and services
|
||||
list_hosts() {
|
||||
echo ""
|
||||
echo "Configured Hosts and Services"
|
||||
echo "=============================="
|
||||
|
||||
for host in $(get_hosts); do
|
||||
local host_type
|
||||
host_type=$(get_host_type "$host")
|
||||
local description
|
||||
description=$(get_host_property "$host" "description")
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}$host${NC} ($host_type)"
|
||||
echo " $description"
|
||||
|
||||
local local_dir="$SCRIPT_DIR/$host/docker-compose"
|
||||
if [[ -d "$local_dir" ]]; then
|
||||
echo " Services:"
|
||||
for service in "$local_dir"/*/; do
|
||||
if [[ -d "$service" ]]; then
|
||||
echo " - $(basename "$service")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main command dispatcher
|
||||
main() {
|
||||
if [[ $# -lt 1 ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
local command="$1"
|
||||
shift
|
||||
|
||||
case "$command" in
|
||||
pull)
|
||||
if [[ $# -ge 1 ]]; then
|
||||
pull_host "$1"
|
||||
else
|
||||
for host in $(get_hosts); do
|
||||
pull_host "$host" || true
|
||||
done
|
||||
fi
|
||||
;;
|
||||
push)
|
||||
if [[ $# -ge 1 ]]; then
|
||||
push_host "$1"
|
||||
else
|
||||
for host in $(get_hosts); do
|
||||
push_host "$host" || true
|
||||
done
|
||||
fi
|
||||
;;
|
||||
diff)
|
||||
if [[ $# -ge 1 ]]; then
|
||||
diff_host "$1"
|
||||
else
|
||||
for host in $(get_hosts); do
|
||||
diff_host "$host" || true
|
||||
done
|
||||
fi
|
||||
;;
|
||||
deploy)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
log_error "Deploy requires host and service arguments"
|
||||
usage
|
||||
fi
|
||||
deploy_service "$1" "$2"
|
||||
;;
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
list)
|
||||
list_hosts
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown command: $command"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@ -0,0 +1,25 @@
|
||||
services:
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- NVIDIA_DRIVER_CAPABILITIES=all
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
ports:
|
||||
- "8096:8096" # Web UI
|
||||
- "7359:7359/udp" # Client discovery
|
||||
volumes:
|
||||
- ./config:/config
|
||||
- /mnt/NV2/jellyfin-cache:/cache
|
||||
- /mnt/truenas/media:/media:ro
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
@ -0,0 +1,46 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
tdarr:
|
||||
image: ghcr.io/haveagitgat/tdarr:latest
|
||||
container_name: tdarr-server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8265:8265" # Web UI
|
||||
- "8266:8266" # Server port (for nodes)
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- serverIP=0.0.0.0
|
||||
- serverPort=8266
|
||||
- webUIPort=8265
|
||||
volumes:
|
||||
- ./server-data:/app/server
|
||||
- ./configs:/app/configs
|
||||
- ./logs:/app/logs
|
||||
- /mnt/truenas/media:/media
|
||||
|
||||
tdarr-node:
|
||||
image: ghcr.io/haveagitgat/tdarr_node:latest
|
||||
container_name: tdarr-node
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- serverIP=tdarr
|
||||
- serverPort=8266
|
||||
- nodeName=manticore-gpu
|
||||
volumes:
|
||||
- ./node-data:/app/configs
|
||||
- /mnt/truenas/media:/media
|
||||
- /mnt/NV2/tdarr-cache:/temp
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
depends_on:
|
||||
- tdarr
|
||||
@ -0,0 +1,12 @@
|
||||
services:
|
||||
watchstate:
|
||||
image: ghcr.io/arabcoders/watchstate:latest
|
||||
container_name: watchstate
|
||||
restart: unless-stopped
|
||||
user: "1000:1000"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- ./data:/config:rw
|
||||
255
tcg/README.md
Normal file
255
tcg/README.md
Normal file
@ -0,0 +1,255 @@
|
||||
# Baseball Trading Card Game - Design Document
|
||||
|
||||
## Core Concept
|
||||
A 1v1 trading card game themed around baseball with 9 rounds representing innings. Players stay engaged during opponent turns through interactive defensive mechanics.
|
||||
|
||||
## Design Philosophy
|
||||
- **Initial Release Focus**: Elegant, streamlined mechanics for easy learning
|
||||
- **Future Expansion**: Complex mechanics (bench systems, advanced pitcher rules) in later sets
|
||||
- **Player Engagement**: Interactive turns prevent downtime
|
||||
- **Thematic Integration**: All mechanics should feel authentically baseball
|
||||
|
||||
## Game Structure
|
||||
|
||||
### Round System
|
||||
- **9 Innings**: Each game consists of 9 rounds
|
||||
- **Turn Structure**: TBD - options include:
|
||||
- Alternating batting/pitching each inning
|
||||
- Both players bat/pitch within each inning
|
||||
- Other hybrid approaches
|
||||
|
||||
### Win Conditions
|
||||
- **Primary**: Score more runs over 9 innings
|
||||
- **Scoring Mechanism**: TBD - could involve card combinations, stat comparisons, or other systems
|
||||
|
||||
## Card Types
|
||||
|
||||
### Player Cards (Core Mechanic)
|
||||
**Function**: Act like creatures with offensive and defensive capabilities
|
||||
|
||||
**Stats Structure**:
|
||||
- **Multiple Offensive Stats**: Power (home runs), Contact (base hits), Speed (stealing/advancing)
|
||||
- **Defensive Stats**: Fielding rating, potentially position-specific bonuses
|
||||
- **Keywords**: *Clutch*, *Speed*, *Power Hitter*, *Gold Glove*, *Veteran*, etc.
|
||||
|
||||
**Activation System**:
|
||||
- **Fresh → Tapped**: Normal activation for offense or defense
|
||||
- **Tapped → Discarded**: "Overexertion" - pushing beyond limits for critical plays
|
||||
- **Strategic Decision**: Save stars for defense or commit fully to offense?
|
||||
|
||||
**Player Types**:
|
||||
- **Named Players**:
|
||||
- Legendary rule (only one copy in play)
|
||||
- High stats and unique abilities
|
||||
- Risk: Limited board presence if overcommitted
|
||||
- **Generic Players**:
|
||||
- Multiple copies allowed in play
|
||||
- Lower stats, fewer/no abilities
|
||||
- Benefit: Consistent board presence and replacements
|
||||
|
||||
### Manager Cards
|
||||
**Function**: Pseudo-players with resource management
|
||||
|
||||
**Influence System**:
|
||||
- **Starting Influence**: 3-5 counters
|
||||
- **Gain Influence**: Successful plays (double plays, home runs, defensive gems)
|
||||
- **Spend Influence**: Special abilities
|
||||
- "Call for Steal" (2 Influence)
|
||||
- "Intentional Walk" (1 Influence)
|
||||
- "Challenge Call" (3 Influence)
|
||||
|
||||
**Manager Examples**:
|
||||
- "Tony La Russa" - Gains Influence when *Veteran* keyword players succeed
|
||||
- Abilities could interact with specific player types or game situations
|
||||
|
||||
### Strategy/Coaching Cards
|
||||
**Function**: Modify player effectiveness and create synergies
|
||||
|
||||
**Interaction Models Considered**:
|
||||
|
||||
**Option 1: Persistent Effects (Stadium-style)**
|
||||
- Remain in play throughout game
|
||||
- Provide ongoing bonuses
|
||||
- Examples: "Small Ball Strategy", "Manager's Philosophy"
|
||||
|
||||
**Option 2: Counter-Based Effects**
|
||||
- Start with counters, spend for abilities
|
||||
- Hybrid of persistent + active effects
|
||||
- Example: "Veteran Manager" - Passive: +1 defensive plays, Active: Spend 2 counters to prevent opponent's special play
|
||||
|
||||
**Synergy Examples**:
|
||||
- "Stealing Signs" - Players with *Veteran* get +2 Contact
|
||||
- "Aggressive Base Running" - Players with *Speed* advance extra bases
|
||||
- "Situational Hitting" - *Clutch* players activate abilities with runners in scoring position
|
||||
- "Moneyball" - Players with *Contact* cost 1 less Influence to activate
|
||||
|
||||
### Equipment Cards (Future Consideration)
|
||||
**Function**: Modify player stats and abilities
|
||||
- Bats, gloves, cleats affecting specific stats
|
||||
- Could provide keywords or enhance existing ones
|
||||
- Interaction with player types TBD
|
||||
|
||||
## Deck Construction Strategy
|
||||
|
||||
### Core Tension: Power vs Consistency
|
||||
**Star-Heavy Decks**:
|
||||
- Few powerful named players
|
||||
- High ceiling but vulnerable to exhaustion
|
||||
- Risk of limited board presence
|
||||
|
||||
**Deep Bench Decks**:
|
||||
- Mostly generic players
|
||||
- Consistent performance but lower ceiling
|
||||
- Reliable board presence throughout 9 innings
|
||||
|
||||
**Hybrid Approach**:
|
||||
- Mix of named and generic players
|
||||
- Tactical flexibility
|
||||
- Balanced risk/reward
|
||||
|
||||
### Multiple Copies System
|
||||
- **Named Players**: Deck can contain multiple copies but only one in play
|
||||
- **Benefit**: Can replace discarded stars with fresh copies
|
||||
- **Risk**: Dead draws if legendary already in play
|
||||
- **Generic Players**: Multiple copies can be in play simultaneously
|
||||
|
||||
## Mechanics Under Consideration
|
||||
|
||||
### Keywords System
|
||||
**Purpose**: Create synergies between players and strategy cards
|
||||
|
||||
**Potential Keywords**:
|
||||
- **Clutch**: Enhanced performance in high-pressure situations
|
||||
- **Speed**: Base running and stealing bonuses
|
||||
- **Power Hitter**: Home run potential and extra-base hits
|
||||
- **Gold Glove**: Defensive excellence
|
||||
- **Veteran**: Experience-based bonuses and synergies
|
||||
- **Contact Hitter**: Consistent base hits over power
|
||||
|
||||
**Integration**: Strategy and coaching cards provide different bonuses based on keywords
|
||||
|
||||
### Resource Systems Explored
|
||||
|
||||
**Pitcher Stamina (Future)**:
|
||||
- Counters that deplete with use
|
||||
- Start with 6-8, lose one per batter faced
|
||||
- Overuse leads to reduced effectiveness
|
||||
|
||||
**Team Energy/Momentum**:
|
||||
- System-wide resource affecting entire team
|
||||
- Builds with successful plays, depletes with failures
|
||||
- Could affect activation costs or effectiveness
|
||||
|
||||
**Rally Counters**:
|
||||
- Build when trailing in score
|
||||
- Provide comeback mechanics
|
||||
- Thematic late-game tension
|
||||
|
||||
### Interactive Defense Mechanics
|
||||
**Core Concept**: Opponent can respond to offensive plays
|
||||
|
||||
**Process**:
|
||||
1. Opponent declares attack/offensive play
|
||||
2. Defending player can tap defenders to respond
|
||||
3. Failed defense attempts result in defender discard
|
||||
4. Creates meaningful decisions about when to commit defenders
|
||||
|
||||
**Thematic Examples**:
|
||||
- Diving catches that risk injury
|
||||
- Collision plays at home plate
|
||||
- Double play attempts under pressure
|
||||
|
||||
## Rejected/Deferred Concepts
|
||||
|
||||
### Bench/Rest Mechanics
|
||||
**Concept**: Three-state system (Fresh → Tapped → Benched → Fresh)
|
||||
**Reasoning for Deferral**:
|
||||
- Added complexity without proportional strategic depth
|
||||
- Could slow game flow
|
||||
- Conflicts with elegance goal for initial release
|
||||
- Better suited for future expansion sets
|
||||
|
||||
**Potential Future Implementation**:
|
||||
- Manager abilities to rest players
|
||||
- Inning break mechanics (7th inning stretch untap)
|
||||
- Position-specific rest rules (complex pitcher stamina)
|
||||
|
||||
### Complex Pitcher Systems
|
||||
**Concept**: Separate activation rules and stamina tracking for pitchers
|
||||
**Reasoning for Deferral**:
|
||||
- Adds significant complexity to initial ruleset
|
||||
- Better as expansion mechanic
|
||||
- Current activation system works for all player types
|
||||
|
||||
### Multi-Resource Systems
|
||||
**Concept**: Different resource types for different card types
|
||||
**Reasoning for Deferral**:
|
||||
- Could create analysis paralysis
|
||||
- Simpler Influence system covers most strategic decisions
|
||||
- Additional resources better introduced gradually
|
||||
|
||||
## Development Priorities
|
||||
|
||||
### Phase 1: Core Mechanics
|
||||
1. **Finalize turn structure** - How innings progress and who acts when
|
||||
2. **Scoring system** - How runs are actually scored and tracked
|
||||
3. **Basic card interactions** - Player stats vs defense, keyword basics
|
||||
4. **Manager Influence** - Specific abilities and costs
|
||||
|
||||
### Phase 2: Card Design
|
||||
1. **Stat balance** - Appropriate ranges for offense/defense
|
||||
2. **Keyword refinement** - Final keyword list and interactions
|
||||
3. **Strategy card effects** - Specific bonus types and triggers
|
||||
4. **Deck construction rules** - Size limits, copy limits, etc.
|
||||
|
||||
### Phase 3: Playtesting
|
||||
1. **Game length validation** - Does 9 innings create appropriate game time?
|
||||
2. **Strategic depth** - Are decisions meaningful throughout the game?
|
||||
3. **Comeback mechanics** - Can trailing players mount comebacks?
|
||||
4. **Balance testing** - Star vs generic player balance
|
||||
|
||||
## Open Questions
|
||||
|
||||
### Turn Structure
|
||||
- How do innings progress?
|
||||
- When do both players get to "bat"?
|
||||
- How does defensive interaction timing work?
|
||||
|
||||
### Scoring Mechanism
|
||||
- What actually generates runs?
|
||||
- Card combinations? Stat contests? Other?
|
||||
- How are runs tracked and accumulated?
|
||||
|
||||
### Game Pacing
|
||||
- Target game length?
|
||||
- How does player exhaustion affect late-game strategy?
|
||||
- Are there catch-up mechanics for trailing players?
|
||||
|
||||
### Card Economy
|
||||
- How many cards in hand?
|
||||
- Draw mechanics between innings?
|
||||
- Mulligan rules?
|
||||
|
||||
## Future Expansion Opportunities
|
||||
|
||||
### Set 2: Advanced Mechanics
|
||||
- Bench systems with rest mechanics
|
||||
- Complex pitcher stamina and specialization
|
||||
- Equipment cards with stacking effects
|
||||
- Multi-inning strategy cards
|
||||
|
||||
### Set 3: League Play
|
||||
- Team-building across multiple games
|
||||
- Season-long campaigns
|
||||
- Manager advancement systems
|
||||
- Legacy effects between games
|
||||
|
||||
### Special Sets
|
||||
- Historic players and teams
|
||||
- All-Star game special rules
|
||||
- Playoff mechanics with higher stakes
|
||||
- World Series format tournaments
|
||||
|
||||
---
|
||||
|
||||
*This document captures the brainstorming session of 2025-08-15. Design decisions are fluid and subject to revision based on playtesting and further development.*
|
||||
28
tcg/card-design-notes.md
Normal file
28
tcg/card-design-notes.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Card Design Notes
|
||||
|
||||
## Evolution Mechanic: "Up Your Game"
|
||||
|
||||
Concept: Pokemon-esque evolution system for higher-powered player versions.
|
||||
|
||||
### Initial Design Ideas
|
||||
|
||||
- **Named Players**: Primary target for evolution mechanic
|
||||
- Examples: Elite versions of star players with enhanced stats/abilities
|
||||
|
||||
- **Unnamed Players**: Also viable for evolution
|
||||
- Example: "Backup SS" → "Everyday SS"
|
||||
- Represents player development and skill progression
|
||||
|
||||
### Mechanics to Consider
|
||||
|
||||
- Evolution requirements (experience, achievements, resources?)
|
||||
- Stat boosts and new abilities for evolved forms
|
||||
- Visual indicators for evolved players
|
||||
- Balance implications for gameplay
|
||||
|
||||
### Questions for Further Development
|
||||
|
||||
- Should evolution be permanent or temporary?
|
||||
- What triggers evolution eligibility?
|
||||
- How many evolution stages should exist?
|
||||
- Should evolved forms have different costs/requirements to play?
|
||||
299
tcg/project-sol-rulebook/CONVERSION-REPORT.md
Normal file
299
tcg/project-sol-rulebook/CONVERSION-REPORT.md
Normal file
@ -0,0 +1,299 @@
|
||||
# Project Sol Rulebook - Notion to Markdown Conversion Report
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Successfully retrieved and converted the Project Sol - Online Rulebook from Notion to Markdown format. The conversion process retrieved content from 14 pages (1 main page + 13 child pages) and created a structured documentation directory.
|
||||
|
||||
## Main Page Information
|
||||
|
||||
**Page ID:** 0a46e40f-a418-4605-a862-4e412be0a380
|
||||
**Title:** Project Sol - Online Rulebook
|
||||
**Icon:** 📖
|
||||
**Last Edited:** 2025-10-16T06:10:00.000Z
|
||||
|
||||
## Directory Structure Created
|
||||
|
||||
```
|
||||
/mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/
|
||||
├── README.md (Main rulebook page)
|
||||
├── how-to-play.md
|
||||
├── character-creation.md
|
||||
├── attributes-and-skills.md
|
||||
├── character-qualities.md
|
||||
├── wealth.md
|
||||
├── equipment.md
|
||||
├── physical-combat.md
|
||||
├── social-combat.md (placeholder)
|
||||
├── spacecraft-combat.md (placeholder)
|
||||
├── drones.md (placeholder)
|
||||
├── hacking.md (placeholder)
|
||||
├── structures.md (placeholder)
|
||||
├── afflictions-and-conditions.md (placeholder)
|
||||
└── CONVERSION-REPORT.md (this file)
|
||||
```
|
||||
|
||||
## Pages Processed - Complete Data Retrieved
|
||||
|
||||
### 1. Main Page (README.md)
|
||||
- **Page ID:** 0a46e40f-a418-4605-a862-4e412be0a380
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/README.md
|
||||
- **Status:** ✅ Complete
|
||||
- **Content:** Main introduction, goals, and table of contents with links to all child pages
|
||||
|
||||
### 2. How to Play
|
||||
- **Page ID:** 9ea29ceb-e0b8-4758-84bd-eead464ba080
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/how-to-play.md
|
||||
- **Status:** ✅ Complete with full content
|
||||
- **Sections:**
|
||||
- Dice Pools
|
||||
- Skill Tests (Threshold & Opposed)
|
||||
- Bonuses and Penalties
|
||||
- Glitches & Critical Successes
|
||||
- Defaulting on Tests
|
||||
- Physical vs Mental Tests
|
||||
- Teamwork (Assist & Group Action)
|
||||
- Try Again
|
||||
- Edge (Spending, Regaining, Burning)
|
||||
- Character Advancement (Experience & Training)
|
||||
- **Databases Retrieved:**
|
||||
- Skill Test Thresholds (6 entries)
|
||||
- Spending Edge Effects (6 entries)
|
||||
- Burning Edge Effects (2 entries)
|
||||
|
||||
### 3. Character Creation
|
||||
- **Page ID:** b44deb86-bfb7-4490-b9cc-7d24a3d4a019
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/character-creation.md
|
||||
- **Status:** ✅ Complete structure retrieved
|
||||
- **Sections:**
|
||||
- Step 1: Character's Lifepath (Fledgling through Seventies)
|
||||
- Step 2: Round Out Character (Qualities & Equipment)
|
||||
- Step 3: Final Calculations
|
||||
- **Has Child Content:** Toggle blocks for each age bracket (content needs expansion)
|
||||
- **Databases Referenced:**
|
||||
- Occupations database
|
||||
- Final Calculations database
|
||||
|
||||
### 4. Attributes and Skills
|
||||
- **Page ID:** 30ba58af-9769-4df8-bd22-82b5fddf9610
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/attributes-and-skills.md
|
||||
- **Status:** ✅ Complete with full content
|
||||
- **Sections:**
|
||||
- Physical Attributes (Agility, Body, Reaction, Strength)
|
||||
- Mental Attributes (Charisma, Intuition, Logic, Willpower)
|
||||
- Special Attribute (Edge)
|
||||
- Skills overview
|
||||
- **Databases Referenced:**
|
||||
- Skill Table (detailed skill list)
|
||||
|
||||
### 5. Character Qualities
|
||||
- **Page ID:** 483d7013-bd50-40aa-94c7-99758b56a32a
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/character-qualities.md
|
||||
- **Status:** ✅ Structure complete
|
||||
- **Databases Referenced:**
|
||||
- Positive Qualities database
|
||||
- Negative Qualities database
|
||||
|
||||
### 6. Wealth
|
||||
- **Page ID:** b8b7a209-630e-4f7c-b826-b8babd126fee
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/wealth.md
|
||||
- **Status:** ✅ Complete with full content
|
||||
- **Sections:**
|
||||
- Wealth Rating system
|
||||
- Increasing Wealth Rating
|
||||
- Cost Ratings & Credits
|
||||
- Pooling Credits
|
||||
- **Content Type:** Full text with examples and callout boxes
|
||||
|
||||
### 7. Equipment
|
||||
- **Page ID:** 6c2bf3f1-dbfa-47bc-a0b0-f8c76643e911
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/equipment.md
|
||||
- **Status:** ✅ Complete overview
|
||||
- **Sections:**
|
||||
- Item Bulk & Bulk Limits
|
||||
- Buying and Selling Gear
|
||||
- Gear Durability & Repair
|
||||
- Illegal Equipment
|
||||
- Gear Listing (with child page links)
|
||||
- **Child Pages Identified:**
|
||||
- Armor
|
||||
- Augmentation
|
||||
- Biotech & Drugs
|
||||
- Computers & Electronics
|
||||
- Covert Ops
|
||||
- Drones and Vehicles
|
||||
- Explosives
|
||||
- Spacecrafts
|
||||
- Weapons
|
||||
|
||||
### 8. Physical Combat
|
||||
- **Page ID:** 4e3163ef-ef7d-455d-aace-56d9b5d2c6ac
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/physical-combat.md
|
||||
- **Status:** ✅ Complete with full content
|
||||
- **Sections:**
|
||||
- Combat Sequence (4-step process)
|
||||
- Actions in Combat
|
||||
- Damage in Combat (Wounds, Critical Successes)
|
||||
- Combat Modifiers (Concealment, Cover, Flanked, Prone)
|
||||
- Injury and Recovery (Natural Recovery & Medkits)
|
||||
- **Databases Referenced:**
|
||||
- Actions in Combat database
|
||||
- Wound Penalties database
|
||||
- Cover Bonuses database
|
||||
|
||||
## Pages Processed - Placeholder Created
|
||||
|
||||
The following pages have placeholder files created but require full content retrieval:
|
||||
|
||||
### 9. Social Combat
|
||||
- **Page ID:** 3f951086-b61b-4c91-a838-5e60f993b84e
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/social-combat.md
|
||||
- **Status:** ⚠️ Placeholder created - needs full retrieval
|
||||
|
||||
### 10. Spacecraft Combat
|
||||
- **Page ID:** 37d2ff13-b1ed-4bdf-b6d4-16995ce37e88
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/spacecraft-combat.md
|
||||
- **Status:** ⚠️ Placeholder created - needs full retrieval
|
||||
|
||||
### 11. Drones
|
||||
- **Page ID:** 7f49463f-50a9-4812-a57c-820fe78bba48
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/drones.md
|
||||
- **Status:** ⚠️ Placeholder created - needs full retrieval
|
||||
|
||||
### 12. Hacking
|
||||
- **Page ID:** 94a4bb88-b9eb-4900-862f-3231fc3bdac5
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/hacking.md
|
||||
- **Status:** ⚠️ Placeholder created - needs full retrieval
|
||||
|
||||
### 13. Structures
|
||||
- **Page ID:** ceba7fa2-9a64-4ad9-b9d0-2dc1d4ac9809
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/structures.md
|
||||
- **Status:** ⚠️ Placeholder created - needs full retrieval
|
||||
|
||||
### 14. Afflictions and Conditions
|
||||
- **Page ID:** 43610355-65ea-41c5-b875-56e13e21a6f0
|
||||
- **File:** /mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/afflictions-and-conditions.md
|
||||
- **Status:** ⚠️ Placeholder created - needs full retrieval
|
||||
|
||||
## Databases Encountered
|
||||
|
||||
### Fully Retrieved Databases
|
||||
1. **Skill Test Thresholds** (ID: 7c05713b-86dc-4763-b473-de52795d8374)
|
||||
- 6 entries: Simple (1), Average (2), Difficult (3), Very Difficult (4), Formidable (6), Absurd (8+)
|
||||
- Converted to: Markdown table in how-to-play.md
|
||||
|
||||
2. **Spending Edge Effects** (ID: a905ddcc-371d-4023-8ca6-931a495fc36f)
|
||||
- 6 entries: Push the Limit, Dead Man's Trigger, Blitz, Seize the Initiative, Close Call, Second Chance
|
||||
- Converted to: Markdown table in how-to-play.md
|
||||
|
||||
3. **Burning Edge Effect** (ID: 89eed180-afb6-4d73-8716-d1441d2b535c)
|
||||
- 2 entries: Not Dead Yet, Smackdown
|
||||
- Converted to: Markdown table in how-to-play.md
|
||||
|
||||
### Referenced But Not Fully Retrieved
|
||||
4. **Occupations** (Character Creation page)
|
||||
5. **Final Calculations** (Character Creation page)
|
||||
6. **Positive Qualities** (Character Qualities page)
|
||||
7. **Negative Qualities** (Character Qualities page)
|
||||
8. **Skill Table** (Attributes and Skills page)
|
||||
9. **Actions in Combat** (Physical Combat page)
|
||||
10. **Wound Penalties** (Physical Combat page)
|
||||
11. **Cover Bonuses** (Physical Combat page)
|
||||
|
||||
## Content Formatting Applied
|
||||
|
||||
### Markdown Conversions Implemented
|
||||
- ✅ Heading levels (H1, H2, H3) preserved
|
||||
- ✅ Paragraph text formatted
|
||||
- ✅ Bulleted lists converted
|
||||
- ✅ Numbered lists converted
|
||||
- ✅ Callout blocks converted to blockquotes with emoji
|
||||
- ✅ Dividers added (---)
|
||||
- ✅ Bold, italic, underline formatting preserved
|
||||
- ✅ Internal Notion links identified
|
||||
- ✅ Child page links converted to relative markdown links
|
||||
- ✅ Database entries converted to markdown tables
|
||||
- ✅ Toggle blocks noted (converted to HTML details tags where appropriate)
|
||||
|
||||
### Special Handling
|
||||
- **Toggle Blocks:** Converted to `<details><summary>` HTML tags in character-creation.md
|
||||
- **Child Databases:** Converted to markdown tables where data was available
|
||||
- **Child Pages:** Created links in parent pages and separate markdown files
|
||||
- **Callouts:** Converted to blockquotes with emoji prefixes (e.g., `> 💡`)
|
||||
- **Rich Text Arrays:** Concatenated with formatting preserved
|
||||
|
||||
## Known Limitations & Next Steps
|
||||
|
||||
### Content Not Fully Retrieved
|
||||
1. **Toggle Block Content:** Many toggle blocks in Character Creation contain nested content that needs expansion
|
||||
2. **Database Details:** Several databases are referenced but not fully converted to tables
|
||||
3. **Child Equipment Pages:** 9 equipment subcategory pages need individual retrieval
|
||||
4. **Remaining Combat Pages:** 3 combat system pages (Social, Spacecraft, Drones) need full content
|
||||
5. **System Pages:** Hacking, Structures, and Afflictions pages need full content
|
||||
|
||||
### Recommendations for Complete Conversion
|
||||
1. **Run Additional Retrieval:** Execute API calls for the 6 placeholder pages
|
||||
2. **Expand Toggle Content:** Retrieve nested content from all toggle blocks in Character Creation
|
||||
3. **Database Conversion:** Query and convert all referenced databases to complete tables
|
||||
4. **Equipment Subcategories:** Retrieve the 9 equipment child pages
|
||||
5. **Cross-Reference Links:** Update internal Notion links to point to correct markdown files
|
||||
|
||||
## Technical Details
|
||||
|
||||
### API Calls Made
|
||||
- `mcp__notion__API-retrieve-a-page`: 4 calls
|
||||
- `mcp__notion__API-get-block-children`: 8 calls
|
||||
- `mcp__notion__API-post-database-query`: 3 calls
|
||||
- Total API operations: 15
|
||||
|
||||
### Files Created
|
||||
- Total markdown files: 14
|
||||
- Fully populated files: 8
|
||||
- Placeholder files: 6
|
||||
- Report file: 1 (this document)
|
||||
|
||||
### Data Volume
|
||||
- Pages with complete block data: 4
|
||||
- Pages with partial data: 3
|
||||
- Pages awaiting retrieval: 6
|
||||
- Databases converted: 3
|
||||
- Databases referenced: 8
|
||||
|
||||
## File Paths Summary
|
||||
|
||||
All files created in: `/mnt/NV2/Development/claude-home/tcg/project-sol-rulebook/`
|
||||
|
||||
**Complete Files:**
|
||||
- README.md
|
||||
- how-to-play.md
|
||||
- character-creation.md (structure complete)
|
||||
- attributes-and-skills.md
|
||||
- character-qualities.md (structure complete)
|
||||
- wealth.md
|
||||
- equipment.md
|
||||
- physical-combat.md
|
||||
|
||||
**Placeholder Files:**
|
||||
- social-combat.md
|
||||
- spacecraft-combat.md
|
||||
- drones.md
|
||||
- hacking.md
|
||||
- structures.md
|
||||
- afflictions-and-conditions.md
|
||||
|
||||
## Conclusion
|
||||
|
||||
Successfully created a structured Markdown documentation system for the Project Sol Rulebook. The conversion captured:
|
||||
- Main rulebook structure and navigation
|
||||
- 8 pages with substantial content
|
||||
- 3 fully converted databases
|
||||
- Proper markdown formatting with rich text preservation
|
||||
- Cross-references and links
|
||||
|
||||
The foundation is complete and ready for:
|
||||
1. Additional content retrieval for placeholder pages
|
||||
2. Database expansion for referenced tables
|
||||
3. Equipment subcategory page creation
|
||||
4. Toggle block content expansion
|
||||
|
||||
The documentation is immediately usable for the pages with complete content, and the structure is in place for easy expansion of the remaining pages.
|
||||
|
||||
32
tcg/project-sol-rulebook/README.md
Normal file
32
tcg/project-sol-rulebook/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Project Sol - Online Rulebook
|
||||
|
||||
Hey, guys! Thanks for checking this out. Below are my goals for the system. I think if we've ever discussed rpgs in any detail you all know I love the dice pool mechanic, but hate Shadowrun's magic system. Those feelings combined with my love of sci-fi spawned this project. What was originally my attempt at "fixing" Shadowrun 5, grew legs and turned into its own system.
|
||||
|
||||
# Goals of the System
|
||||
|
||||
- Utilize Shadowrun's dice pool system combined with systems influenced by Starfinder, Traveller, and Red Markets.
|
||||
- Facilitate awesome cyberpunk and sci-fi adventures that could be any ratio of Shadowrun, Firefly, Star Trek, The Expanse, or Star Wars.
|
||||
|
||||
# Table of Contents
|
||||
|
||||
---
|
||||
|
||||
## What is Project Sol?
|
||||
|
||||
- [How to Play](./how-to-play.md)
|
||||
- [Character Creation](./character-creation.md)
|
||||
- [Attributes and Skills](./attributes-and-skills.md)
|
||||
- [Character Qualities](./character-qualities.md)
|
||||
- [Wealth](./wealth.md)
|
||||
- [Equipment](./equipment.md)
|
||||
|
||||
## Why Can't We Be Friends?
|
||||
|
||||
- [Physical Combat](./physical-combat.md)
|
||||
- [Social Combat](./social-combat.md)
|
||||
- [Spacecraft Combat](./spacecraft-combat.md)
|
||||
- [Drones](./drones.md)
|
||||
- [Hacking](./hacking.md)
|
||||
- [Structures](./structures.md)
|
||||
- [Afflictions and Conditions](./afflictions-and-conditions.md)
|
||||
|
||||
6
tcg/project-sol-rulebook/afflictions-and-conditions.md
Normal file
6
tcg/project-sol-rulebook/afflictions-and-conditions.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Afflictions and Conditions
|
||||
|
||||
*(This page requires full data retrieval from Notion page ID: 43610355-65ea-41c5-b875-56e13e21a6f0)*
|
||||
|
||||
Affliction and condition mechanics would be documented here based on the Notion page content.
|
||||
|
||||
56
tcg/project-sol-rulebook/attributes-and-skills.md
Normal file
56
tcg/project-sol-rulebook/attributes-and-skills.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Attributes and Skills
|
||||
|
||||
# Attributes
|
||||
|
||||
Attributes make up your character's physical and mental self. They are used as part of every test made and form the basis of all of your character's calculated stats. All attributes (including Edge) have a minimum of 1 and maximum of 6.
|
||||
|
||||
# Physical Attributes
|
||||
|
||||
## Agility
|
||||
|
||||
Agility measures things like hand-eye coordination, flexibility, nimbleness, and balance. Agility is the most important attribute when it comes to scoring hits during combat, as you need to be coordinated to land your blows, whether you're swinging a sword or carefully aiming a rifle. It also is critical in non-combat situations, such as sneaking quietly past security guards or smoothly lifting a keycard from its secured position.
|
||||
|
||||
## Body
|
||||
|
||||
Body measures your physical health and resiliency. It affects how much damage you can take and stay on your feet, how well you resist damage coming your way, your ability to recover from poisons and diseases, and things of that nature.
|
||||
|
||||
## Reaction
|
||||
|
||||
Reaction is about reflexes, awareness, and your character's ability to respond to events happening around them. Reaction plays an important role in deciding how soon characters act in combat and how skilled they are in avoiding attacks from others. It also affects your piloting ability in vehicles of all sizes.
|
||||
|
||||
## Strength
|
||||
|
||||
Strength is an indicator of, well, how strong your character is. The higher your strength, the more damage you'll do when you're raining blows down on an opponent, and the more you'll be able to move or carry when there's stuff that needs to be moved. Or carried. Strength is also important with athletic tasks such as climbing, running, and swimming.
|
||||
|
||||
# Mental Attributes
|
||||
|
||||
## Charisma
|
||||
|
||||
Charisma is your force of personality, the persuasiveness and charm you can call on to get people to do what you want without having to go to the trouble of pulling a gun on them. It's not entirely about your appearance, but it's also not entirely not about your appearance. What it's mostly about is how you use what you have—your voice, your face, your words, and all the tools at your disposal—to charm and/or intimidate the people you encounter.
|
||||
|
||||
## Intuition
|
||||
|
||||
Intuition is the voice of your gut, the instinct that tells you things before your logical brain can figure them out. Intuition helps you anticipate ambushes, notice that something is amiss or out of place, and stay on the trail of someone you're pursuing.
|
||||
|
||||
## Logic
|
||||
|
||||
The Logic attribute measures the cold, calculating power of your rational mind. Whether you are attempting to repair complicated machinery or patch up an injured teammate, Logic helps you get things right.
|
||||
|
||||
## Willpower
|
||||
|
||||
Willpower is your character's desire to push through adversity and to stay upright after being nailed in the head with a sap. Whether you're testing yourself against a toxic wilderness or a pack of leather-clad orks with crowbars, Willpower will help you make it through.
|
||||
|
||||
# Special Attribute
|
||||
|
||||
## Edge
|
||||
|
||||
Edge is the ultimate intangible, that certain something that provides a boost when you need it, that gets you out of a tough spot when the chips are down. It's not used to calculate dice pools; instead, you spend a point of Edge to acquire a certain effect. It is the number one factor differentiation player characters from everyone else in the universe. Edge can be permanently decreased to miraculously survive a sure-death event.
|
||||
|
||||
# Skills
|
||||
|
||||
Skills are all of the learned abilities by your character. Each skill have up to 12 ranks with no minimum.
|
||||
|
||||
## Skill Table
|
||||
|
||||
*(See Skill Table database in Notion for complete skill list with attributes and descriptions)*
|
||||
|
||||
134
tcg/project-sol-rulebook/character-creation.md
Normal file
134
tcg/project-sol-rulebook/character-creation.md
Normal file
@ -0,0 +1,134 @@
|
||||
# Character Creation
|
||||
|
||||
When you begin, your character is a fledgling with just enough ability to walk and not regularly shit themselves. You will guide your character through as many steps of their life as you would like before running the final calculations and being ready for play. It is recommended to take your character to at least early adulthood for your GM's mental well being.
|
||||
|
||||
For each age, take the advancements listed first. Once those are added to your character sheet, continue to the choice provided by the age. You may not decrease any attribute (including Edge) below 1.
|
||||
|
||||
**Advancement Notation**
|
||||
|
||||
- **+/-X <Attribute/Skill>** - this adds or subtracts to the attribute or skill listed
|
||||
- **+/-X <Attribute/Skill> or +/-Y <Attribute/Skill>** - you must choose between the two options
|
||||
- Reminder: attributes have a max of 6 and minimum of 1; skills have a max of 12 and a minimum of 0
|
||||
|
||||
At adult ages, the final advancement is selecting an Occupation. These do not include ranks, but progression (or lack thereof) through the career paths should be considered with each selection.
|
||||
|
||||
## Occupations
|
||||
|
||||
*(See Occupations database in Notion for full list)*
|
||||
|
||||
# Step 1: Character's Lifepath
|
||||
|
||||
Starting with Fledgling, take your character through as many ages as you would like. Apply their advancements to your character in the order presented. After completing any age, you may end this step and continue to step 2.
|
||||
|
||||
Your character starts as a Fledgling with the attributes listed below.
|
||||
|
||||
## Fledgling
|
||||
|
||||
<details>
|
||||
<summary>Expand for Fledgling</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific attributes and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Early Childhood
|
||||
|
||||
<details>
|
||||
<summary>Expand for Early Childhood</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Pre-Teen Years
|
||||
|
||||
<details>
|
||||
<summary>Expand for Pre-Teen Years</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Teenage Years
|
||||
|
||||
<details>
|
||||
<summary>Expand for Teenage Years</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Young Adult Years
|
||||
|
||||
<details>
|
||||
<summary>Expand for Young Adult Years</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Thirties
|
||||
|
||||
<details>
|
||||
<summary>Expand for Thirties</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Fourties
|
||||
|
||||
<details>
|
||||
<summary>Expand for Fourties</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Fifties
|
||||
|
||||
<details>
|
||||
<summary>Expand for Fifties</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Sixties
|
||||
|
||||
<details>
|
||||
<summary>Expand for Sixties</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
## Seventies
|
||||
|
||||
<details>
|
||||
<summary>Expand for Seventies</summary>
|
||||
|
||||
*(Toggle content would be expanded here with specific advancements and choices)*
|
||||
|
||||
</details>
|
||||
|
||||
# Step 2: Round Out Character
|
||||
|
||||
Your character has likely accumulated some wealth. As detailed in the Wealth chapter, this affords your character some buying power. In addition to the wealth you have accumulated, characters may purchase positive and negative qualities.
|
||||
|
||||
## Select Qualities
|
||||
|
||||
You may purchase both positive and negative qualities. Negative Qualities provide points to spend on Positive Qualities. You may receive up to 10 points this way - you can take more negative qualities, but receive no more points. Complete information on qualities can be found on the qualities page.
|
||||
|
||||
## Purchase Key Equipment
|
||||
|
||||
Characters accumulate wealth through their lifepath. At this time, gain [Wealth x 2] Credits for purchasing equipment. Wealth Rating limitations apply to purchases at character generation. Complete information on equipment can be found on the equipment page.
|
||||
|
||||
# Step 3: Final Calculations
|
||||
|
||||
At this point, your character is just about complete. There are only a handful of final touches to be made before the character is ready to be played.
|
||||
|
||||
## Final Calculations
|
||||
|
||||
*(See Final Calculations database in Notion for specific formulas)*
|
||||
|
||||
12
tcg/project-sol-rulebook/character-qualities.md
Normal file
12
tcg/project-sol-rulebook/character-qualities.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Character Qualities
|
||||
|
||||
Qualities can only be selected by players during character creation. Positive qualites cost points and negative qualities provide bonus points to be spent on positive qualities.
|
||||
|
||||
# Positive Qualities
|
||||
|
||||
*(See Positive Qualities database in Notion for full list)*
|
||||
|
||||
# Negative Qualities
|
||||
|
||||
*(See Negative Qualities database in Notion for full list)*
|
||||
|
||||
6
tcg/project-sol-rulebook/drones.md
Normal file
6
tcg/project-sol-rulebook/drones.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Drones
|
||||
|
||||
*(This page requires full data retrieval from Notion page ID: 7f49463f-50a9-4812-a57c-820fe78bba48)*
|
||||
|
||||
Drone mechanics and rules would be documented here based on the Notion page content.
|
||||
|
||||
69
tcg/project-sol-rulebook/equipment.md
Normal file
69
tcg/project-sol-rulebook/equipment.md
Normal file
@ -0,0 +1,69 @@
|
||||
# Equipment
|
||||
|
||||
<details>
|
||||
<summary>Table of Contents</summary>
|
||||
|
||||
*(Equipment sections organized by category)*
|
||||
|
||||
</details>
|
||||
|
||||
# Item Bulk
|
||||
|
||||
Each item has a representation of its Bulk which is a number, the letter "L" if it has light bulk, or a dash "-" if it has negligible bulk. For example, a Heavy machine gun has Bulk 4, binoculars have bulk L, and a flashlight has bulk "-".
|
||||
|
||||
Every 5 items that have light bulk have a total of 1 bulk and fractions don't count - so 5 items with light bulk have a total bulk of 1 and 9 such items also have a total of 1 bulk. Items that have negligible bulk count toward your bulk limit only if the GM determines that you are carrying an unreasonable number of them.
|
||||
|
||||
# Bulk Limits
|
||||
|
||||
A character may carry an amount of Bulk equal to BOD + STR without penalty. Characters carrying more than that suffer the impaired condition (default on all physical actions) and their Movement speed decreases by 1.
|
||||
|
||||
Characters may not willingly carry more than double their BOD + STR. If the character is forced to do so (due to changing gravity, for example), they suffer the debilitated condition (default on all physical actions and suffer glitch). If the character does not get any hits on the test, it would be a critical glitch.
|
||||
|
||||
# Buying and Selling Gear
|
||||
|
||||
In general, all legal gear can be purchased in any civilized area for a portion of its Cost Rating. The details of Cost Rating and Wealth Rating and how purchasing works can be found in Wealth section.
|
||||
|
||||
### Selling Gear
|
||||
|
||||
- Action Type: Out of Combat
|
||||
- Test: Negotiation + CHA (4)
|
||||
- *(Additional selling mechanics details would be nested here)*
|
||||
|
||||
# Gear Durability
|
||||
|
||||
All gear is expected to survive the basic wear and tear a crew's dangerous life. Under special circumstances such as targeted attacks or mishaps, equipment can be damaged in the line of duty. If the gear is not irreparably damaged, an engineer with adequate work space may attempt to repair it with a threshold 3 repair test. Without proper work space, the threshold to repair is 5. Weapons and armor are repaired with the Armorer skill; all other equipment is repaired with the Mechanic General skill.
|
||||
|
||||
### Fragile Quality
|
||||
|
||||
If equipment with the fragile quality is ever damaged, it is destroyed. The cost to repair is half the cost of the base (unmodded) item.
|
||||
|
||||
### Repairing Equipment
|
||||
|
||||
- Action Type: Out of Combat
|
||||
- Test: [Armorer, Engineering, or Mechanic] + LOG (2 or 4)
|
||||
- *(Nested: Success and failure conditions)*
|
||||
- Description: If the character does not have access to a proper work space, the threshold to repair is 4.
|
||||
|
||||
# Illegal Equipment
|
||||
|
||||
Some equipment will be prohibited from specific regions based on the law level and any idiosyncrasies of the ruling body. With GM approval, most equipment can be found through local black markets at the regular price.
|
||||
|
||||
### Find Illegal Equipment
|
||||
|
||||
- Action Type: Out of Combat
|
||||
- Test: Etiquette + CHA (2 - 5)
|
||||
- *(Nested: threshold variations by law level)*
|
||||
- Description: The threshold is set by the GM based on the law level of the region. A highly regulated region might be threshold 5 whereas a backwater area might be 2.
|
||||
|
||||
# Gear Listing
|
||||
|
||||
- [Armor](./equipment/armor.md)
|
||||
- [Augmentation](./equipment/augmentation.md)
|
||||
- [Biotech & Drugs](./equipment/biotech-and-drugs.md)
|
||||
- [Computers & Electronics](./equipment/computers-and-electronics.md)
|
||||
- [Covert Ops](./equipment/covert-ops.md)
|
||||
- [Drones and Vehicles](./equipment/drones-and-vehicles.md)
|
||||
- [Explosives](./equipment/explosives.md)
|
||||
- [Spacecrafts](./equipment/spacecrafts.md)
|
||||
- [Weapons](./equipment/weapons.md)
|
||||
|
||||
6
tcg/project-sol-rulebook/hacking.md
Normal file
6
tcg/project-sol-rulebook/hacking.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Hacking
|
||||
|
||||
*(This page requires full data retrieval from Notion page ID: 94a4bb88-b9eb-4900-862f-3231fc3bdac5)*
|
||||
|
||||
Hacking mechanics and rules would be documented here based on the Notion page content.
|
||||
|
||||
131
tcg/project-sol-rulebook/how-to-play.md
Normal file
131
tcg/project-sol-rulebook/how-to-play.md
Normal file
@ -0,0 +1,131 @@
|
||||
# How to Play
|
||||
|
||||
# Dice Pools
|
||||
|
||||
To get anything done, you'll be rolling a pool of six-sided dice and comparing hits to either a threshold or an opposing dice pool's number of hits. Just like Shadowrun, 5s and 6s count as hits.
|
||||
|
||||
In cases where time is on your side, the GM may allow you to buy hits. For every four dice in your dice pool, you may buy 1 hit. For example: 12 dice buys 3 hits, 8 dice buys 2 hits, and 10 dice also buys 2 hits.
|
||||
|
||||
# Skill Tests
|
||||
|
||||
There are two types of skill tests: threshold tests and opposed tests.
|
||||
|
||||
A threshold test generally occurs when a character has to use their abilities to accomplish something non-trivial. Threshold test notation looks like this: <Skill Used> + <Attribute Used> (<Difficulty/Threshold>). An example would be: Perception + INT (2). If the number of hits you roll equals or exceeds the threshold, you succeed in the action. Any hits rolled over the threshold are called net hits.
|
||||
|
||||
Opposed test notation looks like this: <Skill Used> + <Attribute Used> vs <Skill/Attribute Used> + <Attribute Used>. An example would be: Sidearms + AGI vs REA + INT. The attacking (or instigating) character has the advantage so must achieve at least the same number of hits as the defending character in order to succeed.
|
||||
|
||||
### Skill Test Thresholds
|
||||
|
||||
| Difficulty | Threshold |
|
||||
| --- | --- |
|
||||
| Simple | 1 |
|
||||
| Average | 2 |
|
||||
| Difficult | 3 |
|
||||
| Very Difficult | 4 |
|
||||
| Formidable | 6 |
|
||||
| Absurd | 8+ |
|
||||
|
||||
# Bonuses and Penalties
|
||||
|
||||
Different sources may apply bonuses and penalties to a character's dice pool. These are combined and applied as a single dice pool modification. A penalty of 2 and a bonus of 4 would combine to be a bonus of 2.
|
||||
|
||||
A final dice pool bonus may never exceed the rating of the skill being tested. In addition, a dice pool may never be lower than 1.
|
||||
|
||||
> 💡 For example, Cole has 0 ranks in Longarms (he's more of a pistolero), but picks up a sniper rifle with a scope mod on it. The scope increases the accuracy of the rifle by one bringing it up to 1. Unfortunately, Cole may not receive a bonus on his longarms test since he has no ranks in the skill.
|
||||
|
||||
> 💡 In another example, Hyde has 2 ranks in Electronic Warfare and is attempting to hack a terminal with a modified computer. Normally that computer would provide a dice pool bonus of 3, but that is limited to 2 since Hyde only has 2 ranks in the skill.
|
||||
|
||||
# Glitches
|
||||
|
||||
Glitches introduce a consequence to the action being taken - they do not cause failure on their own. Glitches happen when <u>half or more</u> of the dice rolled are 1s. Three out of six dice is a glitch - three out of seven dice is not a glitch. This is different than Shadowrun. If you glitch a roll and also don't get any hits, that is a critical glitch. Those are super bad.
|
||||
|
||||
By definition, a critical glitch can only occur on a failed test so these results include both a failure of the test as well as a consequence to the action.
|
||||
|
||||
# Critical Successes
|
||||
|
||||
A critical success occurs when you succeed on a test with three net hits. The outcome of a critical success is generally a success on the test plus providing the character with an added bonus.
|
||||
|
||||
Critical success results are generally not listed in action definitions as they are very context or scenario dependent.
|
||||
|
||||
# Defaulting on Tests
|
||||
|
||||
If a character does not have a skill required for a skill test, they may still attempt the test by defaulting. This means their dice pool will just be the Attribute. If the character is asked to make a Pilot Ground Craft + Reaction test and they have 0 ranks in Pilot Ground Craft, they may default on the test and just roll Reaction.
|
||||
|
||||
If the skill has an untrained penalty, there is a dice pool penalty of 3 for defaulting. For example, if a character is asked to make a Mechanic Vehicles + Logic test and they have no ranks in Mechanic Vehicles, they would instead roll Logic - 3 (minimum 1 die).
|
||||
|
||||
# Physical vs Mental Tests
|
||||
|
||||
Skills may be tested in two different manners: physical tests and mental tests. Physical tests use the physical attributes linked to skills to perform active tests such as rolling Sidearms + AGI in order to shoot the bad guy. As a demonstration of the flexibility of skill and attribute pairings, skills can be tested as mental tests. For example, a character may attempt to identify the intricacies of a specific weapon using Sidearms + LOG.
|
||||
|
||||
# Teamwork
|
||||
|
||||
Characters may work together to achieve their goals mechanically in two ways. The assist action allows one character to provide bonus dice to another. The group action allows characters to work together and take the best result. All characters participating in the teamwork test suffer any consequences of the test.
|
||||
|
||||
## Assist
|
||||
|
||||
When you assist another character, you make your own test and provide the other character a number of bonus dice equal to your hits. As always, the bonus they receive is capped by their skill ranks. By tying yourself to this action, you suffer from consequences of the other character's action if they fail.
|
||||
|
||||
## Group Action
|
||||
|
||||
To perform a group action, a group leader is elected. Leading a group action, you coordinate multiple members of the team to tackle a problem together. Each involved character makes a test with the best result result being used for the entire group. The danger of a group action comes from the stress of herding figurative cats - for every character who rolled zero successes on their test, the group leader takes an unresisted Level 1 wound from the stress of dragging them along.
|
||||
|
||||
# Try Again
|
||||
|
||||
At the GM's discretion, players may attempt a failed skill test again at a dice pool penalty of 2. This would typically only be allowed if the the only danger to the characters is the time sunk into an additional attempt. Tests may be tried again until the character's dice pool would be reduced to 0.
|
||||
|
||||
# Edge
|
||||
|
||||
Edge is a measure of the character's luck. Their Edge attribute represents the number of Edge points a character has to spend during game play. Edge points can be spent for a variety of benefits listed below. There are two ways to use Edge points: Spending Edge and Burning Edge. Spent Edge is temporarily unavailable, but does not affect your Edge attribute. Burnt Edge decreases your Edge attribute by one, but has extremely potent effects.
|
||||
|
||||
## Spending Edge
|
||||
|
||||
To activate any of the effects listed below, a character must spend one point of Edge. This does not decrease the Edge attribute - the point is temporarily unavailable and may be regained as described in the following section. A character can only spend one point of Edge on any test or action. For example, if you spend a point of Edge to Push the Limit, you may not then spend another point of Edge to negate the effects of a glitch.
|
||||
|
||||
### Spending Edge Effects
|
||||
|
||||
| Effect Name | Description |
|
||||
| --- | --- |
|
||||
| Push the Limit | Declare Push the Limit before rolling. For this test, die results of 3, 4, 5, and 6 count as successes. |
|
||||
| Dead Man's Trigger | When your character is about to fall unconscious or die, you can spend a point of Edge to make a BOD + WIL (3), instant test. If you succeed, you may spend any remaining action points you have on a single action before your character blacks out. |
|
||||
| Blitz | Receive a dice pool bonus equal to REA + INT to your initiative test. For most characters, this will double your Initiative dice pool. Usable once per test. |
|
||||
| Seize the Initiative | Move to the top of the initiative order, regardless of your initiative score for one round of combat. |
|
||||
| Close Call | Either negate the effects of a glitch or turn a critical glitch into a glitch. Usable once per test. |
|
||||
| Second Chance | After rolling for a test, re-roll all dice which weren't hits. Usable once per test. |
|
||||
|
||||
## Regaining Edge
|
||||
|
||||
A character's Edge refreshes at the beginning of each play session. It is not tied to any in-game metrics. Additionally, the Game Master can reward players by refreshing a single point of Edge in exchange for inventive or entertaining actions in the course of a game play session. These refreshed Edge points do not have any effect on the character's Edge attribute.
|
||||
|
||||
## Burning Edge
|
||||
|
||||
For guaranteed results, characters may burn a point of Edge which decreases their Edge attribute by one. The point may not be refreshed, but the character may still advance the attribute through normal means.
|
||||
|
||||
### Burning Edge Effect
|
||||
|
||||
| Effect Name | Description |
|
||||
| --- | --- |
|
||||
| Not Dead Yet | When a character would die, they may instead miraculously survive and decrease their highest Physical Attribute by 1. If there is a tie for highest, the character may choose which to decrease. |
|
||||
| Smackdown | Automatically succeed in the action with four net hits. You may use this on any test. |
|
||||
|
||||
# Character Advancement
|
||||
|
||||
## Skills
|
||||
|
||||
### Experience
|
||||
|
||||
Characters grow in power through successfully using their skills. Each time a character rolls a number of hits greater than their ranks in that skill (one if the character has zero ranks), they gain one progress point in that skill. For every five progress points in a skill, the character gains one rank in the skill. Progress points in one skill have no bearing on progress points for any other skills. Characters may only gain one progress point per encounter. This means if a skill is being used repeatedly to confront the same challenge, only one progress point is earned.
|
||||
|
||||
> 💡 Example: Assume Malcolm has 4 ranks in the Etiquette skill. Each time Malcolm rolls 5 or more hits on an Etiquette test, he gains one progress point for the Etiquette skill. This includes tests during which Malcolm uses Edge. When Malcolm gets his fifth progress point, he increases his Etiquette skill rating by 1 to 5.
|
||||
|
||||
### Training
|
||||
|
||||
Characters may receive skill training from a variety of sources. It could be virtual self-study or time with a trainer; this is just a matter of flavor and does not affect the cost. Characters must train the next rank in the skill and may not train multiple ranks in one session. For example, if Kyra has 2 ranks in Sidearms, she may not train her fourth rank until she has earned her third rank (through training or experience). Training a skill to the next rank has a Cost Rating of <New Rank> x 2.
|
||||
|
||||
Training sessions should be limited to one per game session.
|
||||
|
||||
> 💡 Example: Kyra wants to train her Sidearms skill form rank 2 to rank 3. The training has a Cost Rating of 6. Assuming Kyra can afford the training, she may increase her rank and clear any unused progress points.
|
||||
|
||||
## Attributes
|
||||
|
||||
Attributes can only be increased through augmentations (see equipment chapter).
|
||||
|
||||
92
tcg/project-sol-rulebook/physical-combat.md
Normal file
92
tcg/project-sol-rulebook/physical-combat.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Physical Combat
|
||||
|
||||
Physical combat is cyclical: everyone rolls Initiative and acts from highest to lowest and then loops back to the top. Each combat round takes roughly 10 seconds. If any combatants are surprised, they start the combat with only one action point. When a character may act during a combat round, they have three action points to spend on combat actions. At the end of their turn, their points refresh. The action points may be spent as Reactions before their turn, but they lose access to the points until their points refresh at the end of their next turn.
|
||||
|
||||
# Combat Sequence
|
||||
|
||||
1. **Determining Awareness:** The GM establishes whether any combatants may be surprised. Any combatants who may be surprised may make a Surprise Test: Reaction + Intuition (3), Instant. Surprised characters only start combat with one action point.
|
||||
|
||||
2. **Determining Initiative Order:** The players roll an Initiative Test: REA + INT + any Initiative bonuses for all present combatants. Enemies have a static initiative score rolled by their leader. Each player who rolls higher, acts before the enemies each round. Other players act after the enemies. Players may act in any order when it is their turn.
|
||||
|
||||
3. **Combat Round:** In initiative order, characters may spend their action points. At the end of each character's turn, their action points refresh as normal.
|
||||
|
||||
4. **Continuing Combat:** If a new character enters combat, they roll an initiative test to determine their initiative score and the GM inserts them into the established initiative order with their full action points. They do not take a turn if they joined in the middle of a round; they are eligible for a regular turn the following round.
|
||||
|
||||
# Actions in Combat
|
||||
|
||||
Many pieces of gear provide actions available to the character. In general, attack actions will be <Weapon Skill> + Attribute vs REA + INT to inflict a wound.
|
||||
|
||||
In addition to gear- and weapon-specific actions, below are actions available to all characters.
|
||||
|
||||
## Actions in Combat
|
||||
|
||||
*(See Actions in Combat database in Notion for complete list)*
|
||||
|
||||
# Damage in Combat
|
||||
|
||||
When an attack is successful, meaning the attacker rolled as many or more hits than the defender, attack damage is determined by the weapon used.
|
||||
|
||||
Before the defender takes the wound listed for the weapon's attack, they roll their Armor dice pool to soak damage. Each hit on this test decreases the level of the wound to a minimum of zero. If it is reduced to zero, the attack was a grazing hit and deals no damage.
|
||||
|
||||
## Wounds
|
||||
|
||||
Player characters start with the ability to take 1 level 3 wound, 2 level 2 wounds, and 3 level 1 wounds. When taking a wound, note the in-character wound taken in the box. If you have no boxes available for the wound being taken, increase the wound level by one. If a character ever runs out of boxes to take a wound, they die.
|
||||
|
||||
Each wound level imposes a penalty on the character. Multiple wounds of a single level do not increase the penalty. So 1 level 1 wound would impose the same penalty as 3 level 1 wounds.
|
||||
|
||||
Level 3 wounds will leave behind evidence of the wound long after it is healed. That may be a scar or a character personality effect based on the source of the wound.
|
||||
|
||||
When a character takes a number of wounds greater than or equal to their BOD, they enter their last stand. They no longer refresh their action points; when they run out, they fall unconscious.
|
||||
|
||||
## Critical Successes In Combat
|
||||
|
||||
Unless otherwise noted, a critical success on an attack roll increases the Wound level of the attack by 1. This stacks for multiples of 3 net hits: 6 net hits increases by 2, 9 net hits increases by 3, etc.
|
||||
|
||||
### Wound Penalties
|
||||
|
||||
| Wound Level | Penalty |
|
||||
| --- | --- |
|
||||
| *(See Wound Penalties database for specific penalties)* | |
|
||||
|
||||
# Combat Modifiers
|
||||
|
||||
## Concealment
|
||||
|
||||
Concealment and line of sight between characters is determined by drawing lines between corners of the two characters. If a straight line can be drawn from all corners of Character A's square to all corners of Character B's square, they have clear line of sight and no concealment. If no straight lines can be drawn from any corner of Character A's square to any corner of Character B's square, they have concealment and may not be targeted by an attack.
|
||||
|
||||
## Cover
|
||||
|
||||
When a character has cover from another character attacking them, they gain a dice pool bonus to their dodge (REA + INT) pool based on the type of cover. A targeted character is considered to have cover if the attacking character has line of sight on the target and lines from all corners of the attacking character's square cross through the targeted character's cover.
|
||||
|
||||
### Cover Bonuses
|
||||
|
||||
| Cover Type | Bonus |
|
||||
| --- | --- |
|
||||
| *(See Cover Bonuses database for specific bonuses)* | |
|
||||
|
||||
## Flanked
|
||||
|
||||
Flanking is determined in a similar fashion to line of sight. A targeted character is flanked if any two enemy characters can draw a line from any corner of enemy character A's square to any corner of enemy character B's that crosses through the targeted character. A flanked character suffers a dice pool penalty of 2 when dodging attacks.
|
||||
|
||||
## Prone
|
||||
|
||||
Being prone provides a dice pool bonus of 2 to dodge ranged attacks and a dice pool penalty of 2 to dodge melee attacks. The Stand Up/Go Prone action can be used in combat to change position.
|
||||
|
||||
# Injury and Recovery
|
||||
|
||||
## Natural Recovery
|
||||
|
||||
To recover from wounds, characters make one BOD + WIL test per wound detailed below. Wounds must be recovered from lowest level to highest. The character must rest for the entire duration for it to count - forced naps and unconsciousness also do the trick.
|
||||
|
||||
### Natural Recovery
|
||||
|
||||
- Action Type: None
|
||||
- Test: BOD + WIL (1 / 3 / 5), (2 / 8 / 24 hours)
|
||||
- *(Nested: Level 1 wounds heal after 2 hours rest, Level 2 after 8 hours, Level 3 after 24 hours)*
|
||||
- Point Cost: None
|
||||
- Description: The threshold and duration of the natural recovery attempt depends on the level of the wound being recovered. For level 1 wounds, the first numbers are used; for level 2 wounds, the second numbers are used; and for level 3 wounds, the third numbers are used.
|
||||
|
||||
## Medkits
|
||||
|
||||
Medkits may be be used to both heal characters in the heat of battle as well as support the natural recovery of wounds. Full details can be found in the biotech equipment section.
|
||||
|
||||
6
tcg/project-sol-rulebook/social-combat.md
Normal file
6
tcg/project-sol-rulebook/social-combat.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Social Combat
|
||||
|
||||
*(This page requires full data retrieval from Notion page ID: 3f951086-b61b-4c91-a838-5e60f993b84e)*
|
||||
|
||||
Social combat mechanics and rules would be documented here based on the Notion page content.
|
||||
|
||||
6
tcg/project-sol-rulebook/spacecraft-combat.md
Normal file
6
tcg/project-sol-rulebook/spacecraft-combat.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Spacecraft Combat
|
||||
|
||||
*(This page requires full data retrieval from Notion page ID: 37d2ff13-b1ed-4bdf-b6d4-16995ce37e88)*
|
||||
|
||||
Spacecraft combat mechanics and rules would be documented here based on the Notion page content.
|
||||
|
||||
6
tcg/project-sol-rulebook/structures.md
Normal file
6
tcg/project-sol-rulebook/structures.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Structures
|
||||
|
||||
*(This page requires full data retrieval from Notion page ID: ceba7fa2-9a64-4ad9-b9d0-2dc1d4ac9809)*
|
||||
|
||||
Structure mechanics and rules would be documented here based on the Notion page content.
|
||||
|
||||
47
tcg/project-sol-rulebook/wealth.md
Normal file
47
tcg/project-sol-rulebook/wealth.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Wealth
|
||||
|
||||
# Wealth Rating
|
||||
|
||||
A character's wealth rating encompasses more than just the figures in their bank account. It encompasses their ability to gain credit, transfer assets and take advantage of special pricing. It's an abstract value, which means the players don't need to track every credstick, investment, transfer and mortgage. They need only worry about the big picture.
|
||||
|
||||
A wealth rating is a value commonly ranging from 0 (dirt broke and in debt) to 10 (filthy rich), although the super-rich can have wealth ratings that well exceed 10. The average middle-class character has around Wealth 5, although some places are more wealthy than others and local average may vary signiicantly.
|
||||
|
||||
> 💡
|
||||
> - Wealth 0: completely broke
|
||||
> - Wealth 5: average, middle-class family
|
||||
> - Wealth 10: wealthy
|
||||
> - Wealth 15+: the corporate elite
|
||||
|
||||
## Increasing Wealth Rating
|
||||
|
||||
Each character may individually increase their Wealth by investing credits into their Wealth Rating. The cost of upgrading wealth is equal to the new rating in Credits. Like skills, each Wealth Rating has to be purchased individually without skipping any.
|
||||
|
||||
> 💡 Alyssa currently has a Wealth Rating of 6 and wants to increase her purchasing power. By spending 7 credits, she can increase her Wealth Rating to 7.
|
||||
|
||||
# Cost Ratings & Credits
|
||||
|
||||
Whenever a character purchases a piece of equipment or incurs some other sort of expense, this expense has a cost rating. Like the character's wealth ratings, this cost rating is an abstract measure of the cost to the character, abstracting away the need to worry about rebates, loan payments or maintenance costs. Like wealth rating, a cost rating is exponential with each cost rating being approximately double the cost of the rating below it.
|
||||
|
||||
Purchases are made by a character spending credits equal to the cost rating of the item. Characters may purchase anything with a Cost Rating up to their current Wealth Rating by reducing their credit amount by the cost of the item.
|
||||
|
||||
If the item's Cost Rating is higher than the character's Wealth Rating, they may still make the purchase with their available credits, but must also decrease their Wealth Rating by [Item's Cost Rating] - [Current Wealth]. Wealth Rating may never go below 0.
|
||||
|
||||
> 💡 Jensen wants to purchase a vehicle that has a Cost Rating of 8, but Jensen only has a Wealth Rating of 6. If he has 8 available Credits, he may still make the purchase, but must decrease his Wealth Rating by 2 (the difference between Cost 8 and Wealth 6) to 4.
|
||||
|
||||
## Pooling Credits
|
||||
|
||||
Characters may choose to pool their money to make a large, collective purchase. Anyone who contributes to the pool is subject to the Cost to Wealth Rating limits. If any of the pooling characters have an insufficient Wealth Rating, once the purchase is made the purchasing group must combine to decrease their Wealth by the greatest Cost to Wealth Rating difference starting with the highest Wealth.
|
||||
|
||||
If more than one character contributing to a purchase has an insufficient Wealth Rating, only the greatest difference is applied to the purchase.
|
||||
|
||||
> 💡 Allen, Blade, and Caesar are pooling their credits to make a Cost Rating 13 purchase. Their Wealth Ratings are 6, 7, and 9 respectively. After spending the 13 credits to make the purchase, the group must decrease Wealth Ratings by a total of 7 (the difference between Cost 13 and Wealth 6).
|
||||
>
|
||||
> The following steps would be taken:
|
||||
> - Caesar from 9 to 8 (1 total decrease)
|
||||
> - Caesar from 8 to 7 (2 total decrease)
|
||||
> - Caesar from 7 to 6 (3 total decrease)
|
||||
> - Blade from 7 to 6 (4 total decrease)
|
||||
> - Caesar from 6 to 5 (5 total decrease)
|
||||
> - Blade from 6 to 5 (6 total decrease)
|
||||
> - Allen from 6 to 5 (7 total decrease)
|
||||
|
||||
292
tdarr/CONTEXT.md
292
tdarr/CONTEXT.md
@ -1,152 +1,204 @@
|
||||
# Tdarr Transcoding System - Technology Context
|
||||
|
||||
## Overview
|
||||
Tdarr is a distributed transcoding system that converts media files to optimized formats. This implementation uses an intelligent gaming-aware scheduler with unmapped node architecture for optimal performance and system stability.
|
||||
Tdarr is a distributed transcoding system that converts media files to optimized formats. The current deployment runs on a dedicated Ubuntu server with GPU transcoding and NFS-based media storage.
|
||||
|
||||
## Architecture Patterns
|
||||
## Current Deployment
|
||||
|
||||
### Distributed Unmapped Node Architecture (Recommended)
|
||||
**Pattern**: Server-Node separation with local high-speed cache
|
||||
- **Server**: Tdarr Server manages queue, web interface, and coordination
|
||||
- **Node**: Unmapped nodes with local NVMe cache for processing
|
||||
- **Benefits**: 3-5x performance improvement, network I/O reduction, linear scaling
|
||||
### Server: ubuntu-manticore (10.10.0.226)
|
||||
- **OS**: Ubuntu 24.04.3 LTS (Noble Numbat)
|
||||
- **GPU**: NVIDIA GeForce GTX 1070 (8GB VRAM)
|
||||
- **Driver**: 570.195.03
|
||||
- **Container Runtime**: Docker with Compose
|
||||
- **Web UI**: http://10.10.0.226:8265
|
||||
|
||||
**When to Use**:
|
||||
- Multiple transcoding nodes across network
|
||||
- High-performance requirements (10GB+ files)
|
||||
- Network bandwidth limitations
|
||||
- Gaming systems requiring GPU priority management
|
||||
### Storage Architecture
|
||||
| Mount | Source | Purpose |
|
||||
|-------|--------|---------|
|
||||
| `/mnt/truenas/media` | NFS from 10.10.0.35 | Media library (48TB total, ~29TB used) |
|
||||
| `/mnt/NV2/tdarr-cache` | Local NVMe | Transcode work directory (1.9TB, ~40% used) |
|
||||
|
||||
### Configuration Principles
|
||||
1. **Cache Optimization**: Use local NVMe storage for work directories
|
||||
2. **Gaming Detection**: Automatic pause during GPU-intensive activities
|
||||
3. **Resource Isolation**: Container limits prevent kernel-level crashes
|
||||
4. **Monitoring Integration**: Automated cleanup and Discord notifications
|
||||
### Container Configuration
|
||||
**Location**: `/home/cal/docker/tdarr/docker-compose.yml`
|
||||
|
||||
## Core Components
|
||||
|
||||
### Gaming-Aware Scheduler
|
||||
**Purpose**: Automatically manages Tdarr node to avoid conflicts with gaming
|
||||
**Location**: `scripts/tdarr-schedule-manager.sh`
|
||||
|
||||
**Key Features**:
|
||||
- Detects gaming processes (Steam, Lutris, Wine, etc.)
|
||||
- GPU usage monitoring (>15% threshold)
|
||||
- Configurable time windows
|
||||
- Automated temporary directory cleanup
|
||||
|
||||
**Schedule Format**: `"HOUR_START-HOUR_END:DAYS"`
|
||||
- `"22-07:daily"` - Overnight transcoding
|
||||
- `"09-17:1-5"` - Business hours weekdays only
|
||||
- `"14-16:6,7"` - Weekend afternoon window
|
||||
|
||||
### Monitoring System
|
||||
**Purpose**: Prevents staging section timeouts and system instability
|
||||
**Location**: `scripts/monitoring/tdarr-timeout-monitor.sh`
|
||||
|
||||
**Capabilities**:
|
||||
- Staging timeout detection (300-second hardcoded limit)
|
||||
- Automatic work directory cleanup
|
||||
- Discord notifications with user pings
|
||||
- Log rotation and retention management
|
||||
|
||||
### Container Architecture
|
||||
**Server Configuration**:
|
||||
```yaml
|
||||
# Hybrid storage with resource limits
|
||||
version: "3.8"
|
||||
services:
|
||||
tdarr:
|
||||
image: ghcr.io/haveagitgat/tdarr:latest
|
||||
ports: ["8265:8266"]
|
||||
container_name: tdarr-server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8265:8265" # Web UI
|
||||
- "8266:8266" # Server port (for nodes)
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- serverIP=0.0.0.0
|
||||
- serverPort=8266
|
||||
- webUIPort=8265
|
||||
volumes:
|
||||
- "./tdarr-data:/app/configs"
|
||||
- "/mnt/media:/media"
|
||||
- ./server-data:/app/server
|
||||
- ./configs:/app/configs
|
||||
- ./logs:/app/logs
|
||||
- /mnt/truenas/media:/media
|
||||
|
||||
tdarr-node:
|
||||
image: ghcr.io/haveagitgat/tdarr_node:latest
|
||||
container_name: tdarr-node
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- serverIP=tdarr
|
||||
- serverPort=8266
|
||||
- nodeName=manticore-gpu
|
||||
volumes:
|
||||
- ./node-data:/app/configs
|
||||
- /mnt/truenas/media:/media
|
||||
- /mnt/NV2/tdarr-cache:/temp
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
depends_on:
|
||||
- tdarr
|
||||
```
|
||||
|
||||
**Node Configuration**:
|
||||
### Node Configuration
|
||||
- **Node Name**: manticore-gpu
|
||||
- **Node Type**: Mapped (both server and node access same NFS mount)
|
||||
- **Workers**: 1 GPU transcode worker, 4 GPU healthcheck workers
|
||||
- **Schedule**: Disabled (runs 24/7)
|
||||
|
||||
### Current Queue Status (Dec 2025)
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Transcode Queue | ~7,675 files |
|
||||
| Success/Not Required | 8,378 files |
|
||||
| Healthy Files | 16,628 files |
|
||||
| Job History | 37,406 total jobs |
|
||||
|
||||
### Performance Metrics
|
||||
- **Throughput**: ~13 files/hour (varies by file size)
|
||||
- **Average Compression**: ~64% of original size (35% space savings)
|
||||
- **Codec**: HEVC (h265) output at 1080p
|
||||
- **Typical File Sizes**: 3-7 GB input → 2-4.5 GB output
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### Mapped Node with Shared Storage
|
||||
**Pattern**: Server and node share the same media mount via NFS
|
||||
- **Advantage**: Simpler configuration, no file transfer overhead
|
||||
- **Trade-off**: Depends on stable NFS connection during transcoding
|
||||
|
||||
**When to Use**:
|
||||
- Dedicated transcoding server (not a gaming/desktop system)
|
||||
- Reliable network storage infrastructure
|
||||
- Single-node deployments
|
||||
|
||||
### Local NVMe Cache
|
||||
Work directory on local NVMe (`/mnt/NV2/tdarr-cache:/temp`) provides:
|
||||
- Fast read/write for transcode operations
|
||||
- Isolation from network latency during processing
|
||||
- Sufficient space for large remux files (1TB+ available)
|
||||
|
||||
## Operational Notes
|
||||
|
||||
### Recent Activity
|
||||
System is actively processing with strong throughput. Recent successful transcodes include:
|
||||
- Dead Like Me (2003) - multiple episodes
|
||||
- Supernatural (2005) - S03 episodes
|
||||
- I Dream of Jeannie (1965) - S01 episodes
|
||||
- Da Vinci's Demons (2013) - S01 episodes
|
||||
|
||||
### Minor Issues
|
||||
- **Occasional File Not Found (400)**: Files deleted/moved while queued fail after 5 retries
|
||||
- Impact: Minimal - system continues processing remaining queue
|
||||
- Resolution: Automatic - failed files are skipped
|
||||
|
||||
### Monitoring
|
||||
- **Server Logs**: `/home/cal/docker/tdarr/logs/Tdarr_Server_Log.txt`
|
||||
- **Docker Logs**: `docker logs tdarr-server` / `docker logs tdarr-node`
|
||||
- **Library Scans**: Automatic hourly scans (2 libraries: ZWgKkmzJp, EjfWXCdU8)
|
||||
|
||||
### Common Operations
|
||||
|
||||
**Check Status**:
|
||||
```bash
|
||||
# Unmapped node with local cache
|
||||
podman run -d \
|
||||
--name tdarr-node-gpu \
|
||||
-e nodeType=unmapped \
|
||||
-v "/mnt/NV2/tdarr-cache:/cache" \
|
||||
--device nvidia.com/gpu=all \
|
||||
ghcr.io/haveagitgat/tdarr_node:latest
|
||||
ssh 10.10.0.226 "docker ps --format 'table {{.Names}}\t{{.Status}}' | grep tdarr"
|
||||
```
|
||||
|
||||
## Implementation Patterns
|
||||
**View Recent Logs**:
|
||||
```bash
|
||||
ssh 10.10.0.226 "docker logs tdarr-node --since 1h 2>&1 | tail -50"
|
||||
```
|
||||
|
||||
### Performance Optimization
|
||||
1. **Local Cache Strategy**: Download → Process → Upload (vs. streaming)
|
||||
2. **Resource Limits**: Prevent memory exhaustion and kernel crashes
|
||||
3. **Network Resilience**: CIFS mount options for stability
|
||||
4. **Automated Cleanup**: Prevent accumulation of stuck directories
|
||||
**Restart Services**:
|
||||
```bash
|
||||
ssh 10.10.0.226 "cd /home/cal/docker/tdarr && docker compose restart"
|
||||
```
|
||||
|
||||
### Error Prevention
|
||||
1. **Plugin Safety**: Null-safe forEach operations `(streams || []).forEach()`
|
||||
2. **Clean Installation**: Avoid custom plugin mounts causing version conflicts
|
||||
3. **Container Isolation**: Resource limits prevent system-level crashes
|
||||
4. **Network Stability**: Unmapped architecture reduces CIFS dependency
|
||||
**Check GPU Usage**:
|
||||
```bash
|
||||
ssh 10.10.0.226 "nvidia-smi"
|
||||
```
|
||||
|
||||
### Gaming Integration
|
||||
1. **Process Detection**: Monitor for gaming applications and utilities
|
||||
2. **GPU Threshold**: Stop transcoding when GPU usage >15%
|
||||
3. **Time Windows**: Respect user-defined allowed transcoding hours
|
||||
4. **Manual Override**: Direct start/stop commands bypass scheduler
|
||||
### API Access
|
||||
Base URL: `http://10.10.0.226:8265/api/v2/`
|
||||
|
||||
## Common Workflows
|
||||
**Get Node Status**:
|
||||
```bash
|
||||
curl -s "http://10.10.0.226:8265/api/v2/get-nodes" | jq '.'
|
||||
```
|
||||
|
||||
### Initial Setup
|
||||
1. Start server with "Allow unmapped Nodes" enabled
|
||||
2. Configure node as unmapped with local cache
|
||||
3. Install gaming-aware scheduler via cron
|
||||
4. Set up monitoring system for automated cleanup
|
||||
## GPU Resource Sharing
|
||||
This server also runs Jellyfin with GPU transcoding. Coordinate usage:
|
||||
- Tdarr uses NVENC for encoding
|
||||
- Jellyfin uses NVDEC for decoding
|
||||
- Both can run simultaneously for different workloads
|
||||
- Monitor GPU memory if running concurrent heavy transcodes
|
||||
|
||||
### Troubleshooting Patterns
|
||||
1. **forEach Errors**: Clean plugin installation, avoid custom mounts
|
||||
2. **Staging Timeouts**: Monitor system handles automatic cleanup
|
||||
3. **System Crashes**: Convert to unmapped node architecture
|
||||
4. **Network Issues**: Implement CIFS resilience options
|
||||
## Legacy: Gaming-Aware Architecture
|
||||
The previous deployment on the local desktop used an unmapped node architecture with gaming detection. This is preserved for reference but not currently in use:
|
||||
|
||||
### Performance Tuning
|
||||
1. **Cache Size**: 100-500GB NVMe for concurrent jobs
|
||||
2. **Bandwidth**: Unmapped nodes reduce streaming requirements
|
||||
3. **Scaling**: Linear scaling with additional unmapped nodes
|
||||
4. **GPU Priority**: Gaming detection ensures responsive system
|
||||
### Unmapped Node Pattern (Historical)
|
||||
For gaming desktops requiring GPU priority management:
|
||||
- Node downloads files to local cache before processing
|
||||
- Gaming detection pauses transcoding automatically
|
||||
- Scheduler script manages time windows
|
||||
|
||||
**When to Consider**:
|
||||
- Transcoding on a gaming/desktop system
|
||||
- Need GPU priority for interactive applications
|
||||
- Multiple nodes across network
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Production Deployment
|
||||
- Use unmapped node architecture for stability
|
||||
- Implement comprehensive monitoring
|
||||
- Configure gaming-aware scheduling for desktop systems
|
||||
- Set appropriate container resource limits
|
||||
### For Current Deployment
|
||||
1. Monitor NFS stability - Tdarr depends on reliable media access
|
||||
2. Check cache disk space periodically (`df -h /mnt/NV2`)
|
||||
3. Review queue for stale files after media library changes
|
||||
4. GPU memory: Leave headroom for Jellyfin concurrent usage
|
||||
|
||||
### Development Guidelines
|
||||
- Test with internal Tdarr test files first
|
||||
- Implement null-safety checks in custom plugins
|
||||
- Use structured logging for troubleshooting
|
||||
- Separate concerns: scheduling, monitoring, processing
|
||||
### Error Prevention
|
||||
1. **Plugin Updates**: Automatic hourly plugin sync from server
|
||||
2. **Retry Logic**: 5 attempts with exponential backoff for file operations
|
||||
3. **Container Health**: `restart: unless-stopped` ensures recovery
|
||||
|
||||
### Security Considerations
|
||||
- Container isolation prevents system-level failures
|
||||
- Resource limits protect against memory exhaustion
|
||||
- Network mount resilience prevents kernel crashes
|
||||
- Automated cleanup prevents disk space issues
|
||||
### Troubleshooting Patterns
|
||||
1. **File Not Found**: Source was deleted - clear from queue via UI
|
||||
2. **Slow Transcodes**: Check NFS latency, GPU utilization
|
||||
3. **Node Disconnected**: Restart node container, check server connectivity
|
||||
|
||||
## Migration Patterns
|
||||
## Space Savings Estimate
|
||||
With ~7,675 files in queue averaging 35% reduction:
|
||||
- If average input is 5 GB → saves ~1.75 GB per file
|
||||
- Potential savings: ~13 TB when queue completes
|
||||
|
||||
### From Mapped to Unmapped Nodes
|
||||
1. Enable "Allow unmapped Nodes" in server options
|
||||
2. Update node configuration (add nodeType=unmapped)
|
||||
3. Change cache volume to local storage
|
||||
4. Remove media volume mapping
|
||||
5. Test workflow and monitor performance
|
||||
|
||||
### Plugin System Cleanup
|
||||
1. Remove all custom plugin mounts
|
||||
2. Force server restart to regenerate plugin ZIP
|
||||
3. Restart nodes to download fresh plugins
|
||||
4. Verify forEach fixes in downloaded plugins
|
||||
|
||||
This technology context provides the foundation for implementing, troubleshooting, and optimizing Tdarr transcoding systems in home lab environments.
|
||||
This technology context reflects the ubuntu-manticore deployment as of December 2025.
|
||||
|
||||
162
tdarr/ubuntu-manticore-setup.md
Normal file
162
tdarr/ubuntu-manticore-setup.md
Normal file
@ -0,0 +1,162 @@
|
||||
# Tdarr Setup on ubuntu-manticore
|
||||
|
||||
## Overview
|
||||
Tdarr server and GPU-accelerated node deployed on ubuntu-manticore (10.10.0.226) with NVIDIA GTX 1070 for hardware transcoding. Migrated from old server at 10.10.0.43.
|
||||
|
||||
**Date**: 2025-12-04
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
ubuntu-manticore (10.10.0.226)
|
||||
├── tdarr-server (container)
|
||||
│ ├── Web UI: http://10.10.0.226:8265
|
||||
│ ├── Node Port: 8266
|
||||
│ └── Data: ~/docker/tdarr/server-data/
|
||||
├── tdarr-node (container)
|
||||
│ ├── Node Name: manticore-gpu
|
||||
│ ├── GPU: GTX 1070 (NVENC/NVDEC)
|
||||
│ └── Cache: /mnt/NV2/tdarr-cache (NVMe)
|
||||
└── Media: /mnt/truenas/media (CIFS mount)
|
||||
```
|
||||
|
||||
## Docker Compose Configuration
|
||||
|
||||
**Location**: `~/docker/tdarr/docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
tdarr:
|
||||
image: ghcr.io/haveagitgat/tdarr:latest
|
||||
container_name: tdarr-server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8265:8265" # Web UI
|
||||
- "8266:8266" # Server port (for nodes)
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- serverIP=0.0.0.0
|
||||
- serverPort=8266
|
||||
- webUIPort=8265
|
||||
volumes:
|
||||
- ./server-data:/app/server
|
||||
- ./configs:/app/configs
|
||||
- ./logs:/app/logs
|
||||
- /mnt/truenas/media:/media
|
||||
|
||||
tdarr-node:
|
||||
image: ghcr.io/haveagitgat/tdarr_node:latest
|
||||
container_name: tdarr-node
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- serverIP=tdarr
|
||||
- serverPort=8266
|
||||
- nodeName=manticore-gpu
|
||||
volumes:
|
||||
- ./node-data:/app/configs
|
||||
- /mnt/truenas/media:/media
|
||||
- /mnt/NV2/tdarr-cache:/temp
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
depends_on:
|
||||
- tdarr
|
||||
```
|
||||
|
||||
## GPU Configuration
|
||||
|
||||
### Hardware
|
||||
- **GPU**: NVIDIA GeForce GTX 1070 (Pascal architecture)
|
||||
- **Driver**: 570.195.03
|
||||
- **CUDA**: 12.8
|
||||
|
||||
### NVENC Capabilities
|
||||
| Encoder | Supported | Working |
|
||||
|---------|-----------|---------|
|
||||
| h264_nvenc | Yes | Yes |
|
||||
| hevc_nvenc | Yes | Yes |
|
||||
| av1_nvenc | Yes | No (requires Turing+) |
|
||||
|
||||
### Important Limitation: B-Frames
|
||||
**GTX 1070 does NOT support B-frames for HEVC encoding** (Pascal limitation).
|
||||
|
||||
If using `Tdarr_Plugin_MC93_Migz1FFMPEG` or similar plugins:
|
||||
- Set `enable_bframes` to `false`
|
||||
- Otherwise you'll get: `Max B-frames 5 exceed 0` / `No capable devices found`
|
||||
|
||||
B-frame support for HEVC NVENC requires RTX 20-series (Turing) or newer.
|
||||
|
||||
## Migration from Old Server
|
||||
|
||||
### Source
|
||||
- **Server**: 10.10.0.43 (ubuntu-ct, SSH alias: `tdarr`)
|
||||
- **Container**: tdarr-clean
|
||||
- **Data**: `/home/cal/container-data/tdarr/tdarr-clean/server/` (~4.4GB)
|
||||
|
||||
### Migration Process
|
||||
```bash
|
||||
# Stop old server
|
||||
ssh tdarr "docker stop tdarr-clean"
|
||||
|
||||
# Pull data to local workstation (relay)
|
||||
rsync -avz --progress tdarr:/home/cal/container-data/tdarr/tdarr-clean/server/ /tmp/tdarr-server-data/
|
||||
|
||||
# Push to new server
|
||||
rsync -avz --progress /tmp/tdarr-server-data/ cal@10.10.0.226:/home/cal/docker/tdarr/server-data/
|
||||
|
||||
# Start new server
|
||||
ssh cal@10.10.0.226 "cd ~/docker/tdarr && docker compose up -d"
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Observed Speeds
|
||||
- **1080p H.264 → HEVC**: 200+ fps (~8-9x real-time)
|
||||
- **GPU Utilization**: 30-60% encoder, 40-70% decoder
|
||||
- **Power Draw**: ~52W during transcoding (166W max)
|
||||
|
||||
### Monitoring GPU Usage
|
||||
```bash
|
||||
# Basic status
|
||||
nvidia-smi
|
||||
|
||||
# Detailed encoder/decoder utilization
|
||||
nvidia-smi dmon -s u
|
||||
```
|
||||
|
||||
### Recommended Settings
|
||||
- **GPU Workers**: 1 (leaves headroom for Jellyfin)
|
||||
- **CPU Workers**: 0 (GPU-only transcoding)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "Max B-frames X exceed 0"
|
||||
**Cause**: Plugin configured with B-frames, but GTX 1070 doesn't support them for HEVC
|
||||
**Fix**: Disable `enable_bframes` in the plugin settings
|
||||
|
||||
### Error: "No capable devices found"
|
||||
**Cause**: Usually means incompatible encoding parameters, not missing GPU
|
||||
**Check**: Run `nvidia-smi` inside container to verify GPU access:
|
||||
```bash
|
||||
docker exec tdarr-node nvidia-smi
|
||||
```
|
||||
|
||||
### Slow File Copy (0% progress)
|
||||
**Cause**: Large files copying from network share to local cache
|
||||
**Expected**: ~90 seconds for 10GB file over gigabit
|
||||
**Note**: This is normal for mapped node architecture - file must copy before transcoding starts
|
||||
|
||||
## Related Documentation
|
||||
- Server inventory: `networking/server-inventory.md`
|
||||
- Tdarr technology context: `tdarr/CONTEXT.md`
|
||||
- Tdarr troubleshooting: `tdarr/troubleshooting.md`
|
||||
821
vm-management/lxc-migration-plan.md
Normal file
821
vm-management/lxc-migration-plan.md
Normal file
@ -0,0 +1,821 @@
|
||||
# VM to LXC Migration Plan - Proxmox Infrastructure
|
||||
|
||||
**Created**: 2025-01-12
|
||||
**Status**: ✅ Wave 2 Complete - In Progress
|
||||
**Owner**: Cal Corum
|
||||
**Last Updated**: 2025-12-05
|
||||
|
||||
## 🎯 Wave 1 Status: ✅ **COMPLETE**
|
||||
- **VM 111 (docker-7days)** → **LXC 211** ✅ Successful
|
||||
- **Migration Date**: 2025-01-12
|
||||
- **Container Status**: Running and validated
|
||||
- **Detailed Results**: See `wave1-migration-results.md`
|
||||
|
||||
## 🎯 Wave 2 Status: ✅ **COMPLETE**
|
||||
- **VM 121 (docker-vpn)** → **LXC 221 (arr-stack)** ✅ Successful
|
||||
- **Migration Date**: 2025-12-05
|
||||
- **Container Status**: Running and validated
|
||||
- **Key Changes**:
|
||||
- Eliminated Mullvad VPN (Usenet + SSL is sufficient, no torrents)
|
||||
- Replaced Overseerr with Jellyseerr (native Jellyfin support)
|
||||
- Simplified stack: Sonarr, Radarr, Readarr, Jellyseerr, SABnzbd
|
||||
- **Detailed Results**: See `wave2-migration-results.md`
|
||||
|
||||
## ✅ Confirmed Decisions
|
||||
- **Networking**: Reuse existing IP addresses (transparent migration)
|
||||
- **Storage**: Fresh install + volume copy for all Docker hosts
|
||||
- **Timeline**: 4-6 weeks (updated from initial 6-8 based on Wave 1 experience)
|
||||
- **GPU Services**: No GPU hardware available - Plex (107) and Tdarr (113) can migrate without special considerations
|
||||
- **AppArmor Fix**: ALL docker-compose files need `security_opt: [apparmor=unconfined]` ⚠️ CRITICAL
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Migrating services from full VMs to LXC containers on Proxmox to:
|
||||
- Reduce resource overhead (memory, CPU, storage)
|
||||
- Improve density and efficiency
|
||||
- Faster provisioning and backup/restore
|
||||
- Lower management complexity
|
||||
|
||||
**Current State**: 16 VMs (9 running, 7 stopped)
|
||||
**Target State**: Strategic mix of LXC containers and VMs based on workload requirements
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Assessment & Categorization
|
||||
|
||||
### Current VM Inventory Analysis
|
||||
|
||||
#### Running Production VMs (9)
|
||||
| VMID | Name | Service Type | Migration Candidate? | Priority | Notes |
|
||||
|------|------|--------------|---------------------|----------|-------|
|
||||
| 105 | docker-vpn | Docker Host | ✅ YES | HIGH | VPN routing considerations |
|
||||
| 106 | docker-home | Docker Host | ✅ YES | HIGH | Critical home services |
|
||||
| 107 | plex | Media Server | ✅ YES | MEDIUM | Software transcoding (no GPU hardware) |
|
||||
| 109 | hass-io | Home Assistant | ❌ NO | N/A | HassOS requires VM, not standard Linux |
|
||||
| 110 | discord-bots | Application | ✅ YES | MEDIUM | Simple Python services |
|
||||
| 111 | docker-7days | Game Server | ✅ YES | HIGHEST | Lowest risk - migrate first |
|
||||
| 112 | databases-bots | Database | ✅ YES | HIGH | PostgreSQL/databases |
|
||||
| 113 | docker-tdarr | Transcode | ✅ YES | MEDIUM | Software transcoding (no GPU hardware) |
|
||||
| 114 | docker-pittsburgh | Docker Host | ✅ YES | MEDIUM | Regional services |
|
||||
| 115 | docker-sba | Docker Host | ✅ YES | MEDIUM | SBA baseball services |
|
||||
| 116 | docker-home-servers | Docker Host | ✅ YES | HIGH | Critical infrastructure |
|
||||
|
||||
#### Stopped/Template VMs (7)
|
||||
| VMID | Name | Purpose | Action |
|
||||
|------|------|---------|--------|
|
||||
| 100 | ubuntu-template | Template | KEEP as VM for flexibility |
|
||||
| 101 | 7d-solo | Game Server | EVALUATE when needed |
|
||||
| 102 | 7d-staci | Game Server | EVALUATE when needed |
|
||||
| 103 | docker-template | Template | CONVERT to LXC template |
|
||||
| 104 | 7d-wotw | Game Server | EVALUATE when needed |
|
||||
| 117 | docker-unused | Unused | DELETE or ARCHIVE |
|
||||
|
||||
### Migration Suitability Matrix
|
||||
|
||||
#### ✅ **IDEAL for LXC** (All Migrate)
|
||||
- **Game server - docker-7days (111)**: LOWEST RISK - Migrate first to validate process
|
||||
- **Docker hosts** (105, 106, 114, 115, 116): Standard Docker workloads without special hardware
|
||||
- **Application servers** (110): Discord bots, Python services
|
||||
- **Database servers** (112): PostgreSQL, Redis, standard databases
|
||||
- **Media servers** (107, 113): Plex and Tdarr using software transcoding (no GPU available)
|
||||
- **Stopped game servers** (101, 102, 104): Migrate when needed
|
||||
- **Docker template** (103): Convert to LXC template for faster provisioning
|
||||
|
||||
**Why**: No GPU hardware in system - all services can run in LXC without special considerations. Pure Linux workloads benefit from reduced overhead.
|
||||
|
||||
#### ❌ **KEEP as VM** (Do Not Migrate)
|
||||
- **Home Assistant (109)**: HassOS is VM-optimized, not standard Linux
|
||||
- **Ubuntu template (100)**: Keep VM flexibility for future VM deployments
|
||||
|
||||
**Why**: Technical incompatibility or strategic value as VM
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Technical Planning
|
||||
|
||||
### Service Consolidation Decision Framework
|
||||
|
||||
When deciding whether to keep services in separate LXCs or consolidate into a single LXC:
|
||||
|
||||
#### **Keep Separate** (1 LXC per service) when:
|
||||
| Factor | Reason |
|
||||
|--------|--------|
|
||||
| **Blast radius** | Failure of one shouldn't take down others |
|
||||
| **Different update cycles** | Services need independent maintenance windows |
|
||||
| **Resource contention** | CPU/memory-hungry services that compete |
|
||||
| **Security boundaries** | Different trust levels or network access needs |
|
||||
| **Different owners/teams** | Separate accountability |
|
||||
| **Databases** | Always isolate for backup/restore simplicity |
|
||||
| **Critical infrastructure** | VPN, DNS, reverse proxy - high availability needs |
|
||||
|
||||
#### **Consolidate** (multiple services in 1 LXC) when:
|
||||
| Factor | Reason |
|
||||
|--------|--------|
|
||||
| **Related services** | Naturally belong together (e.g., all SBA services) |
|
||||
| **Low resource usage** | Services that barely use resources individually |
|
||||
| **Same lifecycle** | Updated/restarted together anyway |
|
||||
| **Shared dependencies** | Same database, same configs |
|
||||
| **Simplicity wins** | Fewer LXCs to manage, backup, monitor |
|
||||
| **Same project** | Discord bots for same league, microservices for same app |
|
||||
|
||||
#### Practical Examples:
|
||||
|
||||
| Keep Separate | Why |
|
||||
|---------------|-----|
|
||||
| Databases (112) | Backup/restore, data integrity |
|
||||
| VPN (105) | Security boundary, networking critical |
|
||||
| Critical home services (106) | High availability |
|
||||
| n8n (210) | Workflow automation, independent maintenance |
|
||||
|
||||
| Candidate for Consolidation | Why |
|
||||
|-----------------------------|-----|
|
||||
| Discord bots + related API services | Same project, low resources, same maintainer |
|
||||
| Multiple low-traffic web apps | Minimal resource usage |
|
||||
| Dev/test environments | Non-critical, shared lifecycle |
|
||||
|
||||
---
|
||||
|
||||
### LXC vs VM Decision Criteria
|
||||
|
||||
| Criteria | LXC Container | Full VM | Notes |
|
||||
|----------|--------------|---------|-------|
|
||||
| **OS Type** | Linux only | Any OS | LXC shares host kernel |
|
||||
| **Resource Overhead** | Minimal (~50-200MB RAM) | High (full OS stack) | LXC 5-10x more efficient |
|
||||
| **Boot Time** | 1-5 seconds | 30-90 seconds | Near-instant container start |
|
||||
| **Kernel Modules** | Shared host kernel | Own kernel | LXC cannot load custom modules |
|
||||
| **Hardware Passthrough** | Limited (requires privileges) | Full passthrough | GPU/USB may need testing |
|
||||
| **Nested Virtualization** | Not supported | Supported | Cannot run Docker-in-Docker easily |
|
||||
| **Backup/Restore** | Very fast | Slower | Container backups are incremental |
|
||||
| **Disk Performance** | Native | Near-native | Both excellent on modern storage |
|
||||
|
||||
### Key Technical Decisions
|
||||
|
||||
#### 1. **Networking Strategy** ✅ CONFIRMED
|
||||
**Decision**: Reuse existing IP addresses
|
||||
|
||||
**Implementation**:
|
||||
- ✅ No DNS changes required
|
||||
- ✅ Existing firewall rules work
|
||||
- ✅ Monitoring continues without changes
|
||||
- ✅ Transparent migration for users
|
||||
- ⚠️ Requires careful IP conflict management during parallel running
|
||||
|
||||
**Migration Process**:
|
||||
1. Build LXC with temporary IP (or offline)
|
||||
2. Test and validate LXC functionality
|
||||
3. Stop VM during maintenance window
|
||||
4. Reconfigure LXC to production IP
|
||||
5. Start LXC and validate
|
||||
6. Keep VM stopped for 48hr rollback window
|
||||
|
||||
#### 2. **Storage Strategy** ✅ CONFIRMED
|
||||
**Decision**: Fresh install + volume copy for all Docker hosts
|
||||
|
||||
**Implementation for Docker Hosts**:
|
||||
1. **Fresh LXC installation**:
|
||||
- Clean Ubuntu 22.04 LTS base
|
||||
- Install Docker via standard script
|
||||
- Install docker-compose plugin
|
||||
- No migration of system configs
|
||||
|
||||
2. **Volume migration**:
|
||||
- Copy `/var/lib/docker/volumes/` from VM to LXC
|
||||
- Copy docker-compose files from VM to LXC
|
||||
- Copy environment files (.env) if applicable
|
||||
- Validate volume data integrity
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Clean configuration, no cruft
|
||||
- ✅ Opportunity to update/standardize configs
|
||||
- ✅ Smaller container images
|
||||
- ✅ Document infrastructure-as-code
|
||||
- ✅ Latest Docker version on fresh install
|
||||
|
||||
#### 3. **Docker in LXC** ✅ CONFIRMED
|
||||
**Decision**: Privileged LXC containers for all Docker hosts
|
||||
|
||||
**Configuration**:
|
||||
- Set `--unprivileged 0` (privileged mode)
|
||||
- Enable nesting: `--features nesting=1,keyctl=1`
|
||||
- Docker works without issues
|
||||
- All Docker features supported
|
||||
- No complex UID mapping required
|
||||
|
||||
**Rationale**:
|
||||
- ✅ Docker compatibility guaranteed
|
||||
- ✅ Simpler configuration and troubleshooting
|
||||
- ✅ Balanced approach for home lab environment
|
||||
- ⚠️ Acceptable security trade-off for isolated home network
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Migration Strategy
|
||||
|
||||
### Phased Rollout Approach (Risk-Based Ordering)
|
||||
|
||||
#### **Wave 1: Lowest Risk - Game Server** (Week 1)
|
||||
**Target**: Lowest-risk service to validate entire migration process
|
||||
|
||||
1. **docker-7days (111)** - Game server via Docker, lowest impact if issues occur
|
||||
|
||||
**Why This First**:
|
||||
- ✅ Non-critical service (gaming only)
|
||||
- ✅ Can migrate during off-hours when not in use
|
||||
- ✅ Clear validation criteria (game server starts and runs)
|
||||
- ✅ Builds confidence in process with minimal risk
|
||||
- ✅ Tests Docker-in-LXC configuration end-to-end
|
||||
|
||||
**Success Criteria**:
|
||||
- Game server accessible and playable
|
||||
- Docker containers running stable for 48+ hours
|
||||
- Backup/restore tested successfully
|
||||
- Rollback procedure validated
|
||||
- Process documented for next waves
|
||||
|
||||
#### **Wave 2: Docker Hosts - Regional/Isolated** (Week 1-2)
|
||||
**Target**: Docker hosts with lower criticality and good isolation
|
||||
|
||||
2. **docker-pittsburgh (114)** - Regional services, lower criticality
|
||||
3. **docker-vpn (105)** - VPN routing (isolated workload)
|
||||
|
||||
**Prerequisites**:
|
||||
- Wave 1 successful (docker-7days stable)
|
||||
- Process refined based on learnings
|
||||
- Confidence in Docker-in-LXC configuration
|
||||
|
||||
**Validation Points**:
|
||||
- VPN routing works correctly (105)
|
||||
- Regional services accessible (114)
|
||||
- No cross-service impact
|
||||
|
||||
#### **Wave 3: Additional Docker Hosts** (Week 2-3)
|
||||
**Target**: More Docker infrastructure, increasing criticality
|
||||
|
||||
4. **docker-sba (115)** - Baseball services (defined maintenance windows)
|
||||
5. **docker-unused (117)** - Migrate or decommission
|
||||
6. **docker-home-servers (116)** - Home server infrastructure
|
||||
|
||||
**Critical Considerations**:
|
||||
- SBA has known maintenance windows - use those
|
||||
- docker-home-servers may have dependencies - validate carefully
|
||||
- docker-unused can be decommissioned if no longer needed
|
||||
|
||||
#### **Wave 4: Application & Database Servers** (Week 3-4)
|
||||
**Target**: Non-Docker services requiring extra care
|
||||
|
||||
7. **discord-bots (110)** - Python services, straightforward
|
||||
8. **databases-bots (112)** - PostgreSQL/databases (highest care required)
|
||||
|
||||
**Critical Steps for Databases**:
|
||||
- ⚠️ Full database backup before migration
|
||||
- ⚠️ Validate connection strings from all dependent services
|
||||
- ⚠️ Test database performance in LXC thoroughly
|
||||
- ⚠️ Monitor for 48+ hours before decommissioning VM
|
||||
- ⚠️ Have rollback plan ready and tested
|
||||
|
||||
#### **Wave 5: Media Services** ~~(Week 4-5)~~ **SKIPPED**
|
||||
**Status**: ❌ SKIPPED - Services retired or decommissioned
|
||||
|
||||
~~9. **docker-tdarr (113)**~~ - **RETIRED**: Tdarr moved to dedicated GPU server (ubuntu-manticore)
|
||||
~~10. **plex (107)**~~ - **DECOMMISSIONING**: Plex being retired, no migration needed
|
||||
|
||||
**Notes**:
|
||||
- Tdarr now runs on ubuntu-manticore (10.10.0.226) with GPU transcoding
|
||||
- Plex scheduled for decommission - Jellyfin is the replacement
|
||||
|
||||
#### **Wave 6: Final Critical Infrastructure** (Week 5-6)
|
||||
**Target**: Most critical Docker infrastructure (save for last)
|
||||
|
||||
11. **docker-home (106)** - Critical home services (highest risk)
|
||||
|
||||
**Why Last**:
|
||||
- Most critical infrastructure
|
||||
- All other waves provide confidence
|
||||
- Process fully refined and validated
|
||||
- All potential issues already encountered and resolved
|
||||
|
||||
**Do NOT Migrate**:
|
||||
- **hass-io (109)** - Keep as VM (HassOS requirement)
|
||||
- **ubuntu-template (100)** - Keep as VM (strategic flexibility)
|
||||
|
||||
### Parallel Running Strategy
|
||||
|
||||
**For Each Migration**:
|
||||
|
||||
1. **Build LXC container** (new ID, temporary IP or offline)
|
||||
2. **Configure and test** (validate all functionality)
|
||||
3. **Sync data** from VM to LXC (while VM still running)
|
||||
4. **Maintenance window**:
|
||||
- Stop VM
|
||||
- Final data sync
|
||||
- Change LXC to production IP
|
||||
- Start LXC
|
||||
- Validate services
|
||||
5. **Monitor for 24-48 hours** (VM kept in stopped state)
|
||||
6. **Decommission VM** after confidence period
|
||||
|
||||
**Rollback Procedure**:
|
||||
- Stop LXC
|
||||
- Start VM (already has data up to cutover point)
|
||||
- Resume production on VM
|
||||
- Document what failed for retry
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Implementation Checklist
|
||||
|
||||
### Pre-Migration (Per Service)
|
||||
|
||||
- [ ] Document current VM configuration
|
||||
- [ ] CPU, memory, storage allocation
|
||||
- [ ] Network configuration (IP, gateway, DNS)
|
||||
- [ ] Installed packages and services
|
||||
- [ ] Docker compose files (if Docker host)
|
||||
- [ ] Volume mounts and storage locations
|
||||
- [ ] Environment variables and secrets
|
||||
- [ ] Cron jobs and systemd services
|
||||
|
||||
- [ ] Create LXC container
|
||||
- [ ] Select appropriate template (Ubuntu 22.04 LTS recommended)
|
||||
- [ ] Allocate resources (start conservative, can increase)
|
||||
- [ ] Configure networking (temporary IP for testing)
|
||||
- [ ] Set privileged mode if Docker host
|
||||
- [ ] Configure storage (bind mounts for data volumes)
|
||||
|
||||
- [ ] Prepare migration scripts
|
||||
- [ ] Data sync script (rsync-based)
|
||||
- [ ] Configuration export/import
|
||||
- [ ] Service validation tests
|
||||
|
||||
- [ ] Backup current VM
|
||||
- [ ] Full VM backup in Proxmox
|
||||
- [ ] Export critical data separately
|
||||
- [ ] Document backup location and restore procedure
|
||||
|
||||
### During Migration
|
||||
|
||||
- [ ] Announce maintenance window (if user-facing)
|
||||
- [ ] Stop services on VM (or entire VM)
|
||||
- [ ] Perform final data sync to LXC
|
||||
- [ ] Update DNS/networking (if using new IP temporarily)
|
||||
- [ ] Start services in LXC
|
||||
- [ ] Run validation tests
|
||||
- [ ] Service responding?
|
||||
- [ ] Data accessible?
|
||||
- [ ] External connectivity working?
|
||||
- [ ] Dependent services connecting successfully?
|
||||
- [ ] Performance acceptable?
|
||||
|
||||
### Post-Migration
|
||||
|
||||
- [ ] Monitor for 24 hours
|
||||
- [ ] Check logs for errors
|
||||
- [ ] Monitor resource usage
|
||||
- [ ] Validate backups working
|
||||
- [ ] Test restore procedure
|
||||
|
||||
- [ ] Update documentation
|
||||
- [ ] Update VM inventory
|
||||
- [ ] Document new container configuration
|
||||
- [ ] Update monitoring configs
|
||||
- [ ] Update runbooks/procedures
|
||||
|
||||
- [ ] After 48-hour success period
|
||||
- [ ] Backup LXC container
|
||||
- [ ] Delete VM backup (or archive)
|
||||
- [ ] Destroy original VM
|
||||
- [ ] Update network documentation
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Technical Implementation Details
|
||||
|
||||
### Standard LXC Container Creation
|
||||
|
||||
```bash
|
||||
# Create privileged LXC container for Docker host
|
||||
pct create 205 local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
|
||||
--hostname docker-home-lxc \
|
||||
--memory 4096 \
|
||||
--cores 2 \
|
||||
--net0 name=eth0,bridge=vmbr0,ip=10.10.0.106/24,gw=10.10.0.1 \
|
||||
--storage local-lvm \
|
||||
--rootfs local-lvm:32 \
|
||||
--unprivileged 0 \
|
||||
--features nesting=1,keyctl=1
|
||||
|
||||
# Start container
|
||||
pct start 205
|
||||
|
||||
# Enter container
|
||||
pct enter 205
|
||||
```
|
||||
|
||||
### Docker Installation in LXC
|
||||
|
||||
```bash
|
||||
# Inside LXC container
|
||||
# Update system
|
||||
apt update && apt upgrade -y
|
||||
|
||||
# Install Docker
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sh get-docker.sh
|
||||
|
||||
# Install Docker Compose
|
||||
apt install docker-compose-plugin -y
|
||||
|
||||
# Verify
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
### Data Migration Script Template
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# migrate-docker-host.sh
|
||||
|
||||
VM_IP="10.10.0.106"
|
||||
LXC_IP="10.10.0.206" # Temporary during migration
|
||||
VM_DATA="/var/lib/docker"
|
||||
LXC_DATA="/var/lib/docker"
|
||||
|
||||
# Sync Docker volumes (while VM still running for initial sync)
|
||||
rsync -avz --progress \
|
||||
root@${VM_IP}:${VM_DATA}/ \
|
||||
root@${LXC_IP}:${LXC_DATA}/
|
||||
|
||||
# Sync docker-compose files
|
||||
rsync -avz --progress \
|
||||
root@${VM_IP}:/opt/docker/ \
|
||||
root@${LXC_IP}:/opt/docker/
|
||||
|
||||
# Sync environment files
|
||||
rsync -avz --progress \
|
||||
root@${VM_IP}:/root/.env \
|
||||
root@${LXC_IP}:/root/.env
|
||||
|
||||
echo "Initial sync complete. Ready for cutover."
|
||||
```
|
||||
|
||||
### Service Validation Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# validate-migration.sh
|
||||
|
||||
CONTAINER_IP="$1"
|
||||
SERVICE_TYPE="$2"
|
||||
|
||||
echo "Validating migration for ${SERVICE_TYPE} at ${CONTAINER_IP}..."
|
||||
|
||||
case $SERVICE_TYPE in
|
||||
docker)
|
||||
# Check Docker is running
|
||||
ssh root@${CONTAINER_IP} "docker ps" || exit 1
|
||||
|
||||
# Check compose services
|
||||
ssh root@${CONTAINER_IP} "cd /opt/docker && docker compose ps" || exit 1
|
||||
|
||||
echo "✅ Docker services validated"
|
||||
;;
|
||||
|
||||
database)
|
||||
# Check PostgreSQL
|
||||
ssh root@${CONTAINER_IP} "systemctl status postgresql" || exit 1
|
||||
|
||||
# Test connection
|
||||
ssh root@${CONTAINER_IP} "sudo -u postgres psql -c 'SELECT version();'" || exit 1
|
||||
|
||||
echo "✅ Database validated"
|
||||
;;
|
||||
|
||||
web)
|
||||
# Check HTTP response
|
||||
curl -f http://${CONTAINER_IP} || exit 1
|
||||
|
||||
echo "✅ Web service validated"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "✅ All validation checks passed!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Risk Management
|
||||
|
||||
### Risk Assessment
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|------|-----------|--------|------------|
|
||||
| Service downtime during migration | HIGH | MEDIUM | Off-hours migration, parallel running, fast rollback |
|
||||
| Data loss during sync | LOW | HIGH | Multiple backups, checksums, validation |
|
||||
| GPU passthrough failure | MEDIUM | MEDIUM | Test first, keep VMs as fallback |
|
||||
| Performance degradation | LOW | MEDIUM | Monitor closely, can revert easily |
|
||||
| Networking issues | MEDIUM | HIGH | Keep VM stopped but intact for rollback |
|
||||
| Forgotten dependencies | MEDIUM | HIGH | Document thoroughly, test before cutover |
|
||||
|
||||
### Rollback Procedures
|
||||
|
||||
#### Quick Rollback (During Cutover)
|
||||
```bash
|
||||
# If migration fails during cutover window
|
||||
pct stop 205 # Stop new LXC
|
||||
qm start 106 # Start original VM
|
||||
# Service restored in <2 minutes
|
||||
```
|
||||
|
||||
#### Rollback After Migration
|
||||
```bash
|
||||
# If issues discovered post-migration
|
||||
pct stop 205 # Stop LXC
|
||||
qm start 106 # Start original VM
|
||||
qm restore 106 backup-file.vma.zst # If needed
|
||||
# May need to sync recent data from LXC to VM
|
||||
```
|
||||
|
||||
### Success Metrics
|
||||
|
||||
**Per-Service Success Criteria**:
|
||||
- Service uptime: 99.9% after 48 hours
|
||||
- Response time: Same or better than VM
|
||||
- Resource usage: 30-50% reduction in RAM usage
|
||||
- No errors in logs
|
||||
- Backups completing successfully
|
||||
- Dependent services connecting properly
|
||||
|
||||
**Overall Migration Success**:
|
||||
- 80%+ of suitable VMs migrated to LXC
|
||||
- Zero data loss incidents
|
||||
- Total downtime <4 hours across all migrations
|
||||
- Documentation complete and validated
|
||||
- Team confident in managing LXC infrastructure
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Resource Planning
|
||||
|
||||
### Expected Resource Gains
|
||||
|
||||
**Current VM Resource Usage** (estimated):
|
||||
- 9 running VMs × 2GB average overhead = ~18GB RAM overhead
|
||||
- 9 running VMs × 500MB average storage overhead = ~4.5GB storage
|
||||
|
||||
**Post-Migration LXC Resource Usage** (estimated):
|
||||
- 7-8 LXC containers × 100MB average overhead = ~800MB RAM overhead
|
||||
- 7-8 LXC containers × 100MB average storage overhead = ~800MB storage
|
||||
|
||||
**Net Gain**:
|
||||
- ~17GB RAM freed (can support 17 more LXC containers or larger workloads)
|
||||
- ~3.7GB storage freed
|
||||
- Faster backup/restore times (5-10x improvement)
|
||||
- Faster provisioning (minutes vs hours)
|
||||
|
||||
### Resource Allocation Strategy
|
||||
|
||||
**Conservative Approach** (Recommended for initial migration):
|
||||
- Allocate **same resources as VM** to LXC initially
|
||||
- Monitor usage for 1-2 weeks
|
||||
- Right-size after baseline established
|
||||
- Iterate and optimize
|
||||
|
||||
**Example**: VM with 4GB RAM, 2 cores
|
||||
- LXC Initial: 4GB RAM, 2 cores
|
||||
- After monitoring: Adjust to 2GB RAM, 2 cores (if appropriate)
|
||||
- Freed resources: 2GB RAM for other uses
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Documentation & Knowledge Transfer
|
||||
|
||||
### Required Documentation Updates
|
||||
|
||||
- [ ] **VM Inventory** → **LXC Inventory**
|
||||
- Update VMID mappings
|
||||
- Update IP addresses (if changed)
|
||||
- Update resource allocations
|
||||
|
||||
- [ ] **Runbooks**
|
||||
- Update operational procedures for LXC
|
||||
- Document `pct` commands vs `qm` commands
|
||||
- Update backup/restore procedures
|
||||
|
||||
- [ ] **Monitoring**
|
||||
- Update monitoring configs for LXC IDs
|
||||
- Verify alerts still firing correctly
|
||||
- Update dashboards
|
||||
|
||||
- [ ] **Troubleshooting Guide**
|
||||
- Common LXC issues and solutions
|
||||
- Docker in LXC quirks
|
||||
- Performance tuning tips
|
||||
- Software transcoding optimization (Plex/Tdarr)
|
||||
|
||||
### Key Differences: VM vs LXC Operations
|
||||
|
||||
| Operation | VM Command | LXC Command |
|
||||
|-----------|-----------|-------------|
|
||||
| List | `qm list` | `pct list` |
|
||||
| Start | `qm start 106` | `pct start 206` |
|
||||
| Stop | `qm stop 106` | `pct stop 206` |
|
||||
| Enter console | `qm terminal 106` | `pct enter 206` |
|
||||
| Create | `qm create ...` | `pct create ...` |
|
||||
| Backup | `vzdump 106` | `vzdump 206` |
|
||||
| Restore | `qm restore ...` | `pct restore ...` |
|
||||
| Config | `/etc/pve/qemu-server/106.conf` | `/etc/pve/lxc/206.conf` |
|
||||
|
||||
---
|
||||
|
||||
## Phase 9: Timeline & Milestones
|
||||
|
||||
### Proposed Timeline (4-6 Weeks - Likely to Accelerate)
|
||||
|
||||
**Week 1: Wave 1 - Lowest Risk**
|
||||
- Day 1-2: Build and migrate docker-7days (111)
|
||||
- Day 3-7: Monitor and validate - if stable, proceed immediately
|
||||
|
||||
**Week 1-2: Wave 2 - Regional/Isolated Docker Hosts**
|
||||
- Day 5-6: Migrate docker-pittsburgh (114)
|
||||
- Day 7-8: Migrate docker-vpn (105)
|
||||
- Day 9-14: Monitor both services
|
||||
|
||||
**Week 2-3: Wave 3 - Additional Docker Hosts**
|
||||
- Day 10-11: Migrate docker-sba (115)
|
||||
- Day 12-13: Migrate docker-unused (117) or decommission
|
||||
- Day 14-15: Migrate docker-home-servers (116)
|
||||
- Day 16-21: Monitor all Wave 3 services
|
||||
|
||||
**Week 3-4: Wave 4 - Application & Database Servers**
|
||||
- Day 17-18: Migrate discord-bots (110)
|
||||
- Day 19-20: Migrate databases-bots (112) - EXTRA CARE
|
||||
- Day 21-28: Extended monitoring for database migration
|
||||
|
||||
**Week 4-5: Wave 5 - Media Services**
|
||||
- Day 22-23: Migrate docker-tdarr (113)
|
||||
- Day 24-25: Migrate plex (107)
|
||||
- Day 26-35: Monitor transcoding performance and CPU usage
|
||||
|
||||
**Week 5-6: Wave 6 - Final Critical Infrastructure**
|
||||
- Day 29-30: Migrate docker-home (106) - Most critical
|
||||
- Day 31-42: Extended monitoring and final optimization
|
||||
|
||||
**Post-Migration: Cleanup & Optimization**
|
||||
- Resource optimization (right-sizing containers)
|
||||
- Documentation finalization
|
||||
- Final VM decommissioning after confidence period
|
||||
|
||||
**Note**: Timeline likely to accelerate based on success and comfort level. Waves may overlap if previous waves are stable ahead of schedule.
|
||||
|
||||
### Decision Gates
|
||||
|
||||
**Gate 1 (After Wave 1)**: docker-7days Success
|
||||
- ✅ Game server stable and playable → Proceed to Wave 2
|
||||
- ❌ Issues encountered → Pause, troubleshoot, refine process
|
||||
|
||||
**Gate 2 (After Wave 2)**: Regional Docker Hosts Success
|
||||
- ✅ VPN routing working, regional services stable → Proceed to Wave 3
|
||||
- ❌ Critical issues → Pause and reassess approach
|
||||
|
||||
**Gate 3 (After Wave 3)**: Docker Infrastructure Success
|
||||
- ✅ All Docker hosts stable → Proceed to Wave 4
|
||||
- ❌ Issues → Pause, may need to adjust LXC configuration
|
||||
|
||||
**Gate 4 (After Wave 4)**: Database Migration Success
|
||||
- ✅ Database performance acceptable, no data issues → Proceed to Wave 5
|
||||
- ❌ Database performance issues → Investigate before proceeding
|
||||
|
||||
**Gate 5 (After Wave 5)**: Media Services Success
|
||||
- ✅ Software transcoding performance acceptable → Proceed to Wave 6
|
||||
- ❌ Transcoding too CPU-intensive → May need resource adjustment or keep as VMs
|
||||
|
||||
**Gate 6 (After Wave 6)**: Final Critical Service Success
|
||||
- ✅ docker-home stable → Begin cleanup and decommissioning
|
||||
- ❌ Issues → Rollback and reassess
|
||||
|
||||
---
|
||||
|
||||
## Phase 10: Post-Migration Operations
|
||||
|
||||
### Ongoing Management
|
||||
|
||||
**Monthly Tasks**:
|
||||
- Review resource utilization and right-size containers
|
||||
- Validate backup/restore procedures
|
||||
- Check for LXC template updates
|
||||
- Review and update documentation
|
||||
|
||||
**Quarterly Tasks**:
|
||||
- Evaluate new services for LXC vs VM placement
|
||||
- Performance benchmarking
|
||||
- Disaster recovery drill
|
||||
- Capacity planning review
|
||||
|
||||
### Continuous Improvement
|
||||
|
||||
**Optimization Opportunities**:
|
||||
- Standardize LXC templates with common tooling
|
||||
- Automate container provisioning (Terraform/Ansible)
|
||||
- Implement infrastructure-as-code for configs
|
||||
- Build CI/CD for container updates
|
||||
|
||||
**Future Considerations**:
|
||||
- Evaluate Proxmox clustering for HA
|
||||
- Consider container orchestration (Kubernetes) if container count grows
|
||||
- Explore automated resource balancing
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: LXC Container ID Mapping
|
||||
|
||||
**Proposed New Container IDs** (200-series for LXC):
|
||||
|
||||
| Wave | VM ID | VM Name | New LXC ID | LXC Name | Migration Priority |
|
||||
|------|-------|---------|-----------|----------|-------------------|
|
||||
| 1 | 111 | docker-7days | 211 | docker-7days-lxc | FIRST - Lowest risk validation |
|
||||
| 2 | 114 | docker-pittsburgh | 214 | docker-pittsburgh-lxc | Regional/isolated |
|
||||
| 2 | 121 | docker-vpn | 221 | arr-stack | ✅ COMPLETE - VPN eliminated, simplified to arr stack |
|
||||
| 3 | 115 | docker-sba | 215 | docker-sba-lxc | Additional Docker hosts |
|
||||
| 3 | 117 | docker-unused | 217 | docker-unused-lxc | Migrate or decommission |
|
||||
| 3 | 116 | docker-home-servers | 216 | docker-home-servers-lxc | Additional Docker hosts |
|
||||
| 4 | 110 | discord-bots | 210 | discord-bots-lxc | Application servers |
|
||||
| 4 | 112 | databases-bots | 212 | databases-bots-lxc | Database (EXTRA CARE) |
|
||||
| ~~5~~ | ~~113~~ | ~~docker-tdarr~~ | ~~213~~ | ~~docker-tdarr-lxc~~ | ❌ RETIRED - moved to GPU server |
|
||||
| ~~5~~ | ~~107~~ | ~~plex~~ | ~~207~~ | ~~plex-lxc~~ | ❌ DECOMMISSIONING - replaced by Jellyfin |
|
||||
| 6 | 106 | docker-home | 206 | docker-home-lxc | FINAL - Most critical |
|
||||
|
||||
**Keep as VM**:
|
||||
- 109 (hass-io) - HassOS requirement
|
||||
- 100 (ubuntu-template) - Strategic VM template
|
||||
- 103 (docker-template) - Convert to LXC template eventually
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: Quick Reference Commands
|
||||
|
||||
### Create Standard Docker LXC
|
||||
```bash
|
||||
pct create 2XX local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
|
||||
--hostname NAME \
|
||||
--memory 4096 \
|
||||
--cores 2 \
|
||||
--net0 name=eth0,bridge=vmbr0,ip=10.10.0.XX/24,gw=10.10.0.1 \
|
||||
--storage local-lvm \
|
||||
--rootfs local-lvm:32 \
|
||||
--unprivileged 0 \
|
||||
--features nesting=1,keyctl=1
|
||||
```
|
||||
|
||||
### Data Sync During Migration
|
||||
```bash
|
||||
# Initial sync (while VM running)
|
||||
rsync -avz --progress root@VM_IP:/data/ root@LXC_IP:/data/
|
||||
|
||||
# Final sync (VM stopped)
|
||||
rsync -avz --progress --delete root@VM_IP:/data/ root@LXC_IP:/data/
|
||||
```
|
||||
|
||||
### Quick Validation
|
||||
```bash
|
||||
# Check LXC is running
|
||||
pct status 2XX
|
||||
|
||||
# Check services inside
|
||||
pct enter 2XX
|
||||
systemctl status docker
|
||||
docker ps
|
||||
exit
|
||||
|
||||
# Network connectivity
|
||||
ping -c 3 10.10.0.2XX
|
||||
curl -f http://10.10.0.2XX
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix C: Contact & Escalation
|
||||
|
||||
**Migration Owner**: Cal Corum (cal.corum@gmail.com)
|
||||
|
||||
**Key Resources**:
|
||||
- Proxmox skill: `~/.claude/skills/proxmox/`
|
||||
- VM management docs: `/mnt/NV2/Development/claude-home/vm-management/`
|
||||
- Proxmox API: `~/.claude/skills/proxmox/proxmox_client.py`
|
||||
|
||||
**Support Channels**:
|
||||
- Proxmox forums: https://forum.proxmox.com/
|
||||
- LXC documentation: https://linuxcontainers.org/
|
||||
- Docker in LXC: https://forum.proxmox.com/threads/docker-in-lxc.38129/
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**:
|
||||
1. ✅ Migration plan approved with confirmed decisions
|
||||
2. Schedule Wave 1 migration window for docker-7days (111)
|
||||
3. Build first LXC container for docker-7days
|
||||
4. Execute Wave 1 migration and validate process
|
||||
|
||||
**Document Version**: 2.0 (Approved)
|
||||
**Last Updated**: 2025-01-12
|
||||
**Status**: Approved & Ready for Execution
|
||||
129
vm-management/migration-quick-start.md
Normal file
129
vm-management/migration-quick-start.md
Normal file
@ -0,0 +1,129 @@
|
||||
# VM to LXC Migration - Quick Start Guide
|
||||
|
||||
**Status**: Approved & Ready for Execution
|
||||
**Last Updated**: 2025-01-12
|
||||
|
||||
## ✅ Confirmed Decisions
|
||||
- **Networking**: Reuse existing IP addresses
|
||||
- **Storage**: Fresh install + volume copy for Docker hosts
|
||||
- **Timeline**: 4-6 weeks (expected to accelerate)
|
||||
- **GPU**: No GPU hardware - all services can migrate
|
||||
|
||||
## Migration Order (Risk-Based)
|
||||
|
||||
### Wave 1: docker-7days (111) - LOWEST RISK
|
||||
**Goal**: Validate entire migration process
|
||||
- Non-critical game server
|
||||
- Docker-in-LXC test
|
||||
- Build confidence
|
||||
|
||||
### Wave 2: docker-pittsburgh (114) + docker-vpn (105)
|
||||
**Goal**: Regional/isolated Docker hosts
|
||||
- Test VPN routing
|
||||
- Regional services validation
|
||||
|
||||
### Wave 3: docker-sba (115) + docker-unused (117) + docker-home-servers (116)
|
||||
**Goal**: Additional Docker infrastructure
|
||||
- Use SBA maintenance windows
|
||||
- Decommission unused if appropriate
|
||||
|
||||
### Wave 4: discord-bots (110) + databases-bots (112)
|
||||
**Goal**: Application & database servers
|
||||
- ⚠️ EXTRA CARE for database migration
|
||||
- Full backups required
|
||||
|
||||
### Wave 5: docker-tdarr (113) + plex (107)
|
||||
**Goal**: Media services (software transcoding)
|
||||
- Monitor CPU usage
|
||||
- Validate transcode performance
|
||||
|
||||
### Wave 6: docker-home (106) - MOST CRITICAL
|
||||
**Goal**: Final critical infrastructure
|
||||
- Migrate last after all confidence built
|
||||
- Most important home services
|
||||
|
||||
## Keep as VMs
|
||||
- **hass-io (109)**: HassOS requirement
|
||||
- **ubuntu-template (100)**: Strategic flexibility
|
||||
|
||||
## LXC Container IDs (200-series)
|
||||
|
||||
| VM → LXC | Service | Wave |
|
||||
|----------|---------|------|
|
||||
| 111 → 211 | docker-7days | 1 |
|
||||
| 114 → 214 | docker-pittsburgh | 2 |
|
||||
| 105 → 205 | docker-vpn | 2 |
|
||||
| 115 → 215 | docker-sba | 3 |
|
||||
| 117 → 217 | docker-unused | 3 |
|
||||
| 116 → 216 | docker-home-servers | 3 |
|
||||
| 110 → 210 | discord-bots | 4 |
|
||||
| 112 → 212 | databases-bots | 4 |
|
||||
| 113 → 213 | docker-tdarr | 5 |
|
||||
| 107 → 207 | plex | 5 |
|
||||
| 106 → 206 | docker-home | 6 |
|
||||
|
||||
## Quick Commands
|
||||
|
||||
### Create LXC for Docker
|
||||
```bash
|
||||
pct create 2XX local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst \
|
||||
--hostname docker-7days-lxc \
|
||||
--memory 4096 \
|
||||
--cores 2 \
|
||||
--net0 name=eth0,bridge=vmbr0,ip=10.10.0.TMP/24,gw=10.10.0.1 \
|
||||
--storage local-lvm \
|
||||
--rootfs local-lvm:32 \
|
||||
--unprivileged 0 \
|
||||
--features nesting=1,keyctl=1
|
||||
|
||||
pct start 2XX
|
||||
pct enter 2XX
|
||||
```
|
||||
|
||||
### Install Docker in LXC
|
||||
```bash
|
||||
apt update && apt upgrade -y
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sh get-docker.sh
|
||||
apt install docker-compose-plugin -y
|
||||
```
|
||||
|
||||
### Migrate Docker Volumes
|
||||
```bash
|
||||
# While VM running - initial sync
|
||||
rsync -avz --progress root@VM_IP:/var/lib/docker/volumes/ root@LXC_IP:/var/lib/docker/volumes/
|
||||
rsync -avz --progress root@VM_IP:/opt/docker/ root@LXC_IP:/opt/docker/
|
||||
|
||||
# During cutover - final sync with VM stopped
|
||||
rsync -avz --progress --delete root@VM_IP:/var/lib/docker/volumes/ root@LXC_IP:/var/lib/docker/volumes/
|
||||
```
|
||||
|
||||
### Cutover Process
|
||||
1. Stop VM: `qm stop 111`
|
||||
2. Reconfigure LXC to production IP
|
||||
3. Start LXC: `pct start 211`
|
||||
4. Validate services
|
||||
5. Monitor for 48 hours
|
||||
6. Keep VM stopped for rollback capability
|
||||
|
||||
### Rollback (if needed)
|
||||
```bash
|
||||
pct stop 211
|
||||
qm start 111
|
||||
```
|
||||
|
||||
## Next Immediate Steps
|
||||
|
||||
1. **Schedule Wave 1**: Pick maintenance window for docker-7days
|
||||
2. **Build LXC 211**: Create first container
|
||||
3. **Test & Migrate**: Execute Wave 1
|
||||
4. **Document Learnings**: Refine process for Wave 2
|
||||
|
||||
## Full Documentation
|
||||
See `/mnt/NV2/Development/claude-home/vm-management/lxc-migration-plan.md` for comprehensive details.
|
||||
|
||||
## Expected Benefits
|
||||
- **~17GB RAM freed** (87% reduction in overhead)
|
||||
- **5-10x faster backups/restores**
|
||||
- **Near-instant container starts** (1-5 seconds)
|
||||
- **Improved resource density**
|
||||
242
vm-management/scripts/LXC-MIGRATION-GUIDE.md
Normal file
242
vm-management/scripts/LXC-MIGRATION-GUIDE.md
Normal file
@ -0,0 +1,242 @@
|
||||
# LXC Migration Automation Scripts
|
||||
|
||||
This guide covers automation scripts for migrating VM-based Docker containers to LXC containers.
|
||||
|
||||
## Scripts Overview
|
||||
|
||||
### 1. `lxc-docker-create.sh` - LXC Container Creation
|
||||
|
||||
Automates the creation of LXC containers with Docker pre-installed and configured for container workloads.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
./lxc-docker-create.sh <VMID> <HOSTNAME> <IP> <DISK_SIZE> <MEMORY> <CORES> [PROXMOX_HOST]
|
||||
```
|
||||
|
||||
**Example (local Proxmox):**
|
||||
```bash
|
||||
./lxc-docker-create.sh 214 docker-pittsburgh-lxc 10.10.0.214 128G 16384 4
|
||||
```
|
||||
|
||||
**Example (remote Proxmox):**
|
||||
```bash
|
||||
./lxc-docker-create.sh 214 docker-pittsburgh-lxc 10.10.0.214 128G 16384 4 root@10.10.0.11
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Creates LXC container with specified resources
|
||||
- Configures AppArmor for Docker compatibility
|
||||
- Enables nesting and keyctl features
|
||||
- Installs Docker and docker-compose-plugin
|
||||
- Sets container to start on boot
|
||||
|
||||
**Time:** ~10 minutes (includes Docker installation)
|
||||
|
||||
---
|
||||
|
||||
### 2. `fix-docker-apparmor.sh` - Docker Compose AppArmor Fix
|
||||
|
||||
Adds AppArmor unconfined security options to all services in docker-compose.yml files. Required for Docker containers running inside LXC.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
./fix-docker-apparmor.sh <LXC_IP> [COMPOSE_DIR]
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
./fix-docker-apparmor.sh 10.10.0.214
|
||||
./fix-docker-apparmor.sh 10.10.0.214 /home/cal/container-data
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- SSHs into the LXC container
|
||||
- Finds all docker-compose.yml files
|
||||
- Adds `security_opt: ["apparmor=unconfined"]` to each service
|
||||
- Creates backups of original files
|
||||
|
||||
**Time:** ~1-2 minutes
|
||||
|
||||
---
|
||||
|
||||
## Complete Migration Workflow
|
||||
|
||||
### Wave 2 Example: VM 114 (Pittsburgh) → LXC 214
|
||||
|
||||
#### Step 1: Create LXC Container
|
||||
```bash
|
||||
cd /mnt/NV2/Development/claude-home/vm-management/scripts
|
||||
|
||||
./lxc-docker-create.sh 214 docker-pittsburgh-lxc 10.10.0.214 128G 16384 4 root@10.10.0.11
|
||||
```
|
||||
|
||||
**Wait ~10 minutes for Docker installation**
|
||||
|
||||
#### Step 2: Copy SSH Key (if needed)
|
||||
```bash
|
||||
ssh root@10.10.0.11 "cat ~/.ssh/id_rsa.pub | pct exec 214 -- tee /root/.ssh/authorized_keys"
|
||||
```
|
||||
|
||||
Or setup password-less SSH to LXC:
|
||||
```bash
|
||||
ssh root@10.10.0.11 "
|
||||
ssh-keyscan -H 10.10.0.214 >> ~/.ssh/known_hosts 2>/dev/null
|
||||
pct exec 214 -- mkdir -p /root/.ssh
|
||||
cat ~/.ssh/id_rsa.pub | pct exec 214 -- tee -a /root/.ssh/authorized_keys
|
||||
"
|
||||
```
|
||||
|
||||
#### Step 3: Migrate Data
|
||||
|
||||
**Option A: rsync from VM (recommended)**
|
||||
```bash
|
||||
# From Proxmox host - direct rsync from old VM to new LXC
|
||||
ssh root@10.10.0.11 "
|
||||
rsync -avz --info=progress2 \
|
||||
/mnt/vm114/home/cal/container-data/ \
|
||||
root@10.10.0.214:/home/cal/container-data/
|
||||
"
|
||||
```
|
||||
|
||||
**Option B: Mount and copy from backup**
|
||||
```bash
|
||||
# If VM is already shut down and you have backups/snapshots
|
||||
ssh root@10.10.0.11 "
|
||||
mount /dev/vm114-vg/vm114-data /mnt/vm114
|
||||
rsync -avz /mnt/vm114/home/cal/container-data/ root@10.10.0.214:/home/cal/container-data/
|
||||
"
|
||||
```
|
||||
|
||||
#### Step 4: Fix AppArmor in Docker Compose Files
|
||||
```bash
|
||||
./fix-docker-apparmor.sh 10.10.0.214
|
||||
```
|
||||
|
||||
#### Step 5: Start Containers
|
||||
```bash
|
||||
ssh root@10.10.0.214
|
||||
|
||||
# Navigate to service directory
|
||||
cd /home/cal/container-data/[service-name]
|
||||
|
||||
# Start containers
|
||||
docker compose up -d
|
||||
|
||||
# Check status
|
||||
docker compose ps
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
#### Step 6: Verify and Test
|
||||
- Test service functionality
|
||||
- Check container logs for errors
|
||||
- Verify network connectivity
|
||||
- Test external access (if applicable)
|
||||
|
||||
#### Step 7: Update Documentation
|
||||
- Mark VM as migrated in wave plan
|
||||
- Document any issues encountered
|
||||
- Update service inventory with new IP
|
||||
|
||||
---
|
||||
|
||||
## Time Estimates
|
||||
|
||||
| Step | Duration | Notes |
|
||||
|------|----------|-------|
|
||||
| Create LXC | 10 min | Includes Docker installation |
|
||||
| Setup SSH | 1 min | One-time setup |
|
||||
| Data migration | Varies | Depends on data size (58GB ≈ 60 min @ 16MB/s) |
|
||||
| Fix AppArmor | 2 min | Automated script |
|
||||
| Start containers | 5 min | Per service stack |
|
||||
| **Total (small data)** | **~20 min** | For <10GB data |
|
||||
| **Total (large data)** | **~80 min** | For ~60GB data like Wave 1 |
|
||||
|
||||
---
|
||||
|
||||
## Migration Waves Reference
|
||||
|
||||
Based on `vm-management/wave1-migration-results.md`:
|
||||
|
||||
### Remaining Migrations
|
||||
|
||||
| Wave | VM | Hostname | Services | Data Size | LXC ID | Priority |
|
||||
|------|-----|----------|----------|-----------|--------|----------|
|
||||
| 2 | 114 | Pittsburgh | Docker services | ~30GB | 214 | High |
|
||||
| 3 | 112 | Louisville | Docker services | ~20GB | 212 | High |
|
||||
| 4 | 115 | Denver | Docker services | ~25GB | 215 | Medium |
|
||||
| 5 | 113 | Fresno | Docker services | ~15GB | 213 | Medium |
|
||||
| 6 | Multiple | Misc services | Varies | Varies | TBD | Low |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container Creation Fails
|
||||
- **Check:** Template exists: `pct list | grep template`
|
||||
- **Check:** VMID not in use: `pct list | grep <VMID>`
|
||||
- **Check:** Sufficient storage: `pvesm status`
|
||||
|
||||
### SSH Connection Failed
|
||||
- **Check:** Container is running: `pct status <VMID>`
|
||||
- **Check:** SSH key copied: `pct exec <VMID> -- cat /root/.ssh/authorized_keys`
|
||||
- **Check:** Network connectivity: `ping <IP>`
|
||||
|
||||
### Docker Containers Won't Start
|
||||
- **Check:** AppArmor fix applied: `grep security_opt docker-compose.yml`
|
||||
- **Check:** Docker service running: `systemctl status docker`
|
||||
- **Check:** Container logs: `docker compose logs`
|
||||
|
||||
### Data Migration Slow
|
||||
- **Use:** rsync with compression: `rsync -avz`
|
||||
- **Use:** Direct VM-to-LXC transfer (avoid copying to intermediate location)
|
||||
- **Monitor:** Network usage: `iftop` or `nload`
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always create backups** before destroying source VMs
|
||||
2. **Test services** thoroughly before decommissioning VMs
|
||||
3. **Document changes** in wave results files
|
||||
4. **Monitor resources** during migration (CPU, RAM, disk I/O)
|
||||
5. **Schedule migrations** during low-usage periods
|
||||
6. **Keep scripts updated** with learnings from each wave
|
||||
|
||||
---
|
||||
|
||||
## Script Maintenance
|
||||
|
||||
### Adding New Features
|
||||
|
||||
Both scripts are designed to be easily extended:
|
||||
|
||||
- **lxc-docker-create.sh**: Add additional package installations, configuration steps, or validation checks
|
||||
- **fix-docker-apparmor.sh**: Modify the Python script to add additional docker-compose fixes
|
||||
|
||||
### Testing Changes
|
||||
|
||||
Before using updated scripts in production:
|
||||
1. Test on a non-critical VM migration
|
||||
2. Verify all steps complete successfully
|
||||
3. Check container functionality post-migration
|
||||
4. Document any new issues or improvements
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Wave 1 Migration Results](../wave1-migration-results.md) - Lessons learned from first migration
|
||||
- [Migration Quick Start](../migration-quick-start.md) - Fast reference guide
|
||||
- [LXC Migration Plan](../lxc-migration-plan.md) - Overall migration strategy
|
||||
- [VM Management Context](../CONTEXT.md) - VM infrastructure overview
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check troubleshooting section above
|
||||
2. Review wave results for similar issues
|
||||
3. Check Proxmox logs: `journalctl -u pve-container@<VMID>`
|
||||
4. Review Docker logs: `docker compose logs`
|
||||
275
vm-management/scripts/fix-docker-apparmor.sh
Executable file
275
vm-management/scripts/fix-docker-apparmor.sh
Executable file
@ -0,0 +1,275 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Docker Compose AppArmor Fix Script
|
||||
#
|
||||
# Adds 'security_opt: ["apparmor=unconfined"]' to all services in docker-compose.yml files
|
||||
# This is required for Docker containers running inside LXC containers.
|
||||
#
|
||||
# Usage: ./fix-docker-apparmor.sh <LXC_IP> [COMPOSE_DIR]
|
||||
#
|
||||
# Example: ./fix-docker-apparmor.sh 10.10.0.214
|
||||
# Example: ./fix-docker-apparmor.sh 10.10.0.214 /home/cal/container-data
|
||||
#
|
||||
# Arguments:
|
||||
# LXC_IP - IP address of the LXC container to SSH into
|
||||
# COMPOSE_DIR - Optional directory containing docker-compose files (default: /home/cal/container-data)
|
||||
#
|
||||
# What this script does:
|
||||
# 1. SSHs into the LXC container
|
||||
# 2. Finds all docker-compose.yml files
|
||||
# 3. Adds security_opt configuration to each service
|
||||
# 4. Creates backups of original files
|
||||
#
|
||||
# Why this is needed:
|
||||
# Docker containers in LXC need AppArmor disabled to function properly.
|
||||
# Without this fix, containers may fail to start or have permission issues.
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored messages
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_debug() {
|
||||
echo -e "${BLUE}[DEBUG]${NC} $1"
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
if [[ $# -lt 1 ]]; then
|
||||
log_error "Insufficient arguments"
|
||||
echo "Usage: $0 <LXC_IP> [COMPOSE_DIR]"
|
||||
echo ""
|
||||
echo "Example: $0 10.10.0.214"
|
||||
echo "Example: $0 10.10.0.214 /home/cal/container-data"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LXC_IP=$1
|
||||
COMPOSE_DIR=${2:-/home/cal/container-data}
|
||||
|
||||
log_info "Starting AppArmor fix for Docker Compose files"
|
||||
log_info "Target: root@$LXC_IP"
|
||||
log_info "Directory: $COMPOSE_DIR"
|
||||
echo ""
|
||||
|
||||
# Check SSH connectivity
|
||||
log_info "Testing SSH connection to $LXC_IP..."
|
||||
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes root@"$LXC_IP" "echo 'SSH OK'" &>/dev/null; then
|
||||
log_error "Cannot connect to root@$LXC_IP via SSH"
|
||||
log_error "Please ensure:"
|
||||
echo " 1. SSH key is copied to the LXC container"
|
||||
echo " 2. Container is running"
|
||||
echo " 3. IP address is correct"
|
||||
exit 1
|
||||
fi
|
||||
log_info "✅ SSH connection successful"
|
||||
echo ""
|
||||
|
||||
# Create Python script on remote host
|
||||
log_info "Creating AppArmor fix script on remote host..."
|
||||
ssh root@"$LXC_IP" "cat > /tmp/fix_apparmor.py" <<'PYTHON_SCRIPT'
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix Docker Compose files to work in LXC by adding AppArmor unconfined security option.
|
||||
"""
|
||||
import yaml
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def add_apparmor_fix(compose_file):
|
||||
"""Add security_opt to all services in a docker-compose file."""
|
||||
print(f"\n📄 Processing: {compose_file}")
|
||||
|
||||
# Create backup
|
||||
backup_file = f"{compose_file}.backup"
|
||||
if not os.path.exists(backup_file):
|
||||
os.system(f"cp '{compose_file}' '{backup_file}'")
|
||||
print(f" ✅ Backup created: {backup_file}")
|
||||
else:
|
||||
print(f" ⏭️ Backup already exists: {backup_file}")
|
||||
|
||||
# Load compose file
|
||||
try:
|
||||
with open(compose_file, 'r') as f:
|
||||
compose_data = yaml.safe_load(f)
|
||||
except yaml.YAMLError as e:
|
||||
print(f" ❌ Error parsing YAML: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ Error reading file: {e}")
|
||||
return False
|
||||
|
||||
if not compose_data or 'services' not in compose_data:
|
||||
print(f" ⚠️ No services found in compose file")
|
||||
return False
|
||||
|
||||
# Track changes
|
||||
services_modified = 0
|
||||
services_skipped = 0
|
||||
|
||||
# Add security_opt to each service
|
||||
for service_name, service_config in compose_data['services'].items():
|
||||
if service_config is None:
|
||||
service_config = {}
|
||||
compose_data['services'][service_name] = service_config
|
||||
|
||||
# Check if security_opt already exists
|
||||
existing_security = service_config.get('security_opt', [])
|
||||
|
||||
if 'apparmor=unconfined' in existing_security or 'apparmor:unconfined' in existing_security:
|
||||
print(f" ⏭️ {service_name}: Already has AppArmor unconfined")
|
||||
services_skipped += 1
|
||||
else:
|
||||
# Add apparmor=unconfined
|
||||
if not existing_security:
|
||||
service_config['security_opt'] = ['apparmor=unconfined']
|
||||
else:
|
||||
if 'apparmor=unconfined' not in existing_security:
|
||||
existing_security.append('apparmor=unconfined')
|
||||
service_config['security_opt'] = existing_security
|
||||
|
||||
print(f" ✅ {service_name}: Added AppArmor unconfined")
|
||||
services_modified += 1
|
||||
|
||||
# Write updated compose file
|
||||
try:
|
||||
with open(compose_file, 'w') as f:
|
||||
yaml.dump(compose_data, f, default_flow_style=False, sort_keys=False, indent=2)
|
||||
|
||||
if services_modified > 0:
|
||||
print(f" 💾 Saved changes ({services_modified} services modified)")
|
||||
|
||||
return services_modified > 0
|
||||
except Exception as e:
|
||||
print(f" ❌ Error writing file: {e}")
|
||||
# Restore backup
|
||||
os.system(f"cp '{backup_file}' '{compose_file}'")
|
||||
print(f" 🔄 Restored from backup")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to process all docker-compose files."""
|
||||
compose_dir = sys.argv[1] if len(sys.argv) > 1 else "/home/cal/container-data"
|
||||
|
||||
print(f"🔍 Searching for docker-compose.yml files in {compose_dir}")
|
||||
|
||||
# Find all docker-compose files
|
||||
patterns = [
|
||||
f"{compose_dir}/**/docker-compose.yml",
|
||||
f"{compose_dir}/**/docker-compose.yaml",
|
||||
]
|
||||
|
||||
compose_files = []
|
||||
for pattern in patterns:
|
||||
compose_files.extend(glob.glob(pattern, recursive=True))
|
||||
|
||||
# Remove duplicates and sort
|
||||
compose_files = sorted(set(compose_files))
|
||||
|
||||
if not compose_files:
|
||||
print(f"⚠️ No docker-compose files found in {compose_dir}")
|
||||
return 1
|
||||
|
||||
print(f"📋 Found {len(compose_files)} docker-compose file(s)")
|
||||
|
||||
# Process each file
|
||||
total_modified = 0
|
||||
total_errors = 0
|
||||
|
||||
for compose_file in compose_files:
|
||||
try:
|
||||
if add_apparmor_fix(compose_file):
|
||||
total_modified += 1
|
||||
except Exception as e:
|
||||
print(f" ❌ Unexpected error: {e}")
|
||||
total_errors += 1
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*60)
|
||||
print("📊 SUMMARY")
|
||||
print("="*60)
|
||||
print(f"Total files found: {len(compose_files)}")
|
||||
print(f"Files modified: {total_modified}")
|
||||
print(f"Files with errors: {total_errors}")
|
||||
print(f"Files unchanged: {len(compose_files) - total_modified - total_errors}")
|
||||
print("="*60)
|
||||
|
||||
if total_modified > 0:
|
||||
print("\n✅ AppArmor fix applied successfully!")
|
||||
print("\n💡 Next steps:")
|
||||
print(" 1. Review changes in modified files")
|
||||
print(" 2. Start containers: docker compose up -d")
|
||||
print(" 3. Check container status: docker compose ps")
|
||||
print("\n📝 Note: Backups created with .backup extension")
|
||||
|
||||
return 0 if total_errors == 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
PYTHON_SCRIPT
|
||||
|
||||
log_info "✅ Script uploaded to LXC container"
|
||||
echo ""
|
||||
|
||||
# Install PyYAML if needed
|
||||
log_info "Ensuring Python and PyYAML are installed..."
|
||||
ssh root@"$LXC_IP" "apt-get update -qq && apt-get install -y -qq python3 python3-yaml > /dev/null 2>&1" || true
|
||||
log_info "✅ Dependencies ready"
|
||||
echo ""
|
||||
|
||||
# Run the fix script
|
||||
log_info "Running AppArmor fix script..."
|
||||
echo ""
|
||||
ssh root@"$LXC_IP" "python3 /tmp/fix_apparmor.py '$COMPOSE_DIR'"
|
||||
EXIT_CODE=$?
|
||||
echo ""
|
||||
|
||||
# Cleanup
|
||||
log_info "Cleaning up temporary files..."
|
||||
ssh root@"$LXC_IP" "rm /tmp/fix_apparmor.py"
|
||||
log_info "✅ Cleanup complete"
|
||||
echo ""
|
||||
|
||||
if [[ $EXIT_CODE -eq 0 ]]; then
|
||||
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
log_info "🎉 AppArmor Fix Complete!"
|
||||
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Your docker-compose files have been updated to work in LXC."
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. SSH into container:"
|
||||
echo " ssh root@$LXC_IP"
|
||||
echo ""
|
||||
echo " 2. Navigate to a service directory:"
|
||||
echo " cd $COMPOSE_DIR/[service-name]"
|
||||
echo ""
|
||||
echo " 3. Start containers:"
|
||||
echo " docker compose up -d"
|
||||
echo ""
|
||||
echo " 4. Check status:"
|
||||
echo " docker compose ps"
|
||||
echo ""
|
||||
else
|
||||
log_error "AppArmor fix encountered errors. Please review output above."
|
||||
exit 1
|
||||
fi
|
||||
214
vm-management/scripts/lxc-docker-create.sh
Executable file
214
vm-management/scripts/lxc-docker-create.sh
Executable file
@ -0,0 +1,214 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# LXC Docker Container Creation Script
|
||||
#
|
||||
# Creates a new LXC container with Docker pre-installed and configured
|
||||
# for running containerized services.
|
||||
#
|
||||
# Usage: ./lxc-docker-create.sh <VMID> <HOSTNAME> <IP> <DISK_SIZE> <MEMORY> <CORES> [PROXMOX_HOST]
|
||||
#
|
||||
# Example: ./lxc-docker-create.sh 214 docker-pittsburgh-lxc 10.10.0.214 128G 16384 4
|
||||
# Example with remote host: ./lxc-docker-create.sh 214 docker-pittsburgh-lxc 10.10.0.214 128G 16384 4 root@10.10.0.11
|
||||
#
|
||||
# Arguments:
|
||||
# VMID - Proxmox container ID (e.g., 214)
|
||||
# HOSTNAME - Container hostname (e.g., docker-pittsburgh-lxc)
|
||||
# IP - Static IP address without CIDR (e.g., 10.10.0.214)
|
||||
# DISK_SIZE - Root filesystem size (e.g., 128G)
|
||||
# MEMORY - RAM in MB (e.g., 16384)
|
||||
# CORES - CPU cores (e.g., 4)
|
||||
# PROXMOX_HOST - Optional SSH host for remote Proxmox (e.g., root@10.10.0.11)
|
||||
#
|
||||
# What this script does:
|
||||
# 1. Creates LXC container with specified resources
|
||||
# 2. Configures AppArmor for Docker compatibility
|
||||
# 3. Enables nesting and keyctl features
|
||||
# 4. Installs Docker and docker-compose-plugin
|
||||
# 5. Sets up container to start on boot
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Ubuntu 20.04 template downloaded on Proxmox host
|
||||
# - Sufficient storage on local-lvm
|
||||
# - Network bridge vmbr0 configured
|
||||
# - Gateway at 10.10.0.1
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored messages
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to execute commands on Proxmox host
|
||||
execute_on_proxmox() {
|
||||
if [[ -n "${PROXMOX_HOST:-}" ]]; then
|
||||
ssh "$PROXMOX_HOST" "$@"
|
||||
else
|
||||
bash -c "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
if [[ $# -lt 6 ]]; then
|
||||
log_error "Insufficient arguments"
|
||||
echo "Usage: $0 <VMID> <HOSTNAME> <IP> <DISK_SIZE> <MEMORY> <CORES> [PROXMOX_HOST]"
|
||||
echo ""
|
||||
echo "Example: $0 214 docker-pittsburgh-lxc 10.10.0.214 128G 16384 4"
|
||||
echo "Example: $0 214 docker-pittsburgh-lxc 10.10.0.214 128G 16384 4 root@10.10.0.11"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VMID=$1
|
||||
HOSTNAME=$2
|
||||
IP=$3
|
||||
DISK_SIZE=$4
|
||||
MEMORY=$5
|
||||
CORES=$6
|
||||
PROXMOX_HOST=${7:-}
|
||||
|
||||
# Configuration
|
||||
TEMPLATE="local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz"
|
||||
GATEWAY="10.10.0.1"
|
||||
NAMESERVER="8.8.8.8"
|
||||
CIDR="24"
|
||||
|
||||
log_info "Starting LXC container creation"
|
||||
log_info "Configuration:"
|
||||
echo " VMID: $VMID"
|
||||
echo " Hostname: $HOSTNAME"
|
||||
echo " IP: $IP/$CIDR"
|
||||
echo " Disk: $DISK_SIZE"
|
||||
echo " Memory: $MEMORY MB"
|
||||
echo " Cores: $CORES"
|
||||
[[ -n "${PROXMOX_HOST:-}" ]] && echo " Proxmox: $PROXMOX_HOST" || echo " Proxmox: local"
|
||||
echo ""
|
||||
|
||||
# Check if container already exists
|
||||
log_info "Checking if container $VMID already exists..."
|
||||
if execute_on_proxmox "pct status $VMID 2>/dev/null"; then
|
||||
log_error "Container $VMID already exists!"
|
||||
read -p "Do you want to destroy and recreate it? (yes/no): " -r
|
||||
if [[ $REPLY == "yes" ]]; then
|
||||
log_warn "Stopping and destroying container $VMID..."
|
||||
execute_on_proxmox "pct stop $VMID 2>/dev/null || true"
|
||||
execute_on_proxmox "pct destroy $VMID"
|
||||
log_info "Container $VMID destroyed"
|
||||
else
|
||||
log_error "Aborted by user"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create the LXC container
|
||||
log_info "Creating LXC container $VMID..."
|
||||
execute_on_proxmox "pct create $VMID $TEMPLATE \
|
||||
--hostname $HOSTNAME \
|
||||
--memory $MEMORY \
|
||||
--cores $CORES \
|
||||
--rootfs local-lvm:$DISK_SIZE \
|
||||
--net0 name=eth0,bridge=vmbr0,ip=$IP/$CIDR,gw=$GATEWAY \
|
||||
--unprivileged 0 \
|
||||
--onboot 1 \
|
||||
--nameserver $NAMESERVER"
|
||||
|
||||
log_info "✅ Container created"
|
||||
|
||||
# Configure AppArmor and features
|
||||
log_info "Configuring AppArmor profile and container features..."
|
||||
execute_on_proxmox "cat >> /etc/pve/lxc/$VMID.conf << 'EOF'
|
||||
lxc.apparmor.profile: unconfined
|
||||
lxc.cgroup2.devices.allow: a
|
||||
lxc.cap.drop:
|
||||
EOF"
|
||||
|
||||
# Update features line
|
||||
execute_on_proxmox "sed -i 's/^features:.*/features: nesting=1,keyctl=1/' /etc/pve/lxc/$VMID.conf"
|
||||
|
||||
log_info "✅ AppArmor and features configured"
|
||||
|
||||
# Start the container
|
||||
log_info "Starting container $VMID..."
|
||||
execute_on_proxmox "pct start $VMID"
|
||||
|
||||
log_info "Waiting 10 seconds for container to boot..."
|
||||
sleep 10
|
||||
|
||||
# Install Docker
|
||||
log_info "Installing Docker and dependencies..."
|
||||
execute_on_proxmox "pct exec $VMID -- bash <<'DOCKER_INSTALL'
|
||||
set -e
|
||||
|
||||
# Update package list
|
||||
apt-get update
|
||||
|
||||
# Install prerequisites
|
||||
apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
|
||||
# Download and run Docker installation script
|
||||
curl -fsSL https://get.docker.com -o /tmp/get-docker.sh
|
||||
sh /tmp/get-docker.sh
|
||||
|
||||
# Install docker-compose-plugin
|
||||
apt-get install -y docker-compose-plugin
|
||||
|
||||
# Enable Docker service
|
||||
systemctl enable docker
|
||||
systemctl start docker
|
||||
|
||||
# Verify installation
|
||||
docker --version
|
||||
docker compose version
|
||||
|
||||
echo '✅ Docker installation complete'
|
||||
DOCKER_INSTALL"
|
||||
|
||||
log_info "✅ Docker installed successfully"
|
||||
|
||||
# Display completion message
|
||||
echo ""
|
||||
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
log_info "🎉 LXC Container $VMID Ready!"
|
||||
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Container Details:"
|
||||
echo " ID: $VMID"
|
||||
echo " Hostname: $HOSTNAME"
|
||||
echo " IP: $IP"
|
||||
echo " Status: Running"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo " 1. Copy SSH key (if needed):"
|
||||
if [[ -n "${PROXMOX_HOST:-}" ]]; then
|
||||
echo " ssh $PROXMOX_HOST \"cat ~/.ssh/id_rsa.pub | pct exec $VMID -- tee /root/.ssh/authorized_keys\""
|
||||
else
|
||||
echo " cat ~/.ssh/id_rsa.pub | pct exec $VMID -- tee /root/.ssh/authorized_keys"
|
||||
fi
|
||||
echo ""
|
||||
echo " 2. Migrate data from source VM"
|
||||
echo ""
|
||||
echo " 3. Fix AppArmor in docker-compose files:"
|
||||
echo " ./fix-docker-apparmor.sh $IP"
|
||||
echo ""
|
||||
echo " 4. Start containers:"
|
||||
echo " ssh root@$IP 'cd /home/cal/container-data/[service] && docker compose up -d'"
|
||||
echo ""
|
||||
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
369
vm-management/wave1-migration-results.md
Normal file
369
vm-management/wave1-migration-results.md
Normal file
@ -0,0 +1,369 @@
|
||||
# Wave 1 Migration Results - docker-7days (VM 111 → LXC 211)
|
||||
|
||||
**Date**: 2025-01-12
|
||||
**Status**: ✅ **SUCCESSFUL**
|
||||
**Migration Time**: ~4 hours (including troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Successfully migrated docker-7days game server from VM 111 to LXC 211. Container is running with all data intact. AppArmor configuration issue was resolved, and the migration process has been validated for future waves.
|
||||
|
||||
---
|
||||
|
||||
## Migration Details
|
||||
|
||||
### Source (VM 111)
|
||||
- **OS**: Ubuntu (in VM)
|
||||
- **Resources**: 32GB RAM, 4 cores, 256GB disk
|
||||
- **Uptime before migration**: 307.4 hours
|
||||
- **Services**: 3 docker-compose projects (7 Days to Die game servers)
|
||||
- **Data size**: 62GB
|
||||
|
||||
### Destination (LXC 211)
|
||||
- **OS**: Ubuntu 20.04 LTS (in privileged LXC)
|
||||
- **Resources**: 32GB RAM, 4 cores, 128GB disk (expanded from initial 64GB)
|
||||
- **IP**: 10.10.0.250 (temporary)
|
||||
- **Services**: 1 game server running (7dtd-solo-game)
|
||||
- **Container ID**: d87df36c2dcd
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
| Time | Action | Status |
|
||||
|------|--------|--------|
|
||||
| Start | Gathered VM configuration | ✅ Complete |
|
||||
| +15min | Created LXC 211 with Docker | ✅ Complete |
|
||||
| +30min | Stopped VM 111 | ✅ Complete |
|
||||
| +45min | Mounted VM disk and started rsync (62GB) | ✅ Complete |
|
||||
| +2h 30min | Rsync completed | ✅ Complete |
|
||||
| +2h 35min | **Disk full** - expanded from 64GB to 128GB | ✅ Resolved |
|
||||
| +3h 00min | AppArmor blocking Docker containers | ⚠️ Issue |
|
||||
| +3h 45min | Fixed AppArmor in docker-compose files | ✅ Resolved |
|
||||
| +4h 00min | Container started successfully | ✅ Complete |
|
||||
|
||||
---
|
||||
|
||||
## Issues Encountered & Solutions
|
||||
|
||||
### Issue 1: Disk Space Insufficient
|
||||
**Problem**: 64GB disk filled to 100% with only 62GB of data
|
||||
**Cause**: Thin provisioning still requires space for the data being written
|
||||
**Solution**: Expanded LXC disk from 64GB to 128GB
|
||||
**Command**:
|
||||
```bash
|
||||
pct resize 211 rootfs +64G
|
||||
```
|
||||
**Learning**: Allocate 2x data size for LXC root filesystem to account for overhead
|
||||
|
||||
---
|
||||
|
||||
### Issue 2: AppArmor Prevents Docker Container Start
|
||||
**Problem**: Containers fail to start with error:
|
||||
```
|
||||
AppArmor enabled on system but the docker-default profile could not be loaded:
|
||||
Permission denied; attempted to load a profile while confined?
|
||||
error: exit status 243
|
||||
```
|
||||
|
||||
**Root Cause**: LXC containers run "confined" by AppArmor, preventing Docker from loading its own AppArmor profiles
|
||||
|
||||
**Solutions Attempted**:
|
||||
1. ❌ Disabled AppArmor at LXC level (`lxc.apparmor.profile: unconfined`) - Didn't help
|
||||
2. ❌ Tried to configure Docker daemon.json with security options - Invalid config option
|
||||
3. ✅ **Added security_opt to docker-compose.yml files** - WORKED!
|
||||
|
||||
**Working Solution**:
|
||||
```yaml
|
||||
# Add to each service in docker-compose.yml
|
||||
services:
|
||||
service-name:
|
||||
image: ...
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
# ... rest of config
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```bash
|
||||
# Used Python to properly modify YAML files
|
||||
python3 <<'PYTHON'
|
||||
import yaml
|
||||
import glob
|
||||
|
||||
for compose_path in glob.glob("/home/cal/container-data/ul-*/docker-compose.yml"):
|
||||
with open(compose_path, 'r') as f:
|
||||
compose = yaml.safe_load(f)
|
||||
|
||||
for service_name, service_config in compose.get('services', {}).items():
|
||||
service_config['security_opt'] = ['apparmor=unconfined']
|
||||
|
||||
with open(compose_path, 'w') as f:
|
||||
yaml.dump(compose, f, default_flow_style=False, sort_keys=False)
|
||||
PYTHON
|
||||
```
|
||||
|
||||
**Why This Works**: Tells Docker to run containers without AppArmor confinement, bypassing the LXC AppArmor conflict
|
||||
|
||||
**Learning**: **ALL future Docker-in-LXC migrations require this modification**
|
||||
|
||||
---
|
||||
|
||||
## Resource Usage Comparison
|
||||
|
||||
### Before Migration (VM)
|
||||
- **Memory**: 345MB used / 32GB allocated (1% utilization, 99% wasted)
|
||||
- **Disk**: Unknown actual usage / 256GB allocated
|
||||
- **CPU**: 0% (idle)
|
||||
- **Boot time**: ~30-90 seconds
|
||||
|
||||
### After Migration (LXC)
|
||||
- **Memory**: 248MB used / 32GB allocated (similar usage, but faster access)
|
||||
- **Disk**: 60GB used / 128GB allocated (47% utilization)
|
||||
- **CPU**: 0% (idle, same as before)
|
||||
- **Boot time**: ~5 seconds
|
||||
|
||||
### Efficiency Gains
|
||||
- **Memory overhead**: Reduced from ~700MB (VM OS) to ~100MB (LXC overhead) = **600MB saved**
|
||||
- **Disk usage**: More transparent (thin provisioning visible)
|
||||
- **Boot time**: **6-18x faster** (5s vs 30-90s)
|
||||
- **Backup time**: Expected **5-10x faster** (LXC incremental backups)
|
||||
|
||||
---
|
||||
|
||||
## Final Configuration
|
||||
|
||||
### LXC 211 Config (`/etc/pve/lxc/211.conf`)
|
||||
```
|
||||
arch: amd64
|
||||
cores: 4
|
||||
hostname: docker-7days-lxc
|
||||
memory: 32768
|
||||
nameserver: 8.8.8.8
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,hwaddr=CE:7E:8F:B2:40:C2,ip=10.10.0.250/24,type=veth
|
||||
onboot: 1
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-211-disk-0,size=128G
|
||||
searchdomain: local
|
||||
swap: 2048
|
||||
features: nesting=1,keyctl=1
|
||||
lxc.apparmor.profile: unconfined
|
||||
```
|
||||
|
||||
### Running Container
|
||||
```bash
|
||||
CONTAINER ID IMAGE STATUS PORTS
|
||||
d87df36c2dcd vinanrra/7dtd-server Up 12 seconds 0.0.0.0:26900->26900/tcp,
|
||||
0.0.0.0:26900-26902->26900-26902/udp
|
||||
```
|
||||
|
||||
### Docker-Compose Projects
|
||||
1. **ul-solo-game** - ✅ Running on port 26900
|
||||
2. **ul-test** - ⏸️ Stopped (port conflict with ul-solo-game)
|
||||
3. **ul-public** - ⏸️ Stopped (port conflict with ul-solo-game)
|
||||
|
||||
**Note**: All three projects work, but only one can run at a time due to shared port 26900 (expected behavior)
|
||||
|
||||
---
|
||||
|
||||
## Validation Results
|
||||
|
||||
✅ **Container Status**: Running and healthy
|
||||
✅ **Data Integrity**: All 62GB of game server data accessible
|
||||
✅ **Network**: Listening on expected ports (26900-26902)
|
||||
✅ **Docker**: Working correctly with AppArmor fix
|
||||
✅ **Performance**: Container started successfully, no errors in logs
|
||||
|
||||
---
|
||||
|
||||
## Key Learnings for Future Waves
|
||||
|
||||
### 1. Disk Sizing
|
||||
- **Rule**: Allocate **2x the data size** for LXC root filesystem
|
||||
- **Why**: Accounts for overhead, temporary files, and headroom
|
||||
- **Example**: 62GB data → 128GB allocation (not 64GB)
|
||||
|
||||
### 2. AppArmor Configuration
|
||||
- **Critical**: ALL docker-compose files need `security_opt: [apparmor=unconfined]`
|
||||
- **When**: Add this BEFORE starting containers (not after)
|
||||
- **How**: Use Python/YAML library for proper syntax (sed breaks YAML)
|
||||
- **Template**:
|
||||
```python
|
||||
import yaml
|
||||
for compose_path in glob.glob("*/docker-compose.yml"):
|
||||
with open(compose_path, 'r') as f:
|
||||
compose = yaml.safe_load(f)
|
||||
for service_name, service_config in compose.get('services', {}).items():
|
||||
service_config['security_opt'] = ['apparmor=unconfined']
|
||||
with open(compose_path, 'w') as f:
|
||||
yaml.dump(compose, f, default_flow_style=False, sort_keys=False)
|
||||
```
|
||||
|
||||
### 3. LXC Configuration Requirements
|
||||
- **Privileged mode**: Required (`--unprivileged 0`)
|
||||
- **Features**: `nesting=1,keyctl=1` for Docker
|
||||
- **AppArmor**: `lxc.apparmor.profile: unconfined` in config
|
||||
|
||||
### 4. Data Migration Strategy
|
||||
- **Method**: rsync over network worked well (16MB/s average)
|
||||
- **Time**: ~1 hour for 62GB (acceptable)
|
||||
- **Alternative**: Direct disk mount + copy would be faster but more complex
|
||||
|
||||
### 5. Ubuntu Version
|
||||
- **Used**: Ubuntu 20.04 LTS (Proxmox didn't support 22.04 template)
|
||||
- **Works**: Perfectly fine, Docker 28.1.1 installed successfully
|
||||
- **Note**: Not a blocker for migration
|
||||
|
||||
---
|
||||
|
||||
## Rollback Capability
|
||||
|
||||
✅ **VM 111 preserved**: Stopped but intact, can restart if needed
|
||||
✅ **VM disk mounted**: Available at `/mnt/vm111` on Proxmox host
|
||||
✅ **Rollback time**: <5 minutes (just start VM 111)
|
||||
✅ **Data loss risk**: None (original data untouched)
|
||||
|
||||
**Rollback command if needed**:
|
||||
```bash
|
||||
pct stop 211
|
||||
qm start 111
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended Monitoring Period
|
||||
|
||||
- **24-48 hours**: Keep VM 111 stopped but available
|
||||
- **After 48 hours**: If LXC stable, can delete VM 111
|
||||
- **Backup before delete**: Create LXC backup first
|
||||
|
||||
**Monitoring checklist**:
|
||||
- [ ] Game server connectable and playable
|
||||
- [ ] No crashes or restarts
|
||||
- [ ] Memory usage stable
|
||||
- [ ] No disk space issues
|
||||
- [ ] Backup/restore tested
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate (Optional)
|
||||
- [ ] Test game server connectivity from client
|
||||
- [ ] Switch LXC 211 from temp IP (10.10.0.250) to production IP if needed
|
||||
- [ ] Update DNS/firewall rules if required
|
||||
|
||||
### Short Term (24-48 hours)
|
||||
- [ ] Monitor LXC stability
|
||||
- [ ] Validate container doesn't crash
|
||||
- [ ] Check resource usage patterns
|
||||
|
||||
### Before Wave 2
|
||||
- [ ] Create LXC backup
|
||||
- [ ] Verify backup restore procedure
|
||||
- [ ] Delete VM 111 (or archive)
|
||||
- [ ] Update migration scripts with AppArmor fix
|
||||
- [ ] Update Wave 2 plan with learnings
|
||||
|
||||
---
|
||||
|
||||
## Updated Migration Checklist for Waves 2-6
|
||||
|
||||
Based on Wave 1 learnings, future migrations should follow this checklist:
|
||||
|
||||
### Pre-Migration
|
||||
- [ ] Document VM configuration (IP, resources, services)
|
||||
- [ ] Calculate disk space: **data_size × 2** for LXC allocation
|
||||
- [ ] Create LXC with privileged mode + nesting + keyctl
|
||||
- [ ] Add `lxc.apparmor.profile: unconfined` to LXC config
|
||||
- [ ] Install Docker in LXC
|
||||
|
||||
### Migration
|
||||
- [ ] Stop VM
|
||||
- [ ] Mount VM disk OR rsync data
|
||||
- [ ] **Apply AppArmor fix to all docker-compose.yml files**
|
||||
- [ ] Start containers
|
||||
- [ ] Validate services
|
||||
|
||||
### Post-Migration
|
||||
- [ ] Monitor for 24-48 hours
|
||||
- [ ] Create LXC backup
|
||||
- [ ] Delete/archive VM after validation
|
||||
|
||||
---
|
||||
|
||||
## Migration Efficiency Metrics
|
||||
|
||||
| Metric | Value | Notes |
|
||||
|--------|-------|-------|
|
||||
| **Planning time** | 30 minutes | Documentation review |
|
||||
| **Execution time** | 4 hours | Including troubleshooting |
|
||||
| **Troubleshooting time** | 1.5 hours | AppArmor + disk space |
|
||||
| **Data migration time** | 1 hour | 62GB rsync |
|
||||
| **Downtime** | 4 hours | Game server unavailable |
|
||||
| **Success rate** | 100% | All services working |
|
||||
|
||||
### Expected Improvement for Wave 2+
|
||||
With AppArmor fix pre-applied and proper disk sizing:
|
||||
- **Execution time**: ~2 hours (50% reduction)
|
||||
- **Troubleshooting time**: <30 minutes
|
||||
- **Downtime**: ~2 hours
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
### Docker-Compose Files (AppArmor Fix Applied)
|
||||
- `/home/cal/container-data/ul-solo-game/docker-compose.yml`
|
||||
- `/home/cal/container-data/ul-test/docker-compose.yml`
|
||||
- `/home/cal/container-data/ul-public/docker-compose.yml`
|
||||
|
||||
### Proxmox Configuration
|
||||
- `/etc/pve/lxc/211.conf` (LXC config with AppArmor unconfined)
|
||||
|
||||
### Backups Created
|
||||
- `docker-compose.yml.backup` (all three directories)
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria Met
|
||||
|
||||
✅ All success criteria from migration plan achieved:
|
||||
|
||||
- [x] Services running stable in LXC
|
||||
- [x] No performance degradation
|
||||
- [x] Backup/restore procedure understood
|
||||
- [x] Rollback procedure validated
|
||||
- [x] Process documented for next waves
|
||||
- [x] AppArmor solution identified and documented
|
||||
|
||||
---
|
||||
|
||||
## Recommendations for Remaining Waves
|
||||
|
||||
### Wave 2 (docker-pittsburgh + docker-vpn)
|
||||
- **Pre-apply AppArmor fix** before starting containers
|
||||
- **Size disks appropriately** from the start
|
||||
- **Test VPN routing** carefully (docker-vpn specific)
|
||||
- **Expected time**: 2-3 hours per host
|
||||
|
||||
### General Recommendations
|
||||
1. **Batch similar services**: Migrate Docker hosts together (leverage learnings)
|
||||
2. **Off-hours migrations**: Minimize user impact
|
||||
3. **Document per-wave**: Capture unique issues for each service type
|
||||
4. **Automate AppArmor fix**: Create script to modify docker-compose files automatically
|
||||
5. **Right-size after monitoring**: Review resource allocation after 1-2 weeks
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
**Migration Owner**: Cal Corum (cal.corum@gmail.com)
|
||||
**Date Completed**: 2025-01-12
|
||||
**Next Wave**: Wave 2 (docker-pittsburgh, docker-vpn) - TBD
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ **Wave 1 Complete - Ready for Wave 2**
|
||||
278
vm-management/wave2-migration-results.md
Normal file
278
vm-management/wave2-migration-results.md
Normal file
@ -0,0 +1,278 @@
|
||||
# Wave 2 Migration Results - docker-vpn (VM 121 → LXC 221 arr-stack)
|
||||
|
||||
**Date**: 2025-12-05
|
||||
**Status**: **SUCCESSFUL**
|
||||
**Migration Time**: ~2 hours
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Successfully migrated and restructured docker-vpn VM (121) to arr-stack LXC (221). The migration involved a significant architecture simplification - eliminating the Mullvad VPN entirely since only Usenet is used (SSL to Usenet provider is sufficient, no torrents). Additionally replaced Overseerr with Jellyseerr for native Jellyfin support.
|
||||
|
||||
---
|
||||
|
||||
## Migration Details
|
||||
|
||||
### Source (VM 121 - docker-vpn)
|
||||
- **OS**: Ubuntu (in VM)
|
||||
- **Services**: Sonarr, Radarr, Readarr, Overseerr, SABnzbd, Mullvad VPN
|
||||
- **Architecture**: All traffic routed through Mullvad VPN container
|
||||
- **Complexity**: High (VPN routing, multiple network namespaces)
|
||||
|
||||
### Destination (LXC 221 - arr-stack)
|
||||
- **OS**: Ubuntu 20.04 LTS (privileged LXC)
|
||||
- **Resources**: 2 cores, 4GB RAM, 32GB disk
|
||||
- **IP**: 10.10.0.221
|
||||
- **Services**: Sonarr, Radarr, Readarr, Jellyseerr, SABnzbd
|
||||
- **Architecture**: Direct network access (no VPN)
|
||||
- **Complexity**: Low (standard Docker containers)
|
||||
|
||||
---
|
||||
|
||||
## Architecture Changes
|
||||
|
||||
### Before (docker-vpn)
|
||||
```
|
||||
Internet
|
||||
↓
|
||||
Mullvad VPN Container
|
||||
↓ (all traffic tunneled)
|
||||
├─ Sonarr
|
||||
├─ Radarr
|
||||
├─ Readarr
|
||||
├─ Overseerr
|
||||
└─ SABnzbd
|
||||
```
|
||||
|
||||
### After (arr-stack)
|
||||
```
|
||||
Internet
|
||||
↓ (direct, SSL encrypted to Usenet)
|
||||
├─ Sonarr
|
||||
├─ Radarr
|
||||
├─ Readarr
|
||||
├─ Jellyseerr (replaced Overseerr)
|
||||
└─ SABnzbd
|
||||
```
|
||||
|
||||
### Key Decision: VPN Elimination
|
||||
**Rationale**:
|
||||
- Only using Usenet (not torrents)
|
||||
- Usenet providers support SSL encryption
|
||||
- SSL to Usenet provider provides sufficient privacy
|
||||
- VPN added complexity without meaningful benefit
|
||||
- Simplified troubleshooting and maintenance
|
||||
|
||||
---
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### LXC Configuration
|
||||
```
|
||||
# /etc/pve/lxc/221.conf
|
||||
arch: amd64
|
||||
cores: 2
|
||||
features: nesting=1,keyctl=1
|
||||
hostname: arr-stack
|
||||
memory: 4096
|
||||
net0: name=eth0,bridge=vmbr0,gw=10.10.0.1,ip=10.10.0.221/24,type=veth
|
||||
ostype: ubuntu
|
||||
rootfs: local-lvm:vm-221-disk-0,size=32G
|
||||
swap: 512
|
||||
lxc.apparmor.profile: unconfined
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
```yaml
|
||||
services:
|
||||
sonarr:
|
||||
image: linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
ports: ["8989:8989"]
|
||||
volumes:
|
||||
- ./config/sonarr:/config
|
||||
- /mnt/media:/media
|
||||
security_opt: [apparmor=unconfined]
|
||||
restart: unless-stopped
|
||||
|
||||
radarr:
|
||||
image: linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
ports: ["7878:7878"]
|
||||
volumes:
|
||||
- ./config/radarr:/config
|
||||
- /mnt/media:/media
|
||||
security_opt: [apparmor=unconfined]
|
||||
restart: unless-stopped
|
||||
|
||||
readarr:
|
||||
image: ghcr.io/hotio/readarr:latest
|
||||
container_name: readarr
|
||||
ports: ["8787:8787"]
|
||||
volumes:
|
||||
- ./config/readarr:/config
|
||||
- /mnt/media:/media
|
||||
security_opt: [apparmor=unconfined]
|
||||
restart: unless-stopped
|
||||
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr:latest
|
||||
container_name: jellyseerr
|
||||
ports: ["5055:5055"]
|
||||
volumes:
|
||||
- ./config/jellyseerr:/app/config
|
||||
security_opt: [apparmor=unconfined]
|
||||
restart: unless-stopped
|
||||
|
||||
sabnzbd:
|
||||
image: linuxserver/sabnzbd:latest
|
||||
container_name: sabnzbd
|
||||
ports: ["8080:8080"]
|
||||
volumes:
|
||||
- ./config/sabnzbd:/config
|
||||
- ./downloads:/downloads
|
||||
- /mnt/media:/media
|
||||
security_opt: [apparmor=unconfined]
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
### CIFS Mount
|
||||
```fstab
|
||||
//10.10.0.35/media /mnt/media cifs vers=3.0,uid=0,credentials=/root/.smbcredentials 0 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issues Encountered & Solutions
|
||||
|
||||
### Issue 1: linuxserver.io Registry (lscr.io) Pull Failures
|
||||
**Problem**: `no matching manifest for linux/amd64` errors from lscr.io registry
|
||||
**Solution**: Switched to Docker Hub images directly (`linuxserver/sonarr` instead of `lscr.io/linuxserver/sonarr`)
|
||||
|
||||
### Issue 2: Readarr Image Not Available
|
||||
**Problem**: linuxserver/readarr:develop tag not available for amd64
|
||||
**Solution**: Switched to hotio image (`ghcr.io/hotio/readarr:latest`)
|
||||
|
||||
### Issue 3: Jellyseerr Tag Validation Error
|
||||
**Problem**: Radarr rejecting requests with "Label: Allowed characters a-z, 0-9 and -"
|
||||
**Cause**: Jellyseerr sending tags with invalid characters to Radarr
|
||||
**Solution**: Disabled tags in Jellyseerr Radarr integration settings
|
||||
|
||||
---
|
||||
|
||||
## Data Migration
|
||||
|
||||
### Configs Migrated
|
||||
- **Sonarr**: ~1.4GB (database, MediaCover cache, backups)
|
||||
- **Radarr**: ~1.6GB (database, MediaCover cache, backups)
|
||||
- **Readarr**: ~88MB (database, backups)
|
||||
- **Overseerr**: ~7.7MB (database, settings) - Not used, replaced with Jellyseerr
|
||||
|
||||
### Fresh Configuration Required
|
||||
- **SABnzbd**: Fresh install (user configured Usenet provider)
|
||||
- **Jellyseerr**: Fresh install (connected to Jellyfin)
|
||||
|
||||
---
|
||||
|
||||
## Validation Results
|
||||
|
||||
| Service | Port | Status | Test |
|
||||
|---------|------|--------|------|
|
||||
| Sonarr | 8989 | HTTP 200 | Database loaded, shows configured |
|
||||
| Radarr | 7878 | HTTP 200 | Database loaded, movie requests working |
|
||||
| Readarr | 8787 | HTTP 200 | Database loaded, shows configured |
|
||||
| Jellyseerr | 5055 | HTTP 307 | Connected to Jellyfin, requests working |
|
||||
| SABnzbd | 8080 | HTTP 303 | Configured with Usenet provider |
|
||||
| CIFS Mount | - | Working | Media accessible in containers |
|
||||
|
||||
---
|
||||
|
||||
## Resource Comparison
|
||||
|
||||
### Before (VM 121)
|
||||
- **Memory**: Full VM overhead (~1-2GB for OS)
|
||||
- **Disk**: Larger allocation for VM image
|
||||
- **Complexity**: VPN routing, multiple network namespaces
|
||||
- **Maintenance**: VPN updates, connection monitoring
|
||||
|
||||
### After (LXC 221)
|
||||
- **Memory**: ~100MB LXC overhead
|
||||
- **Disk**: 32GB (minimal)
|
||||
- **Complexity**: Standard Docker containers
|
||||
- **Maintenance**: Standard container updates only
|
||||
|
||||
### Efficiency Gains
|
||||
- **~1.5GB RAM saved** (VM overhead eliminated)
|
||||
- **Simplified networking** (no VPN routing)
|
||||
- **Reduced attack surface** (fewer services)
|
||||
- **Faster boot time** (LXC vs VM)
|
||||
|
||||
---
|
||||
|
||||
## NPM/Reverse Proxy Updates
|
||||
|
||||
Updated Nginx Proxy Manager entries to point to new IP:
|
||||
- sonarr.manticorum.com → 10.10.0.221:8989
|
||||
- radarr.manticorum.com → 10.10.0.221:7878
|
||||
- readarr.manticorum.com → 10.10.0.221:8787
|
||||
- jellyseerr.manticorum.com → 10.10.0.221:5055 (new, replaces overseerr)
|
||||
- sabnzbd.manticorum.com → 10.10.0.221:8080
|
||||
|
||||
---
|
||||
|
||||
## Rollback Capability
|
||||
|
||||
- **VM 121 preserved**: Can be restarted if issues arise
|
||||
- **Rollback time**: <5 minutes
|
||||
- **Recommendation**: Keep VM 121 stopped for 48 hours, then decommission
|
||||
|
||||
---
|
||||
|
||||
## Key Learnings
|
||||
|
||||
### 1. VPN Complexity Often Unnecessary
|
||||
For Usenet-only setups, VPN adds complexity without meaningful benefit. SSL to the Usenet provider is sufficient.
|
||||
|
||||
### 2. Image Registry Issues
|
||||
lscr.io can have availability issues. Docker Hub images work as fallback.
|
||||
|
||||
### 3. Application Substitution
|
||||
Jellyseerr is a drop-in replacement for Overseerr with native Jellyfin support - worth the switch if using Jellyfin.
|
||||
|
||||
### 4. Tag/Label Validation
|
||||
When connecting Jellyseerr to arr apps, be careful with tag configurations - invalid characters cause silent failures.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate
|
||||
- [x] Configure SABnzbd with Usenet provider
|
||||
- [x] Connect arr apps to new SABnzbd
|
||||
- [x] Update NPM reverse proxy entries
|
||||
- [x] Test movie/show requests through Jellyseerr
|
||||
|
||||
### After 48 Hours
|
||||
- [ ] Decommission VM 121 (docker-vpn)
|
||||
- [ ] Clean up local migration temp files (`/tmp/arr-config-migration/`)
|
||||
|
||||
---
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
### On LXC 221
|
||||
- `/opt/arr-stack/docker-compose.yml`
|
||||
- `/opt/arr-stack/config/` (all service configs)
|
||||
- `/root/.smbcredentials`
|
||||
- `/etc/fstab` (CIFS mount)
|
||||
|
||||
### Documentation Updated
|
||||
- `vm-management/lxc-migration-plan.md` - Wave 2 status
|
||||
- `networking/server-inventory.md` - Added arr-stack entry
|
||||
- `vm-management/wave2-migration-results.md` - This file
|
||||
|
||||
---
|
||||
|
||||
**Status**: **Wave 2 Complete - Ready for Wave 3**
|
||||
**Contact**: Cal Corum (cal.corum@gmail.com)
|
||||
Loading…
Reference in New Issue
Block a user