Files
xingrin/install.sh
yyhuni 7f2af7f7e2 feat(search): add result export functionality and pagination limit support
- Add optional limit parameter to AssetSearchService.search() method for controlling result set size
- Implement AssetSearchExportView for exporting search results as CSV files with UTF-8 BOM encoding
- Add CSV export endpoint at GET /api/assets/search/export/ with configurable MAX_EXPORT_ROWS limit (10000)
- Support both website and endpoint asset types with type-specific column mappings in CSV export
- Format array fields (tech, matched_gf_patterns) and dates appropriately in exported CSV
- Update URL routing to include new search export endpoint
- Update views __init__.py to export AssetSearchExportView
- Add CSV generation with streaming response for efficient memory usage on large exports
- Update frontend search service to support export functionality
- Add internationalization strings for export feature in en.json and zh.json
- Update smart-filter-input and search-results-table components to support export UI
- Update installation and Docker startup scripts for deployment compatibility
2026-01-03 13:22:21 +08:00

753 lines
30 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -e
# ==============================================================================
# 用法:
# sudo ./install.sh 生产模式(拉取 Docker Hub 镜像)
# sudo ./install.sh --dev 开发模式(本地构建 + 调试日志)
# sudo ./install.sh --no-frontend 安装并只启动后端
# sudo ./install.sh --dev --no-frontend 开发模式 + 只启动后端
# ==============================================================================
# 解析参数
START_ARGS=""
DEV_MODE=false
GIT_MIRROR=""
for arg in "$@"; do
case $arg in
--dev)
DEV_MODE=true
START_ARGS="$START_ARGS --dev"
;;
--no-frontend)
START_ARGS="$START_ARGS --no-frontend"
;;
--mirror)
GIT_MIRROR="https://gh-proxy.org"
;;
--mirror=*)
GIT_MIRROR="${arg#*=}"
;;
esac
done
# ==============================================================================
# 颜色定义
# ==============================================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
# ==============================================================================
# 额外颜色定义
# ==============================================================================
MAGENTA='\033[0;35m'
DIM='\033[2m'
BG_BLUE='\033[44m'
BG_CYAN='\033[46m'
# ==============================================================================
# 日志函数
# ==============================================================================
info() {
echo -e " ${CYAN}${RESET} $1"
}
success() {
echo -e " ${GREEN}${RESET} $1"
}
warn() {
echo -e " ${YELLOW}${RESET} $1"
}
error() {
echo -e " ${RED}${RESET} $1"
}
step() {
echo -e "\n${BOLD}${CYAN}┌── $1${RESET}"
}
header() {
echo -e ""
echo -e "${BOLD}${BLUE}╔══════════════════════════════════════════════════════════╗${RESET}"
echo -e "${BOLD}${BLUE}${RESET} $1"
echo -e "${BOLD}${BLUE}╚══════════════════════════════════════════════════════════╝${RESET}"
}
# ==============================================================================
# 显示横幅
# ==============================================================================
show_banner() {
clear
echo -e ""
echo -e "${CYAN}${BOLD} ██╗ ██╗██╗███╗ ██╗ ██████╗ ██████╗ ██╗███╗ ██╗${RESET}"
echo -e "${CYAN} ╚██╗██╔╝██║████╗ ██║██╔════╝ ██╔══██╗██║████╗ ██║${RESET}"
echo -e "${BLUE}${BOLD} ╚███╔╝ ██║██╔██╗ ██║██║ ███╗██████╔╝██║██╔██╗ ██║${RESET}"
echo -e "${BLUE} ██╔██╗ ██║██║╚██╗██║██║ ██║██╔══██╗██║██║╚██╗██║${RESET}"
echo -e "${MAGENTA}${BOLD} ██╔╝ ██╗██║██║ ╚████║╚██████╔╝██║ ██║██║██║ ╚████║${RESET}"
echo -e "${MAGENTA} ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝${RESET}"
echo -e ""
echo -e "${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD} 🔒 分布式安全扫描平台 │ 一键部署 (Ubuntu)${RESET}"
echo -e "${DIM} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e ""
}
# ==============================================================================
# 权限检查
# ==============================================================================
if [ "$EUID" -ne 0 ]; then
error "请使用 sudo 运行此脚本"
echo -e " 正确用法: ${BOLD}sudo ./install.sh${RESET}"
exit 1
fi
# 获取真实用户(通过 sudo 运行时 $SUDO_USER 是真实用户)
REAL_USER="${SUDO_USER:-$USER}"
# macOS 没有 getent使用 dscl 或 ~$USER 替代
if command -v getent &>/dev/null; then
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
else
REAL_HOME=$(eval echo "~$REAL_USER")
fi
# 项目根目录
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$ROOT_DIR"
# 从 VERSION 文件读取版本号
VERSION_FILE="$ROOT_DIR/VERSION"
if [ -f "$VERSION_FILE" ]; then
APP_VERSION=$(cat "$VERSION_FILE" | tr -d '[:space:]')
else
error "VERSION 文件不存在,无法确定安装版本"
exit 1
fi
# 显示标题
show_banner
info "当前用户: ${BOLD}$REAL_USER${RESET}"
info "项目路径: ${BOLD}$ROOT_DIR${RESET}"
info "安装版本: ${BOLD}$APP_VERSION${RESET}"
if [ -n "$GIT_MIRROR" ]; then
info "Git 加速: ${BOLD}${GREEN}已启用${RESET} - $GIT_MIRROR"
fi
# ==============================================================================
# 工具函数
# ==============================================================================
# 生成随机字符串
generate_random_string() {
local length="${1:-32}"
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex "$length" 2>/dev/null | cut -c1-"$length"
else
date +%s%N | sha256sum | cut -c1-"$length"
fi
}
# 跨平台 sed -i兼容 macOS 和 Linux
sed_inplace() {
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
# 更新 .env 文件中的某个键
update_env_var() {
local file="$1"
local key="$2"
local value="$3"
if grep -q "^$key=" "$file"; then
sed_inplace "s|^$key=.*|$key=$value|" "$file"
else
echo "$key=$value" >> "$file"
fi
}
# 用于保存生成的密码,方便最后显示
GENERATED_DB_PASSWORD=""
GENERATED_DJANGO_KEY=""
# 生成自签 HTTPS 证书(使用容器,避免宿主机 openssl 兼容性问题)
generate_self_signed_cert() {
local ssl_dir="$DOCKER_DIR/nginx/ssl"
local fullchain="$ssl_dir/fullchain.pem"
local privkey="$ssl_dir/privkey.pem"
if [ -f "$fullchain" ] && [ -f "$privkey" ]; then
success "检测到已有 HTTPS 证书,跳过自签"
return
fi
info "未检测到 HTTPS 证书正在生成自签证书localhost..."
mkdir -p "$ssl_dir"
# 使用容器生成证书,避免依赖宿主机 openssl 版本
if docker run --rm -v "$ssl_dir:/ssl" alpine/openssl \
req -x509 -nodes -newkey rsa:2048 -days 365 \
-keyout /ssl/privkey.pem \
-out /ssl/fullchain.pem \
-subj "/C=CN/ST=NA/L=NA/O=XingRin/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \
>/dev/null 2>&1; then
success "自签证书已生成: $ssl_dir"
else
warn "自签证书生成失败,请手动放置证书到 $ssl_dir"
fi
}
# 自动为 docker/.env 填充敏感变量
auto_fill_docker_env_secrets() {
local env_file="$1"
info "自动生成 DJANGO_SECRET_KEY、DB_PASSWORD 和 WORKER_API_KEY..."
GENERATED_DJANGO_KEY="$(generate_random_string 64)"
GENERATED_DB_PASSWORD="$(generate_random_string 32)"
GENERATED_WORKER_API_KEY="$(generate_random_string 32)"
update_env_var "$env_file" "DJANGO_SECRET_KEY" "$GENERATED_DJANGO_KEY"
update_env_var "$env_file" "DB_PASSWORD" "$GENERATED_DB_PASSWORD"
update_env_var "$env_file" "WORKER_API_KEY" "$GENERATED_WORKER_API_KEY"
success "密钥生成完成"
}
# 配置 Docker 镜像加速
# 使用国内公共镜像源pull-through cache 模式,首次拉取会从 Docker Hub 同步)
configure_docker_mirror() {
local daemon_json="/etc/docker/daemon.json"
# 这些镜像源采用 pull-through cache 模式:
# - 首次拉取:从 Docker Hub 获取并缓存
# - 后续拉取:直接从缓存返回
# - 支持所有公开镜像,包括用户上传的最新镜像
local mirrors='[
"https://docker.1ms.run",
"https://docker.1panel.live",
"https://hub.rat.dev",
"https://docker.m.daocloud.io",
"https://dockerproxy.net"
]'
info "配置 Docker 镜像加速..."
# 确保 Docker 配置目录存在
mkdir -p /etc/docker
if [ ! -f "$daemon_json" ]; then
# 文件不存在,直接创建
echo "{\"registry-mirrors\": $mirrors}" | jq '.' > "$daemon_json"
success "Docker 镜像加速配置完成"
else
# 文件存在,用 jq 合并配置
if jq -e '.' "$daemon_json" >/dev/null 2>&1; then
# 有效的 JSON合并配置
local temp_file=$(mktemp)
jq --argjson mirrors "$mirrors" '. + {"registry-mirrors": $mirrors}' "$daemon_json" > "$temp_file"
mv "$temp_file" "$daemon_json"
success "Docker 镜像加速配置已合并到现有配置"
else
# 无效的 JSON备份后重新创建
warn "现有 daemon.json 格式无效,已备份为 daemon.json.bak"
cp "$daemon_json" "${daemon_json}.bak"
echo "{\"registry-mirrors\": $mirrors}" | jq '.' > "$daemon_json"
success "Docker 镜像加速配置完成"
fi
fi
# 重启 Docker 使配置生效
if systemctl is-active --quiet docker; then
info "重启 Docker 服务..."
systemctl restart docker
sleep 2
if systemctl is-active --quiet docker; then
success "Docker 服务重启完成"
else
warn "Docker 服务重启失败,请手动检查"
fi
fi
}
# 获取加速后的 Docker 镜像地址(保留函数以兼容现有代码)
get_accelerated_image() {
local image="$1"
# 镜像加速通过 daemon.json 的 registry-mirrors 实现
# 这里直接返回原始地址
echo "$image"
}
# 检测远程 PostgreSQL 是否有 pg_ivm 扩展
check_pg_ivm() {
local db_host="$1"
local db_port="$2"
local db_user="$3"
local db_password="$4"
local db_name="$5"
info "检测 pg_ivm 扩展..."
# 尝试创建 pg_ivm 扩展
if docker run --rm \
-e PGPASSWORD="$db_password" \
postgres:15 \
psql "postgresql://$db_user@$db_host:$db_port/$db_name" \
-c "CREATE EXTENSION IF NOT EXISTS pg_ivm;" 2>/dev/null; then
success "pg_ivm 扩展已启用"
return 0
else
echo
error "pg_ivm 扩展未安装或无法启用"
echo
echo -e "${YELLOW}=========================================="
echo -e "pg_ivm 是必需的扩展,用于增量维护物化视图"
echo -e "要求: PostgreSQL 13+ 版本"
echo -e "==========================================${RESET}"
echo
echo -e "请在远程 PostgreSQL 服务器上执行以下命令一键安装:"
echo
echo -e " ${BOLD}curl -sSL https://raw.githubusercontent.com/yyhuni/xingrin/main/docker/scripts/install-pg-ivm.sh | sudo bash${RESET}"
echo
echo -e "安装完成后,请重新运行 install.sh"
echo -e "${YELLOW}==========================================${RESET}"
return 1
fi
}
# 显示安装总结信息
show_summary() {
echo
if [ "$1" = "success" ]; then
# 成功 Banner
echo -e ""
echo -e "${GREEN}${BOLD} ╔═══════════════════════════════════════════════════╗${RESET}"
echo -e "${GREEN}${BOLD} ║ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ██████╗ ██████╗ ███╗ ██╗███████╗██║ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ██╔══██╗██╔═══██╗████╗ ██║██╔════╝██║ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ██║ ██║██║ ██║██╔██╗ ██║█████╗ ██║ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ██║ ██║██║ ██║██║╚██╗██║██╔══╝ ╚═╝ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ██████╔╝╚██████╔╝██║ ╚████║███████╗██║ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ✨ XingRin 平台部署成功! ║${RESET}"
echo -e "${GREEN}${BOLD} ║ ║${RESET}"
echo -e "${GREEN}${BOLD} ╚═══════════════════════════════════════════════════╝${RESET}"
echo -e ""
else
header "安装完成 Summary"
fi
if [ -f "$DOCKER_DIR/.env" ]; then
# 从 .env 读取配置用于显示
DB_HOST=$(grep "^DB_HOST=" "$DOCKER_DIR/.env" | cut -d= -f2)
DB_USER=$(grep "^DB_USER=" "$DOCKER_DIR/.env" | cut -d= -f2)
DB_PASSWORD=$(grep "^DB_PASSWORD=" "$DOCKER_DIR/.env" | cut -d= -f2)
echo -e "${DIM} ──────────────────────────────────────────────────────${RESET}"
echo -e " ${YELLOW}🗄 数据库配置${RESET}"
echo -e " ${DIM}├─${RESET} 服务器地址: ${BOLD}${DB_HOST:-未知}${RESET}"
echo -e " ${DIM}├─${RESET} 用户名: ${BOLD}${DB_USER:-未知}${RESET}"
echo -e " ${DIM}└─${RESET} 密码: ${BOLD}${DB_PASSWORD:-未知}${RESET}"
echo
fi
# 获取访问地址
PUBLIC_HOST=$(grep "^PUBLIC_HOST=" "$DOCKER_DIR/.env" 2>/dev/null | cut -d= -f2)
if [ -n "$PUBLIC_HOST" ] && [ "$PUBLIC_HOST" != "server" ]; then
ACCESS_HOST="$PUBLIC_HOST"
else
ACCESS_HOST="localhost"
fi
echo -e "${DIM} ──────────────────────────────────────────────────────${RESET}"
echo -e " ${GREEN}🌐 访问地址${RESET}"
echo -e " ${DIM}└─${RESET} XingRin: ${BOLD}${CYAN}https://${ACCESS_HOST}:8083/${RESET}"
echo
echo -e "${DIM} ──────────────────────────────────────────────────────${RESET}"
echo -e " ${MAGENTA}🔑 默认登录${RESET}"
echo -e " ${DIM}├─${RESET} 用户名: ${BOLD}admin${RESET}"
echo -e " ${DIM}└─${RESET} 密码: ${BOLD}admin${RESET}"
echo -e " ${YELLOW} ⚠ 请首次登录后修改密码!${RESET}"
echo
if [ "$1" = "success" ]; then
: # 成功模式,不显示后续命令
else
echo -e "${DIM} ──────────────────────────────────────────────────────${RESET}"
echo -e " ${BLUE}🚀 后续命令${RESET}"
echo -e " ${DIM}├─${RESET} ./start.sh ${DIM}# 启动所有服务${RESET}"
echo -e " ${DIM}├─${RESET} ./start.sh --no-frontend ${DIM}# 只启动后端${RESET}"
echo -e " ${DIM}└─${RESET} ./stop.sh ${DIM}# 停止所有服务${RESET}"
echo
fi
echo -e "${DIM} ──────────────────────────────────────────────────────${RESET}"
echo -e " ${YELLOW}⚠ 云服务器端口提醒${RESET}"
echo -e " ${DIM}└─${RESET} 某些厂商默认开启安全策略(阿里云/腾讯云/华为云)"
echo -e " 端口未放行可能导致无法访问,请在云控制台放行: ${BOLD}8083, 5432${RESET}"
echo
}
# ==============================================================================
# 安装流程
# ==============================================================================
step "[1/3] 检查基础命令"
MISSING_CMDS=()
for cmd in git curl jq; do
if ! command -v "$cmd" >/dev/null 2>&1; then
MISSING_CMDS+=("$cmd")
warn "未安装: $cmd"
else
success "已安装: $cmd"
fi
done
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
info "正在安装缺失命令: ${MISSING_CMDS[*]}..."
apt update -qq
apt install -y "${MISSING_CMDS[@]}"
success "基础命令安装完成"
fi
step "[2/3] 检查 Docker 环境"
if command -v docker >/dev/null 2>&1; then
success "已安装: docker"
else
info "正在安装 Docker..."
# 根据是否启用加速选择下载方式
if [ -n "$GIT_MIRROR" ]; then
# 使用阿里云 Docker 安装脚本(国内加速)
info "使用国内镜像安装 Docker..."
if curl -fsSL https://get.docker.com | sh -s -- --mirror Aliyun; then
success "Docker 安装完成(使用阿里云镜像)"
else
warn "阿里云镜像安装失败,尝试官方源..."
if curl -fsSL https://get.docker.com | sh; then
success "Docker 安装完成"
else
error "Docker 安装失败"
exit 1
fi
fi
else
# 默认从官方源下载
if curl -fsSL https://get.docker.com | sh; then
success "Docker 安装完成"
else
error "Docker 安装脚本下载失败"
error "如果您在中国大陆,建议使用 --mirror 参数启用加速"
exit 1
fi
fi
usermod -aG docker "$REAL_USER"
# 配置 Docker 镜像加速(仅当启用 --mirror 时)
if [ -n "$GIT_MIRROR" ]; then
configure_docker_mirror
fi
fi
# 如果 Docker 已安装但启用了 --mirror也配置镜像加速
if [ -n "$GIT_MIRROR" ] && command -v docker &>/dev/null; then
# 检查是否已配置镜像加速
if [ ! -f "/etc/docker/daemon.json" ] || ! grep -q "registry-mirrors" /etc/docker/daemon.json 2>/dev/null; then
configure_docker_mirror
fi
fi
# 检查 docker compose
if docker compose version >/dev/null 2>&1; then
success "已安装: docker compose"
else
info "正在安装 docker-compose-plugin..."
apt install -y docker-compose-plugin
success "docker compose 安装完成"
fi
# ==============================================================================
# 交换分区配置(仅 Linux
# ==============================================================================
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# 获取当前内存大小GB四舍五入
TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
TOTAL_MEM_GB=$(awk "BEGIN {printf \"%.0f\", $TOTAL_MEM_KB / 1024 / 1024}")
# 获取当前交换分区大小GB四舍五入
CURRENT_SWAP_KB=$(grep SwapTotal /proc/meminfo | awk '{print $2}')
CURRENT_SWAP_GB=$(awk "BEGIN {printf \"%.0f\", $CURRENT_SWAP_KB / 1024 / 1024}")
# 推荐交换分区大小与内存相同最小1G最大8G
RECOMMENDED_SWAP=$TOTAL_MEM_GB
[ "$RECOMMENDED_SWAP" -lt 1 ] && RECOMMENDED_SWAP=1
[ "$RECOMMENDED_SWAP" -gt 8 ] && RECOMMENDED_SWAP=8
echo ""
info "系统内存: ${TOTAL_MEM_GB}GB当前交换分区: ${CURRENT_SWAP_GB}GB"
# 如果交换分区小于推荐值,提示用户
if [ "$CURRENT_SWAP_GB" -lt "$RECOMMENDED_SWAP" ]; then
echo -n -e "${BOLD}${CYAN}[?] 是否开启 ${RECOMMENDED_SWAP}GB 交换分区?可提升扫描稳定性 (Y/n) ${RESET}"
read -r setup_swap
echo
if [[ ! $setup_swap =~ ^[Nn]$ ]]; then
info "正在配置 ${RECOMMENDED_SWAP}GB 交换分区..."
if bash "$ROOT_DIR/docker/scripts/setup-swap.sh" "$RECOMMENDED_SWAP"; then
success "交换分区配置完成"
else
warn "交换分区配置失败,继续安装..."
fi
else
info "跳过交换分区配置"
fi
else
success "交换分区已足够: ${CURRENT_SWAP_GB}GB"
fi
fi
step "[3/3] 初始化配置"
# 创建数据目录
info "创建数据目录..."
mkdir -p /opt/xingrin/{results,logs,fingerprints,wordlists,nuclei-repos}
chmod -R 777 /opt/xingrin
success "数据目录已创建: /opt/xingrin/"
DOCKER_DIR="$ROOT_DIR/docker"
if [ ! -d "$DOCKER_DIR" ]; then
error "未找到 docker 目录,请确认项目结构。"
exit 1
fi
if [ -f "$DOCKER_DIR/.env.example" ]; then
cp "$DOCKER_DIR/.env.example" "$DOCKER_DIR/.env"
success "已创建: docker/.env"
auto_fill_docker_env_secrets "$DOCKER_DIR/.env"
# 写入版本号(锁定安装时的版本)
update_env_var "$DOCKER_DIR/.env" "IMAGE_TAG" "$APP_VERSION"
success "已锁定版本: IMAGE_TAG=$APP_VERSION"
# Git 加速仅用于安装过程,不写入运行时配置
# 运行时用户如需加速,可通过代理或其他方式自行配置
if [ -n "$GIT_MIRROR" ]; then
info "Git 加速已启用(仅用于安装阶段)"
fi
# 开发模式:开启调试日志
if [ "$DEV_MODE" = true ]; then
info "开发模式:开启调试配置..."
update_env_var "$DOCKER_DIR/.env" "DEBUG" "True"
update_env_var "$DOCKER_DIR/.env" "LOG_LEVEL" "INFO"
update_env_var "$DOCKER_DIR/.env" "ENABLE_COMMAND_LOGGING" "true"
success "已开启: DEBUG=True, LOG_LEVEL=INFO, ENABLE_COMMAND_LOGGING=true"
fi
# 询问数据库配置
echo ""
echo -n -e "${BOLD}${CYAN}[?] 是否使用远程 PostgreSQL 数据库?(y/N) ${RESET}"
read -r use_remote_db
echo
if [[ $use_remote_db =~ ^[Yy]$ ]]; then
echo -e "${CYAN} 请输入远程 PostgreSQL 配置:${RESET}"
# 服务器地址(必填)
echo -n -e " ${CYAN}服务器地址: ${RESET}"
read -r db_host
if [ -z "$db_host" ]; then
error "服务器地址不能为空"
exit 1
fi
# 端口(可选)
echo -n -e " ${CYAN}端口 [5432]: ${RESET}"
read -r db_port
db_port=${db_port:-5432}
# 用户名(必填)
echo -n -e " ${CYAN}用户名: ${RESET}"
read -r db_user
if [ -z "$db_user" ]; then
error "用户名不能为空"
exit 1
fi
# 密码(必填)
echo -n -e " ${CYAN}密码: ${RESET}"
read -r db_password
if [ -z "$db_password" ]; then
error "密码不能为空"
exit 1
fi
# 验证远程 PostgreSQL 连接(使用官方 postgres 镜像中的 psql
echo
info "正在验证远程 PostgreSQL 连接..."
# 使用 postgres 默认库验证连接(每个 PostgreSQL 都有这个库)
if ! docker run --rm \
-e PGPASSWORD="$db_password" \
postgres:15 \
psql "postgresql://$db_user@$db_host:$db_port/postgres" -c 'SELECT 1' >/dev/null 2>&1; then
echo
error "无法连接到远程 PostgreSQL请检查 IP/端口/用户名/密码是否正确"
echo " 尝试连接: postgresql://$db_user@$db_host:$db_port/postgres"
exit 1
fi
success "远程 PostgreSQL 连接验证通过"
# 尝试创建业务数据库(如果不存在)
info "检查并创建数据库..."
db_name=$(grep "^DB_NAME=" "$DOCKER_DIR/.env" | cut -d= -f2)
db_name=${db_name:-xingrin}
prefect_db=$(grep "^PREFECT_DB_NAME=" "$DOCKER_DIR/.env" | cut -d= -f2)
prefect_db=${prefect_db:-prefect}
docker run --rm -e PGPASSWORD="$db_password" postgres:15 \
psql "postgresql://$db_user@$db_host:$db_port/postgres" \
-c "CREATE DATABASE $db_name;" 2>/dev/null || true
docker run --rm -e PGPASSWORD="$db_password" postgres:15 \
psql "postgresql://$db_user@$db_host:$db_port/postgres" \
-c "CREATE DATABASE $prefect_db;" 2>/dev/null || true
success "数据库准备完成"
# 检测 pg_ivm 扩展
if ! check_pg_ivm "$db_host" "$db_port" "$db_user" "$db_password" "$db_name"; then
exit 1
fi
sed_inplace "s/^DB_HOST=.*/DB_HOST=$db_host/" "$DOCKER_DIR/.env"
sed_inplace "s/^DB_PORT=.*/DB_PORT=$db_port/" "$DOCKER_DIR/.env"
sed_inplace "s/^DB_USER=.*/DB_USER=$db_user/" "$DOCKER_DIR/.env"
sed_inplace "s/^DB_PASSWORD=.*/DB_PASSWORD=$db_password/" "$DOCKER_DIR/.env"
success "已配置远程数据库: $db_user@$db_host:$db_port"
else
info "使用本地 PostgreSQL 容器"
fi
# 是否为远程 VPS 部署(需要从其它机器 / Worker 访问本系统)
echo ""
echo -n -e "${BOLD}${CYAN}[?] 当前是否为远程 VPS 部署?(y/N) ${RESET}"
read -r set_public_host
echo
if [[ $set_public_host =~ ^[Yy]$ ]]; then
echo -n -e " ${CYAN}请输入当前远程 vps 的外网 IP 地址(例如 10.1.1.1: ${RESET}"
read -r public_host
if [ -z "$public_host" ]; then
warn "未输入外网ip地址将保持 .env 中已有的 PUBLIC_HOST请确保 Worker 能访问该地址)"
else
update_env_var "$DOCKER_DIR/.env" "PUBLIC_HOST" "$public_host"
success "已配置对外访问地址: $public_host"
fi
else
info "检测为本机 docker 部署,将 PUBLIC_HOST 设置为 server容器内部访问后端服务名"
update_env_var "$DOCKER_DIR/.env" "PUBLIC_HOST" "server"
fi
else
error "未找到 docker/.env.example"
exit 1
fi
# 准备 HTTPS 证书(无域名也可使用自签)
generate_self_signed_cert
# ==============================================================================
# 预拉取 Worker 镜像(避免扫描时等待)
# ==============================================================================
step "预拉取 Worker 镜像..."
DOCKER_USER=$(grep "^DOCKER_USER=" "$DOCKER_DIR/.env" 2>/dev/null | cut -d= -f2)
DOCKER_USER=${DOCKER_USER:-yyhuni}
WORKER_IMAGE="${DOCKER_USER}/xingrin-worker:${APP_VERSION}"
# 开发模式下构建本地 worker 镜像
if [ "$DEV_MODE" = true ]; then
info "开发模式:构建本地 Worker 镜像..."
if docker compose -f "$DOCKER_DIR/docker-compose.dev.yml" build worker; then
# 设置 TASK_EXECUTOR_IMAGE 环境变量指向本地构建的镜像(使用版本号-dev标识
update_env_var "$DOCKER_DIR/.env" "TASK_EXECUTOR_IMAGE" "docker-worker:${APP_VERSION}-dev"
success "本地 Worker 镜像构建完成: docker-worker:${APP_VERSION}-dev"
else
error "开发模式下本地 Worker 镜像构建失败!"
error "请检查构建错误并修复后重试"
exit 1
fi
else
info "正在拉取: $WORKER_IMAGE"
# 镜像加速通过 daemon.json 的 registry-mirrors 实现
PULL_IMAGE="$WORKER_IMAGE"
if [ -n "$GIT_MIRROR" ]; then
info "已配置 Docker 镜像加速,拉取将自动走加速通道"
fi
if docker pull "$PULL_IMAGE"; then
success "Worker 镜像拉取完成"
else
error "Worker 镜像拉取失败,无法继续安装"
error "镜像地址: $WORKER_IMAGE"
echo
if [ -z "$GIT_MIRROR" ]; then
warn "如果您在中国大陆,建议使用 --mirror 参数启用加速:"
echo -e " ${BOLD}sudo ./install.sh --mirror${RESET}"
else
warn "镜像加速已配置,但拉取仍然失败,可能原因:"
echo -e " 1. 镜像源暂时不可用,请稍后重试"
echo -e " 2. 网络连接问题"
echo -e " 3. 镜像不存在或版本错误"
fi
echo
exit 1
fi
fi
# ==============================================================================
# 预下载 Nuclei 模板仓库(在容器外执行,支持 Git 加速)
# ==============================================================================
step "预下载 Nuclei 模板仓库..."
NUCLEI_TEMPLATES_DIR="/opt/xingrin/nuclei-repos/nuclei-templates"
NUCLEI_TEMPLATES_REPO="https://github.com/projectdiscovery/nuclei-templates.git"
# 确保目录存在
mkdir -p /opt/xingrin/nuclei-repos
if [ -d "$NUCLEI_TEMPLATES_DIR/.git" ]; then
info "Nuclei 模板仓库已存在,跳过下载"
else
# 构建 clone URL如果启用了 Git 加速)
if [ -n "$GIT_MIRROR" ]; then
CLONE_URL="${GIT_MIRROR}/${NUCLEI_TEMPLATES_REPO}"
info "使用 Git 加速下载: $CLONE_URL"
else
CLONE_URL="$NUCLEI_TEMPLATES_REPO"
fi
# 执行 git clone
if git clone --depth 1 "$CLONE_URL" "$NUCLEI_TEMPLATES_DIR"; then
success "Nuclei 模板仓库下载完成"
else
warn "Nuclei 模板仓库下载失败,将在服务启动后重试"
warn "您也可以稍后在 Web 界面「Nuclei 模板」中手动同步"
fi
fi
# ==============================================================================
# 启动服务
# ==============================================================================
step "正在启动服务..."
"$ROOT_DIR/start.sh" ${START_ARGS} --quiet
# ==============================================================================
# 完成总结
# ==============================================================================
show_summary "success"