#!/bin/sh
# Install only this collector. Never installs packages or changes existing services.
set -eu

usage() {
    cat <<'EOF'
Usage: sudo sh install-agent.sh --agent-file ./do.py [--config /path/to/agent.json]

Requires Python >= 3.6 and a systemd Linux host. Download do.py from
the monitoring dashboard. Its embedded configuration is used unless --config is
provided. The default script is do.py next to this installer, then agent.py.
For a private CA only, ca_file may be provided. Existing installations are never
overwritten. No existing web, database, firewall, proxy or supervisor service is changed.
EOF
}

CONFIG_FILE=
AGENT_FILE=
while [ "$#" -gt 0 ]; do
    case "$1" in
        --config) [ "$#" -ge 2 ] || { usage; exit 2; }; CONFIG_FILE=$2; shift 2;;
        --agent-file) [ "$#" -ge 2 ] || { usage; exit 2; }; AGENT_FILE=$2; shift 2;;
        --help|-h) usage; exit 0;;
        --*) usage; exit 2;;
        *) [ -z "$CONFIG_FILE" ] || { usage; exit 2; }; CONFIG_FILE=$1; shift;;
    esac
done
[ "$(id -u)" -eq 0 ] || { echo 'Run this installer as root.' >&2; exit 1; }
if [ -n "$CONFIG_FILE" ] && [ ! -r "$CONFIG_FILE" ]; then
    echo 'Configuration file cannot be read.' >&2
    exit 1
fi
command -v systemctl >/dev/null 2>&1 || { echo 'systemd is required.' >&2; exit 1; }
[ -d /run/systemd/system ] || { echo 'systemd must be running.' >&2; exit 1; }
PYTHON_BIN=$(command -v python3) || { echo 'Python 3 is required; no packages were installed.' >&2; exit 1; }
case "$PYTHON_BIN" in *[!A-Za-z0-9_./-]*) echo 'Unsupported Python executable path.' >&2; exit 1;; esac
"$PYTHON_BIN" -c 'import sys; assert sys.version_info >= (3, 6), "Python >= 3.6 is required"'

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
if [ -z "$AGENT_FILE" ]; then
    if [ -r "$SCRIPT_DIR/do.py" ]; then AGENT_FILE=$SCRIPT_DIR/do.py; else AGENT_FILE=$SCRIPT_DIR/agent.py; fi
fi
[ -r "$AGENT_FILE" ] || { echo 'do.py or agent.py must be next to the installer, or pass --agent-file.' >&2; exit 1; }
for target in /opt/hostwatch-agent /etc/hostwatch /var/lib/hostwatch-agent /etc/systemd/system/hostwatch-agent.service; do
    if [ -e "$target" ]; then
        echo "Refusing to overwrite existing path: $target" >&2
        exit 1
    fi
done
if systemctl cat hostwatch-agent.service >/dev/null 2>&1; then
    echo 'A hostwatch-agent service already exists; refusing to replace it.' >&2
    exit 1
fi
if getent passwd hostwatch-agent >/dev/null 2>&1 || getent group hostwatch-agent >/dev/null 2>&1; then
    echo 'The hostwatch-agent account/group already exists; refusing to reuse another service account.' >&2
    exit 1
fi
command -v useradd >/dev/null 2>&1 || { echo 'useradd is required; no packages were installed.' >&2; exit 1; }

TEMP_DIR=$(mktemp -d /tmp/hostwatch-agent-install.XXXXXX)
trap 'rm -f "$TEMP_DIR/agent.json" "$TEMP_DIR/ca.crt" "$TEMP_DIR/agent.py"; rmdir "$TEMP_DIR" 2>/dev/null || true' EXIT HUP INT TERM

# Prepare and validate before making persistent changes. public_ip_file remains
# dynamic; do not freeze an IP read at installation into a permanent public_ip.
"$PYTHON_BIN" - "$CONFIG_FILE" "$TEMP_DIR" "$AGENT_FILE" <<'PY'
import copy, importlib.util, json, os, re, shutil, sys
source, stage, agent = sys.argv[1:]
sys.dont_write_bytecode = True
spec = importlib.util.spec_from_file_location('hostwatch_agent', agent)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if source:
    with open(source, 'r', encoding='utf-8') as f:
        config = json.load(f)
    config_base = os.path.dirname(os.path.abspath(source))
elif isinstance(getattr(module, 'EMBEDDED_CONFIG', None), dict):
    config = copy.deepcopy(module.EMBEDDED_CONFIG)
    config_base = os.path.dirname(os.path.abspath(agent))
else:
    raise SystemExit('No --config or embedded configuration found. Download do.py from the authenticated dashboard, or supply --config.')
if not isinstance(config, dict):
    raise SystemExit('Configuration must be a JSON object.')
ca = config.get('ca_file')
if ca:
    if not os.path.isabs(ca):
        ca = os.path.join(config_base, ca)
    shutil.copyfile(ca, os.path.join(stage, 'ca.crt'))
    config['ca_file'] = os.path.join(stage, 'ca.crt')
path = os.path.join(stage, 'agent.json')
with open(path, 'w', encoding='utf-8') as f:
    json.dump(config, f, ensure_ascii=False, indent=2)
os.chmod(path, 0o600)
# Validate without resolving a root-user fallback identity. The service resolves
# its own stable machine identity when started as hostwatch-agent.
module.load_config(path, resolve_identity=False)
if ca:
    config['ca_file'] = '/etc/hostwatch/ca.crt'
with open(path, 'w', encoding='utf-8') as f:
    json.dump(config, f, ensure_ascii=False, indent=2)
    f.write('\n')
# Install a credential-free program: the protected JSON is the only installed
# credential source. This avoids exposing embedded secrets through a 0755 file.
with open(agent, 'r', encoding='utf-8') as f:
    program = f.read()
program, replacements = re.subn(r'^EMBEDDED_CONFIG = .*$', 'EMBEDDED_CONFIG = None', program, flags=re.MULTILINE)
if replacements != 1:
    raise SystemExit('Cannot safely remove embedded credentials from the installed program.')
compile(program, 'agent.py', 'exec')
with open(os.path.join(stage, 'agent.py'), 'w', encoding='utf-8', newline='\n') as f:
    f.write(program)
PY

NOLOGIN=$(command -v nologin || true)
[ -n "$NOLOGIN" ] || NOLOGIN=/bin/false
useradd --system --user-group --home-dir /var/lib/hostwatch-agent --no-create-home --shell "$NOLOGIN" hostwatch-agent
install -d -m 0755 -o root -g root /opt/hostwatch-agent
install -d -m 0750 -o root -g hostwatch-agent /etc/hostwatch
install -d -m 0750 -o hostwatch-agent -g hostwatch-agent /var/lib/hostwatch-agent
install -m 0755 -o root -g root "$TEMP_DIR/agent.py" /opt/hostwatch-agent/agent.py
install -m 0640 -o root -g hostwatch-agent "$TEMP_DIR/agent.json" /etc/hostwatch/agent.json
if [ -f "$TEMP_DIR/ca.crt" ]; then
    install -m 0644 -o root -g root "$TEMP_DIR/ca.crt" /etc/hostwatch/ca.crt
fi

cat > /etc/systemd/system/hostwatch-agent.service <<EOF
[Unit]
Description=HostWatch read-only metrics collector
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=hostwatch-agent
Group=hostwatch-agent
WorkingDirectory=/var/lib/hostwatch-agent
ExecStart=$PYTHON_BIN /opt/hostwatch-agent/agent.py --config /etc/hostwatch/agent.json
Restart=on-failure
RestartSec=10
TimeoutStopSec=10
UMask=0077
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ReadWritePaths=/var/lib/hostwatch-agent
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
CapabilityBoundingSet=
LockPersonality=true
CPUAccounting=true
MemoryAccounting=true
CPUQuota=10%
MemoryMax=128M
TasksMax=32

[Install]
WantedBy=multi-user.target
EOF
chmod 0644 /etc/systemd/system/hostwatch-agent.service
systemctl daemon-reload
systemctl enable --now hostwatch-agent.service
echo 'HostWatch agent installed. Only hostwatch-agent.service was enabled and started.'
echo 'Status: systemctl status hostwatch-agent.service'
echo 'Logs:   journalctl -u hostwatch-agent.service -n 50 --no-pager'
echo 'Preview (contains host metrics, never the secret):'
echo '  sudo -u hostwatch-agent python3 /opt/hostwatch-agent/agent.py --print'
