- Introduced an uninstall script for agents, allowing users to easily remove the agent with a single command. - Updated `install.sh` to quote configuration values for safety, ensuring compatibility with names containing spaces. - Enhanced the installation process to include a warning if the uninstall script cannot be downloaded. - Updated documentation to reflect the new uninstall functionality and changes in configuration file handling.
251 lines
7.9 KiB
Bash
251 lines
7.9 KiB
Bash
#!/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 <<UNIT
|
|
[Unit]
|
|
Description=EvoFirewall sync timer
|
|
|
|
[Timer]
|
|
OnBootSec=30s
|
|
OnUnitActiveSec=${INTERVAL}
|
|
AccuracySec=5s
|
|
Persistent=true
|
|
Unit=evofw-firewall.service
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
UNIT
|
|
systemctl daemon-reload
|
|
systemctl enable --now evofw-firewall.timer
|
|
# First run now (pending → log "pending approval"; after Approve → empty policy is OK).
|
|
systemctl start evofw-firewall.service || true
|
|
else
|
|
(crontab -l 2>/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)"
|