Gitea 自建 Git 服务部署

Gitea 是一个 Go 实现了非常轻量级的 Git 服务。功能上与成熟的 Gitlab 相比,相差较大,但是胜在简单快捷。如果没有奇怪的问题,大概 10 分钟就可以成功使用 Docker 部署

我们假定在把数据文件存储在/opt/gitea目录中,将该文件挂载到 Docker 的/data目录。
由于在 Docker 中启动,而 Git 功能依赖于 SSH 服务,因此需要做一个端口转发,具体实现见后文,需要首先在宿主机建立相应的 Git 账户

上述任务完成后,执行下面的 Docker 即可启动 Gitea 服务。

docker run -d --rm --name=gitea -p 10022:22 -p 10080:3000 -v /opt/gitea:/data -v /home/git/.ssh/:/data/git/.ssh gitea/gitea:latest

这样,访问 10080 端口,即可进入 Gitea 页面。首次进入需要进行初始化配置,不过既然是图形化配置,识字就能配置好。

上面的文件可以存成一个 Bash 脚本,如果后面有修改文件,或其他操作需要重启 Gitea,只需要docker stop gitea,然后再执行上面的脚本即可(不需要清理 Docker,已经配置了结束自动清理)

Nginx 反向代理

如果可以,建议使用 Nginx 反向代理下 10080 端口,并且启用 HTTPS 服务。

server {
        listen       80;
        server_name git.oyohyee.com;
        rewrite ^(.*)$ https://$host$1 permanent;
}

server {
        listen       443 ssl http2;
        server_name git.oyohyee.com;
        client_max_body_size 20m;

        ssl_certificate "/etc/nginx/ssl/1_git.oyohyee.com_bundle.crt";
        ssl_certificate_key "/etc/nginx/ssl/2_git.oyohyee.com.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        proxy_max_temp_file_size 0;
        location ^~ / {
            proxy_pass  http://127.0.0.1:10080;
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Scheme $scheme;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Host $server_name;
        }
}

SSH 映射

该部分逻辑有点绕,如果不想知道原理可以直接跳到最后看操作

我们常用的git pullgit push实际上使用的是 SSH 服务,由于其默认为 22 端口,因此通常省略了相关操作,但是由于这里我们运行在 Docker 中,因此需要把原本的 22 端口映射到宿主机的其他端口(除非宿主机本身不需要在 22 端口运行 SSH)

大致思路就是,宿主机也启用一个 git 用户,当需要传输代码时,实际上连接的是宿主机的 git 用户,而后将该请求转发至 10022 端口(Docker 中的 Gitea 服务)

authorized_keys文件中,可以使用command在连接后自动执行命令,而 Gitea 的authorized_keys会通过该操作执行/app/gitea/gitea文件,由于所有用户都使用 git 账户连接,需要借助公钥来判断其对应的是系统中的哪一个用户,以及其是否拥有仓库权限。该操作就在/app/gitea/gitea中实现

如果我们希望 Gitea 内配置的用户也可以在宿主机拥有连接权限,那么需要将 Gitea 与宿主机共享同一份 authorized_keys文件。而刚好 Gitea 的该文件需要执行前面提到的/app/gitea/gitea鉴权文件,那么我们刚好可以使用这个文件来实现一个 SSH 转发。

另外,还需要建立一份公私钥对,用于宿主机连接到 Docker 内的鉴权。虽然逻辑上属于两个主机,但是由于共享了同一个文件夹,因此id_rsaauthorized_keys。将对应的id_rsa.pub添加到authorized_keys即可。
(这个公钥 不需要 添加command,鉴权使用的参数已经通过宿主机的鉴权文件传输了)

那么我们的整体思路如下:

  1. 用户 A 通过 22 端口连接到宿主机 git 用户
  2. 触发宿主机的鉴权文件
  3. 鉴权文件将连接转发至 10022 端口,连接到 Gitea 中的 git 用户
  4. 触发 Gitea 的鉴权文件
  5. 鉴权文件进行用户鉴权

首先确保宿主机和 Docker 内的 git 用户 id 相同。如果不同需要调整为相同才可以进行后面的步骤

> id git
uid=1000(git) gid=1000(git) groups=1000(git)

然后,生成一份宿主机和 Docker 通信的 SSH 公私钥对
这个公私钥对需要放置在/home/git/.ssh文件夹中,如果有必要需要修改文件权限(文件夹 755,文件 600,用户和用户组都是 git)

ssh-keygen -t rsa -b 4096 -C "Gitea Host Key"

将上面生成的公钥添加到/home/git/.ssh/authorized_keys

cat /home/git/.ssh/id_rsa.pub >> /home/git/.ssh/authorized_keys

在宿主机内配置/app/gitea/gitea文件,并添加可执行权限chmod a+x /app/gitea/gitea

#!/bin/bash
ssh -p 10022 -o StrictHostKeyChecking=no git@127.0.0.1 "SSH_ORIGINAL_COMMAND=\"$SSH_ORIGINAL_COMMAND\" $0 $@"

映射宿主机的/home/git/.ssh/data/git/.ssh(如果前面按照说明启动,那么已经配置好了)

Git Hooks 启用

配置文件修改

Git Hooks 是在 Git 某些特定条件下触发的钩子,可以用来实现代码同步、自动部署。
要启用 Git Hooks 需要在配置文件/opt/gitea/gitea/conf/app.ini(docker 中的 /data/gitea/conf/app.ini)中配置如下内容。默认可能没有这一项,需要手动添加。

[security]
DISABLE_GIT_HOOKS = false

添加后,管理员就可以在用户管理允许部分用户编辑 Git Hooks 了。

Git Hooks 允许在服务器上执行脚本,请不要把权限授予不可信的人

自动同步 Github 的例子

post-receive指接收到提交的代码后触发的钩子,在这里将代码传一份到 Github,即可实现自动同步功能

具体内容如下

#!/bin/bash
repos=("git@github.com:OhYee/code-questions.git")

export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no -i /data/git/.ssh/id_rsa"
for repo in $repos; do
    nohup git push --mirror $repo
done

repos存储了所有需要同步的仓库,使用空格分割,双引号包裹即可。
如果有必要,也可以同步一份到 Gitee。

配置之前,需要把相应的公钥配置到 Github 和 Gitee。
(这里直接使用前面宿主机和 Docker 通信的 SSH 公私钥对即可)

自动备份

由于 Docker 的存在,已经屏蔽了大部分复杂的操作,只有三部分数据被映射在了宿主机上:

  • /app/gitea/gitea
  • /opt/gitea/
  • /home/git/.ssh/

其中,第一个文件内容是固定的,实际上没有备份的必要性,只需要备份后两者即可

可以添加一个 cron 每日任务,定时打包几个文件夹(可以借助onedrivecmd和 ServerChan 实现自动传输到 OneDrive,并在失败时微信通知)

使用 git 用户建立/home/git/gitea_backup/文件夹,内部包含两个文件:

  • /home/git/gitea_backup/backup.crontab.bash
  • /home/git/gitea_backup/backup.conf

需要提前安装onedrivecmdcurl,并使用onedrivecmd init

python3 -m pip install https://github.com/OneDrive/onedrive-sdk-python/archive/master.zip onedrivecmd

文件内容如下

#!/bin/bash

# For user git `crontab -e`
# 
#  0  5 * * * /bin/bash /home/git/gitea_backup/backup.crontab.bash
#

SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
NAME=`date '+%Y_%m_%d'`
FILENAME="${SHELL_FOLDER}/backup/${NAME}.zip"

ONEDRIVE=$(cat ${SHELL_FOLDER}/backup.conf | grep -E '^OneDrive\s+.+$' | tr -s " " | cut -d " " -f 2)
SERVERCHAN=$(cat ${SHELL_FOLDER}/backup.conf | grep -E '^ServerChan\s+.+$' | tr -s " " | cut -d " " -f 2)

function notify() {
    if [[ -n ${SERVERCHAN} ]]; then
        curl -X POST "http://sc.ftqq.com/${SERVERCHAN}.send" \
            -G \
            --data-urlencode "text=${1}" \
            --data-urlencode "desp=${2}"
    fi
}

zip -r ${FILENAME} /opt/gitea /home/git/.ssh /app/gitea/gitea

if [[ $? -ne 0 ]]; then
    notify "Gitea Backup error" ""
else
    if [[ -n ${ONEDRIVE} ]]; then
        onedrivecmd put "${FILENAME}" "od:${ONEDRIVE}"
    fi
    if [[ $? -ne 0 ]]; then
        notify "Gitea Backup upload error" ""
    fi
fi
# OneDrive 备份文件夹
# 需要安装 `onedrivecmd` 并运行 `onedrivecmd init`
# 详情见 https://github.com/cnbeining/onedrivecmd
OneDrive /backup/gitea/

# Server酱微信通知
# 详情见 https://sc.ftqq.com/
# 需要安装 curl 
# Example: ServerChan abcdefg
ServerChan xxxxxxxx

参考资料