使用git的post-receive实现自动部署

这事断断续续折腾我大概两周时间,这就把我经历的事分享给大家。

为了实现在本地提交这个blog项目能不用手动到线上更新而努力着,刚开始在找解决办法时以为就那么简单,结果却折腾在docker、linux权限和如何写shell的几个关键知识里面。

流程

这里先简单介绍一下部署流程:

1
本地--[推送代码]-->git服务器--[触发]-->`post-receive`钩子--[拉取代码]-->部署目录

目前情况

以下是我目前的情况:

  • git服务和网站服务在同一个服务器上
  • 项目不存在构建,只把生成的内容放到固定分支
  • git服务用的是gogs
  • 用ssh方式拉取代码

处理办法

好了,具体怎么做?

  1. 进入gogs打开你的[仓库设置]->[管理Git钩子]
  2. 编辑post-receive

然后把下面代码添加进去:

1
2
3
4
5
6
7
8
9
#!/bin/bash
while read oldrev newrev refname
do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "gh-pages" == "$branch" ]; then
# 在这添加你需要做的部署行为
fi
done

接下来得说说关键部分的部署代码,根据我目前的情况,在部署代码之前需要做两件事情:

移除GIT_DIR环境变量

这里由于GIT_DIR默认为’.’,会导致访问地址不对并给出下面提示:

“fatal: Not a git repository: ‘.’”

处理办法:

1
unset GIT_DIR

生成密钥

为满足使用ssh方式拉取代码就会需要密钥,我gogs是跑在docker容器下,第一步先进入容器然后生成密钥:

1
ssh-keygen -t rsa -C "your name or email"

如何生成密钥这里就不详细说明,各位到网上自查攻略吧!

再来到gogs的[仓库设置]->[管理部署密钥]->[添加部署密钥]把公钥添加进去就能用了。

自定义ssh请求

为满足ssh指定加载特定密钥,需要在git执行前自定义ssh的操作:

1
GIT_SSH_COMMAND="ssh -i /git/.ssh/id_rsa" git fetch

GIT_SSH_COMMAND 只适用于 git version 大于 2.6 的版本

执行后会出现一堆提示而且不会执行git的具体操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
50:e6:cb:58:bc:b7:a3:f6:e8:8f:46:a7:c1:5f:c2:df.
Please contact your system administrator.
Add correct host key in /git/.ssh/known_hosts to get rid of this message.
Offending key in /git/.ssh/known_hosts:7
RSA host key for 192.168.0.4 has changed and you have requested strict checking.
Host key verification failed.

发送ssh请求会询问是否把相关信息记录至known_hosts,但是由于是脚本处理所以并没得到处理,最后会给出上面的提示。

解决办法有三种:

  1. 删除提示信息中,对应的行数,例如上例,需要删除/git/.ssh/known_hosts文件的第7行。
  2. 删除整份/git/.ssh/known_hosts文件。

上面两种并不能从根本解决问题,这里推荐在ssh命令后添加下面两个参数的第三种解决办法:

  • -o StrictHostKeyChecking=no 不检查公钥
  • -o UserKnownHostsFile=/dev/null 不更新known_hosts
1
GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /git/.ssh/id_rsa" git fetch

写成这样就能带上给定的ssh密钥,到这里基本把需要解决的问题都解决了,下面给上完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/bash
while read oldrev newrev refname
do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "gh-pages" == "$branch" ]; then
printf "branch: $branch\n"
PROJECT_ROOT=/www
PROJECT_PATH="$PROJECT_ROOT/[目录地址]"
REPO_PATH="[git地址]"
REPO_BRANCH="$branch"
# GIT_DIR默认为'.',会导致访问地址不对,故需要移除GIT_DIR环境变量
# > https://alfred-long.iteye.com/blog/1836347
unset GIT_DIR
# `-o StrictHostKeyChecking=no` 不检查公钥
# `-o UserKnownHostsFile=/dev/null` 不更新known_hosts
# `-i /git/.ssh/uxfeel` 指定密钥
# > https://www.cnblogs.com/zhengah/p/4959682.html
GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /git/.ssh/uxfeel"
export GIT_SSH_COMMAND
if [ ! -d "$PROJECT_PATH" ]; then
printf "git clone -b $REPO_BRANCH $REPO_PATH \n"
cd "$PROJECT_ROOT"
git clone -b "$REPO_BRANCH" "$REPO_PATH"
printf "git clone finish.\n\n"
else
cd "$PROJECT_PATH"
printf "git fetch \n"
git fetch
printf "git fetch finish.\n\n"
printf "git reset \n"
git reset --hard "origin/$REPO_BRANCH"
printf "git reset finish."
fi
fi
done

这里说几点需要注意的:

  • 外部部署环境的目录权限尽量和容器里面生成目录的权限保持一致(能解决大部分的权限问题)
  • 搞清楚shell当前的权限所属再写shell(少走弯路,特别是你不太清醒的时候)
  • 应用只全局的变量一定要export,export,export(重要的事情说三次,这个害我花了不少时间)

如果你的环境没那么复杂是真的不用考虑那么多,自己撸起可能会比我少走很多弯路,希望大家处理权限的时候记住:linux权限规则还是linux的权限规则,不要给docker给迷惑了!