diff --git a/Caddyfile b/Caddyfile index deea056..7471497 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,7 +1,17 @@ # Caddy 配置文件 - 前端静态文件 + 后端 API 反向代理 # 最小化配置,符合 Caddy 2.x 最佳实践 - +# +# 使用说明: +# 1. 生产环境:将 :8080 替换为你的域名(如 example.com) +# 2. 确保域名 DNS 已解析到服务器 IP +# 3. 确保防火墙开放 80 和 443 端口 +# 4. Caddy 会自动从 Let's Encrypt 获取和续期 SSL 证书 +# 5. 可选:取消注释 email 行,用于证书到期通知 { + # 自动 HTTPS 配置(Let's Encrypt) + # email your-email@example.com # 可选:用于证书到期通知 + # acme_ca https://acme-v02.api.letsencrypt.org/directory # 默认使用 Let's Encrypt + log { output stdout format console @@ -15,21 +25,21 @@ path *.js *.css *.png *.jpg *.jpeg *.gif *.svg *.ico *.woff *.woff2 *.ttf *.eot } header @static Cache-Control "public, max-age=31536000, immutable" - + # HTML 不缓存 @html { file path *.html } header @html Cache-Control "no-cache" - + # 安全头 header { X-Content-Type-Options "nosniff" X-Frame-Options "SAMEORIGIN" Referrer-Policy "strict-origin-when-cross-origin" } - + # API 反向代理 handle /api/* { reverse_proxy localhost:9001 { @@ -38,7 +48,7 @@ header_up X-Forwarded-Proto {scheme} } } - + # 后端 API 反向代理 handle /jingrow/* { reverse_proxy localhost:9001 { @@ -47,7 +57,7 @@ header_up X-Forwarded-Proto {scheme} } } - + # 前端静态文件 + SPA 路由支持 handle { root * apps/jingrow/frontend/dist @@ -56,4 +66,3 @@ file_server } } - diff --git a/caddy b/caddy deleted file mode 100755 index 89ac751..0000000 Binary files a/caddy and /dev/null differ diff --git a/setup_production.sh b/setup_production.sh new file mode 100755 index 0000000..296e8a5 --- /dev/null +++ b/setup_production.sh @@ -0,0 +1,406 @@ +#!/bin/bash +# 一键生成生产环境服务文件脚本(静默安装) +# 包括:Caddy 安装、后端服务 systemd 文件、Caddy 服务 systemd 文件 +# +# 用法: +# ./setup_production.sh # 静默安装所有内容(默认) +# ./setup_production.sh --no-install # 只生成服务文件,不安装到系统 + +set -e + +# 获取脚本所在目录(项目根目录) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" || exit 1 + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 打印函数 +info() { echo -e "${BLUE}ℹ${NC} $1"; } +success() { echo -e "${GREEN}✓${NC} $1"; } +warn() { echo -e "${YELLOW}⚠${NC} $1"; } +error() { echo -e "${RED}✗${NC} $1"; } + +# 配置变量 +CADDY_VERSION="2.10.2" +CADDY_ARCH="linux_amd64" +CADDY_URL="https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_${CADDY_ARCH}.tar.gz" +CADDY_BINARY="caddy" +CADDY_INSTALL_DIR="/usr/local/bin" +CADDY_CONFIG_DIR="/etc/caddy" +CADDY_DATA_DIR="/var/lib/caddy" + +PROJECT_ROOT="$SCRIPT_DIR" +JINGROW_APP_DIR="$PROJECT_ROOT/apps/jingrow" +SERVICE_USER="${SERVICE_USER:-$(whoami)}" +SERVICE_GROUP="${SERVICE_GROUP:-$(id -gn)}" +TMP_DIR="$PROJECT_ROOT/tmp" + +# 检测 uv 路径 +detect_uv_path() { + if command -v uv &> /dev/null; then + UV_PATH=$(command -v uv) + info "检测到 uv 路径: $UV_PATH" + else + # 尝试常见路径 + if [ -f "$HOME/.local/bin/uv" ]; then + UV_PATH="$HOME/.local/bin/uv" + elif [ -f "/usr/local/bin/uv" ]; then + UV_PATH="/usr/local/bin/uv" + else + error "未找到 uv,请先安装" + exit 1 + fi + warn "使用 uv 路径: $UV_PATH" + fi +} + +# 检测系统架构 +detect_arch() { + local arch=$(uname -m) + case "$arch" in + x86_64|amd64) + CADDY_ARCH="linux_amd64" + ;; + aarch64|arm64) + CADDY_ARCH="linux_arm64" + ;; + armv7l|armv6l) + CADDY_ARCH="linux_armv7" + ;; + *) + error "不支持的架构: $arch" + exit 1 + ;; + esac + CADDY_URL="https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_${CADDY_ARCH}.tar.gz" + info "检测到架构: $arch,使用 $CADDY_ARCH" +} + +# 创建 Caddyfile 符号链接 +setup_caddyfile_link() { + # 创建必要的目录 + if [ "$EUID" -eq 0 ]; then + mkdir -p "$CADDY_CONFIG_DIR" "$CADDY_DATA_DIR" + chown -R "$SERVICE_USER:$SERVICE_GROUP" "$CADDY_CONFIG_DIR" "$CADDY_DATA_DIR" 2>/dev/null || true + else + sudo mkdir -p "$CADDY_CONFIG_DIR" "$CADDY_DATA_DIR" + sudo chown -R "$SERVICE_USER:$SERVICE_GROUP" "$CADDY_CONFIG_DIR" "$CADDY_DATA_DIR" 2>/dev/null || true + fi + + # 创建 Caddyfile 符号链接 + if [ -f "$PROJECT_ROOT/Caddyfile" ]; then + info "创建 Caddyfile 符号链接..." + # 如果已存在文件或链接,先删除 + if [ "$EUID" -eq 0 ]; then + [ -e "$CADDY_CONFIG_DIR/Caddyfile" ] && rm -f "$CADDY_CONFIG_DIR/Caddyfile" + ln -s "$PROJECT_ROOT/Caddyfile" "$CADDY_CONFIG_DIR/Caddyfile" + else + [ -e "$CADDY_CONFIG_DIR/Caddyfile" ] && sudo rm -f "$CADDY_CONFIG_DIR/Caddyfile" + sudo ln -s "$PROJECT_ROOT/Caddyfile" "$CADDY_CONFIG_DIR/Caddyfile" + fi + success "Caddyfile 符号链接已创建: $CADDY_CONFIG_DIR/Caddyfile -> $PROJECT_ROOT/Caddyfile" + else + warn "未找到 Caddyfile,请手动创建符号链接: ln -s $PROJECT_ROOT/Caddyfile $CADDY_CONFIG_DIR/Caddyfile" + fi +} + +# 安装 Caddy +install_caddy() { + info "开始安装 Caddy ${CADDY_VERSION}..." + + # 检查是否已安装 + if command -v caddy &> /dev/null; then + local installed_version=$(caddy version 2>/dev/null | head -n1 | grep -oP 'v\d+\.\d+\.\d+' || echo "unknown") + info "Caddy 已安装,版本: $installed_version,跳过二进制安装" + else + # 创建临时目录 + local tmp_dir=$(mktemp -d) + trap "rm -rf $tmp_dir" EXIT + + info "下载 Caddy..." + local caddy_tar="$tmp_dir/caddy.tar.gz" + if ! curl -L -o "$caddy_tar" "$CADDY_URL"; then + error "下载 Caddy 失败" + exit 1 + fi + + info "解压 Caddy..." + cd "$tmp_dir" + tar -xzf "$caddy_tar" + + # 检查二进制文件是否存在 + if [ ! -f "$CADDY_BINARY" ]; then + error "未找到 Caddy 二进制文件" + exit 1 + fi + + # 安装到系统目录(需要 root 权限) + info "安装 Caddy 到 $CADDY_INSTALL_DIR..." + if [ "$EUID" -eq 0 ]; then + cp "$CADDY_BINARY" "$CADDY_INSTALL_DIR/caddy" + chmod +x "$CADDY_INSTALL_DIR/caddy" + else + warn "需要 root 权限安装 Caddy,尝试使用 sudo..." + sudo cp "$CADDY_BINARY" "$CADDY_INSTALL_DIR/caddy" + sudo chmod +x "$CADDY_INSTALL_DIR/caddy" + fi + fi + + # 无论是否已安装,都设置 Caddyfile 符号链接 + setup_caddyfile_link + + success "Caddy 安装完成" + + # 验证安装 + if command -v caddy &> /dev/null; then + local version=$(caddy version 2>/dev/null | head -n1 || echo "unknown") + info "Caddy 版本: $version" + fi +} + +# 生成后端服务 systemd 文件 +generate_backend_service() { + info "生成后端服务 systemd 文件..." + + # 确保 tmp 目录存在 + mkdir -p "$TMP_DIR" + + local service_file="$TMP_DIR/jingrow-backend.service" + local uv_dir=$(dirname "$UV_PATH") + local env_file_line="" + + # 如果 .env 文件存在,使用 EnvironmentFile 引用 + if [ -f "$PROJECT_ROOT/.env" ]; then + env_file_line="EnvironmentFile=${PROJECT_ROOT}/.env" + fi + + cat > "$service_file" < "$service_file" < "$service_file" < /dev/null; then + error "需要 curl,请先安装" + exit 1 + fi + + if ! command -v tar &> /dev/null; then + error "需要 tar,请先安装" + exit 1 + fi + + # 检测 uv 路径 + detect_uv_path + + # 检查项目目录 + if [ ! -d "$JINGROW_APP_DIR" ]; then + error "未找到后端目录: $JINGROW_APP_DIR" + exit 1 + fi + + # 安装 Caddy + install_caddy + + # 生成服务文件 + generate_backend_service + generate_worker_service + generate_caddy_service + + # 安装服务(静默安装,除非指定 --no-install) + install_services "$@" + + success "完成!" + info "生成的文件(位于 $TMP_DIR):" + echo " - $TMP_DIR/jingrow-backend.service" + echo " - $TMP_DIR/jingrow-worker.service" + echo " - $TMP_DIR/caddy.service" + echo "" + info "注意事项:" + echo " 1. 确保已构建前端: npm run build:frontend" + echo " 2. 确保后端依赖已安装: npm run install:backend" + echo " 3. 确保 Redis 已安装并运行" + echo " 4. 检查并配置 .env 文件" + echo " 5. 修改服务文件中的用户和组(如需要)" +} + +main "$@" +