Files
claude-egress-guard/install.sh
T
yinyues2 6af4f23d89 feat: one-stop safe deployment and operation skill
- add references/safe-operation.md: credentials, client settings, secrets,
  blast radius, monitoring, incident response, update discipline
- SKILL.md, READMEs, AGENTS.md, llms.txt now present four parts: deploy,
  egress, operate, respond — gate remains the enforcement core
- version 1.3.0, installer pin updated
2026-10-27 01:52:28 +08:00

211 lines
7.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# claude-egress-guard — one-shot installer.
#
# From a checkout: sudo ./install.sh --proxy URL --exit-ip ADDR
# From the network: curl -fsSL https://github.com/yinyues2/claude-egress-guard/raw/main/install.sh \
# | sudo bash -s -- --proxy URL --exit-ip ADDR
#
# The script downloads a pinned release tarball when it is not run from a
# checkout, verifies the expected layout, then hands over to the project CLI.
# It never edits system files itself.
set -u
REPO="yinyues2/claude-egress-guard"
PINNED_REF="v1.3.0"
SELF_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)
WORK=""
cleanup() { [ -n "$WORK" ] && rm -rf "$WORK"; }
trap cleanup EXIT
say() { printf '%s\n' "$*"; }
err() { printf 'error: %s\n' "$*" >&2; }
die() {
err "$*"
exit 1
}
usage() {
cat <<'EOF'
claude-egress-guard installer
USAGE
install.sh --proxy URL --exit-ip ADDR [options]
install.sh --skill-only [--dest DIR]
install.sh --uninstall [--prefix DIR]
OPTIONS
--proxy URL local HTTP/mixed proxy endpoint (required to install)
--exit-ip ADDR public IPv4 the proxy must exit from (required)
--country CC two-letter country to enforce (optional)
--allow LIST extra CIDRs inside the kernel jail, comma separated
--strict-net MODE on|off kernel jail (default on)
--prefix DIR install under DIR instead of / (testing)
--ref REF release tag or branch to download (default: v1.1.0)
--dry-run show the install plan only
--yes do not prompt
--skill-only copy the agent skill into a skills directory instead
--dest DIR target for --skill-only (default: ~/.hermes/skills)
--uninstall remove the gate and restore the previous files
-h, --help this text
NOTES
The installer needs root for a real install because the gate writes to
/usr/local and /etc. --prefix works unprivileged and is what the test suite
uses. Nothing on the host is modified before the CLI prints its plan.
EOF
}
PROXY=""
EXIT_IP=""
COUNTRY=""
ALLOW=""
STRICT=""
PREFIX=""
REF="$PINNED_REF"
DRY="0"
ASSUME_YES="0"
SKILL_ONLY="0"
DEST="${HOME}/.hermes/skills"
UNINSTALL="0"
while [ $# -gt 0 ]; do
case "$1" in
--proxy) PROXY="${2:-}"; shift 2 ;;
--exit-ip) EXIT_IP="${2:-}"; shift 2 ;;
--country) COUNTRY="${2:-}"; shift 2 ;;
--allow) ALLOW="${2:-}"; shift 2 ;;
--strict-net) STRICT="${2:-}"; shift 2 ;;
--prefix) PREFIX="${2:-}"; shift 2 ;;
--ref) REF="${2:-}"; shift 2 ;;
--dry-run) DRY="1"; shift ;;
--yes | -y) ASSUME_YES="1"; shift ;;
--skill-only) SKILL_ONLY="1"; shift ;;
--dest) DEST="${2:-}"; shift 2 ;;
--uninstall) UNINSTALL="1"; shift ;;
-h | --help) usage; exit 0 ;;
*) die "unknown option: $1 (try --help)" ;;
esac
done
# ---------------------------------------------------------------------------
# Locate the project: a checkout next to this script, or a downloaded tarball.
# ---------------------------------------------------------------------------
project_root() {
if [ -x "$SELF_DIR/skills/claude-egress-guard/scripts/claude-egress-guard" ]; then
printf '%s\n' "$SELF_DIR"
return 0
fi
command -v curl >/dev/null 2>&1 || die "curl is required to download the project"
command -v tar >/dev/null 2>&1 || die "tar is required to unpack the project"
WORK=$(mktemp -d "${TMPDIR:-/tmp}/ceg-install.XXXXXX") || die "cannot create a temp dir"
local url
case "$REF" in
main | master | [0-9]*) url="https://codeload.github.com/$REPO/tar.gz/refs/heads/$REF" ;;
*) url="https://codeload.github.com/$REPO/tar.gz/refs/tags/$REF" ;;
esac
say "downloading $REPO@$REF"
curl -fsSL --max-time 120 --retry 2 "$url" -o "$WORK/src.tar.gz" ||
die "download failed: $url"
tar -xzf "$WORK/src.tar.gz" -C "$WORK" || die "cannot unpack the release tarball"
local root
root=$(find "$WORK" -maxdepth 1 -mindepth 1 -type d -name 'claude-egress-guard-*' | head -n 1)
[ -n "$root" ] || die "unexpected archive layout"
[ -x "$root/skills/claude-egress-guard/scripts/claude-egress-guard" ] ||
die "archive does not contain the expected scripts"
printf '%s\n' "$root"
}
confirm() {
[ "$ASSUME_YES" = "1" ] && return 0
if [ -r /dev/tty ]; then
local answer=""
printf '%s [y/N] ' "$1" >/dev/tty
read -r answer </dev/tty || answer=""
case "$answer" in
y | Y | yes | YES) return 0 ;;
*) die "aborted" ;;
esac
fi
die "no terminal for confirmation; re-run with --yes"
}
# ---------------------------------------------------------------------------
# --skill-only: copy the skill into an agent's skills directory.
# ---------------------------------------------------------------------------
if [ "$SKILL_ONLY" = "1" ]; then
root=$(project_root)
mkdir -p "$DEST" || die "cannot create $DEST"
rm -rf "${DEST:?}/claude-egress-guard"
cp -R "$root/skills/claude-egress-guard" "$DEST/" || die "copy failed"
say "skill installed: $DEST/claude-egress-guard"
say "ask your agent to load the 'claude-egress-guard' skill and follow SKILL.md"
exit 0
fi
# ---------------------------------------------------------------------------
# --uninstall
# ---------------------------------------------------------------------------
if [ "$UNINSTALL" = "1" ]; then
cli=""
if [ -n "$PREFIX" ]; then
[ -x "$PREFIX/usr/local/sbin/claude-egress-guard" ] && cli="$PREFIX/usr/local/sbin/claude-egress-guard"
else
[ -x /usr/local/sbin/claude-egress-guard ] && cli="/usr/local/sbin/claude-egress-guard"
fi
if [ -z "$cli" ]; then
root=$(project_root)
cli="$root/skills/claude-egress-guard/scripts/claude-egress-guard"
fi
confirm "Remove the gate and restore the previous binaries?"
if [ -n "$PREFIX" ]; then
exec "$cli" uninstall --prefix "$PREFIX"
fi
exec "$cli" uninstall
fi
# ---------------------------------------------------------------------------
# install
# ---------------------------------------------------------------------------
[ -n "$PROXY" ] || die "--proxy is required (try --help)"
[ -n "$EXIT_IP" ] || die "--exit-ip is required (try --help)"
case "$PROXY" in
http://* | https://*) ;;
*) die "--proxy must start with http:// or https://" ;;
esac
if [ -z "$PREFIX" ] && [ "$(id -u)" -ne 0 ]; then
die "a real install needs root; re-run with sudo, or use --prefix for a sandbox"
fi
root=$(project_root)
cli="$root/skills/claude-egress-guard/scripts/claude-egress-guard"
args=(install --proxy "$PROXY" --exit-ip "$EXIT_IP")
[ -n "$COUNTRY" ] && args+=(--country "$COUNTRY")
[ -n "$ALLOW" ] && args+=(--allow "$ALLOW")
[ -n "$STRICT" ] && args+=(--strict-net "$STRICT")
[ -n "$PREFIX" ] && args+=(--prefix "$PREFIX")
[ "$DRY" = "1" ] && args+=(--dry-run)
"$cli" "${args[@]}" || die "install failed"
if [ "$DRY" = "1" ]; then
say "dry run only; re-run without --dry-run to apply"
exit 0
fi
say ""
say "running verify --deep ..."
if [ -n "$PREFIX" ]; then
"$cli" verify --deep --prefix "$PREFIX" || die "verify failed - do not use the gate until it passes"
else
"$cli" verify --deep || die "verify failed - do not use the gate until it passes"
fi
say ""
say "installed. next:"
say " claude-egress-guard status"
say " claude-egress-guard drill --service <your-proxy-unit>"
say " journalctl -t claude-egress-guard -f # when LOG_REFUSALS=1"