250 lines
9.5 KiB
Bash
250 lines
9.5 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# install.sh — AASD (API Attack Surface Discovery) installer
|
|
#
|
|
# Supports: Debian 12+ | RHEL 9+ (AlmaLinux, Rocky Linux)
|
|
# Usage: sudo bash install.sh [--release-url URL]
|
|
#
|
|
# Environment variables (all optional):
|
|
# AASD_RELEASE_URL — URL to download the dist/ tarball
|
|
# AASD_AI_API_KEY — DeepSeek API key (prompted if empty)
|
|
# AASD_BASE_URL — Public base URL (prompted if empty)
|
|
# AASD_ADMIN_PASSWORD — Admin dashboard password (prompted if empty)
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Color helpers
|
|
# ──────────────────────────────────────────────
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
|
pass() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; }
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Root check
|
|
# ──────────────────────────────────────────────
|
|
if [[ $EUID -ne 0 ]]; then
|
|
fail "This script must be run as root (sudo)."
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────
|
|
# OS detection
|
|
# ──────────────────────────────────────────────
|
|
info "Detecting operating system..."
|
|
|
|
if [[ -f /etc/os-release ]]; then
|
|
. /etc/os-release
|
|
OS_ID="${ID,,}"
|
|
OS_VERSION_ID="${VERSION_ID:-0}"
|
|
elif [[ -f /etc/redhat-release ]]; then
|
|
OS_ID="rhel"
|
|
OS_VERSION_ID="$(rpm -q --qf '%{VERSION}' $(rpm -q --whatprovides redhat-release) 2>/dev/null || echo '0')"
|
|
else
|
|
fail "Unsupported OS. Only Debian 12+ and RHEL 9+ are supported."
|
|
fi
|
|
|
|
case "$OS_ID" in
|
|
debian|ubuntu)
|
|
PKG_MGR="apt-get"
|
|
PKG_UPDATE="$PKG_MGR update -qq"
|
|
PKG_INSTALL="$PKG_MGR install -y -qq"
|
|
info "Detected Debian/Ubuntu — using apt"
|
|
;;
|
|
rhel|centos|rocky|almalinux|fedora)
|
|
PKG_MGR="dnf"
|
|
PKG_UPDATE="$PKG_MGR check-update || true"
|
|
PKG_INSTALL="$PKG_MGR install -y -q"
|
|
info "Detected RHEL family — using dnf"
|
|
;;
|
|
*)
|
|
fail "Unsupported OS: $OS_ID. Only Debian 12+ and RHEL 9+ are supported."
|
|
;;
|
|
esac
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Prerequisites
|
|
# ──────────────────────────────────────────────
|
|
info "Installing prerequisites..."
|
|
eval "$PKG_UPDATE"
|
|
eval "$PKG_INSTALL curl tar gzip"
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Create 'app' user
|
|
# ──────────────────────────────────────────────
|
|
APP_USER="app"
|
|
APP_HOME="/opt/aasd"
|
|
|
|
if id "$APP_USER" &>/dev/null; then
|
|
pass "User '$APP_USER' already exists"
|
|
else
|
|
info "Creating user '$APP_USER' (system user, no login)..."
|
|
case "$OS_ID" in
|
|
debian|ubuntu)
|
|
useradd --system --no-create-home --shell /usr/sbin/nologin "$APP_USER"
|
|
;;
|
|
rhel|centos|rocky|almalinux|fedora)
|
|
useradd --system --no-create-home --shell /sbin/nologin "$APP_USER"
|
|
;;
|
|
esac
|
|
pass "User '$APP_USER' created"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Determine source of dist/
|
|
# ──────────────────────────────────────────────
|
|
RELEASE_URL="${AASD_RELEASE_URL:-}"
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
LOCAL_DIST="$SCRIPT_DIR/dist"
|
|
|
|
if [[ -n "$RELEASE_URL" ]]; then
|
|
# Download from URL
|
|
info "Downloading release archive from: $RELEASE_URL"
|
|
TMP_DIR=$(mktemp -d)
|
|
TARBALL="$TMP_DIR/aasd-release.tar.gz"
|
|
curl -fsSL "$RELEASE_URL" -o "$TARBALL"
|
|
[[ -s "$TARBALL" ]] || fail "Downloaded archive is empty — check RELEASE_URL"
|
|
|
|
info "Extracting to $APP_HOME ..."
|
|
mkdir -p "$APP_HOME"
|
|
tar -xzf "$TARBALL" -C "$APP_HOME" --strip-components=1
|
|
rm -rf "$TMP_DIR"
|
|
pass "Release downloaded and extracted"
|
|
elif [[ -d "$LOCAL_DIST" ]]; then
|
|
# Copy from local dist/
|
|
info "Copying local dist/ to $APP_HOME ..."
|
|
mkdir -p "$APP_HOME"
|
|
cp -r "$LOCAL_DIST"/* "$APP_HOME/"
|
|
pass "Local dist/ copied"
|
|
else
|
|
fail "No release URL set (AASD_RELEASE_URL) and no local dist/ found at $LOCAL_DIST"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Set ownership
|
|
# ──────────────────────────────────────────────
|
|
chown -R "$APP_USER:$APP_USER" "$APP_HOME"
|
|
chmod 755 "$APP_HOME"
|
|
pass "Ownership set to $APP_USER"
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Create runtime directories
|
|
# ──────────────────────────────────────────────
|
|
mkdir -p "$APP_HOME/reports" "$APP_HOME/logs"
|
|
chown "$APP_USER:$APP_USER" "$APP_HOME/reports" "$APP_HOME/logs"
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Configure secrets (prompt if not set via env)
|
|
# ──────────────────────────────────────────────
|
|
AI_API_KEY="${AASD_AI_API_KEY:-}"
|
|
BASE_URL="${AASD_BASE_URL:-}"
|
|
ADMIN_PASS="${AASD_ADMIN_PASSWORD:-}"
|
|
|
|
if [[ -z "$AI_API_KEY" ]]; then
|
|
echo ""
|
|
read -rp "Enter DeepSeek API key (or press Enter to skip AI reports): " AI_API_KEY
|
|
fi
|
|
|
|
if [[ -z "$BASE_URL" ]]; then
|
|
echo ""
|
|
read -rp "Enter public base URL (e.g. https://aasd.sechpoint.app): " BASE_URL
|
|
fi
|
|
|
|
if [[ -z "$ADMIN_PASS" ]]; then
|
|
echo ""
|
|
read -rp "Enter admin dashboard password: " ADMIN_PASS
|
|
if [[ -z "$ADMIN_PASS" ]]; then
|
|
ADMIN_PASS="$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 16 || echo "aasd$(date +%s)")"
|
|
warn "Generated random password: $ADMIN_PASS"
|
|
fi
|
|
fi
|
|
|
|
# Write config.yaml
|
|
CONFIG_FILE="$APP_HOME/config.yaml"
|
|
cat > "$CONFIG_FILE" <<CONFIGEOF
|
|
ai:
|
|
provider_url: "https://api.deepseek.com"
|
|
api_key: "${AI_API_KEY:-}"
|
|
model: "deepseek-chat"
|
|
|
|
server:
|
|
base_url: "${BASE_URL:-}"
|
|
|
|
admin:
|
|
username: "sechpoint"
|
|
password: "${ADMIN_PASS:-}"
|
|
CONFIGEOF
|
|
|
|
chown "$APP_USER:$APP_USER" "$CONFIG_FILE"
|
|
chmod 600 "$CONFIG_FILE"
|
|
pass "Configuration written to $CONFIG_FILE"
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Create systemd service
|
|
# ──────────────────────────────────────────────
|
|
SYSTEMD_FILE="/etc/systemd/system/aasd.service"
|
|
|
|
cat > "$SYSTEMD_FILE" <<SERVICEEOF
|
|
[Unit]
|
|
Description=AASD — API Attack Surface Discovery
|
|
Documentation=https://github.com/valllabh/aasd
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=$APP_USER
|
|
Group=$APP_USER
|
|
WorkingDirectory=$APP_HOME
|
|
ExecStart=$APP_HOME/aasd
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
LimitNOFILE=4096
|
|
|
|
# Security hardening
|
|
NoNewPrivileges=true
|
|
ProtectSystem=full
|
|
ProtectHome=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVICEEOF
|
|
|
|
chmod 644 "$SYSTEMD_FILE"
|
|
systemctl daemon-reload
|
|
systemctl enable aasd.service
|
|
pass "Systemd service created and enabled"
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Start service
|
|
# ──────────────────────────────────────────────
|
|
info "Starting aasd.service..."
|
|
systemctl start aasd.service
|
|
sleep 2
|
|
|
|
if systemctl is-active --quiet aasd.service; then
|
|
pass "aasd.service is running"
|
|
else
|
|
warn "Service did not start — check: sudo journalctl -u aasd -n 50 --no-pager"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────
|
|
# Summary
|
|
# ──────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN} AASD installation complete${NC}"
|
|
echo -e "${GREEN}══════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
echo " URL: http://$(hostname -I 2>/dev/null | awk '{print $1}'):8080"
|
|
echo " Binary: $APP_HOME/aasd"
|
|
echo " Config: $CONFIG_FILE"
|
|
echo " Service: aasd.service"
|
|
echo ""
|
|
echo " Admin login: sechpoint / ${ADMIN_PASS:-<see config>}"
|
|
echo ""
|
|
echo " Manage:"
|
|
echo " sudo systemctl status aasd"
|
|
echo " sudo journalctl -u aasd -f"
|
|
echo ""
|