#!/bin/sh
# Install the latest stable Linux standalone release as the current user.
set -eu
fail() { printf '%s\n' "mvbar: $*" >&2; exit 1; }
[ "$(id -u)" -ne 0 ] || fail 'Run as your regular user, without sudo. Setup requests sudo when needed.'
[ "$(uname -s)" = Linux ] || fail 'This installer requires Linux.'
case $(uname -m) in x86_64|aarch64|arm64) ;; *) fail 'This installer requires Linux x64 or ARM64.' ;; esac
[ -f /etc/debian_version ] || fail 'Automatic setup supports Debian and Ubuntu distributions.'
[ -d /run/systemd/system ] || fail 'Enable systemd first, or download the executable and use its manual start command.'
[ -r /dev/tty ] && [ -w /dev/tty ] || fail 'Open an interactive terminal to run the setup wizard.'
(: </dev/tty) 2>/dev/null || fail 'Open an interactive terminal to run the setup wizard.'
command -v sudo >/dev/null 2>&1 || fail 'Install sudo first, or use the manual installation guide.'
if ! command -v python3 >/dev/null 2>&1; then
    printf '%s\n' 'Installing Python 3 to verify release metadata and checksums…'
    sudo apt-get update </dev/tty
    sudo apt-get install -y python3 ca-certificates </dev/tty
fi
python3 - <<'PY'
import platform
import hashlib
import json
import re
import subprocess
import sys
import tempfile
import urllib.request
from pathlib import Path
from urllib.parse import urlparse

ORIGIN = 'https://mvbar.co.uk'

def trusted_url(value, tag):
    parsed = urlparse(value)
    if (parsed.scheme != 'https' or parsed.netloc != 'mvbar.co.uk'
            or parsed.query or parsed.fragment
            or not parsed.path.startswith('/downloads/mvbar/' + tag + '/')
            or '%' in parsed.path or '..' in parsed.path.split('/')):
        raise ValueError('Invalid release download address')
    return value

def fetch(url):
    response = urllib.request.urlopen(url, timeout=60)
    if response.geturl() != url:
        response.close()
        raise ValueError('Unexpected download redirect')
    return response

def install():
    with fetch(ORIGIN + '/updates/mvbar/latest.json') as response:
        release = json.loads(response.read(1024 * 1024))
    tag = release.get('tag_name', '')
    if not re.fullmatch(r'v\d+\.\d+\.\d+', tag) or release.get('prerelease'):
        raise ValueError('No valid stable release is available')
    assets = release.get('assets', [])
    arch = {'x86_64': 'x64', 'aarch64': 'arm64', 'arm64': 'arm64'}.get(platform.machine())
    if not arch:
        raise ValueError('Unsupported Linux architecture')
    matches = [a for a in assets if re.fullmatch(r'[A-Za-z0-9._-]+-linux-' + arch, a.get('name', ''))]
    if len(matches) != 1:
        raise ValueError('Linux ' + arch + ' release is unavailable')
    asset = matches[0]
    url = trusted_url(asset['browser_download_url'], tag)
    digest = asset.get('sha256', '')
    if not re.fullmatch(r'[a-fA-F0-9]{64}', digest):
        checksum = next(a for a in assets if a.get('name') == asset['name'] + '.sha256')
        with fetch(trusted_url(checksum['browser_download_url'], tag)) as response:
            text = response.read(4096).decode('ascii').strip()
        match = re.fullmatch(r'([a-fA-F0-9]{64})\s+\*?' + re.escape(asset['name']), text)
        if not match:
            raise ValueError('Invalid release checksum')
        digest = match[1]
    print('Downloading mvbar ' + tag + ' for Linux ' + arch + '…', flush=True)
    with tempfile.TemporaryDirectory(prefix='mvbar-install-') as directory:
        binary = Path(directory) / 'mvbar'
        actual = hashlib.sha256()
        with fetch(url) as response, binary.open('wb') as output:
            while chunk := response.read(1024 * 1024):
                actual.update(chunk)
                output.write(chunk)
        if actual.hexdigest() != digest.lower():
            raise ValueError('Checksum mismatch; downloaded executable was not run')
        binary.chmod(0o700)
        print('Checksum verified. Opening setup and service installation…', flush=True)
        # Piped shell input must not consume the interactive wizard's answers.
        with open('/dev/tty', 'rb') as terminal:
            return subprocess.call([str(binary), 'install-service'], stdin=terminal)

try:
    sys.exit(install())
except (OSError, ValueError, KeyError, StopIteration, TypeError) as error:
    print('mvbar installation failed: ' + str(error), file=sys.stderr)
    sys.exit(1)
PY
