1.背景
服务器上有多个erp站点(圣地亚哥,非国内),都是同一套代码,部署的时候不想进每个站点挨着拉一遍,先用了jenkins,但是服务器内存占用有点高,就换了别的方式。用了这个方法内存从之前的常驻80降低到了常驻56,最少给老板省了200块钱,哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈
2.原理
推送代码到gite自动同步到github,触发代码的自动部署webhook,webhook里面写了git pull/composer install等命令,消息队列的自动部署是写好配置文件,通过脚本把配置文件复制到宝塔supervisor的配置文件目录,进而实现自动部署,有一个问题需要知悉!!!gite同步到github有小小的问题,第一分钟推了代码可以立即同步到github,第一分钟10s再次推送就没办法同步到github,具体规律尚不清晰
3.github操作
创建一个全新的空的仓库!全新的!空的!
4.宝塔操作
下面是部署脚本,在宝塔的webhook里面配置这个脚本,完成之后获取密钥就可以看到完整密钥,获取密钥配置到gite就可以了
#!/usr/bin/env bash
set -Eeuo pipefail
# ======================
# 配置
# ======================
NAME=”video”
PROJECT_PATH=”/www/wwwroot/hsgo.test”
RUN_USER=”www”
COMPOSER=”composer”
PHP=”php”ROLLBACK_COMMIT=””
# ======================
# 日志
# ======================
log() {
echo “”
echo “[$(date ‘+%F %T’)] $1”
}# ======================
# 错误处理
# ======================
on_error() {
local exit_code=$?
local line=$1
echo “”
echo “❌ 部署失败(第 ${line} 行,退出码 ${exit_code})”if [ -n “$ROLLBACK_COMMIT” ]; then
echo “”
echo “如需回滚代码,请执行:”
echo ” sudo -u $RUN_USER bash -c \”cd ‘$PROJECT_PATH’ && git reset –hard $ROLLBACK_COMMIT\””
fi
exit “$exit_code”
}
trap ‘on_error $LINENO’ ERRdie() {
echo “”
echo “❌ 部署失败:$1″
exit 1
}# ======================
# 以运行用户在项目目录执行命令
# ======================
run() {
local desc=”$1”
shift
log “$desc”
sudo -u “$RUN_USER” bash -c “cd ‘$PROJECT_PATH’ && $*” \
|| die “$desc”
}run_capture() {
sudo -u “$RUN_USER” bash -c “cd ‘$PROJECT_PATH’ && $*”
}# 检查仓库是否处于冲突 / merge 未完成状态(不依赖 git 退出码)
check_git_conflict() {
local stage=”$1″if run_capture “test -f .git/MERGE_HEAD”; then
echo “”
echo “❌ ${stage}:检测到未完成的 merge(.git/MERGE_HEAD 存在)”
run_capture “git status –short” || true
die “${stage} 失败,服务器仓库存在未解决的冲突”
filocal unmerged
unmerged=”$(run_capture “git ls-files -u” || true)”
if [ -n “$unmerged” ]; then
echo “”
echo “❌ ${stage}:检测到未解决冲突的文件”
echo “$unmerged”
die “${stage} 失败,服务器仓库存在未解决的冲突”
fi
}echo “=================================”
echo “开始部署:$(date ‘+%F %T’)”
echo “项目:$NAME”
echo “=================================”START_TS=$(date +%s)
# —- 前置检查 —-
[ -d “$PROJECT_PATH” ] || die “项目目录不存在:$PROJECT_PATH”
[ -d “$PROJECT_PATH/.git” ] || die “不是 git 仓库:$PROJECT_PATH”
id “$RUN_USER” >/dev/null 2>&1 || die “用户不存在:$RUN_USER”
command -v “$COMPOSER” >/dev/null || die “未找到 composer”# 拉取前先检查是否已有历史遗留冲突
check_git_conflict “部署前检查”ROLLBACK_COMMIT=”$(run_capture ‘git rev-parse HEAD’)”
log “当前版本:$ROLLBACK_COMMIT”# —- 更新代码 —-
log “更新代码”
if ! run_capture “git pull”; then
echo “”
echo “当前工作区状态:”
run_capture “git status –short” || true
die “更新代码”
fi# 拉取后再校验一次(兜底:即使 git pull 返回 0 也拦住冲突态)
check_git_conflict “git pull”NEW_COMMIT=”$(run_capture ‘git rev-parse HEAD’)”
if [ “$NEW_COMMIT” = “$ROLLBACK_COMMIT” ]; then
log “代码无变化(已是最新),仍继续执行依赖与迁移检查”
else
log “更新到版本:$NEW_COMMIT”
fi# —- 安装依赖(生产参数)—-
run “安装 Composer 依赖” \
“$COMPOSER install –no-dev –optimize-autoloader –no-interaction –no-progress”# —- 数据库迁移 —-
log “执行数据库迁移”
MIGRATE_LOG=”/tmp/deploy_migrate.log”
if ! sudo -u “$RUN_USER” bash -c “cd ‘$PROJECT_PATH’ && $PHP think migrate:run” >”$MIGRATE_LOG” 2>&1; then
cat “$MIGRATE_LOG”
die “执行数据库迁移”
fi
cat “$MIGRATE_LOG”# think 出错时可能仍返回 0,按输出兜底判定
grep -qE ‘All Done|Nothing to migrate’ “$MIGRATE_LOG” \
|| die “数据库迁移未正常结束(详见 $MIGRATE_LOG)”
if grep -qiE ‘exception|fatal|sqlstate’ “$MIGRATE_LOG”; then
die “数据库迁移报错(详见 $MIGRATE_LOG)”
fi# —- 清理 / 生成缓存 —-
log “清理并优化缓存”
OPT_LOG=”/tmp/deploy_optimize.log”
if ! sudo -u “$RUN_USER” bash -c “cd ‘$PROJECT_PATH’ && $PHP think optimize” >”$OPT_LOG” 2>&1; then
cat “$OPT_LOG”
die “清理并优化缓存”
fi
cat “$OPT_LOG”grep -q ‘optimize:config run succeed’ “$OPT_LOG” || die “optimize:config 失败(详见 $OPT_LOG)”
grep -q ‘optimize:route run succeed’ “$OPT_LOG” || die “optimize:route 失败(详见 $OPT_LOG)”
grep -q ‘optimize:schema run succeed’ “$OPT_LOG” || die “optimize:schema 失败(详见 $OPT_LOG)”# —- 修正文件权限 —-
run “修正 runtime 权限” “chown -R $RUN_USER:$RUN_USER runtime && chmod -R 775 runtime”# —- 同步消息队列守护进程配置 —-
log “同步消息队列守护进程配置”
if ! sudo -u root bash “${PROJECT_PATH}/public/supervisor/supervisor.sh”; then
echo “”
echo “提示:检查 .env 中是否已配置 SIGN(缺失时退出码 12),”
echo ” 以及 ${PROJECT_PATH}/.env 是否存在(缺失时退出码 11)。”
die “同步 Supervisor 队列配置”
fi
log “队列守护进程配置已同步”END_TS=$(date +%s)
echo “”
webhook脚本
echo “=================================”
echo “✅ 部署成功”
echo “版本:$NEW_COMMIT”
echo “耗时:$((END_TS – START_TS)) 秒”
echo “结束时间:$(date ‘+%F %T’)”
echo “=================================”
5.gite操作
gite项目仓库–管理–WebHooks–配置自己的url和密码–在宝塔的软件webhook进行配置获取
gite项目仓库–管理–仓库镜像管理–和新创建的github仓库进行关联
6.github自动部署代码
推送到gite之后,gite会自动同步到github镜像仓库,有了下面的代码就能让github自动请求webhook地址
github自动actionname: Deploy on: push: branches: - api jobs: deploy: name: 触发部署 runs-on: ubuntu-latest steps: - name: 23 run: | curl -s "http://bt.zntag.com:8888/hook?access_key=VoUNrzVy5425cy0AmGHJ0EkY4XWGU" - name: 34 run: | curl -s "http://bt.zntag.com:8888/hook?access_key=Dy8mys3451WkjsT6QvWqZ4ER8c3JNkciYT" - name: 234234 run: | curl -s "http://bt.zntag.com:8888/hook?access_key=BOst00lQi345a7BiOzfkm2gASuR4e1"
7.webhook–消息队列和socket
public/supervisor下的自动配置supervisor脚本
supervisor配置脚本#!/bin/bash # 获取脚本的绝对路径 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 定义 .env 文件路径(上上层目录:父目录的父目录) PARENT_DIR=$(dirname "$SCRIPT_DIR") GRANDPARENT_DIR=$(dirname "$PARENT_DIR") ENV_PATH="$GRANDPARENT_DIR/.env" # 检查 .env 文件是否存在 if [ ! -f "$ENV_PATH" ]; then echo "错误: 找不到 .env 文件" exit 11 fi # 安全地读取 .env 文件,获取sign while IFS= read -r line || [[ -n "$line" ]]; do # 跳过空行和注释 if [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*# ]]; then continue fi # 移除 export 关键字和空格 line_clean=$(echo "$line" | sed 's/^export[[:space:]]*//') # 提取键值对 key=$(echo "$line_clean" | cut -d '=' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') value=$(echo "$line_clean" | cut -d '=' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') # 去除可能的引号 value=$(echo "$value" | sed -e "s/^['\"]//;s/['\"]$//") # 根据键设置变量(注意:.env 中是大写的 SIGN) case "$key" in SIGN) sign="$value" ;; esac done < "$ENV_PATH" # 检查必需的变量 if [ -z "${sign}" ]; then echo "错误: .env 文件中缺少 'SIGN' 配置" exit 12 fi # supervisor 配置路径 supervisor_path="/www/server/panel/plugin/supervisor/profile" # 创建一个函数来处理文件复制和替换逻辑 process_file() { local source_file="$1" local target_file="$2" if [ -e "$source_file" ]; then # 读取文件内容 content=$(cat "$source_file") # 替换 %path% 和 %sign%(注意模板中的占位符是小写) # 将模板中的小写占位符替换为实际值 replaced_content=$(echo "$content" | sed "s|%path%|${GRANDPARENT_DIR}/|g; s|%sign%|$sign|g") # 检查目标文件是否存在 if [ ! -e "$target_file" ]; then echo "$replaced_content" > "$target_file" else # 目标文件存在,比较内容 if ! echo "$replaced_content" | diff -q "$target_file" - > /dev/null 2>&1; then echo "更新文件: $target_file" echo "$replaced_content" > "$target_file" else echo "文件内容相同,无需更新: $target_file" fi fi else echo "警告: 源文件不存在: $source_file" fi } # 处理每个文件 for file in "$SCRIPT_DIR"/*.ini; do if [ -f "$file" ]; then filename="${file##*/}" filename="${filename%.ini}" # 去掉 .ini 后缀 origin="${SCRIPT_DIR}/${filename}.ini" target="${supervisor_path}/hsl_${filename}_${sign}.ini" process_file "$origin" "$target" fi done $(/www/server/panel/pyenv/bin/supervisorctl update) echo "处理完成!"
消息队列配置文件如下,这么写推送到服务器能自动在supervisor创建守护进程
配置文件[program:hsl_expire_%sign%] command=php think queue:listen --queue=expire_%sign% directory=%path% autorestart=true startsecs=3 startretries=3 stdout_logfile=/www/server/panel/plugin/supervisor/log/expire_%sign%.out.log stderr_logfile=/www/server/panel/plugin/supervisor/log/expire_%sign%.err.log stdout_logfile_maxbytes=2MB stderr_logfile_maxbytes=2MB user=www priority=999 numprocs=1 process_name=%(program_name)s_%(process_num)02d
.env配置,和配置文件搭配使用
.env配置APP_DEBUG = false APP_URL = https://a23234234234234.cn/ SIGN = amuse TIMEZONE = America/Santiago REDIS_SELECT = 3 REGISTER_ADDRESS = 127.0.0.1:1206 GATEWAY_ADDRESS = websocket://0.0.0.0:8206 GATEWAY_START_PORT = 4006
