#!/bin/bash
#
# Filename     : linuxmuster-release-upgrade
# Description  : Performs a release upgrade to linuxmuster.net 7.4
# Signed-off by: thomas@linuxmuster.net
# Assisted by  : Claude
# Date         : 20260903
#

# get linuxmuster environment

source /usr/share/linuxmuster/helperfunctions.sh
logfile="$LOGDIR/linuxmuster-release-upgrade.log"

# Setup logging, but allow to temporarily restore terminal output for interactive calls
exec > >(tee -a "$logfile") 2>&1

# options
echo "$@" | grep -qw "\-\-force" && donotask="yes"
echo "$@" | grep -qw "\-\-reboot" && autoreboot="yes"
echo "$@" | grep -qw "\-\-clean" && cleancache="yes"
if echo "$@" | grep -q "\-\-help"; then
    echo "Usage: linuxmuster-release-upgrade [--force|--reboot|--clean|--help]"
    echo "  --force   Do not ask for confirmation"
    echo "  --reboot  Reboot after successful upgrade"
    echo "  --clean   Clean up apt cache after upgrade"
    echo "  --help    Show this help message"
    exit 0
fi

echo "############################################################"
echo "#                                                          #"
echo "#                        ATTENTION!                        #"
echo "#                                                          #"
echo "# This script upgrades your system to linuxmuster.net 7.4! #"
echo "# Make sure you have created a snapshot before.            #"
echo "#                                                          #"
echo "############################################################"
echo

# security query
answer="YES"
if [ ! -n "$donotask" ]; then
    echo -n "Do you want to continue? Type \"$answer\" to start the upgrade: "
    read given_answer
    [ "$given_answer" = "$answer" ] || exit 1
fi

# ensure time is correct
echo -e "\n## Adjusting system time..."
ntpdate pool.ntp.org

# save resolv.conf, unless a backup already exists from a previous,
# later-failing run - overwriting it here would replace the true
# pre-upgrade resolv.conf with whatever the system's current one is by
# now, which the later restore step would then wrongly treat as original
res_conf="/etc/resolv.conf"
res_conf_bak="/etc/resolv.conf.release-upgrade"
if [ -f "$res_conf_bak" ]; then
    echo -e "\n## $res_conf_bak already exists, keeping it as-is."
elif [ -f "$res_conf" ]; then
    echo -e "\n## Saving $res_conf to $res_conf_bak..."
    cp -f "$res_conf" "$res_conf_bak"
fi

# test if release upgrade is possible, unless it already happened in a
# previous, later-failing run - do-release-upgrade -c only reports a
# release newer than the currently installed one, so once the system is
# already on $NEXT_RELEASE it would report nothing and this script would
# abort here on every re-run, without ever reaching the steps below that
# might be exactly what failed and needs retrying (#191)
NEXT_RELEASE="26.04"
CURRENT_RELEASE="$(. /etc/os-release && echo "$VERSION_ID")"

if [ "$CURRENT_RELEASE" = "$NEXT_RELEASE" ]; then
    echo "System is already running $NEXT_RELEASE, skipping the release upgrade step."
else
    if do-release-upgrade -c 2>&1 | grep -q "$NEXT_RELEASE"; then
        RELEASE_TYPE="stable"
    else
        if do-release-upgrade -c -d 2>&1 | grep -q "$NEXT_RELEASE"; then
            DFLAG="-d"
            RELEASE_TYPE="development"
        else
            echo "Release $NEXT_RELEASE is not available!"
            exit 1
        fi
    fi
    echo "Upgrade to $NEXT_RELEASE is available as $RELEASE_TYPE release!"

    # get grub disks from debconf database and grub-probe
    get_grub_disks() {
        local grub_disks="$(debconf-show grub-pc 2>/dev/null | grep "grub-pc/install_devices:" | sed 's/.*: //')"
        local probed_disk="$(grub-probe --target=disk /boot 2>/dev/null)"
        stringinstring "$probed_disk" "$grub_disks" || grub_disks="$probed_disk $grub_disks"
        # strip disks from grub_disks that do not exist
        local disk
        for disk in $grub_disks; do
            [ -b "$disk" ] || grub_disks="$(echo "$grub_disks" | sed "s|$disk||")"
        done
        echo "$grub_disks" | xargs
    }

    # first stop linbo-torrent and multicast sessions
    for item in torrent multicast; do
        if "linbo-$item" status | grep -q windows; then
            echo -e "\n## Stopping linbo-$item sessions..."
            "linbo-$item" stop &> /dev/null
        fi
    done

    # detect grub boot disks
    echo -en "\n## Trying to auto-detect all grub boot disks: "
    BOOT_DISKS="$(get_grub_disks)"
    if [ -n "$BOOT_DISKS" ]; then
        echo "$BOOT_DISKS"
        if [ -n "$donotask" ]; then
            boot_disk="$(echo "$BOOT_DISKS" | awk '{print $1}')"
        else
            echo -n "   * Input the grub boot disk to continue: "
            read boot_disk
        fi
    else
        echo "nothing found!"
    fi
    if [ ! -b "$boot_disk" ]; then
        echo -e "\nERROR: Could not determine a valid grub boot disk!"
        echo "Please run 'dpkg-reconfigure grub-pc' to fix the grub configuration and restart the upgrade."
        exit 1
    fi
    echo "## Saving boot disk $boot_disk to grub-pc's debconf database..."
    echo "grub-pc grub-pc/install_devices multiselect $boot_disk" | debconf-set-selections
    # install grub on detected boot disk
    script -q -c "dpkg-reconfigure --priority=critical grub-pc" || exit 1

    # remove modemmanager, if installed - pulled in by the ubuntu-server
    # task but useless on a linuxmuster.net server (no cellular modem
    # hardware), and its constant PolicyKit activation attempts have been
    # observed to badly slow down a release upgrade
    if dpkg -l modemmanager 2>/dev/null | grep -q ^ii; then
        echo -e "\n## Removing modemmanager..."
        apt-get -yu purge modemmanager
    fi

    # start release upgrade
    echo -e "\n## Starting release upgrade..."
    echo DPkg::options \{ \"--force-confdef\"\; \"--force-confold\"\; \} > /etc/apt/apt.conf.d/local
    if ! do-release-upgrade $DFLAG -m server -f DistUpgradeViewNonInteractive; then
        rm -f /etc/apt/apt.conf.d/local
        echo "Upgrade failed obviously!"
        exit 1
    fi
fi

# remove annoying cloud-init package, if installed
echo -e "\n## Removing cloud-init packages, if installed..."
for p in cloud-guest-utils cloud-init cloud-init-base; do
    if dpkg -l $p 2>/dev/null | grep -q ^ii; then
        apt-get -yu purge $p
    fi
done

# disable obsolete services: isc-dhcp-server6 is unused; systemd-resolved
# must stay off since this is a Samba AD DC serving its own DNS on port
# 53; systemd-networkd-wait-online.service must go with it, or boot hangs
# at its timeout waiting on the now-masked systemd-resolved.
echo -e "\n## Disabling obsolete services..."
for s in isc-dhcp-server6 systemd-networkd-wait-online.service systemd-resolved; do
    for c in stop disable mask; do
        systemctl $c $s
    done
done

# restore resolv.conf
if [ -f "$res_conf_bak" ]; then
    echo -e "\n## Restoring $res_conf from $res_conf_bak..."
    mv -f "$res_conf_bak" "$res_conf"
fi

# ensure samba-ad-dc pkg is installed
echo -e "\n## Ensuring samba-ad-dc package is installed..."
DEBIAN_FRONTEND=noninteractive apt-get -yu install samba-ad-dc

# tweak sophomorix
cfg="/usr/share/sophomorix/devel/sophomorix.ini"
if ! grep -q "$NEXT_RELEASE" "$cfg"; then
    echo -e "\n## Tweaking sophomorix configuration..."
    sed -i "s|DISTRIB_EXPERIMENTAL =.*|DISTRIB_EXPERIMENTAL = $NEXT_RELEASE|" "$cfg"
fi

# create repo sources file
echo -e "\n## Creating linuxmuster.net repository sources file..."
repo_key_local="/etc/apt/trusted.gpg.d/linuxmuster.net.gpg"
deb_url="https://deb.linuxmuster.net/"
repo_key_remote="${deb_url}pub.gpg"
lmn_sources="/etc/apt/sources.list.d/lmn.sources"
[ -e "$repo_key_local" ] || wget -qO- "$repo_key_remote" | gpg --dearmour -o "$repo_key_local"
cat << _EOF > "$lmn_sources"
Types: deb
URIs: ${deb_url}
Suites: lmn74
Components: main
Signed-By: ${repo_key_local}
_EOF
apt-get update

# force reconfigure of linuxmuster-webui7 package, if no upgrade exists
if ! apt list --upgradable 2>&1 | grep -q linuxmuster-webui7; then
    echo -e "\n## Reconfiguring linuxmuster-webui7..."
    dpkg-reconfigure linuxmuster-webui7
fi

# upgrade linuxmuster pkgs
echo -e "\n## Upgrading linuxmuster packages..."
DEBIAN_FRONTEND=noninteractive apt-get -yu install linuxmuster-prepare
linuxmuster-distupgrade

# Retrofit ext4 quota fstab options and mask redundant quotaon units.
# Neither lmn-prepare (only runs during initial appliance bootstrap,
# before the OS is even installed) nor linuxmuster-setup's e_fstab.py
# (only runs on a fresh install) ever touch an existing system being
# upgraded here - so without this, an upgraded host keeps its legacy
# journaled-quota fstab options and an unmasked quotaon-root.service that
# fails at every boot. e_fstab.py is idempotent and safe to re-run against
# an already-configured system.
# Invoked via "python3 <path>", not directly - the file is installed
# without the executable bit (it's a Python module meant to be imported by
# linuxmuster-setup, not run standalone), so an earlier version of this
# check ("[ -x "$e_fstab" ]") always failed and silently skipped this
# whole step on every real upgrade, with no error at all.
e_fstab="/usr/lib/python3/dist-packages/linuxmuster_base7/setup/e_fstab.py"
if [ -f "$e_fstab" ]; then
    echo -e "\n## Fixing ext4 quota configuration..."
    python3 "$e_fstab" || echo "WARNING: $e_fstab failed, please check quota configuration manually!"
fi

# clean apt cache
if [ -n "$cleancache" ]; then
    echo -e "\n## Cleaning apt cache..."
    apt-get clean
fi

# remove apt tweak
if [ -e /etc/apt/apt.conf.d/local ]; then
    echo -e "\n## Removing apt tweak..."
    rm -f /etc/apt/apt.conf.d/local
fi

# reboot
if [ -n "$autoreboot" ]; then
    echo -e "\n## Rebooting..."
    reboot
fi
