#!/bin/sh
# Chạy sau khi DSM đã gỡ gói khỏi máy.
#
# Gỡ gói phải trả máy về đúng như trước khi cài. Bản đầu chỉ xoá thư mục var rồi
# thoát, để lại thay đổi hệ thống nằm vĩnh viễn trong máy khách mà không ai biết:
# skip_ip_checking=true.
#
# Nguyên tắc: chỉ trả về trạng thái cũ khi CHÍNH GÓI NÀY đã bật. Căn cứ là file
# dấu do target/fix_proxy_session.sh ghi lúc cài. Khách nào vốn đã tự bật thì gỡ
# RocketZero không được đụng vào cài đặt bảo mật của họ.
#
# VÌ SAO FILE NÀY TỰ CHỨA MỌI THỨ, KHÔNG NẠP target/paths.sh:
# tới lúc này DSM đã tháo cả cây /var/packages/rocketzero — target/ không còn,
# nên không nạp được gì từ gói. Đoạn tìm thư mục dưới đây là BẢN SAO có chủ ý của
# target/paths.sh; sửa một bên phải sửa bên kia. Đổi lại, script gỡ không phụ
# thuộc bất cứ file nào còn sống, tức không thể hỏng vì thiếu file.
#
# HAI LỖI ĐÃ SỬA Ở ĐÂY, cả hai đều do IM LẶNG mà sống lâu:
#
# 1) Bản cũ lấy VAR="/var/packages/rocketzero/var". Log gỡ thật cho thấy DSM
#    tháo cây đó TRƯỚC khi gọi postuninst (preuninst → rm -rf @appstore/<gói> →
#    rm -rf @apptemp/<gói> → postuninst), nên mọi [ -f "$VAR/undo_*" ] đều sai và
#    cả khối hoàn tác bị bỏ qua mà vẫn trả 0.
#    (Lỗi này phát hiện khi còn cả phần WebDAV; WebDAV nay đã bỏ, nhưng cách tìm
#    thư mục thì vẫn đúng và vẫn cần cho skip_ip_checking lẫn bản sao dữ liệu.)
#
# 2) Chỗ cất bản sao cũ (/volume1/@rocketzero-backup) KHÔNG ghi được: gói khai
#    "run-as": "package" nên script không phải root, mkdir ở gốc volume trượt.
#    Lỗi đó bị "mkdir ... 2>/dev/null" che, nên biểu hiện duy nhất là dòng "TAR
#    THẤT BẠI" không ai hiểu vì sao. Giờ chỗ cất được CHỌN BẰNG CÁCH GHI THỬ, và
#    mọi thất bại đều ghi ra log kèm nguyên văn lỗi của hệ thống.

PKG="${SYNOPKG_PKGNAME:-rocketzero}"
TL_ROOT="${TL_ROOT:-}"
OVERRIDE="$TL_ROOT/usr/syno/etc/synoservice.override/webdav-httpd-ssl.cfg"
SYNOAPI="$TL_ROOT/usr/syno/bin/synowebapi"
SYNOPKG_BIN="$TL_ROOT/usr/syno/bin/synopkg"
UPGRADING="$TL_ROOT/tmp/rocketzero-upgrading"

# Log: stdout LUÔN có (DSM gom vào /var/log/packages/<gói>.log — đã kiểm trên
# máy thật, chính nhờ nó mà bắt được lỗi lần này), còn file log thì chỉ có khi
# tìm được chỗ ghi. Nên những dòng nói ra TRƯỚC lúc biết chỗ ghi được giữ trong
# bộ đệm rồi đổ vào file ngay khi có — không mất dòng nào.
LOG=""
BUF=""
say() {
    echo "rocketzero: $*"
    if [ -n "$LOG" ]; then
        if [ -n "$BUF" ]; then
            printf '%s\n' "$BUF" >>"$LOG" 2>/dev/null
            BUF=""
        fi
        echo "$*" >>"$LOG" 2>/dev/null
    else
        BUF="$BUF${BUF:+
}$*"
    fi
}

# --- bản sao của target/paths.sh (xem lý do ở đầu file) ---------------------
#
# Biến môi trường ở giai đoạn gỡ: SYNOPKG_PKG_STATUS là biến duy nhất tài liệu
# Synology nói rõ giá trị ở đây (UNINSTALL trong preuninst và postuninst).
# SYNOPKG_PKGNAME/PKGDEST_VOL có trong tài liệu nhưng không chỗ nào khẳng định
# chúng còn sống ở giai đoạn này, nên chỉ "có thì dùng". SYNOPKG_PKGVAR KHÔNG
# phải biến của DSM (do spksrc đặt), chỉ đọc như gợi ý. Đường lùi cuối là glob
# @appdata, không cần biến nào.
tl_var_dir() {
    for _d in \
            "$TL_ROOT$SYNOPKG_PKGVAR" \
            "$TL_ROOT$SYNOPKG_PKGDEST_VOL/@appdata/$PKG" \
            "$TL_ROOT/var/packages/$PKG/var" \
            "$TL_ROOT"/volume*/"@appdata/$PKG"; do
        case "$_d" in
            ""|"$TL_ROOT"|"$TL_ROOT/"|"$TL_ROOT/@appdata/$PKG") continue ;;
        esac
        [ -d "$_d" ] && { printf '%s' "$_d"; return 0; }
    done
    return 1
}

tl_state_candidates() {
    # Giữ đồng bộ với target/paths.sh — xem chú thích dài ở đó. Tóm lại: gói chạy
    # dưới quyền "package" nên ba ứng viên dưới luôn trượt vì đòi tạo thư mục ở
    # nơi thuộc root; còn @appconf thì DSM tạo sẵn và đặt chủ là user của gói, nên
    # ghi được, và đã đo là còn nguyên sau lệnh gỡ.
    _sv="$(tl_var_dir)"
    case "$_sv" in
        */@appdata/*) echo "${_sv%/@appdata/*}/@appconf/$PKG" ;;
        *)  # Không suy được từ thư mục dữ liệu (lúc này DSM đã tháo cây gói nên
            # cũng không lần theo symlink etc được nữa). Dựng từ volume thấy được,
            # ưu tiên volume đã có sẵn @appconf/<gói>. Nếu bỏ nhánh này thì ứng
            # viên tốt nhất biến mất khỏi danh sách mà không ai hay.
            for _v in "$TL_ROOT"/volume*/"@appconf/$PKG"; do
                [ -d "$_v" ] && { echo "$_v"; break; }
            done ;;
    esac
    echo "$TL_ROOT/usr/syno/etc/packages/nashub-rocketzero"
    echo "$TL_ROOT/var/lib/rocketzero"
    case "$_sv" in
        */@appdata/*) echo "${_sv%/*}/$PKG-backup" ;;
    esac
}

# Mọi chỗ cần TÌM khi đọc, gồm cả chỗ của thiết kế cũ (chỉ đọc, không ghi) và var.
tl_state_places() {
    tl_state_candidates
    for _l in "$TL_ROOT"/volume*/@rocketzero-backup; do
        [ -d "$_l" ] && echo "$_l"
    done
    [ -n "$VAR" ] && echo "$VAR"
    return 0
}

# Ghi thử thật, không tin [ -d ] hay [ -w ]: thư mục có thể tồn tại mà lệnh ghi
# vẫn trượt — đúng cái bẫy vừa mắc.
tl_dir_usable() {
    if [ ! -d "$1" ]; then
        _e="$(mkdir -p "$1" 2>&1)" || {
            say "chỗ cất: KHÔNG tạo được $1${_e:+ — $_e}"
            return 1
        }
    fi
    chmod 700 "$1" 2>/dev/null
    _p="$1/.tl-probe.$$"
    _e="$(: >"$_p" 2>&1)" || {
        say "chỗ cất: $1 có nhưng KHÔNG ghi được${_e:+ — $_e}"
        return 1
    }
    rm -f "$_p" 2>/dev/null
    return 0
}
# --- hết bản sao ------------------------------------------------------------

VAR="$(tl_var_dir)"

say "--- postuninst $(date) ---"
say "trạng thái DSM báo: ${SYNOPKG_PKG_STATUS:-<rỗng>}"
say "chạy dưới tài khoản: $(id -un 2>/dev/null || echo '?') (gói khai run-as=package)"
say "thư mục dữ liệu: ${VAR:-KHÔNG TÌM THẤY}"

# Chọn chỗ cất bằng cách ghi thử từng ứng viên. Mỗi thất bại đều đã được say()
# ghi ra ở trong tl_dir_usable.
STATE=""
for CAND in $(tl_state_candidates); do
    if tl_dir_usable "$CAND"; then
        STATE="$CAND"
        break
    fi
done
if [ -n "$STATE" ]; then
    LOG="$STATE/uninstall.log"      # từ đây bộ đệm được đổ vào file
    say "chỗ cất bản sao/dấu: $STATE (ghi thử OK)"
else
    say "chỗ cất bản sao/dấu: KHÔNG CÓ CHỖ NÀO GHI ĐƯỢC — chỉ còn log này trên stdout"
fi

# Bản sao của LẦN GỠ TRƯỚC có còn không.
#
# Đây là cách trả lời câu "chỗ cất có sống sót qua lệnh gỡ của DSM không" mà không
# phải đoán: log gỡ của DSM chỉ xoá đúng @appstore/<gói> và @apptemp/<gói> theo
# tên gói chứ không quét mọi thư mục, nên về lý thì nó sống. Dòng này biến cái
# "về lý" đó thành bằng chứng ở lần gỡ thứ hai trên cùng một máy.
OLDBAK=""
for D in $(tl_state_places); do
    [ -f "$D/var.tgz" ] && { OLDBAK="$D/var.tgz"; break; }
done
if [ -n "$OLDBAK" ]; then
    say "bản sao cũ: CÒN ($OLDBAK) — chỗ này sống sót qua lần gỡ trước"
else
    say "bản sao cũ: không có (lần gỡ đầu, hoặc chỗ cất đã bị dọn)"
fi

# tl_mark_find TÊN — tìm dấu ở mọi chỗ: chỗ cất mới, chỗ cũ trên volume (chỉ đọc),
# và var (máy cài bản 1.4.x trở về trước đánh dấu trong đó).
tl_mark_find() {
    for _d in $(tl_state_places); do
        [ -f "$_d/$1" ] && { printf '%s' "$_d/$1"; return 0; }
    done
    return 1
}

# Nâng cấp cũng chạy qua đây.
#
# DSM nâng cấp bằng cách gỡ bản cũ rồi cài bản mới (preupgrade → preuninst →
# postuninst → preinst → postinst → postupgrade), nên script này chạy giữa chừng
# mỗi lần khách bấm Update. Nếu cứ bỏ skip_ip_checking ở đây để postinst bật lại
# vài giây sau thì chẳng được gì, mà trong khoảng giữa đó phiên đăng nhập của
# khách đang vào NAS qua tunnel bị đứt — ngay giữa lúc họ bấm Update.
#
# SYNOPKG_PKG_STATUS là cách DSM tự báo, và lời DSM nói bao giờ cũng đúng hơn.
# Chỉ khi bản DSM nào đó không đặt biến này mới xét tới dấu preupgrade để lại:
# một lần nâng cấp hỏng giữa chừng có thể bỏ quên cái dấu trong /tmp tới tận lần
# khởi động máy sau, và nếu để dấu cũ đè lên lời DSM thì lần khách GỠ thật trong
# khoảng đó sẽ im lặng bỏ qua phần hoàn tác.
IS_UPGRADE=0
case "$SYNOPKG_PKG_STATUS" in
    UPGRADE) IS_UPGRADE=1 ;;
    "")      [ -f "$UPGRADING" ] && IS_UPGRADE=1 ;;
    *)       IS_UPGRADE=0 ;;
esac

if [ "$IS_UPGRADE" = "1" ]; then
    say "đang NÂNG CẤP, không hoàn tác thay đổi hệ thống (postinst sẽ bật lại ngay)"
else
    say "đang GỠ THẬT, bắt đầu hoàn tác"

    # 1) Trả skip_ip_checking về false nếu chính ta đã bật nó.
    MARK="$(tl_mark_find undo_skip_ip)"
    if [ -z "$MARK" ]; then
        say "skip_ip_checking: BỎ QUA vì không thấy dấu undo_skip_ip (khách tự bật, hoặc dấu đã mất)"
    elif [ ! -x "$SYNOAPI" ]; then
        say "skip_ip_checking: BỎ QUA vì không thấy $SYNOAPI (có dấu $MARK)"
    else
        "$SYNOAPI" --exec api=SYNO.Core.Security.DSM method=set version=1 \
                   skip_ip_checking=false >/dev/null 2>&1
        # Đọc lại giá trị thật chứ không tin mã thoát: synowebapi trả 0 cả khi
        # không đổi được gì. Cùng cách đọc mà fix_proxy_session.sh dùng lúc bật.
        AFTER=$("$SYNOAPI" --exec api=SYNO.Core.Security.DSM method=get version=1 2>/dev/null \
                | grep -o '"skip_ip_checking"[^,]*' | grep -o 'true\|false')
        say "skip_ip_checking: ĐÃ LÀM (dấu $MARK) -> ${AFTER:-không đọc lại được}"
    fi

    # 2) Dọn tàn dư WebDAV — ĐƯỜNG DỌN CHO MÁY CŨ, không còn là tính năng.
    #
    # WebDAV đã bỏ hẳn ở 1.5.0: gói khai "run-as": "package" nên không có root để
    # bật cổng WebDAV HTTPS, mà đổi sang run-as root thì DSM từ chối cài (lỗi 319).
    # Bản này không bật WebDAV nữa nên bình thường chẳng có gì để hoàn tác.
    #
    # Nhưng máy cài từ bản 1.4.x có thể còn dấu undo_webdav và file override — do
    # bản cũ ghi, hoặc do người vận hành tạo tay lúc mò lỗi. Thấy dấu thì dọn đúng
    # MỘT lần rồi bỏ dấu, để không bỏ máy khách lại với một cổng đang mở cho tính
    # năng không còn tồn tại. Cả khối này bỏ được sau vài bản, khi không còn máy
    # nào chạy 1.4.x.
    MARK="$(tl_mark_find undo_webdav)"
    if [ -z "$MARK" ]; then
        say "WebDAV: không có gì để dọn (tính năng đã bỏ ở 1.5.0)"
    else
        say "WebDAV: thấy dấu cũ $MARK — dọn một lần cho máy cài từ bản 1.4.x"
        if [ -f "$OVERRIDE" ]; then
            if rm -f "$OVERRIDE" 2>/dev/null && [ ! -f "$OVERRIDE" ]; then
                say "WebDAV: đã xoá $OVERRIDE"
            else
                say "WebDAV: KHÔNG xoá được $OVERRIDE"
            fi
        fi
        # Phải sinh lại cấu hình rồi restart thì cổng mới thật sự đóng; chỉ xoá
        # file override là tiến trình vẫn nghe tiếp tới lần khởi động lại máy.
        WD="$TL_ROOT/var/packages/WebDAVServer"
        if [ -x "$WD/target/scripts/synowebdavserver_create_cfg.sh" ]; then
            "$WD/target/scripts/synowebdavserver_create_cfg.sh" >/dev/null 2>&1
            [ -x "$SYNOPKG_BIN" ] && "$SYNOPKG_BIN" restart WebDAVServer >/dev/null 2>&1
            sleep 3
            if netstat -tln 2>/dev/null | grep -q ':5006'; then
                say "WebDAV: đã dọn nhưng cổng 5006 VẪN NGHE — cần khởi động lại máy"
            else
                say "WebDAV: đã dọn, cổng 5006 đã đóng"
            fi
        else
            say "WebDAV: không thấy synowebdavserver_create_cfg.sh, chỉ xoá override"
        fi
    fi

    # Đã trả hệ thống về nguyên trạng thì bỏ dấu ở MỌI chỗ. Lần cài lại sau sẽ tự
    # đánh dấu lại theo tình hình thật của máy lúc đó.
    for M in undo_skip_ip undo_webdav; do
        for D in $(tl_state_places); do
            rm -f "$D/$M" 2>/dev/null
        done
    done
fi

# 3) Cất dữ liệu kích hoạt trước khi xoá.
#
# Mất nó là mất token và giấy phép: cài lại phải nhập đúng mã in trong hộp máy,
# thứ mà khách đã vứt cùng thùng carton. preupgrade có sao lưu, còn đường gỡ cài
# đặt thì không — mà khách bấm nhầm nút Gỡ nhiều hơn bấm nút Update.
BACKED_UP=0
if [ -z "$VAR" ]; then
    say "bản sao: KHÔNG TẠO ĐƯỢC vì không tìm thấy thư mục dữ liệu"
elif [ -z "$STATE" ]; then
    say "bản sao: KHÔNG TẠO ĐƯỢC vì không có chỗ nào ghi được"
elif [ ! -f "$VAR/rocketcore.toml" ] && [ ! -f "$VAR/frpc.toml" ]; then
    say "bản sao: BỎ QUA vì $VAR không có cấu hình đã kích hoạt"
else
    # Chỉ gói thứ CẦN để lấy lại giấy phép, không gói cả thư mục.
    #
    # Trước đây gói sạch mọi thứ, kéo theo cả install.log và rocketcore.log. Lần
    # cài lại bung ra là máy mới mang theo nguyên nhật ký của một đời trước: log
    # mở đầu bằng những dòng cảnh báo của mấy bản cũ, rồi mới tới dòng của lần
    # phục hồi. Đọc log đó mà tưởng đang xem lần chạy hiện tại thì kết luận sai
    # hoàn toàn — chính tôi vừa mất công truy một "mâu thuẫn" không tồn tại vì nó.
    #
    # Bỏ log cũng làm bản sao nhỏ lại và không mang theo file rác ai đó bỏ trong
    # var. Danh sách này là danh sách CHO PHÉP: thêm file mới cần khôi phục thì
    # phải thêm tên vào đây, và đó là điều đúng — im lặng gói mọi thứ mới là cách
    # dễ vô tình đem theo cái không nên đem.
    KEEP=""
    for F in rocketcore.toml frpc.toml p2p.key linkid.txt url.txt api.txt; do
        [ -f "$VAR/$F" ] && KEEP="$KEEP $F"
    done
    ERR="$(tar -C "$VAR" -czf "$STATE/var.tgz.new" $KEEP 2>&1)"
    if [ -f "$STATE/var.tgz.new" ]; then
        chmod 600 "$STATE/var.tgz.new" 2>/dev/null
        # Đổi tên ở bước cuối: mất điện giữa chừng thì bản sao cũ vẫn còn nguyên,
        # còn hơn để lại một file tgz cụt không bung được.
        if mv -f "$STATE/var.tgz.new" "$STATE/var.tgz" 2>/dev/null; then
            say "bản sao: ĐÃ TẠO $STATE/var.tgz ($(wc -c <"$STATE/var.tgz" 2>/dev/null) byte)"
            BACKED_UP=1
        else
            say "bản sao: tar xong nhưng KHÔNG đổi tên được, giữ nguyên bản cũ"
        fi
    else
        rm -f "$STATE/var.tgz.new" 2>/dev/null
        # Ghi nguyên văn lỗi của tar: dòng "TAR THẤT BẠI" trơ trọi của bản trước
        # đúng là thứ làm mất cả buổi để hiểu.
        say "bản sao: TAR THẤT BẠI từ $VAR${ERR:+ — $ERR}"
    fi
fi

# 4) Dọn dữ liệu — chỉ khi đã cất xong.
#
# Trong var có token tunnel và khoá p2p, để lại là không sạch. Nhưng mất dữ liệu
# kích hoạt còn nặng hơn nhiều: khách cài lại sẽ bị đòi mã giấy phép mà họ không
# còn giữ. Nên khi bản sao thất bại thì CỐ Ý giữ lại và ghi log — một file quyền
# 600 chỉ root đọc được, nằm trên máy của chính khách, là cái giá nhẹ hơn. (Đúng
# nhánh này đã cứu dữ liệu ở lần gỡ thử vừa rồi khi chỗ cất không ghi được.)
#
# DSM 7 tự nó KHÔNG xoá /volume<N>/@appdata/<gói> khi gỡ (kiểm trên bench: thư
# mục còn nguyên sau lệnh gỡ), nên không dọn ở đây thì nó nằm lại vĩnh viễn.
if [ "$IS_UPGRADE" = "1" ]; then
    say "dọn dữ liệu: BỎ QUA vì đang nâng cấp"
elif [ -z "$VAR" ]; then
    say "dọn dữ liệu: BỎ QUA vì không tìm thấy thư mục dữ liệu"
elif [ "$BACKED_UP" != "1" ]; then
    say "dọn dữ liệu: BỎ QUA CÓ CHỦ Ý vì chưa cất được bản sao — giữ $VAR để còn cứu được kích hoạt"
else
    rm -rf "$VAR" 2>/dev/null
    if [ -d "$VAR" ]; then
        say "dọn dữ liệu: XOÁ KHÔNG ĐƯỢC $VAR"
    else
        say "dọn dữ liệu: ĐÃ XOÁ $VAR"
    fi
fi

say "--- postuninst xong ---"
exit 0
