Add Caddy production setup with systemd services
This commit is contained in:
parent
a8c77e113d
commit
a3ceb858e0
23
Caddyfile
23
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
406
setup_production.sh
Executable file
406
setup_production.sh
Executable file
@ -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" <<EOF
|
||||
[Unit]
|
||||
Description=Jingrow Backend API Service
|
||||
After=network.target redis.service
|
||||
Requires=redis.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${SERVICE_USER}
|
||||
Group=${SERVICE_GROUP}
|
||||
WorkingDirectory=${JINGROW_APP_DIR}
|
||||
Environment="PATH=${uv_dir}:/usr/local/bin:/usr/bin:/bin"
|
||||
Environment="HOME=${HOME}"
|
||||
${env_file_line}
|
||||
ExecStart=${UV_PATH} run python -m jingrow.app
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=jingrow-backend
|
||||
|
||||
# 安全设置
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
success "已生成: $service_file"
|
||||
}
|
||||
|
||||
# 生成 Worker 服务 systemd 文件
|
||||
generate_worker_service() {
|
||||
info "生成 Worker 服务 systemd 文件..."
|
||||
|
||||
# 确保 tmp 目录存在
|
||||
mkdir -p "$TMP_DIR"
|
||||
|
||||
local service_file="$TMP_DIR/jingrow-worker.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" <<EOF
|
||||
[Unit]
|
||||
Description=Jingrow Worker Service (Dramatiq)
|
||||
After=network.target redis.service
|
||||
Requires=redis.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${SERVICE_USER}
|
||||
Group=${SERVICE_GROUP}
|
||||
WorkingDirectory=${JINGROW_APP_DIR}
|
||||
Environment="PATH=${uv_dir}:/usr/local/bin:/usr/bin:/bin"
|
||||
Environment="HOME=${HOME}"
|
||||
${env_file_line}
|
||||
ExecStart=${UV_PATH} run python -m jingrow.dramatiq
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=jingrow-worker
|
||||
|
||||
# 安全设置
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
success "已生成: $service_file"
|
||||
}
|
||||
|
||||
# 生成 Caddy 服务 systemd 文件
|
||||
generate_caddy_service() {
|
||||
info "生成 Caddy 服务 systemd 文件..."
|
||||
|
||||
# 确保 tmp 目录存在
|
||||
mkdir -p "$TMP_DIR"
|
||||
|
||||
local service_file="$TMP_DIR/caddy.service"
|
||||
|
||||
cat > "$service_file" <<EOF
|
||||
[Unit]
|
||||
Description=Caddy - Modern HTTP/2 web server
|
||||
Documentation=https://caddyserver.com/docs/
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
User=${SERVICE_USER}
|
||||
Group=${SERVICE_GROUP}
|
||||
WorkingDirectory=${PROJECT_ROOT}
|
||||
ExecStart=${CADDY_INSTALL_DIR}/caddy run --environ --config ${CADDY_CONFIG_DIR}/Caddyfile
|
||||
ExecReload=${CADDY_INSTALL_DIR}/caddy reload --config ${CADDY_CONFIG_DIR}/Caddyfile --force
|
||||
TimeoutStopSec=5s
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=512
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
success "已生成: $service_file"
|
||||
}
|
||||
|
||||
# 安装 systemd 服务文件
|
||||
install_services() {
|
||||
# 检查是否有 --no-install 参数
|
||||
local skip_install=false
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--no-install" ]; then
|
||||
skip_install=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$skip_install" = true ]; then
|
||||
info "跳过服务安装(--no-install),服务文件已生成在 $TMP_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "安装 systemd 服务文件..."
|
||||
|
||||
local services=("jingrow-backend.service" "jingrow-worker.service" "caddy.service")
|
||||
|
||||
for service in "${services[@]}"; do
|
||||
local service_file="$TMP_DIR/$service"
|
||||
local service_name=$(basename "$service")
|
||||
if [ -f "$service_file" ]; then
|
||||
info "安装 $service_name..."
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
cp "$service_file" "/etc/systemd/system/$service_name"
|
||||
chmod 644 "/etc/systemd/system/$service_name"
|
||||
else
|
||||
sudo cp "$service_file" "/etc/systemd/system/$service_name"
|
||||
sudo chmod 644 "/etc/systemd/system/$service_name"
|
||||
fi
|
||||
success "$service_name 已安装"
|
||||
else
|
||||
warn "$service_name 不存在,跳过"
|
||||
fi
|
||||
done
|
||||
|
||||
# 重新加载 systemd
|
||||
info "重新加载 systemd..."
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
systemctl daemon-reload
|
||||
else
|
||||
sudo systemctl daemon-reload
|
||||
fi
|
||||
|
||||
success "服务文件安装完成"
|
||||
info "使用以下命令管理服务:"
|
||||
echo " sudo systemctl start jingrow-backend"
|
||||
echo " sudo systemctl start jingrow-worker"
|
||||
echo " sudo systemctl start caddy"
|
||||
echo " sudo systemctl enable jingrow-backend jingrow-worker caddy # 开机自启"
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
info "开始生成生产环境服务文件..."
|
||||
|
||||
# 检测架构
|
||||
detect_arch
|
||||
|
||||
# 检查必要工具
|
||||
if ! command -v curl &> /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 "$@"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user