#!/usr/bin/env python3
"""Auditable repository bootstrap; rendered public pins, standard Ubuntu tools.

Obtain this script/hash and the primary fingerprint through a trusted release
announcement before running it. It does not install/connect the VPN itself.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import ssl
import stat
import subprocess
import tempfile
import time
import urllib.request

POLICY = {'schema': 1, 'origin': 'https://apt.aaris.ru', 'primary_fingerprint': '7344431C0AF35A7456DD9F054227018BA0355E5B', 'signing_fingerprint': 'E7498A27C616F2A63493CEBE816D36E9FE420F01', 'public_keyring_sha256': 'c52dd869d82c424946cc43dabc3fe749e41b3a75715925cedcda855697266b0c', 'public_keyring_path': '/keys/aaris-vpn-archive-keyring-2026.11.13.2.gpg', 'keyring_package_version': '2026.11.13.2', 'keyring_package_sha256': '15bb7656dfa388c09fc8b8ad20ef3c7da573f34372acc3c82654473a8fe97b08', 'keyring_package_path': '/pool/main/a/aaris-vpn-archive-keyring/aaris-vpn-archive-keyring_2026.11.13.2_all.deb'}
SOURCE = Path('/etc/apt/sources.list.d/aaris-vpn-testing.sources')
KEYRING = Path('/usr/share/keyrings/aaris-vpn-archive-keyring.gpg')


class BootstrapError(Exception):
    pass


def fail(category):
    raise BootstrapError(category)


def sha(data):
    return hashlib.sha256(data).hexdigest()


def source_bytes():
    return ('Types: deb\nURIs: ' + POLICY['origin'] + '\nSuites: noble-testing\n'
            'Components: main\nArchitectures: amd64\n'
            'Signed-By: /usr/share/keyrings/aaris-vpn-archive-keyring.gpg\n').encode()


def check_source():
    if SOURCE.is_symlink():
        fail('bootstrap_source_conflict')
    if SOURCE.exists():
        info = SOURCE.stat()
        if not stat.S_ISREG(info.st_mode) or info.st_uid != 0 or info.st_nlink != 1 or info.st_mode & 0o022:
            fail('bootstrap_source_conflict')
        if SOURCE.read_bytes() != source_bytes():
            fail('bootstrap_source_conflict')


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        fail('bootstrap_redirect_denied')


def fetch(path, limit):
    if not path.startswith('/') or '..' in Path(path).parts:
        fail('bootstrap_invalid_release_policy')
    opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect(),
                                        urllib.request.HTTPSHandler(context=ssl.create_default_context(
                                            cafile='/etc/ssl/certs/ca-certificates.crt')))
    deadline = time.monotonic() + 45
    data = bytearray()
    with opener.open(POLICY['origin'] + path, timeout=15) as response:
        if response.status != 200:
            fail('bootstrap_download_failed')
        while True:
            part = response.read(16384)
            if not part:
                break
            data.extend(part)
            if len(data) > limit or time.monotonic() > deadline:
                fail('bootstrap_download_bounds')
    return bytes(data)


def command(argv, required=True):
    p = subprocess.run(argv, capture_output=True, timeout=90)
    if required and p.returncode:
        fail('bootstrap_system_command_failed')
    return p


def verify_signature(home, keyring, package, signature):
    p = command(['gpgv', '--homedir', str(home), '--keyring', str(keyring), '--status-fd', '1',
                 str(signature), str(package)], required=False)
    statuses = [line.split() for line in p.stdout.decode(errors='replace').splitlines()
                if line.startswith('[GNUPG:] ')]
    denied = {'BADSIG', 'ERRSIG', 'NO_PUBKEY', 'EXPSIG', 'EXPKEYSIG', 'REVKEYSIG', 'KEYEXPIRED', 'SIGEXPIRED'}
    valid = [line for line in statuses if len(line) > 3 and line[1] == 'VALIDSIG']
    if (p.returncode or any(line[1] in denied for line in statuses)
            or len(valid) != 1 or valid[0][2] != POLICY['signing_fingerprint']
            or valid[0][-1] != POLICY['primary_fingerprint']):
        fail('bootstrap_signature_denied')


def main():
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument('--expected-primary-fingerprint', required=True)
    p.add_argument('--check-only', action='store_true')
    args = p.parse_args()
    if POLICY is None:
        fail('bootstrap_policy_not_rendered')
    expected = args.expected_primary_fingerprint.replace(' ', '').upper()
    if not re.fullmatch('[0-9A-F]{40}', expected) or expected != POLICY['primary_fingerprint']:
        fail('bootstrap_fingerprint_mismatch')
    os_info = dict(line.split('=', 1) for line in Path('/etc/os-release').read_text().splitlines() if '=' in line)
    if os_info.get('ID', '').strip('"') != 'ubuntu' or os_info.get('VERSION_ID', '').strip('"') != '24.04':
        fail('bootstrap_unsupported_platform')
    if command(['dpkg', '--print-architecture']).stdout.strip() != b'amd64':
        fail('bootstrap_unsupported_platform')
    if not args.check_only and os.geteuid() != 0:
        fail('bootstrap_administrator_required')
    check_source()
    installed = command(['dpkg-query', '-W', '-f=${db:Status-Status} ${Version}', 'aaris-vpn-archive-keyring'], False)
    present = installed.returncode == 0 and installed.stdout.startswith(b'installed ')
    if present:
        current = installed.stdout.decode().split()[1]
        if command(['dpkg', '--compare-versions', current, 'gt', POLICY['keyring_package_version']], False).returncode == 0:
            fail('bootstrap_refuses_keyring_downgrade')
        if current == POLICY['keyring_package_version'] and (KEYRING.is_symlink() or not KEYRING.is_file() or sha(KEYRING.read_bytes()) != POLICY['public_keyring_sha256']):
            fail('bootstrap_installed_keyring_mismatch')
    with tempfile.TemporaryDirectory(prefix='aaris-vpn-bootstrap-') as tmp:
        folder = Path(tmp)
        public = fetch(POLICY['public_keyring_path'], 128 * 1024)
        if sha(public) != POLICY['public_keyring_sha256']:
            fail('bootstrap_public_key_digest_mismatch')
        keyring = folder / 'archive.gpg';keyring.write_bytes(public)
        body = fetch(POLICY['keyring_package_path'], 2 * 1024 * 1024)
        if sha(body) != POLICY['keyring_package_sha256']:
            fail('bootstrap_package_digest_mismatch')
        package = folder / 'archive-keyring.deb';package.write_bytes(body)
        signature = folder / 'archive-keyring.deb.asc'
        signature.write_bytes(fetch(POLICY['keyring_package_path'] + '.asc', 32 * 1024))
        verify_home = folder / 'verify';verify_home.mkdir(mode=0o700)
        verify_signature(verify_home, keyring, package, signature)
        fields = command(['dpkg-deb', '-f', str(package), 'Package', 'Version', 'Architecture']).stdout.decode()
        values = dict(line.split(': ', 1) for line in fields.splitlines())
        if values != {'Package': 'aaris-vpn-archive-keyring', 'Version': POLICY['keyring_package_version'], 'Architecture': 'all'}:
            fail('bootstrap_package_identity_mismatch')
        if args.check_only:
            return {'bootstrap_download_and_signature_verified': True, 'system_changed': False}
        check_source()
        if not present or current != POLICY['keyring_package_version']:
            command(['dpkg', '--install', str(package)])
        if not KEYRING.is_file() or KEYRING.is_symlink() or sha(KEYRING.read_bytes()) != POLICY['public_keyring_sha256']:
            fail('bootstrap_installed_keyring_mismatch')
        check_source()
        if not SOURCE.exists():
            parent = SOURCE.parent
            info = parent.stat()
            if info.st_uid != 0 or info.st_mode & 0o022:
                fail('bootstrap_source_parent_permissions')
            fd, temporary = tempfile.mkstemp(prefix='.aaris-source-', dir=parent)
            try:
                os.fchmod(fd, 0o644)
                with os.fdopen(fd, 'wb') as stream:
                    stream.write(source_bytes());stream.flush();os.fsync(stream.fileno())
                # Do not overwrite a concurrent administrator's source file.
                try: os.link(temporary, SOURCE)
                except FileExistsError: check_source()
                directory = os.open(parent, os.O_RDONLY | os.O_DIRECTORY)
                try: os.fsync(directory)
                finally: os.close(directory)
            finally:
                Path(temporary).unlink(missing_ok=True)
    return {'bootstrap_configured': True, 'next_action': 'apt_update_then_install_aaris_vpn', 'vpn_started': False}


if __name__ == '__main__':
    try:
        print(json.dumps(main()))
    except BootstrapError as error:
        print(json.dumps({'category': str(error)}))
        raise SystemExit(2)
    except (OSError, ValueError, KeyError, subprocess.SubprocessError):
        print(json.dumps({'category': 'bootstrap_incomplete'}))
        raise SystemExit(2)
