414 lines
12 KiB
Bash
Executable File
414 lines
12 KiB
Bash
Executable File
#!/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"
|
||
local env_file_line=""
|
||
|
||
# 如果 .env 文件存在,使用 EnvironmentFile 引用(Caddyfile 会读取环境变量)
|
||
if [ -f "$PROJECT_ROOT/.env" ]; then
|
||
env_file_line="EnvironmentFile=${PROJECT_ROOT}/.env"
|
||
fi
|
||
|
||
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}
|
||
${env_file_line}
|
||
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 "$@"
|
||
|