#!/usr/bin/env bash
# Debian/Ubuntu + systemd. Source: https://github.com/VladTop46/monitoring-service
set -Eeuo pipefail
base_url=""
archive_path=""
port_override=""
check_only=0
work_dir=""
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
usage() {
    cat <<'HELP'
Usage:
  sudo bash install.sh --base-url https://monitor.example.com
  sudo bash install.sh --archive ./monitor_service_linux.tar.gz
  sudo bash agent/install.sh                         # from source repository
Options:
  --port PORT       Port for a new installation or explicit port change (default 46)
  --check           Validate package only; no installation or system changes
  --help            Show this help
Existing configuration and SQLite data are preserved. No firewall rules are changed.
HELP
}
fail() { echo "Error: $*" >&2; exit 1; }
while (($#)); do
    case "$1" in
        --base-url|--archive|--port)
            (($# >= 2)) || fail "Missing value for $1"
            case "$1" in
                --base-url) base_url="${2%/}" ;;
                --archive) archive_path="$2" ;;
                --port) port_override="$2" ;;
            esac
            shift 2 ;;
        --check) check_only=1; shift ;;
        --help|-h) usage; exit 0 ;;
        *) fail "Unknown argument: $1" ;;
    esac
done
if [[ -n "$port_override" ]]; then
    [[ "$port_override" =~ ^[0-9]{1,5}$ ]] || fail "Invalid port"
    ((10#$port_override >= 1 && 10#$port_override <= 65535)) || fail "Port must be 1–65535"
    port_override="$((10#$port_override))"
fi
[[ -z "$archive_path" || -z "$base_url" ]] || fail "Use either --archive or --base-url"
if [[ -n "$base_url" ]]; then
    [[ "$base_url" == https://* || "$base_url" == http://* ]] || fail "Base URL must use HTTP(S)"
fi
if (( ! check_only )); then
    [[ "$EUID" -eq 0 ]] || fail "Run with sudo"
    [[ -d /run/systemd/system ]] || fail "systemd must be running"
    command -v apt-get >/dev/null || fail "Automatic installation supports Debian/Ubuntu only"
    if ! /usr/bin/python3 -c 'import psutil' >/dev/null 2>&1 || ! command -v curl >/dev/null; then
        apt-get update
        apt-get install -y python3 python3-psutil curl ca-certificates
    fi
fi
command -v python3 >/dev/null || fail "Python 3 is required"
work_dir="$(mktemp -d)"
trap 'rm -rf -- "$work_dir"' EXIT
if [[ -n "$base_url" ]]; then
    curl --fail --show-error --silent --location --proto '=http,https' --proto-redir '=http,https' \
        --connect-timeout 10 --max-time 120 "$base_url/monitor_service_linux.tar.gz" -o "$work_dir/agent.tar.gz"
    archive_path="$work_dir/agent.tar.gz"
fi
if [[ -n "$archive_path" ]]; then
    # Extract only expected regular files; reject traversal, symlinks and extra content.
    python3 - "$archive_path" "$work_dir" <<'PY'
import pathlib, sys, tarfile
allowed = {'monitoring-service.py','monitoring-service.service','monitoring-service.env','install.sh','README.md','requirements.txt'}
with tarfile.open(sys.argv[1], 'r:gz') as archive:
    members = archive.getmembers()
    names = [m.name for m in members]
    if len(names) != len(set(names)) or set(names) != allowed:
        raise SystemExit('Unexpected or missing archive files')
    for member in members:
        if not member.isfile() or member.size > 2 * 1024 * 1024:
            raise SystemExit('Invalid archive entry')
        pathlib.Path(sys.argv[2], member.name).write_bytes(archive.extractfile(member).read())
PY
    package_dir="$work_dir"
else
    package_dir="$script_dir"
fi
for file in monitoring-service.py monitoring-service.service monitoring-service.env; do
    [[ -f "$package_dir/$file" ]] || fail "Missing $file. Use --base-url, --archive or run from agent/"
done
python3 - "$package_dir/monitoring-service.py" <<'PY'
import pathlib, sys
compile(pathlib.Path(sys.argv[1]).read_text(), sys.argv[1], 'exec')
PY
if ((check_only)); then
    echo 'Package valid; no system changes made.'
    exit 0
fi
# Preserve an existing environment file, and the old agent's port on first upgrade.
if [[ ! -f /etc/default/monitoring-service ]]; then
    detected_port=46
    if [[ -f /etc/systemd/system/monitoring-service.service ]]; then
        detected_port="$(python3 - <<'PY'
import pathlib, re
text = pathlib.Path('/etc/systemd/system/monitoring-service.service').read_text()
m = re.search(r'^ExecStart=.*monitoring-service\.py\s+(\d+)(?:\s|$)', text, re.M)
print(m.group(1) if m and 1 <= int(m.group(1)) <= 65535 else 46)
PY
)"
    fi
    install -m 0644 "$package_dir/monitoring-service.env" /etc/default/monitoring-service
    sed -i "s/^SERVICE_PORT=.*/SERVICE_PORT=$detected_port/" /etc/default/monitoring-service
fi
if [[ -n "$port_override" ]]; then
    if grep -q '^SERVICE_PORT=' /etc/default/monitoring-service; then
        sed -i "s/^SERVICE_PORT=.*/SERVICE_PORT=$port_override/" /etc/default/monitoring-service
    else
        echo "SERVICE_PORT=$port_override" >> /etc/default/monitoring-service
    fi
fi
# Read simple key=value configuration without executing it as shell code.
mapfile -t settings < <(python3 - <<'PY'
import pathlib
values = {}
for line in pathlib.Path('/etc/default/monitoring-service').read_text().splitlines():
    if line.strip() and not line.lstrip().startswith('#') and '=' in line:
        key, value = line.split('=', 1)
        values[key.strip()] = value.strip().strip('\"').strip("'")
for key, default in [('SERVICE_PORT','46'), ('DB_PATH','/var/lib/monitoring-service/metrics.db'), ('COLLECTION_INTERVAL','60')]:
    print(values.get(key, default))
PY
)
((${#settings[@]} == 3)) || fail 'Invalid /etc/default/monitoring-service'
port="${settings[0]}"
db_path="${settings[1]}"
interval="${settings[2]}"
[[ "$port" =~ ^[0-9]{1,5}$ && "$interval" =~ ^[0-9]{1,5}$ ]] || fail 'Invalid port/interval in environment file'
((10#$port >= 1 && 10#$port <= 65535 && 10#$interval >= 1 && 10#$interval <= 86400)) || fail 'Port/interval out of range'
[[ "$db_path" == /* && "$db_path" != *[[:space:]]* ]] || fail 'DB_PATH must be absolute with no spaces'
# Stop only after the package and configuration have been validated.
if systemctl is-active --quiet monitoring-service; then
    systemctl stop monitoring-service
fi
install -d -m 0755 /opt/monitoring-service "$(dirname -- "$db_path")"
if [[ ! -e "$db_path" && -f /opt/monitoring-service/metrics.db ]]; then
    python3 - "$db_path" <<'PY'
import sqlite3, sys
with sqlite3.connect('/opt/monitoring-service/metrics.db') as src, sqlite3.connect(sys.argv[1]) as dst:
    src.backup(dst)
PY
fi
if [[ -f /opt/monitoring-service/monitoring-service.py ]]; then
    cp -p /opt/monitoring-service/monitoring-service.py /opt/monitoring-service/monitoring-service.py.bak
fi
install -m 0755 "$package_dir/monitoring-service.py" /opt/monitoring-service/monitoring-service.py
install -m 0644 "$package_dir/monitoring-service.service" /etc/systemd/system/monitoring-service.service
systemctl daemon-reload
systemctl enable monitoring-service
systemctl restart monitoring-service
for attempt in 1 2 3 4 5; do
    if curl --noproxy '*' --fail --silent --max-time 3 "http://127.0.0.1:$port/health" >/dev/null; then
        echo "Agent installed. Port: $port. Database: $db_path"
        echo 'Status: systemctl status monitoring-service'
        echo 'Logs: journalctl -u monitoring-service -n 50 --no-pager'
        exit 0
    fi
    sleep 1
done
fail 'Agent did not respond. Check journalctl -u monitoring-service -n 50 --no-pager'
