This commit is contained in:
2026-05-06 08:41:06 +02:00
parent 8f0674b6c2
commit 3d98c9ec9c
38 changed files with 87867 additions and 0 deletions
@@ -0,0 +1,157 @@
#!/usr/bin/env bash
# =============================================================
# install.sh — OpenClaw mailbox.org Kalender + Aufgaben
# Zielplattform: Raspberry Pi (Raspberry Pi OS / Ubuntu ARM)
# Ausführen als: pi-Nutzer (sudo-Rechte erforderlich)
# =============================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
ENV_FILE="$REPO_ROOT/.env"
# --- Farben ---
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
# =============================================================
# 1. .env laden
# =============================================================
if [[ ! -f "$ENV_FILE" ]]; then
warn ".env nicht gefunden — kopiere .env.example nach .env"
cp "$REPO_ROOT/assets/.env.example" "$ENV_FILE"
error "Bitte .env mit deinen mailbox.org-Zugangsdaten befüllen und install.sh erneut ausführen."
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
info ".env geladen (Benutzer: ${MAILBOX_USER:-NICHT GESETZT})"
[[ -z "${MAILBOX_USER:-}" ]] && error "MAILBOX_USER in .env nicht gesetzt!"
[[ -z "${MAILBOX_PASS:-}" ]] && error "MAILBOX_PASS in .env nicht gesetzt!"
# =============================================================
# 2. Systempakete installieren
# =============================================================
info "Installiere Systempakete..."
sudo apt-get update -qq
sudo apt-get install -y \
vdirsyncer \
khal \
todoman \
python3-pass \
gettext-base \
curl \
jq \
python3-pip \
python3-venv
# Locale sicherstellen
sudo locale-gen de_AT.UTF-8 2>/dev/null || true
info "Pakete installiert."
# =============================================================
# 3. Verzeichnisse anlegen
# =============================================================
info "Lege Verzeichnisse an..."
VDIR_BASE="${VDIR_BASE_DIR:-$HOME/.local/share/vdirsyncer}"
mkdir -p "$VDIR_BASE/status"
mkdir -p "$VDIR_BASE/calendars"
mkdir -p "$VDIR_BASE/tasks"
mkdir -p "${KHAL_DB_PATH:-$HOME/.local/share/khal}"
mkdir -p "$HOME/.config/vdirsyncer"
mkdir -p "$HOME/.config/khal"
mkdir -p "$HOME/.config/todoman"
sudo mkdir -p /var/log/openclaw-mailbox-cal
sudo chown "$USER:$USER" /var/log/openclaw-mailbox-cal
# =============================================================
# 4. Konfigurationsdateien deployen (envsubst für Variablen)
# =============================================================
info "Deploye Konfigurationsdateien..."
# vdirsyncer config — Variablen ersetzen
export MAILBOX_USER MAILBOX_PASS
envsubst '${MAILBOX_USER} ${MAILBOX_PASS}' \
< "$REPO_ROOT/config/vdirsyncer.conf" \
> "$HOME/.config/vdirsyncer/config"
chmod 600 "$HOME/.config/vdirsyncer/config"
info " → ~/.config/vdirsyncer/config"
# khal config
cp "$REPO_ROOT/config/khal.conf" "$HOME/.config/khal/config"
info " → ~/.config/khal/config"
# todoman config
cp "$REPO_ROOT/config/todoman.conf" "$HOME/.config/todoman/config.cfg"
info " → ~/.config/todoman/config.cfg"
# =============================================================
# 5. systemd Units installieren (User-Session)
# =============================================================
info "Installiere systemd Units (User-Session)..."
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
mkdir -p "$SYSTEMD_USER_DIR"
# Variablen in systemd-Units ersetzen
for unit_file in "$REPO_ROOT/systemd/"*.{service,timer}; do
[[ -f "$unit_file" ]] || continue
unit_name="$(basename "$unit_file")"
envsubst '${SYNC_INTERVAL}' < "$unit_file" > "$SYSTEMD_USER_DIR/$unit_name"
info "$SYSTEMD_USER_DIR/$unit_name"
done
systemctl --user daemon-reload
systemctl --user enable --now vdirsyncer-sync.timer
info "systemd Timer aktiviert."
# Lingering aktivieren (damit User-Units ohne Login laufen)
loginctl enable-linger "$USER" 2>/dev/null || \
warn "loginctl enable-linger fehlgeschlagen — bitte manuell ausführen: sudo loginctl enable-linger $USER"
# =============================================================
# 6. Erst-Synchronisation
# =============================================================
info "Führe initialen vdirsyncer discover + sync durch..."
vdirsyncer discover calendars || warn "discover calendars hatte Warnungen — prüfe Ausgabe"
vdirsyncer discover tasks || warn "discover tasks hatte Warnungen — prüfe Ausgabe"
vdirsyncer sync || warn "sync hatte Warnungen — prüfe Ausgabe"
info "Erst-Sync abgeschlossen."
# =============================================================
# 7. khal-Index aufbauen
# =============================================================
info "Baue khal-Index auf..."
khal --config "$HOME/.config/khal/config" rebuild-cache || true
# =============================================================
# 8. OpenClaw Skill-Verknüpfung
# =============================================================
OPENCLAW_SKILLS_DIR="${OPENCLAW_SKILLS_DIR:-$HOME/.config/openclaw/skills}"
if [[ -d "$OPENCLAW_SKILLS_DIR" ]]; then
info "Verknüpfe OpenClaw Skill..."
ln -sf "$REPO_ROOT" "$OPENCLAW_SKILLS_DIR/mailbox-cal" 2>/dev/null || true
info "$OPENCLAW_SKILLS_DIR/mailbox-cal"
else
warn "OpenClaw Skills-Verzeichnis nicht gefunden ($OPENCLAW_SKILLS_DIR)."
warn "Bitte Skill manuell in OpenClaw registrieren (siehe README.md)."
fi
# =============================================================
# Fertig
# =============================================================
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ Installation abgeschlossen! ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════════════════╝${NC}"
echo ""
echo " Nächste Schritte:"
echo " 1. khal agenda # Termine anzeigen"
echo " 2. todo list # Aufgaben anzeigen"
echo " 3. systemctl --user status vdirsyncer-sync.timer"
echo " 4. tail -f /var/log/openclaw-mailbox-cal/sync.log"
echo ""
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""
openclaw_skill.py — OpenClaw Telegram-Bot-Skill: mailbox.org Kalender + Aufgaben
Registrierung in OpenClaw: /register skill mailbox-cal
Unterstützte Befehle (via Telegram-Chat an den Bot):
/termine [heute|morgen|woche] — Kalender-Ereignisse anzeigen
/aufgaben [offen|alle] — Aufgabenliste anzeigen
/neu_aufgabe <Text> — Neue Aufgabe anlegen
/sync — manuellen vdirsyncer-Sync auslösen
/status — Sync-Status abfragen
Voraussetzungen:
- khal, todoman, vdirsyncer installiert (install.sh ausgeführt)
- OpenClaw mit Python-Skill-API >= 0.4
"""
from __future__ import annotations
import subprocess
import shlex
import logging
from datetime import datetime, date, timedelta
from typing import Optional
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────
# Hilfsfunktionen
# ─────────────────────────────────────────────
def _run(cmd: list[str], timeout: int = 30) -> tuple[str, str, int]:
"""Führt einen Subprozess aus und gibt (stdout, stderr, returncode) zurück."""
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
)
return result.stdout.strip(), result.stderr.strip(), result.returncode
except subprocess.TimeoutExpired:
return "", "Timeout nach {}s".format(timeout), 1
except FileNotFoundError as e:
return "", str(e), 127
def _khal_agenda(days: int = 1, start: Optional[date] = None) -> str:
"""Gibt khal-Agenda als String zurück."""
start_str = (start or date.today()).strftime("%Y-%m-%d")
end_str = (date.today() + timedelta(days=days)).strftime("%Y-%m-%d")
stdout, stderr, rc = _run(
["khal", "list", "--format", "{start-time} {title}", start_str, end_str]
)
if rc != 0:
logger.warning("khal list Fehler: %s", stderr)
return "⚠️ khal Fehler: " + stderr
return stdout or "Keine Termine gefunden."
def _todo_list(all_tasks: bool = False) -> str:
"""Gibt todoman-Aufgabenliste als String zurück."""
cmd = ["todo", "list"]
if all_tasks:
cmd.append("--all")
stdout, stderr, rc = _run(cmd)
if rc != 0:
return "⚠️ todoman Fehler: " + stderr
return stdout or "Keine Aufgaben."
def _todo_new(title: str) -> str:
"""Legt eine neue Aufgabe an."""
if not title.strip():
return "⚠️ Kein Aufgabentitel angegeben."
stdout, stderr, rc = _run(["todo", "new", "--", title])
if rc != 0:
return "⚠️ Fehler beim Anlegen: " + stderr
return "✅ Aufgabe angelegt: " + title
def _vdirsyncer_sync() -> str:
"""Löst manuellen vdirsyncer-Sync aus."""
stdout, stderr, rc = _run(["vdirsyncer", "sync"], timeout=60)
if rc != 0:
return "⚠️ Sync-Fehler:\n" + stderr
return "✅ Sync erfolgreich.\n" + stdout
def _sync_status() -> str:
"""Prüft systemd-Timer-Status."""
stdout, _, rc = _run(
["systemctl", "--user", "status", "vdirsyncer-sync.timer", "--no-pager", "-l"]
)
if rc not in (0, 3):
return "⚠️ Timer nicht gefunden oder Fehler."
# Nur relevante Zeilen extrahieren
lines = [
line for line in stdout.splitlines()
if any(kw in line for kw in ("Active:", "Trigger:", "Last trigger:"))
]
return "\n".join(lines) or stdout[:500]
# ─────────────────────────────────────────────
# OpenClaw Skill-Handler
# ─────────────────────────────────────────────
def handle(command: str, args: str, context: dict) -> str:
"""
Haupt-Einstiegspunkt für OpenClaw.
:param command: Befehlsname ohne führenden Slash, z. B. "termine"
:param args: Argumente als String, z. B. "woche"
:param context: OpenClaw-Kontext (user_id, chat_id, etc.)
:return: Antwort-String für Telegram
"""
cmd = command.lower().strip()
arg = args.lower().strip()
if cmd == "termine":
if arg in ("morgen",):
start = date.today() + timedelta(days=1)
result = _khal_agenda(days=1, start=start)
return "📅 *Termine morgen:*\n" + result
elif arg in ("woche", "7"):
result = _khal_agenda(days=7)
return "📅 *Termine diese Woche:*\n" + result
else:
result = _khal_agenda(days=1)
return "📅 *Termine heute:*\n" + result
elif cmd == "aufgaben":
all_tasks = arg in ("alle", "all", "fertig")
result = _todo_list(all_tasks=all_tasks)
label = "alle Aufgaben" if all_tasks else "offene Aufgaben"
return "📝 *{}:*\n{}".format(label.capitalize(), result)
elif cmd == "neu_aufgabe":
return _todo_new(args.strip())
elif cmd == "sync":
return "🔄 Starte Sync...\n" + _vdirsyncer_sync()
elif cmd == "status":
return "⏱ *Sync-Status:*\n" + _sync_status()
else:
return (
"❓ Unbekannter Befehl. Verfügbare Befehle:\n"
" /termine [heute|morgen|woche]\n"
" /aufgaben [offen|alle]\n"
" /neu_aufgabe <Text>\n"
" /sync\n"
" /status"
)
# ─────────────────────────────────────────────
# Standalone-Test (ohne OpenClaw)
# ─────────────────────────────────────────────
if __name__ == "__main__":
import sys
logging.basicConfig(level=logging.DEBUG)
tests = [
("termine", "heute"),
("termine", "woche"),
("aufgaben", "offen"),
("sync", ""),
("status", ""),
]
for cmd, arg in tests:
print("\n" + "=" * 50)
print(f"CMD: {cmd!r} ARG: {arg!r}")
print(handle(cmd, arg, {}))
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
# =============================================================
# test_setup.sh — Überprüft die Installation Schritt für Schritt
# Ausführen nach install.sh: bash scripts/test_setup.sh
# =============================================================
set -euo pipefail
# Farben
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ok() { echo -e "${GREEN} [✓] $*${NC}"; }
fail() { echo -e "${RED} [✗] $*${NC}"; FAILED=$((FAILED+1)); }
info() { echo -e "${CYAN} [i] $*${NC}"; }
FAILED=0
echo ""
echo -e "${CYAN}══════════════════════════════════════════════════${NC}"
echo -e "${CYAN} OpenClaw mailbox.org Kalender+Aufgaben — Selbsttest${NC}"
echo -e "${CYAN}══════════════════════════════════════════════════${NC}"
echo ""
# --- 1. Binaries vorhanden? ---
echo "▶ 1. Programm-Verfügbarkeit"
for bin in vdirsyncer khal todo python3; do
if command -v "$bin" &>/dev/null; then
ok "$bin$(command -v $bin)"
else
fail "$bin nicht gefunden — install.sh ausführen"
fi
done
# --- 2. Konfigurationsdateien ---
echo ""
echo "▶ 2. Konfigurationsdateien"
declare -A CONFIGS=(
["vdirsyncer"]="$HOME/.config/vdirsyncer/config"
["khal"]="$HOME/.config/khal/config"
["todoman"]="$HOME/.config/todoman/config.cfg"
)
for name in "${!CONFIGS[@]}"; do
path="${CONFIGS[$name]}"
if [[ -f "$path" ]]; then
ok "$name$path"
else
fail "$name-Config fehlt: $path"
fi
done
# --- 3. Berechtigungen vdirsyncer-Config (enthält Passwort) ---
echo ""
echo "▶ 3. Sicherheit"
VDIR_CONF="$HOME/.config/vdirsyncer/config"
if [[ -f "$VDIR_CONF" ]]; then
PERMS=$(stat -c "%a" "$VDIR_CONF")
if [[ "$PERMS" == "600" ]]; then
ok "vdirsyncer config Berechtigungen: $PERMS (korrekt)"
else
fail "vdirsyncer config Berechtigungen: $PERMS (sollte 600 sein) → chmod 600 $VDIR_CONF"
fi
fi
# Prüfe ob Klartext-Passwort in Config steht
if grep -q 'password.*=.*"[^$]' "$VDIR_CONF" 2>/dev/null; then
info "Passwort in Plaintext erkannt — Erwäge pass(1) oder python-keyring als Alternative"
fi
# --- 4. Sync-Verzeichnisse ---
echo ""
echo "▶ 4. Sync-Verzeichnisse"
for dir in \
"$HOME/.local/share/vdirsyncer/status" \
"$HOME/.local/share/vdirsyncer/calendars" \
"$HOME/.local/share/vdirsyncer/tasks"; do
if [[ -d "$dir" ]]; then
COUNT=$(find "$dir" -name "*.ics" 2>/dev/null | wc -l)
ok "$dir (${COUNT} .ics Dateien)"
else
fail "Verzeichnis fehlt: $dir"
fi
done
# --- 5. vdirsyncer Verbindungstest ---
echo ""
echo "▶ 5. vdirsyncer — Verbindungstest"
info "Führe 'vdirsyncer sync' aus (Timeout: 30s)..."
if timeout 30 vdirsyncer sync 2>&1 | tail -5; then
ok "vdirsyncer sync erfolgreich"
else
fail "vdirsyncer sync fehlgeschlagen — Zugangsdaten und Netzwerk prüfen"
fi
# --- 6. khal-Test ---
echo ""
echo "▶ 6. khal — Termine heute"
if khal list --format "{start-time} {title}" today today 2>/dev/null; then
ok "khal list ausgeführt"
else
fail "khal list fehlgeschlagen — khal.conf prüfen"
fi
# --- 7. todoman-Test ---
echo ""
echo "▶ 7. todoman — Aufgabenliste"
if todo list 2>/dev/null; then
ok "todo list ausgeführt"
else
fail "todo list fehlgeschlagen — todoman.conf prüfen"
fi
# --- 8. systemd-Timer ---
echo ""
echo "▶ 8. systemd Timer"
if systemctl --user is-active vdirsyncer-sync.timer &>/dev/null; then
ok "vdirsyncer-sync.timer: aktiv"
NEXT=$(systemctl --user list-timers vdirsyncer-sync.timer --no-legend 2>/dev/null | awk '{print $1, $2}')
info "Nächster Sync: $NEXT"
else
fail "vdirsyncer-sync.timer nicht aktiv → systemctl --user enable --now vdirsyncer-sync.timer"
fi
# --- 9. Log-Datei ---
echo ""
echo "▶ 9. Log-Datei"
LOG="/var/log/openclaw-mailbox-cal/sync.log"
if [[ -f "$LOG" ]]; then
SIZE=$(du -sh "$LOG" | cut -f1)
ok "Log vorhanden: $LOG ($SIZE)"
info "Letzte 5 Zeilen:"
tail -5 "$LOG" | sed 's/^/ /'
else
info "Log noch nicht vorhanden (wird beim ersten Sync erstellt)"
fi
# --- Zusammenfassung ---
echo ""
echo -e "${CYAN}══════════════════════════════════════════════════${NC}"
if [[ $FAILED -eq 0 ]]; then
echo -e "${GREEN} Alle Tests bestanden! Setup ist korrekt.${NC}"
else
echo -e "${RED} $FAILED Test(s) fehlgeschlagen — siehe Details oben.${NC}"
fi
echo -e "${CYAN}══════════════════════════════════════════════════${NC}"
echo ""
exit $FAILED