自己的 git 托管

争名夺利几时休,早起迟眠不自由。

骑着驴骡思骏马,官居宰相望王侯。

自打在服务器上完成了代码环境的配置,我就在想,要是有一个自己的远程 git 存储库该多好?

gitlab

我按照网上的教程,我更新了清华镜像源,下载安装 gitlab。孰料一次一次在安装过程卡死,而且情况十分严重,CPU 占用率长期维持在 50%以上,内存更是飙升到 90%,不仅安装进行不下去了,甚至一切操作都无法执行,包括Ctrl+C想要杀死当前进程的请求也会石沉大海。没办法,只得强制重启。

如是反复若干次,最终死心,将 gitlab 彻底从云服务器卸载,“革职为民,永不叙用”。

经过上网查找,原因果然出现在配置上。gitlab 推荐的最小内存是 4G,但是目前我们只有 2G,内存爆满也就理所当然了。没办法,期限之内不能更改配置,就算能更改,我都穷到薅资本主义羊毛了,还能有钱升级配置?笑死。

就因为这个问题,我们就不干了?这是不行滴,小同志。

不久之后,黄四郎同志发来了一篇阮一峰的博客:最简单的 git 服务器。看起来似乎不错,但是失之简略。幸好,我在 git 官方教程Pro Git上找到了另一部分,两个拼一拼、试一试,最终成功了。

前置

首先,我建议为 git 托管创建专门的用户。这样做有以下几个目的:

当然,创建单独用户的理由不止这么简单,它在我们使用 http 来托管仓库的时候有专门的作用,下文会讨论到。

我为其创建了一个名为git的用户,家目录/home/git,但不授予 sudo 权限

ssh 服务

首先,本方法要求有一个本地 git 存储库,并且已经有 commit。如果是本地新建的仓库,需要先有一个 commit,然后继续操作。这个 commit 可以用编写.gitignore或者push.sh等不甚重要的文件来凑数。

远程仓库

# 创建远程仓库,这里也叫test吧
ssh git@127.0.0.1 git init --bare test.git

注意,本处指明的 git 为远程用户名,127.0.0.1 代表云服务器的公网 ip,test.git 为远程仓库名。使用的时候都需要换成自己的。如果仓库不想直接存储在用户目录下,需要指出其存储路径,如code/fuck/test.git

本地与远程连接

本地和远程都有了,下一步就是建立联系了。

# 本地添加远程库信息
git remote add origin git@127.0.0.1:test.git

# 此时尚不能直接推送,因为并未指定上游对应分支,需要指定
git push --set-upstream origin master

# 之后就可以直接推送了
bash push.sh

自动化

整体操作过程可以说比较简单了。既然如此,脚本,启动!

#!/bin/bash

read -p "Local repo name: " local_name
read -p "Remote repo name: " remote_name

# 远程仓库创建,用的时候记得改服务器地址
tmp="ssh your-server git init --bare $remote_name.git"
eval "$tmp"

# 本地仓库创建
mkdir "$local_name"
cd "$local_name"
git init

# 本地仓库初始化
# 编写.gitignore
cat > .gitignore << EOF
*.sh
*.bat
*.exe
*.[oa]
*.pyc
__pycache__
*.vscode
*.swp
EOF

# 编写push.sh
cat > push.sh << EOF
git add .
git commit
git push
EOF
chmod +x push.sh

# 提交初始化commit
git add .
git commit -m "Initial commit"
tmp="git remote add origin aliyun-git:$remote_name.git"
eval "$tmp"
git push --set-upstream origin master

echo "Success!"

Attention please:

http(s)服务

到这里似乎已经万事俱备了,但在服务器迁移的过程中,我发现了问题:使用 ssh 服务,就意味着每个仓库记录的远程库都是其 ip 地址(或者自己在~/.ssh/config里规定的服务器别名),当服务器迁移后,需要手动进入一个个本地仓库修改远程库地址,非常麻烦。相比之下,如果使用像 github 一样的 http(s)服务,我们就只需要做好远程库迁移、dns 信息修改和服务器设置即可,本地仓库完全不需要改动。

于是,我发现了一篇教程,照葫芦画瓢起来。

需要注意的是,仍然建议为 git 仓库专门建用户

配套软件安装

首先,我们需要安装gitnginx,这里不再赘述。除此之外,我们会用到以下软件:

nginx 配置与说明

最初的设置是这样的:

server
{
    # 这里用于将 http 请求重定向到 https,是一种常用的方式
    listen 80;
    server_name git.player.com;
    return 301 https://$host$request_uri;
}
server
{
    server_name git.player.com;
    listen 443 ssl;

    ssl_certificate /etc/letsencrypt/live/git.player.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.player.com/privkey.pem;

    location / {
        include fastcgi_params;
        # git-http-backend的路径,每次请求都会转发到这个脚本
        fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
        # git-http-backend默认只能访问目录下有git-daemon-export-ok的仓库
        # 这里设置为可以访问所有仓库
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        # git仓库根目录
        fastcgi_param GIT_PROJECT_ROOT /home/git/;
        fastcgi_param PATH_INFO $uri;
        # 如果有认证,传递给cgi脚本git-http-backend
        fastcgi_param REMOTE_USER $remote_user;
        fastcgi_pass unix:/var/run/fcgiwrap.socket;
    }
}

nginx 重新加载配置之后,我们就可以正常git clone https://git.player.com/test.git了。但当我们 push 时,会出现 403 错误:

$ git push
fatal: unable to access 'https://git.player.com/test.git/': The requested URL returned error: 403

为了解决这个错误,我们可以在 git-http-backend 的官网文档 上找到这样的一段描述:

By default, only the upload-pack service is enabled, which serves git fetch-pack and git ls-remote clients, which are invoked from git fetch, git pull, and git clone. If the client is authenticated, the receive-pack service is enabled, which serves git send-pack clients, which is invoked from git push.

第一次读这段话可能会有些不知所云,这是因为我们对这里提到的 upload-pack/fetch-pack/receive-pack/send-pack 这几个概念还没有什么认识。但是我们大抵可以猜出来,默认情况下,只有认证的用户才可以 push 代码。

我们可以在仓库中执行 git config http.receivepack true 来开启 push 权限,但是这样的话,所有人都可以 push 代码了,这显然不是我们想要的。我们可以通过 git config http.receivepack false 来关闭 push 权限,这样的话,所有人都不能 push 代码了,这也不是我们想要的。那么,我们应该怎么做呢?更好的做法是这样的:

$HTTP["querystring"] =~ "service=git-receive-pack" {
    include "git-auth.conf"
}
$HTTP["url"] =~ "^/git/.*/git-receive-pack$" {
    include "git-auth.conf"
}

看上去挺简单,但是想要理解为什么这样配置,就必须了解下 Git 的内部原理。正如上面 git-http-backend 文档上的那段描述,当 Git 客户端执行 git fetch/git pull/git clone时,会调用 upload-pack 服务,当执行 git push 时,会调用 receive-pack 服务。我们可以查看 nginx 的访问日志,目录在/var/log/nginx/access.log

执行 git clone:

[27/Nov/2018:22:18:00] "GET /test.git/info/refs?service=git-upload-pack HTTP/1.1" 200 363 "-" "git/1.9.1"
[27/Nov/2018:22:18:00] "POST /test.git/git-upload-pack HTTP/1.1" 200 306 "-" "git/1.9.1"

执行 git pull:

[27/Nov/2018:22:20:25] "GET /test.git/info/refs?service=git-upload-pack HTTP/1.1" 200 363 "-" "git/1.9.1"
[27/Nov/2018:22:20:25] "POST /test.git/git-upload-pack HTTP/1.1" 200 551 "-" "git/1.9.1"

执行 git push:

[27/Nov/2018:22:19:33] "GET /test.git/info/refs?service=git-receive-pack HTTP/1.1" 401 204 "-" "git/1.9.1"
admin [27/Nov/2018:22:19:33] "GET /test.git/info/refs?service=git-receive-pack HTTP/1.1" 200 193 "-" "git/1.9.1"
admin [27/Nov/2018:22:19:33] "POST /test.git/git-receive-pack HTTP/1.1" 200 63 "-" "git/1.9.1"

可以看到执行 clone 和 pull 请求的接口是一样的,先请求 /info/refs?service=git-upload-pack,然后再请求 /git-upload-pack;而 push 是先请求 /info/refs?service=git-receive-pack,然后再请求 /git-receive-pack,所以在上面的的配置中我们看到了两条记录,如果要对 push 做访问控制,那么对这两个请求都要限制。关于 Git 传输的原理可以参考 《Pro Git》的 Git 内部原理 - 传输协议 这一节。

于是,我们对 nginx 的配置文件进行修改:

server
{
    listen 80;
    server_name git.player.com;
    return 301 https://$host$request_uri;
}
server
{
    server_name git.player.com;
    listen 443 ssl;

    ssl_certificate /etc/letsencrypt/live/git.player.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.player.com/privkey.pem;

    location @auth {
        auth_basic "Git Server";
        # 生成的认证文件,下文专门说明
        auth_basic_user_file /etc/nginx/conf.d/git.htpasswd;

        include fastcgi_params;
        # git-http-backend的路径,每次请求都会转发到这个脚本
        fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
        # git-http-backend默认只能访问目录下有git-daemon-export-ok的仓库
        # 这里设置为可以访问所有仓库
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        # git仓库根目录
        fastcgi_param GIT_PROJECT_ROOT /home/git/;
        fastcgi_param PATH_INFO $uri;
        # 如果有认证,传递给cgi脚本git-http-backend
        fastcgi_param REMOTE_USER $remote_user;
        fastcgi_pass unix:/var/run/fcgiwrap.socket;
    }

    location / {
        error_page 418 = @auth;
        if ( $query_string = "service=git-receive-pack" ) {
            return 418;
        }
        if ( $uri ~ "git-receive-pack$" ) {
            return 418;
        }
        include fastcgi_params;

        fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        fastcgi_param GIT_PROJECT_ROOT /home/git/;
        fastcgi_param PATH_INFO $uri;
        fastcgi_param REMOTE_USER $remote_user;
        fastcgi_pass unix:/var/run/fcgiwrap.socket;
    }
}

这里我们可以看到,我们对/git/.*/git-receive-pack/info/refs?service=git-receive-pack进行了限制,如果没有认证,就会返回 418 错误,然后 nginx 会将其转发到@auth这个 location,这个 location 会对请求进行认证,如果认证成功,就会将请求转发到fastcgi_pass指定的脚本,也就是git-http-backend,这样就可以正常 push 了。

为什么不直接在/git/.*/git-receive-pack/info/refs?service=git-receive-pack这两个 location 中进行认证呢?如果这样做的话,会导致 clone 和 pull 也需要认证,那多不合适了~

418 是什么,http 协议没见过这个啊?这是 http 协议中的一个状态码,叫做 I’m a teapot(我是一个茶壶),这个状态码是作为一个愚人节玩笑而创建的,但是它确实存在,可以参考 RFC 2324。我们这里用它来做认证标识,以免和别的状态码冲突。当然你也可以自己规定状态码。

认证文件

nginx 的配置中我们用到了认证文件,它的生成是这样的:

# 创建认证文件并添加第一个用户
htpasswd -cd <文件名> <用户名> <密码>
# 在已有的文件中继续添加用户
htpasswd -d <文件名> <用户名> <密码>

到了这里,我们重启 nginx 然后进行测试,也许就可以正常使用了……吗?

问题

我在测试的时候发现了一个问题:

$ git clone https://git.player.com/test.git
Cloning into 'test'...
fatal: repository 'https://git.player.com/test.git/' not found

看起来很匪夷所思:我们明明把仓库放进了指定目录啊,nginx 也已经配置好了,为什么就是找不到呢?我在网上找了很久也没找到答案。最后自己发现了问题所在:仓库文件夹权限不对

Linux 权限描述

当我们使用ls -l命令查看文件夹时,会看到类似这样的输出:

drwxr-xr-x 2 git git 4096 Nov 27 22:19 test.git

其中前 10 个字符就是用来描述该目录权限的。

其中,读权限和写权限时不言自明的,可是当我们在讨论文件夹的执行权限的时候,我们究竟在说什么?其实,这里的执行权限指的是进入该文件夹的权限。如果一个文件夹没有执行权限,那么我们就无法进入该文件夹,也就无法访问该文件夹下的文件,即使有对应权限。

回到这个问题,我们可以看到,我们仓库存放的文件夹的权限很有可能是没有对应读写或进入权限。所以修改办法也很简单,改权限。

解决方案

修改权限的方法有以下几种:

这也就是上文我推荐为 git 仓库托管设立单独用户的原因。因而,最后我选择了第三种方案。具体操作如下:

usermod -aG git root
usermod -aG git www-data

此时再进行测试,应该就可以正常使用了。

cgit 拥抱图形化

到了 http(s)服务这里,我们在命令行里进行 git 操作的需求已经基本得到了满足。但是,生命不息,折腾不止,我们发现有一个能图形化显示仓库的界面、并且要仍然能在命令行里进行仓库操作,最是舒坦。

选择了 cgit,我找到了又一位大佬的博客,非常详细。需要注意的是,我们仍然需要一个专门的用户。

依赖

nginx/git/vim等工具不必赘述,还有一些依赖项需要安装:

# apache2-utils是用其htpasswd命令创建认证文件的
# fcgiwrap是用于将 FastCGI 转换为 HTTP 协议的工具
# 这两个工具上文均已提到和使用,不再赘述
sudo apt update
sudo apt install -y apache2-utils fcgiwrap

# 编译过程中需要openssl的头文件
sudo apt install -y libssl-dev

# 建议为cgit提供lua支持,用来进行个性化的设置
# 本文以 lua5.1 为例
sudo apt install liblua5.1-0 liblua5.1-0-dbg liblua5.1-dev lua5.1

cgit 安装

cgit 最近的正式发行版已经好几年了,但是它的开发仍然很活跃,所以建议从它的 git 仓库中获取最新的代码,而非直接安装:

git clone https://git.zx2c4.com/cgit
cd cgit
git submodule init
git submodule update

在仓库目录下创建cgit.conf文件,用来存放 cgit 构建时可以覆盖的配置:

sed -n '3,31p' Makefile > cgit.conf

我们可以按需编辑之:

CGIT_VERSION = v1.2.3
CGIT_SCRIPT_NAME = cgit.cgi
CGIT_SCRIPT_PATH = /var/www/cgit  # 本文只改了这里
CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
CGIT_CONFIG = /etc/cgitrc  # 默认配置文件路径
CACHE_ROOT = /var/cache/cgit
prefix = /usr/local
libdir = $(prefix)/lib
filterdir = $(libdir)/cgit/filters
docdir = $(prefix)/share/doc/cgit
htmldir = $(docdir)
pdfdir = $(docdir)
mandir = $(prefix)/share/man
SHA1_HEADER = <openssl/sha.h>
GIT_VER = 2.39.0
GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz
INSTALL = install
COPYTREE = cp -r
MAN5_TXT = $(wildcard *.5.txt)
MAN_TXT  = $(MAN5_TXT)
DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
DOC_PDF  = $(patsubst %.txt,%.pdf,$(MAN_TXT))

ASCIIDOC = asciidoc
ASCIIDOC_EXTRA =
ASCIIDOC_HTML = xhtml11
ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA)
TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML)

编译 && 安装:

# 如果不需要lua支持
make NO_LUA=1
# 有lua支持的话
make LUA_PKGCONFIG=lua5.1

# 安装,注意一下安装路径
# 不妨将输出写到日志里,以便查看
sudo make install | tee install.log

nginx 配置

首先,参照这里生成一个自己的认证文件,再继续往下看。

/etc/nginx/git-http-backend.conf中写入以下内容,注意把域名、ssl 路径、htpasswd 认证文件换成自己的:

# /etc/nginx/git-http-backend.conf
fastcgi_pass unix:/var/run/fcgiwrap.socket;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /home/git;
fastcgi_param PATH_INFO $1;
fastcgi_param REMOTE_USER $remote_user;

而后,在/etc/nginx/conf.d/cgit.conf中写:

# /etc/nginx/conf.d/cgit.conf
server {
    listen 80;
    server_name git.player.com;
    return 301 https://$server_name$request_uri;
}

server {
    server_name git.player.com;
    listen 443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/git.player.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/git.player.com/privkey.pem;

    # SSL Security
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256;

    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;

    # Site Log path
    access_log /var/log/nginx/cgit-access.log;
    error_log /var/log/nginx/cgit-error.log;

    root /var/www/cgit;
    try_files $uri @cgit;
    client_max_body_size 10m;

    location @cgit {
        include fastcgi_params;
        # cgit's CGI script path
        fastcgi_param SCRIPT_FILENAME /var/www/cgit/cgit.cgi;
        fastcgi_param DOCUMENT_ROOT /usr/lib/git-core;
        fastcgi_pass unix:/var/run/fcgiwrap.socket;
        fastcgi_param PATH_INFO $uri;
        fastcgi_param QUERY_STRING $args;
        fastcgi_param HTTP_HOST $server_name;
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        fastcgi_param GIT_PROJECT_ROOT /home/git;

        if ($arg_service = git-receive-pack) {
            rewrite (/.*) /git_write/$1 last;
        }

        if ($uri ~ ^/.*/git-receive-pack$) {
            rewrite (/.*) /git_write/$1 last;
        }

        if ($arg_service = git-upload-pack) {
            rewrite (/.*) /git_read/$1 last;
        }

        if ($uri ~ ^/.*/git-upload-pack$) {
            rewrite (/.*) /git_read/$1 last;
        }
    }

    location ~ /git_read/(.*) {
        include git-http-backend.conf;
    }

    location ~ /git_write/(.*) {
        # HTTP Basic Authentication
        auth_basic "Authentication Required To Push";
        auth_basic_user_file /etc/nginx/conf.d/git.htpasswd;
        include git-http-backend.conf;
    }
}

最后,重启 nginx 服务:

sudo nginx -s reload

看吧,一个个仓库,向我们列队走来!

cgit 高级配置

可以安装一些包,用于 cgit 的代码高亮、Markdown 渲染、Gravatar 头像渲染等:

sudo apt install -y python3-docutils python3-markdown highlight python3-pygments

# 编译安装 LuaoSSL
# https://25thandclement.com/~william/projects/luaossl.html
git clone https://github.com/wahern/luaossl.git && cd luaossl
make LUAPKG=lua5.1
sudo make install LUAPKG=lua5.1

sudo mkdir -p /usr/local/share/cgit
sudo ln -s /usr/local/lib/cgit/filters /usr/local/share/cgit/filters
sudo chown -R www-data:www-data /usr/local/share/cgit/

# 给下面使用 filter api 的脚本赋予可执行权限,比如
sudo chmod +x /usr/local/share/cgit/filters/email-gravatar.lua

然后编辑上边我们已经指定的配置文件/etc/cgitrc,更多配置项参见cgitrc.5.txt

# /etc/cgitrc
# 包含 cgit 的所有运行时设置
# 格式 NAME=VALUE
# 以 "#" 开头的行是注释

# 全局配置
css=/cgit.css
logo=/cgit.png
favicon=/favicon.ico
#footer=
virtual-root=/
# 禁用哑克隆
enable-http-clone=0

# Smart HTTP
# 记得改成自己的链接
clone-url=https://git.player.com/$CGIT_REPO_URL
# 首页标题显示的内容,改成你想要的
root-title=GIT.PLAYER.COM
root-desc=YOUR.WORDS
# 在首页展示的介绍信息,可用md/man/html等
# 详参/usr/local/share/cgit/filters/about-formatting.sh
root-readme=/var/www/cgit/README.md

# 建议配置
enable-index-owner=1
enable-index-links=1
enable-blame=1
enable-log-filecount=1
enable-log-linecount=1
enable-commit-graph=1

# 禁止搜素引擎索引
robots=noindex, nofollow

branch-sort=age
commit-sort=date
max-stats=quarter
snapshots=tar.gz zip

# 使用 RAM 的缓存大小 单位 MB
cache-size=1024

# 代码高亮
source-filter=/usr/local/share/cgit/filters/syntax-highlighting.py

# 格式化贡献者,显示Gravatar头像
email-filter=lua:/usr/local/share/cgit/filters/email-gravatar.lua

# 格式化 about 页面
about-filter=/usr/local/share/cgit/filters/about-formatting.sh
readme=:README.md
readme=:readme.md
readme=:README.txt
readme=:readme.txt
readme=:README
readme=:readme

# MIME 类型
mimetype.html=text/html
mimetype.gif=image/gif
mimetype.jpg=image/jpeg
mimetype.jpeg=image/jpeg
mimetype.png=image/png
mimetype.webp=image/webp
mimetype.pdf=application/pdf
mimetype.svg=image/svg+xml

# 移除 .git 后缀,很有必要
remove-suffix=1

# 扫描路径
scan-path=/home/git

# 每个存储库配置
#repo.url=reponame
#repo.path=/home/git/reponame.git
#repo.desc=Some description here
#repo.owner=Owner Name
#repo.logo=/repo-logo.png

高亮风格

在上面的配置文件里,我们使用了Pygments的代码高亮。其默认使用的高亮是 pastie,我们可以根据自己的喜好修改高亮风格。

首先,看看有哪些可用的高亮风格。

# 查看可用的高亮风格
pygmentize -L styles

# 编辑我们使用的代码高亮脚本
sudo vim /usr/local/share/cgit/filters/syntax-highlighting.py

Gravatar 头像

在上述配置中,我们使用了 Gravatar 头像。Gravatar 是一个全球通用的头像服务,根据你使用的邮箱(而非用户身份)来为你提供头像。换言之,只要你的邮箱注册了 Gravatar,那么你在任何一个支持 Gravatar 的网站上都可以使用你的 Gravatar 头像。这对于我们这种不想做登录、又想展示用户的网站来说,是一个很好的选择。

由于 Gravatar 中文官网访问比较慢(不知道为什么,英文官网我挂了梯子还上不去),我们可以使用国内的镜像服务。这里有一篇常用镜像服务的博客。我使用的是Cravatar

使用流程都是基本一致的,在这个网站上注册账号,上传头像,然后根据网站提供的 API 来获取。一般方式为https://域名.com/avatar/邮箱的md5值。在我们使用的脚本/usr/local/share/cgit/filters/email-gravatar.lua中,将原有的域名替换为我们使用的域名即可。

cgit 的官网上不仅能实现 Gravatar 头像,还能在鼠标移动到头像上的时候以大图显示。这不是原生功能,而是需要动 lua 脚本自己实现。原博客大佬在 cgit 的邮件列表找到了实现方式。

sudo vim /usr/local/share/cgit/filters/email-libravatar-korg.lua

在脚本中写入以下内容:

-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
-- It adds gravatar icons to author names. It is designed to be used with the lua:
-- prefix in filters. It is much faster than the corresponding python script.
--
-- Requirements:
--      luaossl
--      <http://25thandclement.com/~william/projects/luaossl.html>
--

local digest = require("openssl.digest")

function md5_hex(input)
        local b = digest.new("md5"):final(input)
        local x = ""
        for i = 1, #b do
                x = x .. string.format("%.2x", string.byte(b, i))
        end
        return x
end

function filter_open(email, page)
        buffer = ""
        md5 = md5_hex(email:sub(2, -2):lower())
end

function filter_close()
   html("<span class='libravatar'>" ..
   "<img class='inline' src='//www.gravatar.com/avatar/" .. md5 .. "?s=13&d=retro' />" ..
   "<img class='onhover' src='//www.gravatar.com/avatar/" .. md5 .. "?s=128&d=retro' />" ..
   "</span>" .. buffer)
   return 0
end

function filter_write(str)
        buffer = buffer .. str
end

只有 lua 还不够,我们需要将以下内容添加到/var/www/cgit/cgit.css中:

/* libgravatar */
div#cgit span.libravatar img.onhover {
  display: none;
  border: 1px solid gray;
  padding: 0px;
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border-radius: 4px;
  width: 128px;
  height: 128px;
}

div#cgit span.libravatar img.inline {
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
  width: 13px;
  height: 13px;
  margin-right: 0.4em;
  opacity: 0.9;
}

div#cgit span.libravatar:hover > img.onhover {
  display: block;
  position: absolute;
  margin-left: 1.5em;
  background-color: #eeeeee;
  box-shadow: 5px 5px 3px #bbb;
}

而后,修改/etc/cgitrc中的email-filter的值为我们新建的脚本lua:/usr/local/share/cgit/filters/email-libravatar-korg.lua,就好了。

添加 README

/etc/cgitrc中,我们指定了root-readme,这是用来在网站主页展示 README 的。我们可以编写一个 README 文件,然后在/etc/cgitrc中指明它的路径。README 可以使用markdown/man/rst/html/txt`等格式。

我使用的是markdown格式,在/var/www/cgit/中创建了一个README.md文件,然后在/etc/cgitrc中指明了路径。

样式修改

对于 cgit 显示出来的界面,你可能并不太满意(比如我就嫌界面的字太小、颜色不舒适啥的)。修改办法也很简单,找到/var/www/cgit/cgit.css文件,然后修改之。

而对于我们的 Markdown 或者别的什么语言写的 README,如果觉得渲染效果不好,都是可以自己去修改的。

找到/usr/local/share/cgit/filters/about-formatting.sh文件,我们会看到以下内容:

#!/bin/sh

# This may be used with the about-filter or repo.about-filter setting in cgitrc.
# It passes formatting of about pages to differing programs, depending on the usage.

# Markdown support requires python and markdown-python.
# RestructuredText support requires python and docutils.
# Man page support requires groff.

# The following environment variables can be used to retrieve the configuration
# of the repository for which this script is called:
# CGIT_REPO_URL        ( = repo.url       setting )
# CGIT_REPO_NAME       ( = repo.name      setting )
# CGIT_REPO_PATH       ( = repo.path      setting )
# CGIT_REPO_OWNER      ( = repo.owner     setting )
# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
# CGIT_REPO_SECTION    ( = section        setting )
# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )

cd "$(dirname $0)/html-converters/"
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
    *.markdown|*.mdown|*.md|*.mkd) exec ./md2html; ;;
    *.rst) exec ./rst2html; ;;
    *.[1-9]) exec ./man2html; ;;
    *.htm|*.html) exec cat; ;;
    *.txt|*) exec ./txt2html; ;;
esac

从这里可以看到,负责渲染的是/usr/local/share/cgit/filters/html-converters文件夹中的对于脚本。我们可以自己修改这些脚本,或者自己添加新的脚本,来实现自己想要的渲染效果。

当然,除了网站主页的 README,我们还可以在每个仓库的主页上添加 README。只需要在仓库的根目录下添加 README 文件即可。然后网页上仓库的标签页就会有“About”标签页,显示 README.md 的内容。

到这里,cgit 的配置就基本完成了。重启 nginx 服务,然后访问你的域名,就可以看到一个很漂亮的界面了;命令行里,也可以 clone、push、fetch、pull 我们托管的仓库。大功告成!

其他存在的问题

除了上述问题已经解决之外,还有一些问题依然存在:

这篇博客前前后后有二十多天了,有空再折腾吧。看电视去也~

2024.1.3