From ff83a88c6d9dca25d69b19d254c2f6a88eb2d855 Mon Sep 17 00:00:00 2001 From: brupje Date: Sun, 18 Oct 2026 16:43:01 +0200 Subject: [PATCH] feat(lidarr): add data migration and host cleanup scripts --- cleanup_old_lidarr.sh | 58 +++++++++++ migrate_lidarr_sqlite_to_postgres.py | 146 +++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100755 cleanup_old_lidarr.sh create mode 100644 migrate_lidarr_sqlite_to_postgres.py diff --git a/cleanup_old_lidarr.sh b/cleanup_old_lidarr.sh new file mode 100755 index 0000000..8057629 --- /dev/null +++ b/cleanup_old_lidarr.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# ============================================================================== +# Opruimen van oude standalone Lidarr installatie op vmserver (10.2.0.4) +# ============================================================================== +set -euo pipefail + +CLUSTER_HOST="10.2.0.4" +SSH_USER="bart" + +COLOR_GREEN="\033[0;32m" +COLOR_YELLOW="\033[1;33m" +COLOR_RED="\033[0;31m" +COLOR_BLUE="\033[0;34m" +COLOR_RESET="\033[0m" + +log_info() { echo -e "${COLOR_GREEN}[INFO]${COLOR_RESET} $1"; } +log_step() { echo -e "\n${COLOR_BLUE}==>${COLOR_RESET} ${COLOR_GREEN}$1${COLOR_RESET}"; } +log_warn() { echo -e "${COLOR_YELLOW}[WARN]${COLOR_RESET} $1"; } + +echo "========================================================================" +echo " OPRUIMEN OUDE STANDALONE LIDARR OP $CLUSTER_HOST" +echo "========================================================================" +echo "Dit stopt en verwijdert de oude standalone bestanden op $CLUSTER_HOST:" +echo " - /etc/systemd/system/lidarr.service" +echo " - /opt/Lidarr" +echo " - /storage/var/lib/lidarr" +echo "Er is al een reservekopie gemaakt in /storage/backups/lidarr_complete_backup_2026-10-18.tar.gz" +echo "========================================================================" +echo "" + +read -p "Weet je zeker dat je de oude Lidarr bestanden wilt verwijderen? (j/N): " -r CONFIRM +if [[ ! "$CONFIRM" =~ ^[jJyY]$ ]]; then + log_info "Opruimen geannuleerd." + exit 0 +fi + +log_step "Verwijderen van systemd service en programmabestanden op ${CLUSTER_HOST}..." +ssh -t "${SSH_USER}@${CLUSTER_HOST}" " + echo '--> Stoppen en uitschakelen van lidarr.service...' + sudo systemctl stop lidarr 2>/dev/null || true + sudo systemctl disable lidarr 2>/dev/null || true + sudo rm -f /etc/systemd/system/lidarr.service + sudo systemctl daemon-reload + sudo systemctl reset-failed 2>/dev/null || true + + echo '--> Verwijderen van /opt/Lidarr...' + sudo rm -rf /opt/Lidarr + + echo '--> Verwijderen van /storage/var/lib/lidarr...' + sudo rm -rf /storage/var/lib/lidarr + + echo '--> Opslagruimte controleren...' + df -h / /storage +" + +echo "" +log_info "De oude standalone Lidarr is succesvol en volledig opgeruimd!" +echo "Alle verkeer naar https://thuus.bnhmeijer.nl/lidarr/ loopt nu direct via de Kubernetes pod." diff --git a/migrate_lidarr_sqlite_to_postgres.py b/migrate_lidarr_sqlite_to_postgres.py new file mode 100644 index 0000000..a919879 --- /dev/null +++ b/migrate_lidarr_sqlite_to_postgres.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +import sqlite3 +import csv +import sys +from collections import defaultdict + +def escape_pg_string(s): + if s is None: + return "NULL" + if isinstance(s, bytes): + s = s.decode('utf-8', errors='replace') + s = str(s).replace("'", "''") + return f"'{s}'" + +def main(): + sqlite_db_path = "/tmp/lidarr.db" + pg_columns_path = "/tmp/pg_lidarr_columns.csv" + output_sql_path = "/tmp/migrate_lidarr_data.sql" + + # 1. Lees PG tabellen en kolommen + pg_schema = defaultdict(dict) + with open(pg_columns_path, mode='r', encoding='utf-8') as f: + reader = csv.reader(f) + for row in reader: + if not row or len(row) < 3: + continue + table, col, dtype = row[0].strip(), row[1].strip(), row[2].strip() + pg_schema[table][col] = dtype + + # 2. Open SQLite + conn = sqlite3.connect(f"file:{sqlite_db_path}?mode=ro&immutable=1", uri=True) + cur = conn.cursor() + + cur.execute("SELECT name FROM sqlite_master WHERE type='table';") + sqlite_tables = set(r[0] for r in cur.fetchall()) + + with open(output_sql_path, mode='w', encoding='utf-8') as out: + out.write("-- Lidarr SQLite naar PostgreSQL Data Migratie\n") + out.write("BEGIN;\n") + out.write("SET session_replication_role = 'replica';\n\n") + + # Tabellen die we NIET moeten overschrijven + skip_tables = {"VersionInfo"} + + total_rows_imported = 0 + + for table, pg_cols in pg_schema.items(): + if table in skip_tables: + continue + + out.write(f"-- Tabel: {table}\n") + out.write(f'TRUNCATE TABLE "{table}" CASCADE;\n') + + if table == "DelayProfiles": + out.write('INSERT INTO "DelayProfiles" ("Id", "EnableUsenet", "EnableTorrent", "PreferredProtocol", "UsenetDelay", "TorrentDelay", "Order", "Tags", "BypassIfHighestQuality", "BypassIfAboveCustomFormatScore", "MinimumCustomFormatScore") VALUES (1, true, true, 1, 0, 0, 2147483647, \'[]\', true, false, 0);\n\n') + total_rows_imported += 1 + continue + + if table not in sqlite_tables: + out.write(f"-- Tabel {table} bestaat niet in SQLite, leeggemaakt en overgeslagen.\n\n") + continue + + # Haal SQLite kolommen op + cur.execute(f'PRAGMA table_info("{table}");') + sqlite_col_names = set(r[1] for r in cur.fetchall()) + + # Gemeenschappelijke kolommen + common_cols = [c for c in pg_cols.keys() if c in sqlite_col_names] + if not common_cols: + out.write(f"-- Geen gemeenschappelijke kolommen voor {table}\n\n") + continue + + # Query data uit SQLite + cols_quoted = [f'"{c}"' for c in common_cols] + cur.execute(f'SELECT {", ".join(cols_quoted)} FROM "{table}";') + rows = cur.fetchall() + + if not rows: + out.write(f"-- Geen rijen in SQLite voor {table}\n\n") + continue + + out.write(f'-- {len(rows)} rijen migreren voor {table}\n') + total_rows_imported += len(rows) + + chunk_size = 500 + for i in range(0, len(rows), chunk_size): + chunk = rows[i:i + chunk_size] + out.write(f'INSERT INTO "{table}" ({", ".join([f"""\"{c}\"""" for c in common_cols])}) VALUES\n') + val_lines = [] + for row_data in chunk: + val_parts = [] + for val, col_name in zip(row_data, common_cols): + dtype = pg_cols[col_name].lower() + if val is None: + val_parts.append("NULL") + elif "bool" in dtype: + val_parts.append("TRUE" if str(val) in ("1", "True", "true") else "FALSE") + elif dtype in ("integer", "bigint", "smallint", "numeric", "double precision", "real"): + s = str(val).strip() + if s == "TorrentDownloadProtocol": + val_parts.append("2") + elif s == "SoulseekDownloadProtocol": + val_parts.append("3") + elif s == "UsenetDownloadProtocol": + val_parts.append("1") + else: + try: + val_parts.append(str(int(s))) + except ValueError: + try: + float(s) + val_parts.append(s) + except ValueError: + val_parts.append("0") + else: + val_parts.append(escape_pg_string(val)) + val_lines.append(f' ({", ".join(val_parts)})') + out.write(",\n".join(val_lines) + ";\n") + out.write("\n") + + # Sequences resetten + out.write("-- Sequences bijwerken naar MAX(Id)\n") + out.write("""DO $$ +DECLARE + r RECORD; + has_seq text; +BEGIN + FOR r IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name != 'VersionInfo' LOOP + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = r.table_name AND column_name = 'Id') THEN + has_seq := pg_get_serial_sequence('"' || r.table_name || '"', 'Id'); + IF has_seq IS NOT NULL THEN + EXECUTE format('SELECT setval(%L, coalesce(max("Id"), 1)) FROM %I', has_seq, r.table_name); + END IF; + END IF; + + END LOOP; +END $$;\n\n""") + + out.write("SET session_replication_role = 'origin';\n") + out.write("COMMIT;\n") + + print(f"SQL dump succesvol gegenereerd: {output_sql_path}") + print(f"Totaal aantal rijen klaargezet voor import: {total_rows_imported}") + +if __name__ == "__main__": + main()