Compare commits

...

3 Commits

Author SHA1 Message Date
Martin Eckardt
4b218a70dd Add dual NVMe storage documentation and snapshot troubleshooting
- Updated architecture diagram with actual disk sizes (100GB/200GB)
- Added Storage section with NVMe layout and snapshot commands
- Added VM/Storage troubleshooting section:
  - Snapshot feature not available (Raw Device fix)
  - Storage overview and disk migration
  - Thin pool warnings explanation
- Updated changelog with storage optimization

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 21:46:50 +01:00
Martin Eckardt
64bcc0091a Migrate Docker containers to dedicated VM
Architecture changes:
- Created VM 100 "docker-services" (Debian 12 Cloud Image)
- 10GB RAM, 6 Cores, 50GB system disk
- Separate 100GB LVM data volume for service data
- WireGuard moved from host to VM (10.0.0.2)
- All containers migrated and running

Updated documentation to reflect new architecture

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 21:04:47 +01:00
Martin Eckardt
3f74077c3e Add VT-x/KVM troubleshooting and VM preparation docs
- Added VT-x BIOS requirement to INSTALL.md
- Added VT-x/KVM troubleshooting section with HP Z2 specific instructions
- Updated changelog: VT-x enabled, VM 100 prepared

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 20:22:27 +01:00
10 changed files with 981 additions and 3 deletions

View File

@@ -15,9 +15,15 @@ Internet
| |
| WireGuard Tunnel (10.0.0.0/24) | WireGuard Tunnel (10.0.0.0/24)
v v
[Proxmox: 192.168.178.111 / 10.0.0.2] [Proxmox: 192.168.178.111]
- Hypervisor
|
v
[VM 100 "docker-services": 192.168.178.200 / 10.0.0.2]
- Docker Host - Docker Host
- Alle Services als Container - Alle Services als Container
- 100GB System (local-lvm/NVMe1)
- 200GB Data (nvme-data/NVMe2)
``` ```
## Services ## Services
@@ -178,6 +184,41 @@ Siehe [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) fuer:
- Netzwerk-Probleme - Netzwerk-Probleme
- SSL-Zertifikat Fehler - SSL-Zertifikat Fehler
- Backup-Probleme - Backup-Probleme
- VT-x/KVM nicht verfuegbar
- VM Snapshots funktionieren nicht
- Storage-Migration zwischen NVMes
## Storage
### Proxmox Storage Layout
| Storage | NVMe | Modell | Verwendung | Kapazitaet |
|---------|------|--------|------------|------------|
| `local-lvm` | nvme0n1 | WDC 476GB | VM System Disks | ~350GB Thin Pool |
| `nvme-data` | nvme1n1 | SKHynix 476GB | Data Volumes | ~450GB Thin Pool |
### VM 100 Disk-Konfiguration
| Disk | Storage | Groesse | Mountpoint |
|------|---------|---------|------------|
| scsi0 | local-lvm | 100GB | / (System) |
| scsi1 | nvme-data | 200GB | /data |
### Snapshots
```bash
# Snapshot erstellen
qm snapshot 100 <name> --description "Beschreibung"
# Snapshots auflisten
qm listsnapshot 100
# Zu Snapshot zurueckkehren
qm rollback 100 <name>
# Snapshot loeschen
qm delsnapshot 100 <name>
```
## Changelog ## Changelog
@@ -200,3 +241,16 @@ Siehe [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) fuer:
- Host + Docker Container Monitoring - Host + Docker Container Monitoring
- WireGuard + Fail2Ban Integration - WireGuard + Fail2Ban Integration
- Separates Git-Repository: proxmox-netdata - Separates Git-Repository: proxmox-netdata
- VT-x im BIOS aktiviert fuer VM-Support
- Docker Container Migration zu VM 100:
- VM erstellt: 10GB RAM, 6 Cores, 100GB System Disk
- Separates 200GB Data Volume fuer Nextcloud/Services
- WireGuard auf VM konfiguriert (10.0.0.2)
- Alle Container erfolgreich migriert
- Alte Container auf Host gestoppt
- Storage-Optimierung:
- Zweite NVMe (SKHynix 476GB) als nvme-data Storage aktiviert
- Data Volume auf nvme-data migriert (Live-Migration)
- Snapshots aktiviert (Raw Device zu Proxmox-managed konvertiert)
- NVMe 1 (WDC): VM System Disks
- NVMe 2 (SKHynix): Nextcloud/Data Volumes

View File

@@ -19,3 +19,12 @@ VAULTWARDEN_ADMIN_TOKEN=<admin-token-hier>
# ============================================ # ============================================
N8N_USER=admin N8N_USER=admin
N8N_PASSWORD=<sicheres-passwort-hier> N8N_PASSWORD=<sicheres-passwort-hier>
# ============================================
# SAMSUNG TV API
# ============================================
# TV IP-Adresse im lokalen Netzwerk (z.B. 192.168.178.50)
SAMSUNG_TV_IP=192.168.178.XXX
# TV MAC-Adresse für Wake-on-LAN (optional, Format: AA:BB:CC:DD:EE:FF)
# Findest du in TV-Einstellungen > Netzwerk > Netzwerkstatus
SAMSUNG_TV_MAC=

View File

@@ -217,6 +217,47 @@ services:
networks: networks:
- websites-net - websites-net
# ============================================
# SAMSUNG TV API - Smart TV Steuerung
# ============================================
samsung-tv-api:
build:
context: ./samsung-tv-api
dockerfile: Dockerfile
container_name: samsung-tv-api
restart: unless-stopped
security_opt:
- apparmor=unconfined
- seccomp=unconfined
ports:
- "5050:5000"
volumes:
- /opt/docker/samsung-tv-api:/data
environment:
- SAMSUNG_TV_IP=${SAMSUNG_TV_IP:-192.168.178.100}
- SAMSUNG_TV_MAC=${SAMSUNG_TV_MAC:-}
- SAMSUNG_TV_NAME=n8n-proxmox
- SAMSUNG_TV_PORT=8002
- SAMSUNG_TV_TOKEN_FILE=/data/tv-token.txt
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
cpus: '0.5'
memory: 128M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- samsung-tv-net
- n8n-net # Damit n8n direkt zugreifen kann
# ============================================ # ============================================
# API - FastAPI Backend # API - FastAPI Backend
# ============================================ # ============================================
@@ -268,3 +309,5 @@ networks:
driver: bridge driver: bridge
api-net: api-net:
driver: bridge driver: bridge
samsung-tv-net:
driver: bridge

View File

@@ -0,0 +1,27 @@
FROM python:3.11-slim
WORKDIR /app
# System-Dependencies für Wake-on-LAN
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# Python Dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# App kopieren
COPY app.py .
# Daten-Verzeichnis für Token-Persistenz
RUN mkdir -p /data
# Non-root User
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app /data
USER appuser
EXPOSE 5000
# Gunicorn für Production
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--timeout", "30", "app:app"]

View File

@@ -0,0 +1,184 @@
# Samsung TV API Service
REST API zur Steuerung von Samsung Smart TVs (2016+, Tizen OS) via WebSocket.
Entwickelt für die Integration mit n8n auf Proxmox.
## Unterstützte Geräte
- Samsung Smart TVs ab 2016 (Tizen OS)
- Getestet mit: UE50RU7409 (2019)
## Setup
### 1. TV IP-Adresse ermitteln
Am TV: **Einstellungen → Allgemein → Netzwerk → Netzwerkstatus**
Oder im Router nachschauen (Fritz!Box: Heimnetz → Netzwerk)
### 2. .env Datei anpassen
```bash
# In /opt/docker/.env auf dem Proxmox Host
SAMSUNG_TV_IP=192.168.178.XXX
SAMSUNG_TV_MAC=AA:BB:CC:DD:EE:FF # Optional für Wake-on-LAN
```
### 3. Container starten
```bash
cd /opt/docker
docker-compose up -d samsung-tv-api
```
### 4. Erste Verbindung - TV Autorisierung
Beim ersten API-Aufruf erscheint auf dem TV ein Dialog:
**"n8n-proxmox möchte sich verbinden - Zulassen?"**
→ Mit der TV-Fernbedienung **Zulassen** wählen
Der Token wird dann in `/opt/docker/samsung-tv-api/tv-token.txt` gespeichert.
**Tipp:** In TV-Einstellungen → Externe Geräteverwaltung → Geräteverbindungsmanager →
Zugriffsbenachrichtigung auf **"Nur beim ersten Mal"** stellen.
## API Endpoints
| Endpoint | Method | Beschreibung |
|----------|--------|--------------|
| `/` | GET | API Dokumentation |
| `/health` | GET | Health-Check |
| `/tv/status` | GET | TV Status prüfen |
| `/tv/power/on` | POST | TV einschalten (Wake-on-LAN) |
| `/tv/power/off` | POST | TV ausschalten |
| `/tv/key` | POST | Taste senden |
| `/tv/volume` | POST | Lautstärke steuern |
| `/tv/channel` | POST | Kanal wechseln |
| `/tv/source` | POST | Eingangsquelle wechseln |
| `/tv/apps` | GET | Installierte Apps auflisten |
| `/tv/app/open` | POST | App starten |
| `/tv/browser` | POST | URL im Browser öffnen |
## Beispiel-Aufrufe
### TV ausschalten
```bash
curl -X POST http://192.168.178.111:5050/tv/power/off
```
### Lautstärke erhöhen
```bash
curl -X POST http://192.168.178.111:5050/tv/volume \
-H "Content-Type: application/json" \
-d '{"action": "up"}'
```
### Taste senden
```bash
curl -X POST http://192.168.178.111:5050/tv/key \
-H "Content-Type: application/json" \
-d '{"key": "KEY_MUTE"}'
```
### Netflix starten
```bash
curl -X POST http://192.168.178.111:5050/tv/app/open \
-H "Content-Type: application/json" \
-d '{"app_id": "Netflix"}'
```
### HDMI 1 auswählen
```bash
curl -X POST http://192.168.178.111:5050/tv/source \
-H "Content-Type: application/json" \
-d '{"source": "hdmi1"}'
```
## Verfügbare Tasten (Keys)
### Navigation
- `KEY_UP`, `KEY_DOWN`, `KEY_LEFT`, `KEY_RIGHT`
- `KEY_ENTER`, `KEY_RETURN`, `KEY_EXIT`
- `KEY_HOME`, `KEY_MENU`, `KEY_INFO`
### Power
- `KEY_POWER`, `KEY_POWEROFF`
### Lautstärke
- `KEY_VOLUP`, `KEY_VOLDOWN`, `KEY_MUTE`
### Kanal
- `KEY_CHUP`, `KEY_CHDOWN`
- `KEY_0` bis `KEY_9`
### Quellen
- `KEY_SOURCE`
- `KEY_HDMI1`, `KEY_HDMI2`, `KEY_HDMI3`, `KEY_HDMI4`
- `KEY_TV`
### Wiedergabe
- `KEY_PLAY`, `KEY_PAUSE`, `KEY_STOP`
- `KEY_REWIND`, `KEY_FF` (Fast Forward)
- `KEY_RECORD`
### Farbtasten
- `KEY_RED`, `KEY_GREEN`, `KEY_YELLOW`, `KEY_BLUE`
## n8n Integration
### Von n8n aus aufrufen (im gleichen Docker-Netzwerk)
Da beide Container im `n8n-net` Netzwerk sind, kann n8n den Service
direkt über den Container-Namen erreichen:
```
URL: http://samsung-tv-api:5000/tv/power/off
```
### HTTP Request Node Konfiguration
1. **Method:** POST
2. **URL:** `http://samsung-tv-api:5000/tv/key`
3. **Body Content Type:** JSON
4. **Body:**
```json
{
"key": "KEY_VOLUP"
}
```
### Beispiel-Workflow importieren
Die Datei `n8n-workflow-example.json` enthält einen Beispiel-Workflow
zum Importieren in n8n.
## Troubleshooting
### TV nicht erreichbar
1. Prüfe ob TV eingeschaltet ist (im Standby funktioniert nur Wake-on-LAN)
2. Prüfe IP-Adresse: `ping 192.168.178.XXX`
3. Prüfe ob Port 8002 offen ist: `nc -zv 192.168.178.XXX 8002`
### "Verbindung abgelehnt"
- Wurde die Verbindung am TV erlaubt?
- Token-Datei löschen und neu verbinden:
```bash
rm /opt/docker/samsung-tv-api/tv-token.txt
docker-compose restart samsung-tv-api
```
### Wake-on-LAN funktioniert nicht
1. Am TV: Einstellungen → Allgemein → Netzwerk → Experteneinstellungen →
**Einschalten mit Mobilgerät** aktivieren
2. MAC-Adresse in `.env` korrekt eingetragen?
3. Broadcast funktioniert nur im gleichen Subnetz
### Container-Logs prüfen
```bash
docker logs -f samsung-tv-api
```

View File

@@ -0,0 +1,367 @@
"""
Samsung TV API Service
Steuert Samsung Smart TVs (2016+) über WebSocket API
Für n8n Integration auf Proxmox
"""
import os
import logging
from flask import Flask, request, jsonify
from samsungtvws import SamsungTVWS
from wakeonlan import send_magic_packet
# Logging konfigurieren
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
# Konfiguration aus Umgebungsvariablen
TV_IP = os.environ.get('SAMSUNG_TV_IP', '192.168.178.100')
TV_MAC = os.environ.get('SAMSUNG_TV_MAC', '') # Optional für Wake-on-LAN
TV_NAME = os.environ.get('SAMSUNG_TV_NAME', 'n8n-proxmox')
TV_PORT = int(os.environ.get('SAMSUNG_TV_PORT', '8002')) # 8002 für SSL, 8001 für non-SSL
# Token-Pfad für persistente Authentifizierung
TOKEN_FILE = os.environ.get('SAMSUNG_TV_TOKEN_FILE', '/data/tv-token.txt')
def get_tv_connection():
"""Erstellt eine Verbindung zum Samsung TV"""
try:
tv = SamsungTVWS(
host=TV_IP,
port=TV_PORT,
name=TV_NAME,
token_file=TOKEN_FILE
)
return tv
except Exception as e:
logger.error(f"TV Verbindung fehlgeschlagen: {e}")
return None
@app.route('/health', methods=['GET'])
def health():
"""Health-Check Endpoint"""
return jsonify({'status': 'ok', 'tv_ip': TV_IP})
@app.route('/tv/status', methods=['GET'])
def tv_status():
"""Prüft ob der TV erreichbar ist"""
try:
tv = get_tv_connection()
if tv:
# Versuche eine einfache Abfrage
info = tv.rest_device_info()
return jsonify({
'status': 'online',
'info': info
})
except Exception as e:
logger.warning(f"TV nicht erreichbar: {e}")
return jsonify({'status': 'offline', 'tv_ip': TV_IP})
@app.route('/tv/power/on', methods=['POST'])
def power_on():
"""TV einschalten via Wake-on-LAN"""
if not TV_MAC:
return jsonify({
'error': 'SAMSUNG_TV_MAC nicht konfiguriert',
'hint': 'Setze SAMSUNG_TV_MAC Umgebungsvariable'
}), 400
try:
send_magic_packet(TV_MAC)
logger.info(f"Wake-on-LAN gesendet an {TV_MAC}")
return jsonify({'status': 'wol_sent', 'mac': TV_MAC})
except Exception as e:
logger.error(f"Wake-on-LAN fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/tv/power/off', methods=['POST'])
def power_off():
"""TV ausschalten"""
try:
tv = get_tv_connection()
if tv:
tv.send_key('KEY_POWER')
return jsonify({'status': 'ok', 'action': 'power_off'})
except Exception as e:
logger.error(f"Power off fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'TV nicht erreichbar'}), 503
@app.route('/tv/key', methods=['POST'])
def send_key():
"""Sendet eine Taste zum TV
Body: {"key": "KEY_VOLUP"} oder {"keys": ["KEY_VOLUP", "KEY_VOLUP"]}
Häufige Keys:
- KEY_POWER, KEY_POWEROFF
- KEY_VOLUP, KEY_VOLDOWN, KEY_MUTE
- KEY_CHUP, KEY_CHDOWN
- KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_ENTER
- KEY_RETURN, KEY_HOME, KEY_MENU
- KEY_SOURCE, KEY_HDMI1, KEY_HDMI2, KEY_HDMI3
- KEY_PLAY, KEY_PAUSE, KEY_STOP
"""
data = request.get_json() or {}
# Einzelne Taste oder Liste von Tasten
keys = data.get('keys', [])
if 'key' in data:
keys = [data['key']]
if not keys:
return jsonify({
'error': 'key oder keys Parameter erforderlich',
'example': {'key': 'KEY_VOLUP'}
}), 400
try:
tv = get_tv_connection()
if tv:
for key in keys:
tv.send_key(key)
logger.info(f"Key gesendet: {key}")
return jsonify({'status': 'ok', 'keys_sent': keys})
except Exception as e:
logger.error(f"Key senden fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'TV nicht erreichbar'}), 503
@app.route('/tv/volume', methods=['POST'])
def set_volume():
"""Setzt die Lautstärke
Body: {"volume": 25} oder {"action": "up|down|mute"}
"""
data = request.get_json() or {}
try:
tv = get_tv_connection()
if not tv:
return jsonify({'error': 'TV nicht erreichbar'}), 503
if 'volume' in data:
# Absolute Lautstärke (nicht alle TVs unterstützen das)
volume = int(data['volume'])
# Sende entsprechend viele KEY_VOLUP/DOWN
return jsonify({
'status': 'ok',
'note': 'Absolute Lautstärke nicht direkt unterstützt, nutze action: up/down'
})
action = data.get('action', '').lower()
key_map = {
'up': 'KEY_VOLUP',
'down': 'KEY_VOLDOWN',
'mute': 'KEY_MUTE'
}
if action in key_map:
tv.send_key(key_map[action])
return jsonify({'status': 'ok', 'action': action})
return jsonify({
'error': 'Ungültige action',
'valid_actions': list(key_map.keys())
}), 400
except Exception as e:
logger.error(f"Volume Steuerung fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/tv/channel', methods=['POST'])
def change_channel():
"""Wechselt den Kanal
Body: {"action": "up|down"} oder {"number": "123"}
"""
data = request.get_json() or {}
try:
tv = get_tv_connection()
if not tv:
return jsonify({'error': 'TV nicht erreichbar'}), 503
action = data.get('action', '').lower()
if action == 'up':
tv.send_key('KEY_CHUP')
return jsonify({'status': 'ok', 'action': 'channel_up'})
elif action == 'down':
tv.send_key('KEY_CHDOWN')
return jsonify({'status': 'ok', 'action': 'channel_down'})
# Kanal per Nummer eingeben
number = data.get('number', '')
if number:
key_map = {str(i): f'KEY_{i}' for i in range(10)}
for digit in str(number):
if digit in key_map:
tv.send_key(key_map[digit])
tv.send_key('KEY_ENTER')
return jsonify({'status': 'ok', 'channel': number})
return jsonify({
'error': 'action oder number Parameter erforderlich',
'example': {'action': 'up'}
}), 400
except Exception as e:
logger.error(f"Kanal wechseln fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/tv/source', methods=['POST'])
def change_source():
"""Wechselt die Eingangsquelle
Body: {"source": "hdmi1|hdmi2|hdmi3|tv"}
"""
data = request.get_json() or {}
source = data.get('source', '').lower()
source_map = {
'hdmi1': 'KEY_HDMI1',
'hdmi2': 'KEY_HDMI2',
'hdmi3': 'KEY_HDMI3',
'hdmi4': 'KEY_HDMI4',
'tv': 'KEY_TV',
'source': 'KEY_SOURCE' # Öffnet Source-Menü
}
if source not in source_map:
return jsonify({
'error': 'Ungültige source',
'valid_sources': list(source_map.keys())
}), 400
try:
tv = get_tv_connection()
if tv:
tv.send_key(source_map[source])
return jsonify({'status': 'ok', 'source': source})
except Exception as e:
logger.error(f"Source wechseln fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'TV nicht erreichbar'}), 503
@app.route('/tv/apps', methods=['GET'])
def list_apps():
"""Listet installierte Apps auf"""
try:
tv = get_tv_connection()
if tv:
apps = tv.app_list()
return jsonify({'status': 'ok', 'apps': apps})
except Exception as e:
logger.error(f"App-Liste abrufen fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'TV nicht erreichbar'}), 503
@app.route('/tv/app/open', methods=['POST'])
def open_app():
"""Startet eine App
Body: {"app_id": "Netflix"} oder {"app_id": "3201907018807"}
Bekannte App-IDs:
- Netflix: 3201907018807
- YouTube: 111299001912
- Prime Video: 3201910019365
- Disney+: 3201901017640
"""
data = request.get_json() or {}
app_id = data.get('app_id')
if not app_id:
return jsonify({
'error': 'app_id Parameter erforderlich',
'hint': 'Nutze GET /tv/apps für eine Liste'
}), 400
try:
tv = get_tv_connection()
if tv:
tv.run_app(app_id)
return jsonify({'status': 'ok', 'app': app_id})
except Exception as e:
logger.error(f"App starten fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'TV nicht erreichbar'}), 503
@app.route('/tv/browser', methods=['POST'])
def open_browser():
"""Öffnet eine URL im TV-Browser
Body: {"url": "https://example.com"}
"""
data = request.get_json() or {}
url = data.get('url')
if not url:
return jsonify({'error': 'url Parameter erforderlich'}), 400
try:
tv = get_tv_connection()
if tv:
tv.open_browser(url)
return jsonify({'status': 'ok', 'url': url})
except Exception as e:
logger.error(f"Browser öffnen fehlgeschlagen: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'TV nicht erreichbar'}), 503
# API Dokumentation
@app.route('/', methods=['GET'])
def api_docs():
"""API Übersicht"""
return jsonify({
'service': 'Samsung TV API',
'tv_ip': TV_IP,
'endpoints': {
'GET /health': 'Health-Check',
'GET /tv/status': 'TV Status prüfen',
'POST /tv/power/on': 'TV einschalten (Wake-on-LAN)',
'POST /tv/power/off': 'TV ausschalten',
'POST /tv/key': 'Taste senden {"key": "KEY_VOLUP"}',
'POST /tv/volume': 'Lautstärke {"action": "up|down|mute"}',
'POST /tv/channel': 'Kanal {"action": "up|down"} oder {"number": "123"}',
'POST /tv/source': 'Quelle {"source": "hdmi1|hdmi2|tv"}',
'GET /tv/apps': 'Installierte Apps auflisten',
'POST /tv/app/open': 'App starten {"app_id": "Netflix"}',
'POST /tv/browser': 'URL öffnen {"url": "https://..."}',
},
'common_keys': [
'KEY_POWER', 'KEY_VOLUP', 'KEY_VOLDOWN', 'KEY_MUTE',
'KEY_CHUP', 'KEY_CHDOWN', 'KEY_UP', 'KEY_DOWN',
'KEY_LEFT', 'KEY_RIGHT', 'KEY_ENTER', 'KEY_RETURN',
'KEY_HOME', 'KEY_MENU', 'KEY_SOURCE',
'KEY_PLAY', 'KEY_PAUSE', 'KEY_STOP'
]
})
if __name__ == '__main__':
logger.info(f"Samsung TV API startet - TV IP: {TV_IP}")
app.run(host='0.0.0.0', port=5000, debug=True)

View File

@@ -0,0 +1,147 @@
{
"name": "Samsung TV Steuerung",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "tv-control",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [250, 300]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition-power-off",
"leftValue": "={{ $json.body.command }}",
"rightValue": "power_off",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "switch-command",
"name": "Switch Command",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [450, 300]
},
{
"parameters": {
"method": "POST",
"url": "http://samsung-tv-api:5000/tv/power/off",
"options": {}
},
"id": "tv-power-off",
"name": "TV Power Off",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [650, 200]
},
{
"parameters": {
"method": "POST",
"url": "http://samsung-tv-api:5000/tv/key",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ key: $json.body.key }) }}",
"options": {}
},
"id": "tv-send-key",
"name": "TV Send Key",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [650, 400]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "respond-webhook",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [850, 300]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Switch Command",
"type": "main",
"index": 0
}
]
]
},
"Switch Command": {
"main": [
[
{
"node": "TV Power Off",
"type": "main",
"index": 0
}
],
[
{
"node": "TV Send Key",
"type": "main",
"index": 0
}
]
]
},
"TV Power Off": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"TV Send Key": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0,
"updatedAt": "2025-12-28T00:00:00.000Z",
"versionId": "1"
}

View File

@@ -0,0 +1,4 @@
samsungtvws[async,encrypted]==2.6.0
flask==3.0.0
gunicorn==21.2.0
wakeonlan==3.1.0

View File

@@ -9,6 +9,7 @@ Detaillierte Anleitung zur Erstinstallation der Proxmox Infrastruktur.
- **CPU:** Min. 2 Cores (4 empfohlen) - **CPU:** Min. 2 Cores (4 empfohlen)
- **RAM:** Min. 4GB (8GB empfohlen) - **RAM:** Min. 4GB (8GB empfohlen)
- **Speicher:** Min. 50GB SSD - **Speicher:** Min. 50GB SSD
- **VT-x/AMD-V:** Im BIOS aktiviert (fuer VMs erforderlich)
### Software ### Software

View File

@@ -8,7 +8,8 @@
4. [Service-spezifische Probleme](#4-service-spezifische-probleme) 4. [Service-spezifische Probleme](#4-service-spezifische-probleme)
5. [Backup/Restore Probleme](#5-backuprestore-probleme) 5. [Backup/Restore Probleme](#5-backuprestore-probleme)
6. [Performance Probleme](#6-performance-probleme) 6. [Performance Probleme](#6-performance-probleme)
7. [Stolperfallen und Lessons Learned](#7-stolperfallen-und-lessons-learned) 7. [VM und Storage Probleme](#7-vm-und-storage-probleme)
8. [Stolperfallen und Lessons Learned](#8-stolperfallen-und-lessons-learned)
--- ---
@@ -344,7 +345,109 @@ docker system df
--- ---
## 7. Stolperfallen und Lessons Learned ## 7. VM und Storage Probleme
### VM Snapshots funktionieren nicht
**Problem:** `qm snapshot` meldet "snapshot feature is not available"
**Ursache:** Disk ist als Raw Device (`/dev/pve/...`) statt als Proxmox-managed Disk eingebunden
**Diagnose:**
```bash
# VM Konfiguration pruefen
qm config 100 | grep scsi
# Falsch (Raw Device - keine Snapshots):
# scsi1: /dev/pve/vm-100-data,size=200G
# Richtig (Proxmox-managed - Snapshots moeglich):
# scsi1: local-lvm:vm-100-data,size=200G
```
**Loesung:**
```bash
# 1. VM stoppen
qm stop 100
# 2. Raw Device entfernen
qm set 100 --delete scsi1
# 3. Als Proxmox-managed Disk neu hinzufuegen
qm set 100 --scsi1 local-lvm:vm-100-data
# 4. VM starten
qm start 100
```
---
### Storage-Uebersicht und Disk Migration
**Aktuelles Storage-Layout:**
| Storage | NVMe | Verwendung | Kapazitaet |
|---------|------|------------|------------|
| `local-lvm` | nvme0n1 (WDC) | VM System Disks | ~350GB Thin Pool |
| `nvme-data` | nvme1n1 (SKHynix) | Nextcloud/Data | ~450GB Thin Pool |
**Storage Status pruefen:**
```bash
pvesm status
```
**Disk zwischen Storages verschieben (Live-Migration):**
```bash
# Disk von local-lvm nach nvme-data verschieben
# --delete 1 = altes Volume nach Migration loeschen
qm disk move 100 scsi1 nvme-data --delete 1
```
---
### VM Snapshot Befehle
```bash
# Snapshot erstellen
qm snapshot 100 <name> --description "Beschreibung"
# Snapshots auflisten
qm listsnapshot 100
# Zu Snapshot zurueckkehren (VM wird neugestartet)
qm rollback 100 <name>
# Snapshot loeschen
qm delsnapshot 100 <name>
```
**Hinweis:** Warnung "QEMU Guest Agent is not running" ist nicht kritisch. Fuer konsistentere Snapshots kann `qemu-guest-agent` in der VM installiert werden:
```bash
apt install qemu-guest-agent
systemctl enable qemu-guest-agent
systemctl start qemu-guest-agent
```
---
### Thin Pool Warnungen
**Problem:** `WARNING: Sum of all thin volume sizes exceeds the size of thin pool`
**Ursache:** Thin Provisioning erlaubt Overprovisioning - die virtuellen Volumes sind groesser als der physische Speicher
**Loesung:** Dies ist normal bei Thin Provisioning. Wichtig ist, den tatsaechlichen Verbrauch zu ueberwachen:
```bash
# Tatsaechliche Nutzung pruefen
lvs -o lv_name,lv_size,data_percent
# Thin Pool Status
lvs pve/data -o lv_size,data_percent,metadata_percent
```
---
## 8. Stolperfallen und Lessons Learned
### nginx auf Windows ### nginx auf Windows
@@ -492,6 +595,45 @@ type C:\nginx\logs\error.log | findstr "limiting"
--- ---
### VT-x/KVM nicht verfuegbar
**Problem:** `KVM virtualisation configured, but not available`
**Symptom:** VMs starten nicht, `/dev/kvm` existiert nicht
**Ursache:** Intel VT-x oder AMD-V ist im BIOS deaktiviert
**Diagnose:**
```bash
# Pruefen ob vmx (Intel) oder svm (AMD) Flags vorhanden
egrep -c '(vmx|svm)' /proc/cpuinfo
# Ausgabe 0 = VT-x deaktiviert
# KVM Device pruefen
ls -la /dev/kvm
```
**Loesung fuer HP Z2 Workstation:**
1. Neustart, F10 fuer BIOS Setup
2. Security -> System Security
3. **Virtualization Technology (VT-x)** -> Enabled
4. **VT-d** -> Enabled (optional, fuer PCI Passthrough)
5. F10 zum Speichern und Beenden
**Loesung fuer andere Systeme:**
- BIOS/UEFI aufrufen (meist F2, F10, DEL beim Boot)
- Suche nach: "Virtualization", "VT-x", "AMD-V", "SVM"
- Aktivieren und speichern
**Nach Aktivierung:**
```bash
# Pruefen
ls -la /dev/kvm
egrep -c '(vmx|svm)' /proc/cpuinfo # Sollte > 0 sein
```
---
## Kontakt / Hilfe ## Kontakt / Hilfe
- **Gitea Issues:** https://eckardt-git.duckdns.org/Martin/proxmox-infrastruktur/issues - **Gitea Issues:** https://eckardt-git.duckdns.org/Martin/proxmox-infrastruktur/issues