#!/bin/sh
set -eu

# DeskNoria Linux bootstrap installer.
# Stable public entry point intended for:
#   wget -qO- https://desknoria.com/install.sh | bash
# The bootstrap itself contains no product binaries. It resolves the correct
# published DeskNoria Linux package from update.remote.desknoria.com.

manifest_url=${DESKNORIA_UPDATE_MANIFEST_URL:-https://update.remote.desknoria.com/api/updates-manifest.php}
prefix=${DESKNORIA_PREFIX:-/opt/desknoria}

say() { printf '%s\n' "$*"; }
fail() { printf 'DeskNoria installer: %s\n' "$*" >&2; exit 1; }

normalize_arch() {
    case "$1" in
        x86_64|amd64) printf '%s\n' x86_64 ;;
        i386|i486|i586|i686|x86) printf '%s\n' i686 ;;
        aarch64|arm64) printf '%s\n' arm64 ;;
        *) return 1 ;;
    esac
}

kernel_variant() {
    release=$1
    numbers=$(printf '%s\n' "$release" | sed 's/[^0-9.].*$//')
    old_ifs=$IFS
    IFS=.
    set -- $numbers
    major=${1:-0}
    minor=${2:-0}
    IFS=$old_ifs
    case "$major:$minor" in
        *[!0-9:]*|:) fail "unrecognized kernel version: $release" ;;
    esac
    if [ "$major" -lt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -lt 2 ]; }; then
        printf '%s\n' legacy
    else
        printf '%s\n' modern
    fi
}

kernel_at_least() {
    current=$1
    minimum=$2
    old_ifs=$IFS
    IFS=.
    set -- $(printf '%s\n' "$current" | sed 's/[^0-9.].*$//')
    c1=${1:-0}; c2=${2:-0}; c3=${3:-0}
    set -- $(printf '%s\n' "$minimum" | sed 's/[^0-9.].*$//')
    m1=${1:-0}; m2=${2:-0}; m3=${3:-0}
    IFS=$old_ifs
    [ "$c1" -gt "$m1" ] && return 0
    [ "$c1" -lt "$m1" ] && return 1
    [ "$c2" -gt "$m2" ] && return 0
    [ "$c2" -lt "$m2" ] && return 1
    [ "$c3" -ge "$m3" ]
}

host_arch_raw=${DESKNORIA_BOOTSTRAP_ARCH_OVERRIDE:-$(uname -m 2>/dev/null || printf unknown)}
host_kernel=${DESKNORIA_BOOTSTRAP_KERNEL_OVERRIDE:-$(uname -r 2>/dev/null || printf unknown)}
host_os=linux
host_arch=$(normalize_arch "$host_arch_raw") || fail "unsupported Linux architecture: $host_arch_raw"
variant=$(kernel_variant "$host_kernel")

# There has never been a legacy arm64 DeskNoria build. Fail explicitly instead
# of silently installing an incompatible modern binary on an old kernel.
if [ "$host_arch" = arm64 ] && [ "$variant" = legacy ]; then
    fail "kernel $host_kernel is too old for the DeskNoria arm64 package (minimum 3.2)."
fi

if [ "${DESKNORIA_BOOTSTRAP_RESOLVE_ONLY:-0}" = 1 ]; then
    printf 'variant=%s arch=%s kernel=%s\n' "$variant" "$host_arch" "$host_kernel"
    exit 0
fi

current_version=0.0.0
for candidate in \
    "$prefix/current/bin/desknoria-agent" \
    "$prefix/bin/desknoria-agent" \
    /usr/local/bin/desknoria-agent \
    /usr/bin/desknoria-agent; do
    if [ -x "$candidate" ]; then
        detected=$($candidate --version 2>/dev/null || true)
        set -- $detected
        detected=${1:-}
        case "$detected" in
            [0-9]*.[0-9]*.[0-9]*) current_version=$detected; break ;;
        esac
    fi
done

# Binary selection is distribution-neutral. DeskNoria Linux release binaries are
# static; compatibility is determined by architecture, kernel family and minimum
# kernel only. Fresh installs deliberately omit version so they always receive
# the latest published package instead of entering a historical upgrade path.
kernel_query=$(printf '%s\n' "$host_kernel" | sed 's/[^0-9.].*$//')
query_url="${manifest_url}?app=agent-linux&os=linux&variant=${variant}&arch=${host_arch}&kernel=${kernel_query}&packageFormat=tar.gz"
if [ "$current_version" != 0.0.0 ]; then
    query_url="${query_url}&version=${current_version}"
fi

http_get_stdout() {
    url=$1
    if command -v curl >/dev/null 2>&1; then
        curl -fkL --connect-timeout 20 --max-time 120 --retry 2 -sS "$url"
    elif command -v wget >/dev/null 2>&1; then
        wget --no-check-certificate -q -O - -T 120 "$url"
    elif command -v busybox >/dev/null 2>&1; then
        busybox wget --no-check-certificate -q -O - -T 120 "$url"
    else
        fail "curl, wget, or BusyBox wget is required for downloading."
    fi
}

http_download() {
    url=$1
    destination=$2
    if command -v curl >/dev/null 2>&1; then
        curl -fkL --connect-timeout 20 --max-time 1800 --retry 2 -sS -o "$destination" "$url"
    elif command -v wget >/dev/null 2>&1; then
        wget --no-check-certificate -q -O "$destination" -T 1800 "$url"
    elif command -v busybox >/dev/null 2>&1; then
        busybox wget --no-check-certificate -q -O "$destination" -T 1800 "$url"
    else
        fail "curl, wget, or BusyBox wget is required for downloading."
    fi
}

json_string() {
    key=$1
    # The update API emits compact JSON and these fields contain no embedded
    # newlines. Decode the JSON escapes needed by HTTPS URLs and filenames.
    printf '%s' "$manifest_json" | sed -n \
        's/.*"'"$key"'"[[:space:]]*:[[:space:]]*"\([^"\\]*\(\\.[^"\\]*\)*\)".*/\1/p' | \
        sed 's#\\/#/#g; s/\\"/"/g; s/\\\\/\\/g'
}

json_bool() {
    key=$1
    printf '%s' "$manifest_json" | sed -n 's/.*"'"$key"'"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p'
}

say "DeskNoria Linux installer"
say "Platform: $variant/$host_arch (kernel $host_kernel)"
say "Installed version: $current_version"

manifest_json=$(http_get_stdout "$query_url") || fail "the update manifest could not be downloaded from update.remote.desknoria.com."
[ -n "$manifest_json" ] || fail "the update manifest is empty."

success=$(json_bool success)
[ "$success" = true ] || {
    message=$(json_string message)
    [ -n "$message" ] || message="the update server rejected the request"
    fail "$message"
}

latest_version=$(json_string latestVersion)
download_url=$(json_string downloadUrl)
sha256_expected=$(json_string sha256)
manifest_variant=$(json_string variant)
manifest_arch=$(json_string arch)
manifest_min_kernel=$(json_string minKernel)
update_available=$(json_bool updateAvailable)

[ -n "$latest_version" ] || fail "the manifest does not contain latestVersion."
if [ "$update_available" != true ] && [ "$current_version" != 0.0.0 ]; then
    say "DeskNoria Linux Agent is already up to date ($current_version)."
    exit 0
fi
[ -n "$download_url" ] || fail "the manifest does not contain downloadUrl for $latest_version."
[ -n "$sha256_expected" ] || fail "the manifest does not contain SHA-256; installation has been stopped."
[ "$manifest_variant" = "$variant" ] || fail "incompatible manifest: variant=$manifest_variant, system=$variant."
[ "$manifest_arch" = "$host_arch" ] || fail "incompatible manifest: arch=$manifest_arch, system=$host_arch."
[ -n "$manifest_min_kernel" ] || fail "the Linux manifest does not contain minKernel."
kernel_at_least "$host_kernel" "$manifest_min_kernel" || fail "kernel $host_kernel is older than the published minimum $manifest_min_kernel."

umask 077
tmp_root=${TMPDIR:-/tmp}
if command -v mktemp >/dev/null 2>&1; then
    work_dir=$(mktemp -d "$tmp_root/desknoria-install.XXXXXX") || fail "unable to create the temporary directory."
else
    work_dir="$tmp_root/desknoria-install.$$"
    (umask 077 && mkdir "$work_dir") || fail "unable to create the temporary directory."
fi
cleanup() { rm -rf "$work_dir"; }
trap cleanup EXIT HUP INT TERM

package_file="$work_dir/desknoria-agent.tar.gz"
extract_dir="$work_dir/package"
mkdir "$extract_dir"

say "Downloading DeskNoria Linux Agent $latest_version..."
http_download "$download_url" "$package_file" || fail "the DeskNoria package download failed."
[ -s "$package_file" ] || fail "the downloaded package is empty."

sha256_actual=
if command -v sha256sum >/dev/null 2>&1; then
    sha256_actual=$(sha256sum "$package_file" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
    sha256_actual=$(shasum -a 256 "$package_file" | awk '{print $1}')
elif command -v openssl >/dev/null 2>&1; then
    sha256_actual=$(openssl dgst -sha256 "$package_file" | sed 's/^.*= //')
elif command -v busybox >/dev/null 2>&1; then
    sha256_actual=$(busybox sha256sum "$package_file" | awk '{print $1}')
else
    fail "sha256sum, shasum, openssl, or BusyBox sha256sum is required for SHA-256 verification."
fi
sha256_actual=$(printf '%s' "$sha256_actual" | tr 'A-F' 'a-f')
sha256_expected=$(printf '%s' "$sha256_expected" | tr 'A-F' 'a-f')
[ "$sha256_actual" = "$sha256_expected" ] || fail "the downloaded package has an invalid SHA-256."

if command -v tar >/dev/null 2>&1; then
    tar -xzf "$package_file" -C "$extract_dir" || fail "the DeskNoria package could not be extracted."
elif command -v busybox >/dev/null 2>&1; then
    busybox tar -xzf "$package_file" -C "$extract_dir" || fail "the DeskNoria package could not be extracted."
else
    fail "tar or BusyBox tar is required to install the DeskNoria package."
fi
[ -f "$extract_dir/install.sh" ] || fail "the package does not contain install.sh."
[ -f "$extract_dir/manifest.json" ] || fail "the package does not contain manifest.json."
chmod 0755 "$extract_dir/install.sh"

say "Installing DeskNoria Linux Agent $latest_version..."
if [ "$(id -u)" = 0 ]; then
    "$extract_dir/install.sh"
elif command -v sudo >/dev/null 2>&1; then
    sudo "$extract_dir/install.sh"
else
    fail "installation requires root; run the command as root or install sudo."
fi

say "DeskNoria Linux Agent $latest_version has been installed/updated."
