jingrow/setup_production.sh

407 lines
11 KiB
Bash
Executable File
Raw 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
# 一键生成生产环境服务文件脚本(静默安装)
# 包括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 "$@"