#!/bin/bash
set -Eeuo pipefail
installer_version="0.2.31"
product_version="0.2.2"
build="0006"
usage() {
  echo "Usage: sudo bash install.sh HTTPS_ARCHIVE_URL [--port 8080] [--user USER] [--python /absolute/python3.12]"
  echo "Offline archive: sudo bash install.sh --archive /path/package.tar.gz [same options]"
  echo "Requires macOS 14+, native Apple Silicon or Intel, and Python 3.12 with venv."
}
if [[ $# -eq 0 || "${1:-}" == --help ]]; then usage; exit 0; fi
[[ "$(uname -s)" == Darwin ]] || { echo "This installer requires macOS."; exit 1; }
case "$(uname -m)" in arm64|x86_64) ;; *) echo "Unsupported Mac architecture."; exit 1 ;; esac
[[ "$(sw_vers -productVersion | cut -d. -f1)" -ge 14 ]] || { echo "macOS 14 or newer is required."; exit 1; }
[[ "$(id -u)" -eq 0 ]] || { echo "Run this installer with sudo."; exit 1; }
archive_path=""; release_url=""; service_user="${SUDO_USER:-}"; port=8080; python_bin=""
if [[ "$1" == --archive ]]; then archive_path="${2:?Supply an archive path}"; shift 2; else release_url="$1"; shift; fi
while [[ $# -gt 0 ]]; do
  case "$1" in
    --port) port="${2:?Supply a port}"; shift 2 ;;
    --user) service_user="${2:?Supply a non-root local user}"; shift 2 ;;
    --python) python_bin="${2:?Supply a Python 3.12 path}"; shift 2 ;;
    *) usage; exit 1 ;;
  esac
done
if [[ -z "$python_bin" ]]; then
  for candidate in /opt/homebrew/bin/python3.12 /usr/local/bin/python3.12 /Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12; do
    if [[ -x "$candidate" ]]; then python_bin="$candidate"; break; fi
  done
fi
[[ "$python_bin" == /* && -x "$python_bin" ]] || { echo "Install Python 3.12 first (as your normal user: brew install python@3.12), or supply --python."; exit 1; }
"$python_bin" -I -c 'import sys,platform; assert sys.version_info[:2]==(3,12), "Python 3.12 required"; assert not (platform.machine()=="x86_64" and __import__("subprocess").run(["/usr/sbin/sysctl","-n","sysctl.proc_translated"],capture_output=True,text=True).stdout.strip()=="1"), "Use native Python, not Rosetta"'
if [[ -n "$release_url" && "$release_url" != "https://portguard.kamindo.co/downloads/server/macos/$product_version/build$build/portguard-console-$product_version-build$build.tar.gz" ]]; then
  echo "Supply the matching macOS archive URL from the PortGuard portal."; exit 1
fi
work_dir="$(mktemp -d /private/tmp/portguard-macos.XXXXXX)"
trap 'rm -rf "$work_dir"' EXIT
if [[ -n "$archive_path" ]]; then
  cp "$archive_path" "$work_dir/package.tar.gz"
  cp "$archive_path.sha256" "$work_dir/package.sha256"
else
  curl --fail --silent --show-error --proto '=https' --max-time 600 --max-filesize 209715200 "$release_url" -o "$work_dir/package.tar.gz"
  curl --fail --silent --show-error --proto '=https' --max-time 30 --max-filesize 4096 "$release_url.sha256" -o "$work_dir/package.sha256"
fi
"$python_bin" -I - "$work_dir" "$installer_version" <<'PY'
import hashlib,re,sys,tarfile
from pathlib import Path,PurePosixPath
work,version=Path(sys.argv[1]),sys.argv[2]
expected=(work/'package.sha256').read_text().split()[0]
if not re.fullmatch('[0-9a-f]{64}',expected) or hashlib.sha256((work/'package.tar.gz').read_bytes()).hexdigest()!=expected:
    raise SystemExit('Package checksum verification failed')
seen=set();total=0
with tarfile.open(work/'package.tar.gz','r:gz') as tar:
    for member in tar:
        path=PurePosixPath(member.name)
        if path.is_absolute() or '..' in path.parts or not path.parts or path.parts[0]!='portguard-server-'+version or str(path) in seen:
            raise SystemExit('Unsafe package path')
        seen.add(str(path))
        if len(seen)>10000: raise SystemExit('Too many package entries')
        target=work.joinpath(*path.parts)
        if member.isdir(): target.mkdir(parents=True,exist_ok=True); continue
        if not member.isfile(): raise SystemExit('Package links and special files are forbidden')
        total+=member.size
        if total>512*1024*1024: raise SystemExit('Package too large')
        target.parent.mkdir(parents=True,exist_ok=True)
        with tar.extractfile(member) as source,target.open('xb') as output:
            while chunk:=source.read(65536): output.write(chunk)
        target.chmod(0o644)
if (work/('portguard-server-'+version)/'VERSION').read_text().strip()!=version:
    raise SystemExit('Package version mismatch')
PY
"$python_bin" -I "$work_dir/portguard-server-$installer_version/macos/manage.py" install --package "$work_dir/portguard-server-$installer_version" --user "$service_user" --port "$port"
