#!/bin/bash
# EasyComp Zeeland - ECZHOA/ECZQHOA servermigratie, versie 1.0.2.
# Zelfstandig script; geen download, Python of config-reset nodig.
set -eo pipefail
export LC_ALL=C
umask 077
EXPECTED_PLATFORM="Linux"

pause_window() {
    if [ "$(uname -s)" = "Darwin" ] && [ -t 0 ]; then
        printf '\nDruk op Enter om dit venster te sluiten...'
        read -r _ || true
    fi
}
if [ "$(uname -s)" != "$EXPECTED_PLATFORM" ]; then
    echo "Dit bestand is bedoeld voor $EXPECTED_PLATFORM."; pause_window; exit 1
fi
check=0
case "${1:-}" in
    --check) check=1; shift ;;
    --elevated) shift ;;
    "") ;;
    *) echo "Gebruik: bash \"$0\" [--check]"; exit 1 ;;
esac
# Ook bij 'sudo bash bestand' hoort de configuratie bij de oorspronkelijke gebruiker.
default_home="$HOME"
default_user="$(id -un)"
if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != root ]; then
    default_user="$SUDO_USER"
    default_home="$(getent passwd "$SUDO_USER" | cut -d: -f6)"
    [ -n "$default_home" ] || { echo 'Gebruikersmap kon niet worden vastgesteld.'; exit 1; }
fi
target_home="${1:-$default_home}"
target_user="${2:-$default_user}"
target_config="${3:-${XDG_CONFIG_HOME:-$target_home/.config}}"
if [ "$(id -u)" -ne 0 ]; then
    echo 'Voor de serviceconfiguratie is het beheerderswachtwoord nodig.'
    if [ "$check" -eq 1 ]; then flag=--check; else flag=--elevated; fi
    rc=0
    sudo /bin/bash "$0" "$flag" "$target_home" "$target_user" "$target_config" || rc=$?
    pause_window
    exit "$rc"
fi
command -v perl >/dev/null || { echo 'Perl ontbreekt. Installeer Perl of neem contact op met EasyComp Zeeland.'; exit 1; }
command -v flock >/dev/null || { echo 'flock ontbreekt; er is niets gewijzigd.'; exit 1; }
exec 9>/run/eczhoa-herstel.lock
flock -n 9 || { echo 'Een andere ECZHOA-migratie is al actief; er is niets gewijzigd.'; exit 1; }
stamp="$(date +%Y%m%d-%H%M%S)-$$"
tmp=''
staged_files=()
backups=()
pending=()
transaction_active=0
migration_complete=0
stopped_units=()
stopped_user_units=()
stopped_plists=()
stopped_domains=()
target_uid="$(id -u "$target_user")"
user_systemctl() {
    sudo -u "$target_user" env XDG_RUNTIME_DIR="/run/user/$target_uid" \
        DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$target_uid/bus" systemctl --user "$@"
}
# Bekende clientprocessen opnieuw inventariseren; geen brede pkill-match.
client_pids() {
    while read -r pid comm; do
        name="${comm##*/}"
        case "$name" in
            rustdesk|RustDesk|ECZHOA|eczhoa|ECZQHOA|eczqhoa) [ "$pid" = "$$" ] || echo "$pid" ;;
        esac
    done < <(ps -axo pid=,comm=)
}
# Vergelijk servervelden en opgeslagen toegangsinstellingen zonder ze te tonen.
stable_fields() {
    perl -0777 -ne '
      my @fields;
      while (/^(?:\xEF\xBB\xBF)?[ \t]*(rendezvous_server|custom-rendezvous-server|relay-server|api-server|key|2fa|password|permanent-password|access-mode|approve-mode|unlock_pin|trusted_devices|(?:enable|allow)[-\w]*)[ \t]*=[ \t]*(["\x27])(.*?)\2[ \t]*(?:\#[^\r\n]*)?\r?$/gm) {
          push @fields, $1."=".$3;
      }
      print join("\n", sort @fields), "\n";
    ' "$1"
}
verify_fields() {
    expected="$(stable_fields "$2")" || return 1
    observed="$(stable_fields "$1")" || return 1
    [ -n "$expected" ] && [ "$expected" = "$observed" ]
}
rollback_current_run() {
    dirty=0
    i=0
    for f in "${pending[@]}"; do
        cmp -s "$f" "${backups[$i]}" || dirty=1
        i=$((i+1))
    done
    [ "$dirty" -eq 1 ] || return 0
    # Stop schrijvende services voordat de snapshot van deze uitvoering teruggaat.
    stop_ok=1
    for unit in "${stopped_units[@]}"; do systemctl stop "$unit" || stop_ok=0; done
    for unit in "${stopped_user_units[@]}"; do user_systemctl stop "$unit" || stop_ok=0; done
    if [ "$stop_ok" -ne 1 ] || [ -n "$(client_pids)" ]; then
        echo 'Automatisch terugzetten kon niet veilig: de client/service draait nog.'
        echo 'De oorspronkelijke gegevens staan in de zojuist gemaakte .ecz-backup-bestanden.'
        return 1
    fi
    i=0
    for f in "${pending[@]}"; do
        if ! cmp -s "$f" "${backups[$i]}"; then
            [ ! -L "$f" ] || { echo "Doel is een koppeling geworden: $f"; return 1; }
            if [ -f "$f" ]; then
                cp -p "$f" "${f}.ecz-interrupted-${stamp}" || return 1
            fi
            tmp="$(mktemp "${f}.ecz-tmp.XXXXXX")" || return 1
            cp -p "${backups[$i]}" "$tmp" || return 1
            mv -f "$tmp" "$f" || return 1
            tmp=''
            cmp -s "$f" "${backups[$i]}" || return 1
        fi
        i=$((i+1))
    done
    # Voorkom dat een service dezelfde gegevens meteen opnieuw leegt.
    stopped_units=()
    stopped_user_units=()
    echo 'Gegevens van voor deze uitvoering teruggezet. Betrokken services blijven gestopt.'
    return 0
}
cleanup() {
    rc=$?
    trap - EXIT INT TERM
    # Geen set -e in cleanup: ook een mislukte restart moet een duidelijke fout geven.
    set +e
    [ -z "$tmp" ] || rm -f "$tmp"
    if [ "$rc" -ne 0 ] && [ "$transaction_active" -eq 1 ]; then
        rollback_current_run || rc=1
    fi
    restarted=0
    for unit in "${stopped_units[@]}"; do
        if systemctl start "$unit"; then echo "Service weer gestart: $unit"; restarted=1
        else echo "Service niet herstart: $unit."; rc=1; fi
    done
    for unit in "${stopped_user_units[@]}"; do
        if user_systemctl start "$unit"; then echo "Gebruikersservice weer gestart: $unit"; restarted=1
        else echo "Gebruikersservice niet herstart: $unit."; rc=1; fi
    done
    if [ "$migration_complete" -eq 1 ]; then
        # Twee meetmomenten na restart, inclusief onmiddellijke en latere herschrijving.
        for pass in 1 2; do
            [ "$restarted" -eq 0 ] || sleep 1
            i=0
            for f in "${pending[@]}"; do
                if [ ! -s "$f" ] || ! verify_fields "$f" "${staged_files[$i]}"; then
                    echo "Configuratie niet behouden na starten: $f"
                    rc=1
                fi
                i=$((i+1))
            done
            [ "$rc" -eq 0 ] || break
        done
        if [ "$rc" -ne 0 ]; then rollback_current_run || rc=1; fi
    fi
    for staged in "${staged_files[@]}"; do rm -f "$staged"; done
    if [ "$rc" -ne 0 ]; then
        echo 'Migratie NIET geslaagd. Er wordt geen succesvolle verbinding geclaimd.'
    elif [ "$migration_complete" -eq 1 ]; then
        echo "Klaar: ${#pending[@]} configuratie(s) bijgewerkt en na servicestart gecontroleerd."
        echo 'Open nu ECZHOA/ECZQHOA en test een verbinding.'
    fi
    exit "$rc"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

# Bewerk uitsluitend de vier netwerkvelden, geen ID/key/wachtwoorden.
# Raw bytes houden Unicode, regelafbrekingen en alle overige inhoud intact.
transform() {
    perl -0777 -pe '
      s{^((?:\xEF\xBB\xBF)?[ \t]*(?:rendezvous_server|custom-rendezvous-server|relay-server|api-server)[ \t]*=[ \t]*)(["\x27])([^"\x27\r\n]*)(\2[ \t]*(?:\#[^\r\n]*)?\r?$)}
       { my ($prefix,$quote,$value,$suffix)=($1,$2,$3,$4);
         $value =~ s{(?<![A-Za-z0-9_.-])hoa\.easycomp\.cloud(?![A-Za-z0-9_.-])}{hoa.easycompcloud.net}ig;
         $prefix.$quote.$value.$suffix;
       }egmx;
    ' "$1"
}
echo 'EasyComp Zeeland - ECZHOA verbindingsherstel 1.0.2'
echo 'Alleen hoa.easycomp.cloud wordt vervangen door hoa.easycompcloud.net.'
echo 'ID, key, 2FA, wachtwoorden en overige instellingen blijven behouden.'
shopt -s nullglob
files=()
if [ "$EXPECTED_PLATFORM" = "Darwin" ]; then
    bases=("$target_home/Library/Preferences" "/var/root/Library/Preferences" "/Library/Preferences")
else
    bases=("$target_config" "$target_home/.config" "/root/.config")
    for sandbox in "$target_home"/.var/app/*[Rr]ust[Dd]esk* "$target_home"/.var/app/*[Ee][Cc][Zz][Hh][Oo][Aa]* "$target_home"/.var/app/*[Ee][Cc][Zz][Qq][Hh][Oo][Aa]*; do
        [ ! -d "$sandbox/config" ] || bases+=("$sandbox/config")
    done
fi
for base in "${bases[@]}"; do
    [ -d "$base" ] || continue
    for dir in "$base"/*[Rr]ust[Dd]esk* "$base"/*[Ee][Cc][Zz][Hh][Oo][Aa]* "$base"/*[Ee][Cc][Zz][Qq][Hh][Oo][Aa]*; do
        [ -d "$dir" ] || continue
        for f in "$dir"/RustDesk2.toml "$dir"/ECZHOA2.toml "$dir"/ECZQHOA2.toml "$dir"/config/RustDesk2.toml "$dir"/config/ECZHOA2.toml "$dir"/config/ECZQHOA2.toml; do
            [ -f "$f" ] || continue
            if [ -L "$f" ]; then echo "Symbolische koppeling niet aangepast: $f"; exit 1; fi
            duplicate=0
            for existing in "${files[@]}"; do [ ! "$existing" -ef "$f" ] || duplicate=1; done
            [ "$duplicate" -eq 1 ] || files+=("$f")
        done
    done
done
pending=()
for f in "${files[@]}"; do
    tmp="$(mktemp "${f}.ecz-tmp.XXXXXX")"
    transform "$f" > "$tmp"
    if ! cmp -s "$f" "$tmp"; then pending+=("$f"); echo "Oud adres gevonden: $f"; fi
    rm -f "$tmp"; tmp=''
done
if [ "${#files[@]}" -eq 0 ]; then
    echo 'Geen bekende configuratie gevonden. Installeer/open de nieuwste ECZHOA of ECZQHOA.'
    echo 'Bij een bestaande installatie: laat EasyComp de configuratielocatie controleren.'
elif [ "${#pending[@]}" -eq 0 ]; then
    echo 'Geen oud serveradres gevonden in de bekende configuraties. Er is niets gewijzigd.'
elif [ "$check" -eq 1 ]; then
    echo "Controle: ${#pending[@]} bestand(en) kunnen worden aangepast. Er is niets gewijzigd."
else
    # Leg eerst de originele inhoud duurzaam vast, voordat een client/service stopt.
    # Als een client bij afsluiten zelf schrijft, behouden we deze vroege backup.
    for f in "${pending[@]}"; do
        backup="${f}.ecz-backup-${stamp}"
        [ ! -e "$backup" ] || { echo "Back-up bestaat al: $backup"; exit 1; }
        [ -s "$f" ] || { echo "Configuratie is onverwacht leeg: $f"; exit 1; }
        cp -p "$f" "$backup"
        backups+=("$backup")
        cmp -s "$f" "$backup" || { echo "Configuratie veranderde tijdens de back-up: $f"; exit 1; }
        tmp="$(mktemp "${f}.ecz-tmp.XXXXXX")"
        staged_files+=("$tmp")
        cp -p "$backup" "$tmp"
        transform "$backup" > "$tmp"
        [ -s "$tmp" ] && [ "$(wc -c < "$tmp")" -gt "$(wc -c < "$backup")" ] || {
            echo "Ongeldige migratie-uitvoer. Origineel blijft behouden: $f"; exit 1;
        }
        echo "Back-up gemaakt voor afsluiten: $backup"
        tmp=''
    done
    transaction_active=1
    if [ "$EXPECTED_PLATFORM" = "Linux" ] && command -v systemctl >/dev/null; then
        while read -r unit rest; do
            case "$unit" in
                rustdesk.service|RustDesk.service|eczhoa.service|ECZHOA.service|eczqhoa.service|ECZQHOA.service)
                    if systemctl is-active --quiet "$unit"; then
                        stopped_units+=("$unit"); systemctl stop "$unit"
                    fi ;;
            esac
        done < <(systemctl list-unit-files --type=service --no-legend 2>/dev/null || true)
        if [ -S "/run/user/$target_uid/bus" ] && command -v sudo >/dev/null; then
            while read -r unit rest; do
                case "$unit" in
                    rustdesk.service|RustDesk.service|eczhoa.service|ECZHOA.service|eczqhoa.service|ECZQHOA.service)
                        if user_systemctl is-active --quiet "$unit"; then
                            stopped_user_units+=("$unit"); user_systemctl stop "$unit"
                        fi ;;
                esac
            done < <(user_systemctl list-unit-files --type=service --no-legend 2>/dev/null || true)
        fi
    elif [ "$EXPECTED_PLATFORM" = "Darwin" ]; then
        for directory in /Library/LaunchDaemons /Library/LaunchAgents "$target_home/Library/LaunchAgents"; do
            for plist in "$directory"/*[Rr]ust[Dd]esk*.plist "$directory"/*[Ee][Cc][Zz][Hh][Oo][Aa]*.plist "$directory"/*[Ee][Cc][Zz][Qq][Hh][Oo][Aa]*.plist; do
                label="$(/usr/libexec/PlistBuddy -c 'Print :Label' "$plist")"
                if [ "$directory" = "/Library/LaunchDaemons" ]; then domain=system; else domain="gui/$target_uid"; fi
                if launchctl print "$domain/$label" >/dev/null 2>&1; then
                    stopped_plists+=("$plist"); stopped_domains+=("$domain")
                    launchctl bootout "$domain/$label"
                fi
            done
        done
    fi
    pids=()
    while read -r pid; do [ -z "$pid" ] || pids+=("$pid"); done < <(client_pids)
    for pid in "${pids[@]}"; do kill -TERM "$pid" 2>/dev/null || true; done
    # Forceer geen SIGKILL: een nog schrijvende client betekent afbreken.
    for round in 1 2 3 4 5; do
        [ -n "$(client_pids)" ] || break
        sleep 1
    done
    [ -z "$(client_pids)" ] || { echo 'Client sluit niet volledig af; migratie afgebroken.'; exit 1; }
    # Controleer ALLE originelen voordat het eerste bestand vervangen wordt.
    # Een gewijzigde/verdwenen config na afsluiten wordt nooit blind overschreven.
    i=0
    for f in "${pending[@]}"; do
        if [ ! -f "$f" ] || [ -L "$f" ] || ! cmp -s "$f" "${backups[$i]}"; then
            echo "Configuratie is tijdens het afsluiten veranderd: $f"
            echo "Migratie afgebroken; bewaarde originele back-up: ${backups[$i]}"
            echo 'Laat EasyComp deze back-up en de clientconfiguratie controleren.'
            exit 1
        fi
        i=$((i+1))
    done
    changed=0
    i=0
    for f in "${pending[@]}"; do
        # Controleer opnieuw direct voor iedere vervanging.
        cmp -s "$f" "${backups[$i]}" || { echo "Configuratie verandert nog steeds: $f"; exit 1; }
        tmp="$(mktemp "${f}.ecz-tmp.XXXXXX")"
        cp -p "${staged_files[$i]}" "$tmp"
        mv -f "$tmp" "$f"; tmp=''
        if ! cmp -s "$f" "${staged_files[$i]}"; then
            echo "Controle na schrijven mislukt: $f"
            echo "Back-up behouden: ${backups[$i]}"
            exit 1
        fi
        changed=$((changed+1))
        i=$((i+1))
        echo "Bijgewerkt en byte voor byte gecontroleerd: $f"
    done
    migration_complete=1
fi
if [ "$migration_complete" -eq 0 ]; then
    echo 'Open ECZHOA/ECZQHOA en test een verbinding.'
fi
