Files
EvoFirewall/apps/api/src/agent-scripts/install.sh
T
Denozordec 38f7a8296e
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m45s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped
feat(api): add uninstall script and enhance install script functionality
- 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.
2026-07-21 22:53:28 +07:00

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)"