#!/bin/bash

# This script is run before the box code is switched. This means that we can
# put network related/curl downloads here. If the script fails, the old code
# will continue to run

set -eu -o pipefail

if [[ ${EUID} -ne 0 ]]; then
    echo "This script should be run as root." > /dev/stderr
    exit 1
fi

function log() {
  echo -e "$(date +'%Y-%m-%dT%H:%M:%S')" "==> installer: $1"
}

export DEBIAN_FRONTEND=noninteractive

apt_ready="no"
function prepare_apt_once() {
    [[ "${apt_ready}" == "yes" ]] && return

    log "Making sure apt is in a good state"

    log "Waiting for all dpkg tasks to finish..."
    # we used to check for locks, but there are many locks it seems
    while pgrep -x "apt-get|apt|dpkg" >/dev/null; do
        sleep 1
    done

    # it's unclear what needs to be run first or whether both these command should be run. so keep trying both
    for count in {1..3}; do
        # alternative to apt-install -y --fix-missing ?
        if ! dpkg --force-confold --configure -a; then
            log "dpkg reconfigure failed (try $count)"
            dpkg_configure="no"
        else
            dpkg_configure="yes"
        fi

        if ! apt update -y; then
            log "apt update failed (try $count)"
            apt_update="no"
        else
            apt_update="yes"
        fi

        [[ "${dpkg_configure}" == "yes" && "${apt_update}" == "yes" ]] && break

        sleep 1
    done

    apt_ready="yes"

    if [[ "${dpkg_configure}" == "yes" && "${apt_update}" == "yes" ]]; then
        log "apt is ready"
    else
        log "apt is not ready but proceeding anyway"
    fi
}

readonly user=yellowtent
readonly box_src_dir=/home/${user}/box

readonly curl="curl --fail --connect-timeout 20 --retry 10 --retry-delay 2 --max-time 2400"
readonly script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly box_src_next_dir="$(realpath ${script_dir}/..)"

readonly ubuntu_version=$(lsb_release -rs)
readonly ubuntu_codename=$(lsb_release -cs)

readonly is_update=$(systemctl is-active -q box && echo "yes" || echo "no")

log "Updating from $(cat $box_src_dir/VERSION 2>/dev/null) to $(cat $box_src_next_dir/VERSION 2>/dev/null)"

if [[ "${ubuntu_version}" == "20.04" ]]; then
    log "This Cloudron version requires atleast Ubuntu 22.04. Please upgrade following https://docs.cloudron.io/guides/upgrade-ubuntu-22/"
    exit 2
fi

# switch over to resolved and uninstall resolvconf
if dpkg -s resolvconf 2>/dev/null >/dev/null; then
    vendor=$(cat /sys/devices/virtual/dmi/id/sys_vendor || true)

    prepare_apt_once # do this first before DNS goes away intermittently

    log "disabling unbound"
    systemctl disable unbound || true
    systemctl stop unbound || true

    log "enabling systemd-resolved"
    systemctl enable --now systemd-resolved

    log "removing resolvconf"
    [[ "${vendor}" == "netcup" && -f /etc/resolvconf/resolv.conf.d/original ]] && cp /etc/resolvconf/resolv.conf.d/original /tmp/resolv.conf.original # stash before purging
    apt -y --purge remove resolvconf # purge required for dpkg -s to return error code

    if [[ "${vendor}" == "netcup" && -f /tmp/resolv.conf.original ]]; then
        log "Fix netcup DNS setup"
        nameservers=$(sed -ne 's/nameserver \(.*\)/"\1"/p' /tmp/resolv.conf.original | paste -sd "," -) # json array
        netplan set --origin-hint 50-cloud-init  "ethernets.eth0.nameservers.addresses=[${nameservers}]"
        netplan apply # generates /run/systemd/resolve/resolv.conf
        systemctl restart systemd-resolved
    fi
fi

# https://docs.docker.com/engine/installation/linux/ubuntulinux/
# https://download.docker.com/linux/ubuntu/dists/noble/pool/stable/amd64/
# this is the highest on Ubuntu 22.04 focal
readonly docker_version="29.1.5"
readonly distro_suffix="ubuntu.${ubuntu_version}~${ubuntu_codename}"
readonly docker_deb_version="5:${docker_version}-1~${distro_suffix}"
readonly containerd_version="2.2.1"
if ! dpkg -s docker-ce >/dev/null 2>&1 || \
   [[ "$(dpkg-query --show --showformat='${Version}' docker-ce 2>/dev/null)" != "${docker_deb_version}" ]]; then
    log "downloading docker ${docker_version}"

    # create systemd drop-in file already to make sure images are with correct driver
    mkdir -p /etc/systemd/system/docker.service.d
    echo -e "[Service]\nExecStart=\nExecStart=/usr/bin/dockerd -H fd:// --log-driver=journald --exec-opt native.cgroupdriver=cgroupfs --storage-driver=overlay2 --experimental --ip6tables" > /etc/systemd/system/docker.service.d/cloudron.conf

    # there are 3 packages for docker - containerd, CLI and the daemon (https://download.docker.com/linux/ubuntu/dists/jammy/pool/stable/amd64/)
    $curl -sL "https://download.docker.com/linux/ubuntu/dists/${ubuntu_codename}/pool/stable/amd64/containerd.io_${containerd_version}-1~${distro_suffix}_amd64.deb" -o /tmp/containerd.deb
    $curl -sL "https://download.docker.com/linux/ubuntu/dists/${ubuntu_codename}/pool/stable/amd64/docker-ce-cli_${docker_version}-1~${distro_suffix}_amd64.deb" -o /tmp/docker-ce-cli.deb
    $curl -sL "https://download.docker.com/linux/ubuntu/dists/${ubuntu_codename}/pool/stable/amd64/docker-ce_${docker_version}-1~${distro_suffix}_amd64.deb" -o /tmp/docker.deb

    log "installing docker ${docker_version}"
    prepare_apt_once
    apt install -y /tmp/containerd.deb  /tmp/docker-ce-cli.deb /tmp/docker.deb
    rm /tmp/containerd.deb /tmp/docker-ce-cli.deb /tmp/docker.deb
fi

readonly old_node_version=22.20.0
readonly node_version=24.13.0
if ! which node 2>/dev/null || [[ "$(node --version)" != "v${node_version}" ]]; then
    log "installing/updating node ${node_version}"
    mkdir -p /usr/local/node-${node_version}
    $curl -sL https://nodejs.org/dist/v${node_version}/node-v${node_version}-linux-x64.tar.gz -o /tmp/node.tar.gz
    tar zxf /tmp/node.tar.gz --strip-components=1 -C /usr/local/node-${node_version}
    rm /tmp/node.tar.gz
    ln -sf /usr/local/node-${node_version}/bin/node /usr/bin/node
    ln -sf /usr/local/node-${node_version}/bin/npm /usr/bin/npm
    rm -rf /usr/local/node-${old_node_version}
fi

# note that rebuild requires the above node
for try in `seq 1 10`; do
    # for reasons unknown, the dtrace package will fail. but rebuilding second time will work

    # We need --unsafe-perm as we run as root and the folder is owned by root,
    # however by default npm drops privileges for npm rebuild
    # https://docs.npmjs.com/misc/config#unsafe-perm
    if cd "${box_src_next_dir}" && npm rebuild --unsafe-perm; then break; fi
    log "Failed to rebuild, trying again"
    sleep 5
done

# update TLDs rules.json (https://github.com/thom4parisot/tld.js?tab=readme-ov-file#updating-the-tlds-list)
if ! node "${box_src_next_dir}/node_modules/tldjs/bin/update.js"; then
    log "Failed to update PSL, continue anyway"
fi

if [[ ${try} -eq 10 ]]; then
    log "npm rebuild failed, giving up"
    exit 4
fi

log "downloading new addon images"
images=$(node --input-type=module -e "import i from '${box_src_next_dir}/src/infra_version.js'; console.log(Object.keys(i.images).map(x => i.images[x]).join(' '));")

# docker hub only uses first 64 bits for ipv6 addressing. this causes many ipv6 rate limit errors
# https://www.docker.com/blog/beta-ipv6-support-on-docker-hub-registry/
log "\tPulling docker images: ${images}"
for image_ref in ${images}; do
    ipv4_image_ref="${image_ref/registry.docker.com/registry.ipv4.docker.com}"
    ipv6_image_ref="${image_ref/registry.docker.com/registry.ipv6.docker.com}"

    # on some machines, ipv6 pull just hangs
    while true; do
        if timeout --kill-after=10s 300s docker pull "${ipv4_image_ref}"; then # this pulls the image untagged using the sha256 but doesn't tag it!
            docker tag "${ipv4_image_ref}" "${image_ref%@sha256:*}" # this will tag the image for readability
            docker rmi "${ipv4_image_ref}"
            break
        fi
        log "Could not pull ${ipv4_image_ref} , trying IPv6"
        if timeout --kill-after=10s 300s docker pull "${ipv6_image_ref}"; then # this pulls the image untagged using the sha256 but doesn't tag it!
            docker tag "${ipv6_image_ref}" "${image_ref%@sha256:*}" # this will tag the image for readability
            docker rmi "${ipv6_image_ref}"
            break
        fi
        log "Could not pull ${ipv6_image_ref} either, waiting for 10s"
        sleep 10
    done
done

if [[ "${is_update}" == "yes" ]]; then
    log "stop box service for update"
    systemctl stop box
fi

# ensure we are not inside the source directory, which we will remove now
cd /root

log "switching the box code"
rm -rf "${box_src_dir}"
mv "${box_src_next_dir}" "${box_src_dir}"
chown -R "${user}:${user}" "${box_src_dir}"

log "calling box setup script"
"${box_src_dir}/setup/start.sh"
