还在用 FTP 一个个文件上传网站?改个 CSS 都要等半天,还容易漏传文件覆盖错版本。今天教你用 Git + Webhook 搭建一套自动化部署流水线,本地 push 代码,服务器自动拉取、更新、重启,全程 10 秒内完成。
一、架构原理
本地开发 → Git 提交 → 推送至远程仓库 → Webhook 触发 → 服务器自动拉取 → 执行部署脚本 → 网站更新
用到的工具:
- Git:版本控制
- Gitea / GitHub / GitLab:远程仓库(Gitea 可自建,适合私有项目)
- Webhook:代码推送时触发 HTTP 请求
- 部署脚本:服务器端执行 git pull + 后续操作
二、服务器端安装 Gitea(自建 Git 仓库)
如果你不想用 GitHub 公开仓库,Gitea 是最佳选择——轻量、开源、一个二进制文件就能跑。
- 下载安装
# 创建用户
useradd -r -m -s /bin/bash git
# 下载 Gitea(以 1.21 为例)
wget -O /usr/local/bin/gitea https://dl.gitea.com/gitea/1.21/gitea-1.21-linux-amd64
chmod +x /usr/local/bin/gitea
# 创建目录
mkdir -p /var/lib/gitea/{custom,data,log}
chown -R git:git /var/lib/gitea
chmod -R 750 /var/lib/gitea
- 创建 systemd 服务
/etc/systemd/system/gitea.service:
[Unit]
Description=Gitea
After=network.target
[Service]
User=git
Group=git
WorkingDirectory=/var/lib/gitea
ExecStart=/usr/local/bin/gitea web --config /var/lib/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
[Install]
WantedBy=multi-user.target
- 启动配置
systemctl enable gitea
systemctl start gitea
浏览器访问 http://your-server:3000,完成初始化配置(数据库用 SQLite 即可,轻量站点够用)。
三、创建仓库并配置 Webhook
- 在 Gitea 创建仓库
登录 Gitea → 右上角 + → 新建仓库 → 名称填 mywebsite → 创建。
- 添加 Webhook
进入仓库 → 设置 → Web 钩子 → 添加 Web 钩子 → Gitea
目标 URL:http://your-server:9000/hook/deploy
HTTP 方法:POST
密钥:设置一个随机字符串,如 MySecretKey123
触发条件:只勾选 推送事件
四、服务器端部署脚本
- 创建 Webhook 接收服务
用 Python 写一个简易的 HTTP 接收器,监听 9000 端口:
/opt/deploy-server.py:
#!/usr/bin/env python3
import hmac
import hashlib
import subprocess
from http.server import BaseHTTPRequestHandler, HTTPServer
# 配置
SECRET = b'MySecretKey123' # 与 Gitea Webhook 密钥一致
REPO_DIR = '/var/www/mywebsite' # 网站代码目录
BRANCH = 'main' # 监听的分支
DEPLOY_SCRIPT = '/opt/deploy.sh' # 部署脚本路径
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != '/hook/deploy':
self.send_error(404)
return
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
# 验证签名
signature = self.headers.get('X-Gitea-Signature', '')
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
self.send_error(403)
return
# 执行部署
try:
result = subprocess.run(
['bash', DEPLOY_SCRIPT],
capture_output=True,
text=True,
timeout=60
)
print(f"Deploy output: {result.stdout}")
if result.returncode != 0:
print(f"Deploy error: {result.stderr}")
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
except Exception as e:
print(f"Deploy failed: {e}")
self.send_error(500)
def log_message(self, format, *args):
# 简化日志输出
print(f"[{self.log_date_time_string()}] {format % args}")
if __name__ == '__main__':
server = HTTPServer(('127.0.0.1', 9000), Handler)
print('Deploy server running on port 9000...')
server.serve_forever()
- 创建部署脚本
/opt/deploy.sh:
#!/bin/bash
set -e
REPO_DIR="/var/www/mywebsite"
LOG_FILE="/var/log/deploy.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] 开始部署..." >> $LOG_FILE
cd $REPO_DIR
# 拉取最新代码
echo "[$TIMESTAMP] 拉取代码..." >> $LOG_FILE
git fetch origin
git reset --hard origin/main
# 如果是 PHP 项目,可以在这里加 composer 安装
# if [ -f "composer.json" ]; then
# composer install --no-dev --optimize-autoloader
# fi
# 如果是 Node 项目,构建前端
# if [ -f "package.json" ]; then
# npm ci
# npm run build
# fi
# 清理缓存(以 WordPress 为例)
# wp cache flush --allow-root
# 重启相关服务
echo "[$TIMESTAMP] 重启 Nginx..." >> $LOG_FILE
systemctl reload nginx
# 设置正确权限
chown -R www-data:www-data $REPO_DIR
find $REPO_DIR -type f -exec chmod 644 {} \;
find $REPO_DIR -type d -exec chmod 755 {} \;
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] 部署完成" >> $LOG_FILE
赋予执行权限:
chmod +x /opt/deploy-server.py /opt/deploy.sh
- 用 systemd 管理部署服务
/etc/systemd/system/deploy-server.service:
[Unit]
Description=Git Auto Deploy Server
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/deploy-server.py
Restart=always
User=root
[Install]
WantedBy=multi-user.target
systemctl enable deploy-server
systemctl start deploy-server
五、本地开发配置
- 初始化本地仓库
cd ~/projects/mywebsite
git init
git add .
git commit -m "Initial commit"
- 添加远程仓库
git remote add origin http://your-server:3000/gituser/mywebsite.git
- 推送代码
git push -u origin main
此时 Gitea 会触发 Webhook,服务器自动执行部署脚本,网站更新。
六、进阶:多环境部署
生产环境 + 测试环境分离
本地仓库结构:
main 分支 → 生产环境 (www.yourdomain.com)
dev 分支 → 测试环境 (dev.yourdomain.com)
修改部署脚本,根据分支执行不同操作:
#!/bin/bash
set -e
BRANCH=$(git rev-parse --abbrev-ref HEAD)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
if [ "$BRANCH" = "main" ]; then
TARGET_DIR="/var/www/production"
NGINX_CONF="production"
echo "[$TIMESTAMP] 部署生产环境..."
elif [ "$BRANCH" = "dev" ]; then
TARGET_DIR="/var/www/staging"
NGINX_CONF="staging"
echo "[$TIMESTAMP] 部署测试环境..."
else
echo "[$TIMESTAMP] 未知分支,跳过部署"
exit 0
fi
cd $TARGET_DIR
git fetch origin
git reset --hard origin/$BRANCH
# 环境特定操作
systemctl reload nginx
echo "[$TIMESTAMP] $BRANCH 部署完成"
七、安全加固
- Webhook 签名验证
上面已经实现了 HMAC-SHA256 签名验证,确保请求来自你的 Gitea。
- 限制 Webhook 来源 IP
在防火墙只允许 Gitea 服务器 IP 访问 9000 端口:
iptables -A INPUT -p tcp --dport 9000 -s 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp --dport 9000 -j DROP
- 使用 HTTPS
生产环境建议用 Nginx 反向代理,给 Gitea 和 Webhook 都加上 HTTPS:
server {
listen 443 ssl;
server_name git.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 443 ssl;
server_name hook.yourdomain.com;
location /hook/ {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
}
}
- 部署日志审计
定期查看部署日志,发现异常及时处理:
tail -f /var/log/deploy.log
八、与 GitHub/GitLab 集成
如果你用 GitHub 而不是自建 Gitea:
GitHub Webhook 配置:
Settings → Webhooks → Add webhook
- Payload URL:
https://hook.yourdomain.com/hook/deploy - Content type:
application/json - Secret:
MySecretKey123 - Which events: Just the push event
修改 Python 接收器验证方式:
GitHub 用 X-Hub-Signature-256 头,算法是 sha256=...:
signature = self.headers.get('X-Hub-Signature-256', '').replace('sha256=', '')
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
九、常见问题排查
问题一:Webhook 触发但代码没更新
检查:
- 部署服务是否运行:
systemctl status deploy-server - 日志输出:
tail /var/log/deploy.log - 目录权限:
git用户是否有写权限到/var/www/mywebsite
问题二:本地 push 失败
git remote -v
# 确认远程地址正确
git push origin main
# 如果提示权限不足,检查 Gitea 用户权限
问题三:Webhook 返回 403
- 检查密钥是否一致
- 检查
X-Gitea-Signature头是否存在 - 确认时间戳不过期(某些平台会校验时间)
十、完整工作流总结
日常开发流程:
- 本地修改代码
git add . && git commit -m "描述"git push origin main- 等待 10 秒,刷新网站查看效果
- 如果出问题,
git revert回滚,再次 push 自动恢复
相比 FTP 上传的优势:
- 版本可追溯,每次更新都有记录
- 团队协作不冲突,多人同时开发
- 回滚秒级完成,出问题立即恢复
- 自动化部署,专注写代码
总结
Git + Webhook 自动化部署是现代网站运维的标准做法。初期配置需要 30 分钟,但之后每次更新节省的时间和避免的失误,绝对值回票价。建议从个人项目开始练手,熟练后推广到团队所有项目。
