#!/usr/bin/env bash # EvoFirewall Linux install one-liner set -euo pipefail if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then echo "evofw install: run as root" >&2 exit 1 fi need_cmd() { command -v "$1" >/dev/null 2>&1; } pkg_install() { # $@ = package names (distro-specific callers pass the right names) if need_cmd apt-get; then export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq "$@" elif need_cmd dnf; then dnf install -y -q "$@" elif need_cmd yum; then yum install -y -q "$@" elif need_cmd apk; then apk add --no-cache "$@" else echo "evofw install: no supported package manager (apt/dnf/yum/apk)" >&2 return 1 fi } ensure_pkg() { # $1 = command to check; remaining = package name(s) to install if missing local bin=$1 shift if need_cmd "$bin"; then return 0 fi echo "evofw install: installing $* (need $bin)..." pkg_install "$@" || { echo "evofw install: failed to install $* — install manually and retry" >&2 return 1 } need_cmd "$bin" } # curl is required to fetch this script in the first place, but re-check for pipe/bash edge cases. ensure_pkg curl curl || exit 1 need_cmd bash || { echo "missing bash" >&2; exit 1; } # JSON parse for enroll; python3 is an acceptable fallback in sync script. if ! need_cmd jq && ! need_cmd python3; then ensure_pkg jq jq || ensure_pkg python3 python3 || { echo "evofw install: need jq or python3" >&2 exit 1 } fi # Firewall tools: never reinstall/replace if already present. if need_cmd nft; then echo "evofw install: nft already present — skip firewall packages" elif need_cmd iptables; then echo "evofw install: iptables already present — skip nftables/iptables install" if ! need_cmd ipset; then echo "evofw install: installing ipset (optional companion for iptables)..." pkg_install ipset 2>/dev/null || true fi else echo "evofw install: no nft/iptables — installing firewall backend..." if need_cmd apt-get; then pkg_install nftables || pkg_install iptables || true elif need_cmd dnf || need_cmd yum; then pkg_install nftables || pkg_install iptables || true elif need_cmd apk; then pkg_install nftables || pkg_install iptables || true fi if need_cmd iptables && ! need_cmd nft && ! need_cmd ipset; then pkg_install ipset 2>/dev/null || true fi fi HAS_SYSTEMD=0 if need_cmd systemctl && [[ -d /run/systemd/system ]]; then HAS_SYSTEMD=1 fi # Cron only when systemd timer is unavailable. if [[ "$HAS_SYSTEMD" -ne 1 ]]; then if ! need_cmd crontab; then echo "evofw install: no systemd — installing cron..." if need_cmd apt-get; then pkg_install cron || pkg_install cronie || true elif need_cmd dnf || need_cmd yum; then pkg_install cronie || true elif need_cmd apk; then pkg_install cronie || pkg_install dcron || true fi fi if ! need_cmd crontab; then echo "evofw install: crontab missing and could not be installed (need systemd or cron)" >&2 exit 1 fi # Ensure cron daemon is running when we fall back to crontab. if need_cmd systemctl; then systemctl enable --now cron 2>/dev/null || \ systemctl enable --now crond 2>/dev/null || true elif need_cmd service; then service cron start 2>/dev/null || service crond start 2>/dev/null || true fi fi : "${EVOFW_CP_URL:?EVOFW_CP_URL required}" : "${EVOFW_SEED:?EVOFW_SEED required}" : "${EVOFW_CLIENT_NAME:?EVOFW_CLIENT_NAME required}" CONF_DIR=/etc/evofw CONF_FILE="${CONF_DIR}/agent.conf" SYNC_SCRIPT=/usr/local/sbin/evofw-firewall.sh PLATFORM="${EVOFW_PLATFORM:-linux}" if [[ -f "$CONF_FILE" && "${EVOFW_INSTALL_FORCE:-}" != "1" ]]; then echo "Already installed ($CONF_FILE). Set EVOFW_INSTALL_FORCE=1 to reinstall." >&2 exit 1 fi gen_token() { if command -v openssl >/dev/null 2>&1; then echo -n "evofw_$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" else echo -n "evofw_$(head -c 32 /dev/urandom | base64 | tr '+/' '-_' | tr -d '=\n')" fi } CLIENT_TOKEN="$(gen_token)" HOSTNAME="$(hostname -f 2>/dev/null || hostname)" CP_URL="${EVOFW_CP_URL%/}" # Fail fast: pull sync script before enroll so we never leave a DB agent without a local agent. SYNC_TMP=$(mktemp) ENROLL_TMP=$(mktemp) trap 'rm -f "$SYNC_TMP" "$ENROLL_TMP"' EXIT if ! curl -fsSL "${CP_URL}/v1/agent/sync-script" -o "$SYNC_TMP"; then echo "failed to download sync script from ${CP_URL}/v1/agent/sync-script" >&2 exit 1 fi if ! head -n1 "$SYNC_TMP" | grep -q '^#!'; then echo "sync script is not a shell script (CP returned unexpected body)" >&2 exit 1 fi if [[ -n "${EVOFW_INSTALL_LINK_ID:-}" ]]; then ENROLL_BODY=$(printf '{"name":"%s","hostname":"%s","platform":"%s","token":"%s","client_version":"install.sh/1","install_link_id":"%s"}' \ "$EVOFW_CLIENT_NAME" "$HOSTNAME" "$PLATFORM" "$CLIENT_TOKEN" "$EVOFW_INSTALL_LINK_ID") else ENROLL_BODY=$(printf '{"name":"%s","hostname":"%s","platform":"%s","token":"%s","client_version":"install.sh/1"}' \ "$EVOFW_CLIENT_NAME" "$HOSTNAME" "$PLATFORM" "$CLIENT_TOKEN") fi ENROLL_CODE=$(curl -sS -o "$ENROLL_TMP" -w "%{http_code}" -X POST "${CP_URL}/v1/agent/enroll" \ -H "Content-Type: application/json" \ -H "X-EvoFW-Seed: ${EVOFW_SEED}" \ -d "$ENROLL_BODY") if [[ "$ENROLL_CODE" != "201" && "$ENROLL_CODE" != "200" ]]; then echo "enroll failed: HTTP ${ENROLL_CODE}" >&2 cat "$ENROLL_TMP" >&2 exit 1 fi RESP=$(cat "$ENROLL_TMP") CLIENT_ID="" if command -v jq >/dev/null 2>&1; then CLIENT_ID=$(echo "$RESP" | jq -r '.client_id // .id') else CLIENT_ID=$(echo "$RESP" | sed -n 's/.*"client_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') fi if [[ -z "$CLIENT_ID" || "$CLIENT_ID" == "null" ]]; then echo "enroll response missing client_id" >&2 cat "$ENROLL_TMP" >&2 exit 1 fi mkdir -p "$CONF_DIR" chmod 700 "$CONF_DIR" # Quote all values — names with spaces must not break `source agent.conf`. { printf 'EVOFW_CP_URL=%q\n' "$CP_URL" printf 'CLIENT_ID=%q\n' "$CLIENT_ID" printf 'CLIENT_TOKEN=%q\n' "$CLIENT_TOKEN" printf 'CLIENT_NAME=%q\n' "$EVOFW_CLIENT_NAME" printf 'KERNEL_BACKEND=%q\n' "auto" } >"$CONF_FILE" chmod 600 "$CONF_FILE" install -m 755 "$SYNC_TMP" "$SYNC_SCRIPT" UNINSTALL_SCRIPT=/usr/local/sbin/evofw-uninstall.sh if curl -fsSL "${CP_URL}/v1/agent/uninstall.sh" -o "$UNINSTALL_SCRIPT" 2>/dev/null; then chmod 755 "$UNINSTALL_SCRIPT" else echo "evofw install: warning — could not download uninstall.sh (optional)" >&2 fi if command -v nft >/dev/null 2>&1; then BACKEND=nft elif command -v ipset >/dev/null 2>&1 && command -v iptables >/dev/null 2>&1; then BACKEND=ipset elif command -v iptables >/dev/null 2>&1; then BACKEND=iptables else echo "no supported firewall backend" >&2 exit 1 fi sed -i "s/^KERNEL_BACKEND=.*/KERNEL_BACKEND=$(printf '%q' "$BACKEND")/" "$CONF_FILE" 2>/dev/null || \ printf 'KERNEL_BACKEND=%q\n' "$BACKEND" >>"$CONF_FILE" INTERVAL="${EVOFW_SYNC_INTERVAL:-1min}" if [[ "$HAS_SYSTEMD" -eq 1 ]]; then cat >/etc/systemd/system/evofw-firewall.service <<'UNIT' [Unit] Description=EvoFirewall sync After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/evofw-firewall.sh UNIT cat >/etc/systemd/system/evofw-firewall.timer </dev/null | grep -v evofw-firewall || true; echo "*/1 * * * * $SYNC_SCRIPT") | crontab - "$SYNC_SCRIPT" || true fi echo "Installed. Client id=${CLIENT_ID}. Approve in EvoFirewall UI (rules optional — can assign later)." echo "If still offline after Approve, run: $SYNC_SCRIPT" echo "Uninstall: $UNINSTALL_SCRIPT (or: curl -fsSL ${CP_URL}/v1/agent/uninstall.sh | bash)"