{
"event": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "\/tmp\/backup-prod-to-ki.sh",
"old_string": " # Meta nachtraeglich uebertragen\n rsync -az \"${meta_dir}\/\" \\\n \"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DAILY}\/meta\/\" \\\n 2>>\"$LOG_FILE\"",
"new_string": " # Meta nachtraeglich uebertragen\n rsync -az -e \"ssh -p ${REMOTE_PORT}\" \"${meta_dir}\/\" \\\n \"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DAILY}\/meta\/\" \\\n 2>>\"$LOG_FILE\"",
"replace_all": false
}
}
{
"tool_response": {
"filePath": "\/tmp\/backup-prod-to-ki.sh",
"oldString": " # Meta nachtraeglich uebertragen\n rsync -az \"${meta_dir}\/\" \\\n \"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DAILY}\/meta\/\" \\\n 2>>\"$LOG_FILE\"",
"newString": " # Meta nachtraeglich uebertragen\n rsync -az -e \"ssh -p ${REMOTE_PORT}\" \"${meta_dir}\/\" \\\n \"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DAILY}\/meta\/\" \\\n 2>>\"$LOG_FILE\"",
"originalFile": "#!\/bin\/bash\n#==============================================================================\n# Datensicherung: prod.karlkratz.com → ki\/st (88.198.50.199)\n# Taeglich um 02:00 via cron\n# Version: 1.0 (2026-02-19)\n#==============================================================================\nset -uo pipefail\n\n#------------------------------------------------------------------------------\n# KONFIGURATION\n#------------------------------------------------------------------------------\nBACKUP_DATE=$(date +%Y-%m-%d)\nREMOTE_HOST=\"88.198.50.199\"\nREMOTE_PORT=\"2022\"\nREMOTE_USER=\"root\"\nREMOTE_BASE=\"\/backup\/prod\"\nREMOTE_DAILY=\"${REMOTE_BASE}\/daily\/${BACKUP_DATE}\"\nREMOTE_INCR=\"${REMOTE_BASE}\/incremental\"\nLOCAL_STAGING=\"\/var\/backup\/staging\"\nLOG_FILE=\"\/var\/log\/backup-prod-to-ki.log\"\nLOCK_FILE=\"\/var\/run\/backup-prod-to-ki.lock\"\nRETENTION_DAYS=7\nMAIL_RECIPIENT=\"i@karlkratz.de\"\nGPG_PASSPHRASE_FILE=\"\/root\/.backup-gpg-passphrase\"\n\n#------------------------------------------------------------------------------\n# HILFSFUNKTIONEN\n#------------------------------------------------------------------------------\nlog() {\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] $1\" | tee -a \"$LOG_FILE\"\n}\n\nlog_error() {\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] FEHLER: $1\" | tee -a \"$LOG_FILE\" >&2\n}\n\nERRORS=()\nrecord_error() {\n ERRORS+=(\"$1\")\n log_error \"$1\"\n}\n\nacquire_lock() {\n if [ -f \"$LOCK_FILE\" ]; then\n local pid\n pid=$(cat \"$LOCK_FILE\" 2>\/dev\/null)\n if [ -n \"$pid\" ] && kill -0 \"$pid\" 2>\/dev\/null; then\n log_error \"Backup laeuft bereits (PID: $pid). Abbruch.\"\n exit 1\n fi\n log \"Verwaiste Lock-Datei gefunden, wird entfernt.\"\n rm -f \"$LOCK_FILE\"\n fi\n echo $$ > \"$LOCK_FILE\"\n trap 'rm -f \"$LOCK_FILE\"; rm -rf \"$LOCAL_STAGING\"' EXIT\n}\n\n#------------------------------------------------------------------------------\n# PHASE 1: MariaDB (45 Datenbanken, einzeln)\n#------------------------------------------------------------------------------\nbackup_mariadb() {\n log \"--- Phase 1a: MariaDB ---\"\n local db_dir=\"${LOCAL_STAGING}\/databases\/mariadb\"\n mkdir -p \"$db_dir\"\n\n local DB_LIST=(\n admin admin_auth agent anachroma_pipeline apache_log_db\n backup_restore bic claudia_grajek_de code_documentation\n code_intelligence codequality content_pipeline doc2vector\n freund freund_lexoffice_369wohlbefinden freund_lexoffice_karlscore\n freund_pipeline karlkratz_de karlkratz_de_dev karlkratz_semantic\n karlscore_net ki_db ki_protocol kiebook kigem_rag kigemeinschaft\n kiglove kiseminar lisa_sundermeyer_de nevoteam nextcloud\n ocr_rechnung payment_system pdf_import ragdemo ragdemo1\n raum_events sprechstunde_physio system_karlkratz_de t_anachroma\n telegram_bot_karlkratz tracking vmail\n )\n\n local ok=0 fail=0\n for db in \"${DB_LIST[@]}\"; do\n if mysqldump --single-transaction --routines --triggers --events \\\n --quick --lock-tables=false \"$db\" 2>\/dev\/null | gzip -9 > \"${db_dir}\/${db}.sql.gz\"; then\n # Verify dump is not empty (gzip header is ~20 bytes)\n local size\n size=$(stat -c%s \"${db_dir}\/${db}.sql.gz\" 2>\/dev\/null || echo 0)\n if [ \"$size\" -gt 50 ]; then\n ok=$((ok + 1))\n else\n record_error \"MariaDB: Dump leer fuer $db\"\n rm -f \"${db_dir}\/${db}.sql.gz\"\n fail=$((fail + 1))\n fi\n else\n record_error \"MariaDB: Dump fehlgeschlagen fuer $db\"\n fail=$((fail + 1))\n fi\n done\n\n # Grants sichern\n mysql -N -e \"SELECT CONCAT('SHOW GRANTS FOR ''',user,'''@''',host,''';') FROM mysql.user WHERE user NOT IN ('root','mariadb.sys','')\" 2>\/dev\/null \\\n | mysql -N 2>\/dev\/null | sed 's\/$\/;\/' | gzip -9 > \"${db_dir}\/_grants.sql.gz\" 2>\/dev\/null\n\n log \"MariaDB: ${ok}\/${#DB_LIST[@]} OK, ${fail} Fehler\"\n}\n\n#------------------------------------------------------------------------------\n# PHASE 1b: Redis\n#------------------------------------------------------------------------------\nbackup_redis() {\n log \"--- Phase 1b: Redis ---\"\n local redis_dir=\"${LOCAL_STAGING}\/databases\/redis\"\n mkdir -p \"$redis_dir\"\n\n redis-cli BGSAVE >\/dev\/null 2>&1\n sleep 3\n\n local rdb_dir\n rdb_dir=$(redis-cli CONFIG GET dir 2>\/dev\/null | tail -1)\n local rdb_file\n rdb_file=$(redis-cli CONFIG GET dbfilename 2>\/dev\/null | tail -1)\n\n if [ -f \"${rdb_dir}\/${rdb_file}\" ]; then\n cp \"${rdb_dir}\/${rdb_file}\" \"${redis_dir}\/dump.rdb\"\n gzip -9 \"${redis_dir}\/dump.rdb\"\n log \"Redis: $(du -sh \"${redis_dir}\/dump.rdb.gz\" | cut -f1)\"\n else\n record_error \"Redis: RDB nicht gefunden (${rdb_dir}\/${rdb_file})\"\n fi\n}\n\n#------------------------------------------------------------------------------\n# PHASE 1c: Qdrant (API Snapshots)\n#------------------------------------------------------------------------------\nbackup_qdrant() {\n log \"--- Phase 1c: Qdrant ---\"\n local qdrant_dir=\"${LOCAL_STAGING}\/databases\/qdrant\"\n mkdir -p \"$qdrant_dir\"\n\n local collections\n collections=$(curl -sf http:\/\/localhost:6333\/collections 2>\/dev\/null \\\n | python3 -c \"import sys,json; [print(c['name']) for c in json.load(sys.stdin)['result']['collections']]\" 2>\/dev\/null)\n\n if [ -z \"$collections\" ]; then\n record_error \"Qdrant: API nicht erreichbar oder keine Collections\"\n return\n fi\n\n local ok=0\n while IFS= read -r coll; do\n [ -z \"$coll\" ] && continue\n local snap_result\n snap_result=$(curl -sf -X POST \"http:\/\/localhost:6333\/collections\/${coll}\/snapshots\" 2>\/dev\/null)\n local snap_name\n snap_name=$(echo \"$snap_result\" | python3 -c \"import sys,json; print(json.load(sys.stdin)['result']['name'])\" 2>\/dev\/null)\n\n if [ -n \"$snap_name\" ]; then\n curl -sf -o \"${qdrant_dir}\/${coll}.snapshot\" \\\n \"http:\/\/localhost:6333\/collections\/${coll}\/snapshots\/${snap_name}\" 2>\/dev\/null\n curl -sf -X DELETE \"http:\/\/localhost:6333\/collections\/${coll}\/snapshots\/${snap_name}\" >\/dev\/null 2>&1\n ok=$((ok + 1))\n else\n record_error \"Qdrant: Snapshot fehlgeschlagen fuer ${coll}\"\n fi\n done <<< \"$collections\"\n\n log \"Qdrant: ${ok} Snapshots erstellt\"\n}\n\n#------------------------------------------------------------------------------\n# PHASE 1d: ArangoDB\n#------------------------------------------------------------------------------\nbackup_arangodb() {\n log \"--- Phase 1d: ArangoDB ---\"\n local arango_dir=\"${LOCAL_STAGING}\/databases\/arangodb\"\n mkdir -p \"$arango_dir\"\n\n if command -v arangodump &>\/dev\/null; then\n if arangodump --output-directory \"$arango_dir\" --overwrite true --compress-output true 2>>\"$LOG_FILE\"; then\n log \"ArangoDB: Dump erstellt\"\n else\n record_error \"ArangoDB: Dump fehlgeschlagen\"\n fi\n else\n record_error \"ArangoDB: arangodump nicht installiert\"\n fi\n}\n\n#------------------------------------------------------------------------------\n# PHASE 1e: ChromaDB\n#------------------------------------------------------------------------------\nbackup_chromadb() {\n log \"--- Phase 1e: ChromaDB ---\"\n local chroma_dir=\"${LOCAL_STAGING}\/databases\/chromadb\"\n mkdir -p \"$chroma_dir\"\n\n if [ -d \/var\/www\/chromadb ]; then\n tar czf \"${chroma_dir}\/chromadb-data.tar.gz\" \\\n --exclude='*.log' \\\n -C \/var\/www chromadb 2>>\"$LOG_FILE\" \\\n && log \"ChromaDB: $(du -sh \"${chroma_dir}\/chromadb-data.tar.gz\" | cut -f1)\" \\\n || record_error \"ChromaDB: tar fehlgeschlagen\"\n else\n log \"ChromaDB: \/var\/www\/chromadb nicht vorhanden (uebersprungen)\"\n fi\n}\n\n#------------------------------------------------------------------------------\n# PHASE 2: E-Mail\n#------------------------------------------------------------------------------\nbackup_mail() {\n log \"--- Phase 2: E-Mail ---\"\n local mail_dir=\"${LOCAL_STAGING}\/mail\"\n mkdir -p \"$mail_dir\"\n\n if [ -d \/var\/vmail ]; then\n tar czf \"${mail_dir}\/vmail.tar.gz\" -C \/var vmail 2>>\"$LOG_FILE\" \\\n && log \"vmail: $(du -sh \"${mail_dir}\/vmail.tar.gz\" | cut -f1)\" \\\n || record_error \"Mail: vmail tar fehlgeschlagen\"\n fi\n\n if [ -d \/var\/mail ] && [ \"$(ls -A \/var\/mail 2>\/dev\/null)\" ]; then\n tar czf \"${mail_dir}\/mail.tar.gz\" -C \/var mail 2>>\"$LOG_FILE\" \\\n || record_error \"Mail: \/var\/mail tar fehlgeschlagen\"\n fi\n}\n\n#------------------------------------------------------------------------------\n# PHASE 3: Konfigurationsdateien\n#------------------------------------------------------------------------------\nbackup_configs() {\n log \"--- Phase 3: Konfigurationen ---\"\n local conf_dir=\"${LOCAL_STAGING}\/configs\"\n mkdir -p \"$conf_dir\"\n\n declare -A CONFIGS=(\n [\"apache2\"]=\"\/etc\/apache2\"\n [\"php\"]=\"\/etc\/php\"\n [\"mysql\"]=\"\/etc\/mysql\"\n [\"redis\"]=\"\/etc\/redis\"\n [\"postfix\"]=\"\/etc\/postfix\"\n [\"dovecot\"]=\"\/etc\/dovecot\"\n [\"opendkim\"]=\"\/etc\/opendkim\"\n [\"rspamd\"]=\"\/etc\/rspamd\"\n [\"fail2ban\"]=\"\/etc\/fail2ban\"\n [\"nftables\"]=\"\/etc\/nftables\"\n [\"monit\"]=\"\/etc\/monit\"\n [\"ipset\"]=\"\/etc\/ipset\"\n [\"cron.d\"]=\"\/etc\/cron.d\"\n [\"letsencrypt\"]=\"\/etc\/letsencrypt\"\n [\"nats\"]=\"\/etc\/nats\"\n [\"qdrant\"]=\"\/etc\/qdrant\"\n )\n\n local ok=0\n for name in \"${!CONFIGS[@]}\"; do\n local dir=\"${CONFIGS[$name]}\"\n if [ -d \"$dir\" ]; then\n tar czf \"${conf_dir}\/${name}.tar.gz\" \\\n --exclude='*.log' \\\n -C \"$(dirname \"$dir\")\" \"$(basename \"$dir\")\" 2>\/dev\/null \\\n && ok=$((ok + 1)) \\\n || record_error \"Config: ${name} tar fehlgeschlagen\"\n fi\n done\n\n # Custom systemd units\n local sd_tmp=\"${conf_dir}\/_systemd_tmp\"\n mkdir -p \"$sd_tmp\"\n find \/etc\/systemd\/system\/ -maxdepth 1 \\( -name \"*.service\" -o -name \"*... [TRUNCATED-14c31e191cdebcdd]",
"structuredPatch": [
{
"oldStart": 410,
"oldLines": 7,
"newStart": 410,
"newLines": 7,
"lines": [
" cp \"$LOG_FILE\" \"${meta_dir}\/backup.log\" 2>\/dev\/null",
" ",
" # Meta nachtraeglich uebertragen",
"- rsync -az \"${meta_dir}\/\" \\",
"+ rsync -az -e \"ssh -p ${REMOTE_PORT}\" \"${meta_dir}\/\" \\",
" \"${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DAILY}\/meta\/\" \\",
" 2>>\"$LOG_FILE\"",
" "
]
}
],
"userModified": false,
"replaceAll": false
}
}