ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

接手外包项目后的两件事:修复分裂的 Git 历史 + 搭建 Gitee 推送自动部署(含 10 个真实踩坑)

接手外包项目后的两件事:修复分裂的 Git 历史 + 搭建 Gitee 推送自动部署(含 10 个真实踩坑) 前言接手一个外包开发的 PHP 项目做了两件事把分裂成两条的 Git 历史合回一条以及打通「本地 push → Gitee → 宝塔服务器自动更新」的链路。过程中踩的坑比预想多得多。网上教程大多只写理想路径而真实环境里挡路的是自签证书、老版本 Git 的行为差异、插件路由未注册、面板反扫描机制这类细节。本文按实际排查顺序记录每个坑都给出定位方法和判据。环境CentOS 7 宝塔面板 v11.8.1 PHP 7.4 Gitee 私有仓库。一、两个 master不是分支是两棵树现象在 VSCode 的 Git 图里看到两条完全平行的提交线各自带着一个master标签一条是origin/master3 个提交另一条是本地master74 个提交。定位gitmerge-base master origin/master# 无输出 —— 没有共同祖先gitrev-list --max-parents0--all# 输出两个根提交两个根提交意味着这是两棵互不相干的历史树unrelated histories不是普通的分支分叉。成因接手项目时没有沿用原有的.git目录而是在工作目录里重新git init 一次性 “first commit” 推到了自己的新仓库。外包的 74 条提交历史被压扁成了一个提交。而本地的克隆仍然保留着原始历史于是两边对不上。修复先确认两边的代码内容差异有多大gitdiff--statmaster origin/master如果差异只是几个文件说明代码基本一致只是历史被压扁了。那么把远程那几个「真实提交」摘到本地这条完整历史上再覆盖远程gitfetch origingitcherry-pickcommit1commit2# 只摘有内容的提交跳过那个全量快照式的 first commitgitpush --force-with-lease origin mastercherry-pick只看提交的 diff不要求共同祖先所以跨历史树也能用。强推前必须确认两件事有没有别人也在用这个仓库有的话他们必须重新 clone。旧历史里有没有敏感信息如果当初压扁历史正是为了抹掉密钥那就不该恢复应该反过来以远程为准。正确的接手姿势以后遇到「接手项目、换到自己的仓库」保留原.git目录直接改远程地址即可历史不会断gitremote set-url origin新仓库地址gitpush-uorigin master二、清理运行时产物老项目常见问题缓存、日志、上传文件全被跟踪每次git status都是一堆脏 diff。# 更新 .gitignore 后gitrm--cachedcache/xxx.json member/callback/xxx.loggitcommit-mchore: 忽略运行时产物⚠️ 这里有个容易忽略的连锁反应git rm --cached提交之后服务器执行git pull时会把这些文件从磁盘上删掉。缓存文件无所谓会重新生成但日志文件里的历史记录会丢。另一个判断点不要无差别地忽略整个uploads/。这类目录常常混着「后台上传的真实站点资产」logo、广告图和「程序生成的临时文件」。全忽略会导致重新部署时资产丢失。只忽略生成物cache/ uploads/sample_report_*.html *.log三、搭建 Gitee → 宝塔自动部署目标链路本地 git push → Gitee WebHook → 宝塔 WebHook 插件 → 服务器 git fetch reset --hard坑 1Gitee 拒绝自签证书第一次配好 WebHook测试报错SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target这个报错其实是好消息——TCP 连接已经建立说明云服务器安全组和防火墙都放行了卡住的只是 TLS 握手。原因宝塔面板默认用自签证书而 WebHook 地址填的是 IP。Gitee 的 Java 客户端校验证书链时找不到可信签发机构。自签证书天然不在任何信任库里这是必然结果。解法给面板绑一个子域名并申请 Let’s Encrypt 证书。# 1. DNS 加 A 记录bt.example.com → 服务器 IP# 2. 验证解析生效注意本机若开着代理ping 可能返回 198.18.x.x 这类 fake-ip结果不可信nslookupbt.example.com223.5.5.5# 3. 面板 → 设置 → 常用设置 → 绑定域名 → 填 bt.example.com# 4. 面板 → 设置 → 常用设置 → 面板SSL配置 → Lets Encrypt → 文件验证 → 确定绑定域名前先记下解锁命令。一旦绑定就只能通过域名访问面板DNS 出问题会把自己关在门外rm-f/www/server/panel/data/domain.confbt restart申请证书时选「文件验证」即可宝塔会自动创建一个用于验证和续签的站点不要手动删除它删了 90 天后证书就续不上。验证证书是否真的生效不要看浏览器点过「继续访问」会留下陈旧例外一直标红。用 curl它和 Gitee 一样走系统信任库curl.exe-Ihttps://bt.example.com:8888/正常返回 HTTP 头 证书受信任报SSL certificate problem 没成功。坑 2装完插件必须重启面板WebHook 插件装好、钩子也建了请求/hook却返回 404htmlheadtitle404 Not Found/title/headbodycenterh1404 Not Found/h1/centerhrcenternginx/center/body/html一开始怀疑是安全入口拦截。排查发现不是ss-lntp|grep8888# LISTEN 0 100 *:8888 users:((BT-Panel,pidxxx,fd3))nginx-T2/dev/null|grep8888# 无输出端口是 BT-PanelPython自己在监听nginx 根本没参与。那个Server: nginx响应头是宝塔伪装的。所以 404 来自面板本体——/hook路由没被加载。解法bt restart重启后立刻返回{code: 1} HTTP 200。插件安装后路由需要重新注册这一步几乎所有教程都没提。坑 3安全入口不影响 /hook顺带澄清一个常见误解宝塔开启安全入口后/hook接口不受影响不需要在 URL 里加入口前缀。实测加了反而还是 404。不要为了让 webhook 工作而关闭安全入口——那等于把面板登录页直接挂在公网上。坑 4宝塔反扫描机制会让 curl 误判排查面板可达性时用 curl 请求安全入口地址返回 404curl-s-o/dev/null-w%{http_code}\nhttps://bt.example.com:8888/a1b2c3d4# 404一度以为面板挂了。但服务端明明正常bt status# Bt-Panel (pid 952) already running# Bt-Task (pid 965) already runningcat/www/server/panel/data/admin_path.pl# /a1b2c3d4 —— 入口配置正确真相是宝塔的反扫描机制检测到非浏览器 User-Agent 就一律返回 404避免被扫描器探测到面板入口。加上浏览器 UA 立刻正常curl-s-i-AMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\https://bt.example.com:8888/a1b2c3d4|head-5# HTTP/1.1 200 OK# Content-Type: text/html; charsetutf-8# Set-Cookie: xxx_ssl...; HttpOnly; Path/排查启示用命令行工具测宝塔面板的 HTTP 可达性时务必带浏览器 UA否则得到的 404 完全没有诊断价值。这个特性也解释了排查/hook时看到的部分 404 噪音。另外/login这类路径在开启安全入口后会返回「请使用正确的入口登录面板」页面——这是正常的拦截不是故障。完整访问地址必须带入口路径https://bt.example.com:8888/a1b2c3d4忘了入口是什么在服务器上执行/etc/init.d/bt default四、服务器目录接管为 Git 仓库坑 5dubious ownershipcd/www/wwwroot/example.comgitconfig--global--addsafe.directory /www/wwwroot/example.comgitinitgitremoteaddorigin gitgitee.com:user/repo.gitgitfetch origingitcheckout-f-bmaster--trackorigin/masterchown-Rwww:www /www/wwwroot/example.comsafe.directory那条是必需的。Git 2.35 有防护目录属主www与执行者root不一致时会拒绝操作。不加这条后面钩子自动执行时也会撞上同样的问题。最后的chown同样不能省——Git 以 root 身份写入的文件属主是 root而 PHP-FPM 跑在www下不改会导致部分功能没权限读写。git init和git fetch都不会动工作目录里的现有文件真正覆盖文件的是git checkout -f。这一步执行前务必有可用备份。认证用部署公钥不用账号密码ssh-keygen-ted25519-Cdeployserver-f/root/.ssh/gitee_deploy-Ncat/root/.ssh/gitee_deploy.pub# 贴到 Gitee → 仓库 → 管理 → 部署公钥管理printfHost gitee.com\n IdentityFile /root/.ssh/gitee_deploy\n StrictHostKeyChecking no\n/root/.ssh/configssh-Tgitgitee.com两个细节-N 必须空密码。钩子是无人值守执行的有密码就无法自动认证。StrictHostKeyChecking no避免首次连接弹出「是否信任此主机」的交互确认——自动执行时没人能回答会直接卡死。部署公钥是只读的服务器即使被拿下也改不了仓库比账号密码安全得多。坑 6Git 1.8 不更新 origin/masterCentOS 7 自带 Git 1.8.3.12013 年。钩子脚本这样写会永远部署不到最新代码gitfetch origin mastergitreset--hardorigin/master# ❌ 重置到过期的引用原因老版本里git fetch origin master只把结果写进FETCH_HEAD不更新refs/remotes/origin/master。现象很具迷惑性fetch 明明成功了输出也正常但git log显示的还是旧提交。坑 7FETCH_HEAD 同名文件改成git reset --hard FETCH_HEAD后又报fatal: ambiguous argument FETCH_HEAD: both revision and filename工作目录里恰好有个叫FETCH_HEAD的空文件某次误操作的重定向产物Git 无法判断你指的是文件还是引用。最终的稳妥写法——用显式 refspec既绕开版本差异也不依赖任何容易被同名文件干扰的特殊名字#!/bin/bashset-eSITE_DIR/www/wwwroot/example.comcd$SITE_DIRgitfetch origin master:refs/remotes/origin/mastergitreset--hardorigin/masterchown-Rwww:www$SITE_DIRechodeployed:$(gitrev-parse--shortHEAD)at$(date)master:refs/remotes/origin/master显式指定「把远程 master 强制写入本地的远程跟踪引用」行为在所有 Git 版本上一致。五、三个安全问题坑 8备份包放进了网站根目录cd/www/wwwroottarczf ~/backup.tar.gz example.com# 结果落在了站点目录里宝塔默认的 nginx 敏感文件规则挡了.sql、.bak、.log但不挡.tar.gz。也就是说全站源码含config/config.php里的数据库密码可以被任何人直接下载。写备份命令用绝对路径 -C参数tarczf /root/backup-$(date%F).tar.gz-C/www/wwwroot example.com坑 9备份是空的更严重的问题上面那个错位的备份实际大小只有45 字节——一个空 gzip 归档。整个接管过程实际上没有安全网。备份必须验证不能假设它成功了ls-lh/root/backup-*.tar.gz# 看大小tartzf /root/backup-*.tar.gz|wc-l# 看文件数同理备份命令里慎用2/dev/null——它会把「文件不存在」这类关键错误一并吞掉。坑 10明文密码躺在服务器上cat/root/.git-credentials# https://user:passwordgitee.com# https://user:passwordcodeup.aliyun.comgitconfig--list|grepcredential# credential.helperstorecredential.helperstore是全局配置此后任何在这台机器上输入的 Git 密码都会明文追加进这个文件。而外包开发者有过这台服务器的 root 权限。改用 SSH 后清理gitconfig--global--unsetcredential.helperrm-f/root/.git-credentials先unset再删否则下次触发认证又会重新生成。清理文件不等于凭据安全——密码已经暴露过必须去平台后台轮换。同理代码里的硬编码密钥即使被 refactor 掉了只要旧值没换历史记录里的那份依然有效。六、验证与恢复端到端验证# 本地gitcommit --allow-empty-mtest: 验证自动部署gitpush origin master# 15 秒后在服务器cd/www/wwwroot/example.comgitlog--oneline-1同时检查未被跟踪的配置文件是否安然无恙ls-lconfig/config.php .user.ini# 修改时间应该还是很久以前以及敏感路径防护foruinhttps://example.com/https://example.com/.git/confighttps://example.com/.git/HEAD;doprintf%-45s %s\n$u$(curl-s-o/dev/null-w%{http_code}$u)done# 首页 200.git 路径全部 404顺带一提宝塔默认的 nginx 站点配置里已经包含了「敏感目录」规则.git、.svn、.idea等都在其中通常不需要额外添加。加之前先读一遍现有配置——nginx 的正则 location 按定义顺序匹配追加到文件末尾的规则很可能永远不会命中成为死代码。误删文件的恢复被git rm --cached移出版本库、又被reset --hard从磁盘删掉的文件内容仍然在 Git 历史里。从移除操作的父提交里取出来即可gitshow移除提交^:path/to/file/root/file.bakcp/root/file.bak path/to/filechownwww:www path/to/file因为该文件现在已被.gitignore忽略还原后不会再被后续部署删除。总结Checklist搭建这类自动部署时按这个顺序走能少踩很多坑✅ 备份并验证归档大小和文件数✅ 备份放/root绝不放网站根目录✅ 面板绑定域名 Let’s Encrypt 证书自签证书过不了 Gitee 的校验✅ 用curl而非浏览器验证证书是否受信任✅ 用命令行测面板可达性时带浏览器 UA否则反扫描机制会返回误导性的 404✅ WebHook 插件装完bt restart✅ 用部署公钥只读、空密码而非账号密码✅ 配safe.directory部署后chown✅ 脚本用显式 refspec不依赖origin/master或FETCH_HEAD的隐式行为✅ 清理.git-credentials并去平台轮换密码✅ 空提交做端到端验证确认配置文件未被触碰最大的教训其实不是技术性的每一个「应该没问题」的环节都要有验证判据而每一个报错也要先确认它是否真的是报错。这次遇到的两类问题恰好构成对照——备份「看起来成功了」实际是 45 字节的空壳面板「看起来挂了」实际是反扫描机制的正常行为。前者要验大小、验文件数后者要换个 UA 再试。假设和结论之间永远需要一条可执行的判据。文中所有 IP、域名、密钥、路径均已替换为占位符。
返回列表