> 争名夺利几时休,早起迟眠不自由。 > > 骑着驴骡思骏马,官居宰相望王侯。 自打在服务器上完成了代码环境的配置,我就在想,要是有一个自己的远程 git 存储库该多好? # gitlab 我按照网上的教程,我更新了清华镜像源,下载安装 gitlab。孰料一次一次在安装过程卡死,而且情况十分严重,CPU 占用率长期维持在 50%以上,内存更是飙升到 90%,不仅安装进行不下去了,甚至一切操作都无法执行,包括Ctrl+C想要杀死当前进程的请求也会石沉大海。没办法,只得强制重启。 如是反复若干次,最终死心,将 gitlab 彻底从云服务器卸载,"革职为民,永不叙用"。 经过上网查找,原因果然出现在配置上。**gitlab 推荐的最小内存是 4G,但是目前我们只有 2G,内存爆满也就理所当然了。**没办法,期限之内不能更改配置,就算能更改,我都穷到薅资本主义羊毛了,还能有钱升级配置?笑死。 就因为这个问题,我们就不干了?这是不行滴,小同志。 不久之后,黄四郎同志发来了一篇阮一峰的博客:[最简单的 git 服务器](https://www.ruanyifeng.com/blog/2022/10/git-server.html)。看起来似乎不错,但是失之简略。幸好,我在 git 官方教程[Pro Git](https://git-scm.com/book/zh/v2)上找到了另一部分,两个拼一拼、试一试,最终成功了。 # 前置 首先,我建议为 git 托管创建专门的用户。这样做有以下几个目的: - 安全 - 如果使用常用用户,仓库托管在家目录下的某一级目录,容易一个不注意自己把仓库删了 - 如果使用 root 用户,找一个目录进行管理,则权限过高,也容易被误操作;除此之外,root 用户一般不会设置为 ssh 登录,这就意味着想简单创建一个库,我们需要先 ssh 登陆,然后切换到 root 用户、进入存放目录/创建,最后退出,整个操作过程较为烦人且无法自动化实现 - 方便使用。创建专门用户后,我们可以为之配置 ssh 密钥,从而实现免密登陆,而且创建新仓库、clone 仓库都较为简单(因为直接托管在家目录之下,不需要记较长的路径名) 当然,创建单独用户的理由不止这么简单,它在我们使用 http 来托管仓库的时候有专门的作用,下文会讨论到。 我为其创建了一个名为`git`的用户,家目录`/home/git`,但**不授予 sudo 权限**。 # ssh 服务 首先,本方法要求有一个本地 git 存储库,并且已经有 commit。如果是本地新建的仓库,需要先有一个 commit,然后继续操作。这个 commit 可以用编写`.gitignore`或者`push.sh`等不甚重要的文件来凑数。 ## 远程仓库 ```bash # 创建远程仓库,这里也叫test吧 ssh git@127.0.0.1 git init --bare test.git ``` 注意,本处指明的 git 为远程用户名,127.0.0.1 代表云服务器的公网 ip,test.git 为远程仓库名。使用的时候都需要换成自己的。如果仓库不想直接存储在用户目录下,需要指出其存储路径,如`code/fuck/test.git` ## 本地与远程连接 本地和远程都有了,下一步就是建立联系了。 ```bash # 本地添加远程库信息 git remote add origin git@127.0.0.1:test.git # 此时尚不能直接推送,因为并未指定上游对应分支,需要指定 git push --set-upstream origin master # 之后就可以直接推送了 bash push.sh ``` ## 自动化 整体操作过程可以说比较简单了。既然如此,**脚本,启动!** ```bash #!/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:** - 本地仓库名和远程仓库名不要加.git 后缀,程序会自动添加 - 如果要把这个脚本改写为其他语言的脚本的话,在转换本地当前工作目录(进入本地新创建的仓库)的时候,**一定要使用语言自带的调整当前工作目录的函数,不要调用系统命令**(如写为 C 语言程序的时候,应当是调用库函数`chdir()`而不是选择`system("cd <目录名>")`),否则编译器/解释器自动在这里作多线程执行,创建一个新的线程,运行一个终端,进入了该目录,然后没有后续,线程挂了;另一个线程在原来的代码上继续执行,但根本没进入目录,也就是执行操作的位置不对,最终出现错误。 # http(s)服务 到这里似乎已经万事俱备了,但在服务器迁移的过程中,我发现了问题:使用 ssh 服务,就意味着每个仓库记录的远程库都是其 ip 地址(或者自己在`~/.ssh/config`里规定的服务器别名),当服务器迁移后,需要手动进入一个个本地仓库修改远程库地址,非常麻烦。相比之下,如果使用像 github 一样的 http(s)服务,我们就只需要做好远程库迁移、dns 信息修改和服务器设置即可,本地仓库完全不需要改动。 于是,我发现了一篇[教程](https://www.aneasystone.com/archives/2018/12/build-your-own-git-server.html),照葫芦画瓢起来。 需要注意的是,**仍然建议为 git 仓库专门建用户**。 ## 配套软件安装 首先,我们需要安装`git`和`nginx`,这里不再赘述。除此之外,我们会用到以下软件: - `git-http-backend`:git 官方提供的 git 的 http(s) 服务后端。该软件包含在`git-core`中,一般会在安装 git 时就默认装了,可以通过`which git-http-backend`查看是否安装,没有的话需要安装`git-core`。默认安装位置在`/usr/lib/git-core/git-http-backend`,需要查看自己的路径,后边会用。 - `fcgiwrap`:一个用于将 FastCGI 转换为 HTTP 协议的工具。 - `htpasswd`:用于创建和更新基于文本的认证文件,用于存储用户名和密码。它是`apache2-utils`的一部分,可以通过`which htpasswd`查看是否安装,没有的话需要安装`apache2-utils`。 ## nginx 配置与说明 最初的设置是这样的: ```conf 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 错误: ```bash $ 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 代码了,这也不是我们想要的。那么,我们应该怎么做呢?更好的做法是这样的: ```conf $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: ```log [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: ```log [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: ```log [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 内部原理 - 传输协议](https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE) 这一节。 于是,我们对 nginx 的配置文件进行修改: ```conf 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](https://tools.ietf.org/html/rfc2324)。我们这里用它来做认证标识,以免和别的状态码冲突。当然你也可以自己规定状态码。 ## 认证文件 nginx 的配置中我们用到了认证文件,它的生成是这样的: ```bash # 创建认证文件并添加第一个用户 htpasswd -cd <文件名> <用户名> <密码> # 在已有的文件中继续添加用户 htpasswd -d <文件名> <用户名> <密码> ``` 到了这里,我们重启 nginx 然后进行测试,也许就可以正常使用了……吗? ## 问题 我在测试的时候发现了一个问题: ```bash $ 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`命令查看文件夹时,会看到类似这样的输出: ```bash drwxr-xr-x 2 git git 4096 Nov 27 22:19 test.git ``` 其中前 10 个字符就是用来描述该目录权限的。 - 第一位字符代表文件类型 - `d`表示目录 - `-`表示文件 - `l`表示链接文件 - `b`表示块设备文件 - `c`表示字符设备文件 - `p`表示管道文件 - `s`表示套接字文件。 - 接下来 9 个字符每 3 个一组,分别表示文件所有者、文件所有者所在组、其他用户对该文件的权限。 - `r`表示可读 - `w`表示可写 - `x`表示可执行 - `-`表示无对应权限 其中,读权限和写权限时不言自明的,可是**当我们在讨论文件夹的执行权限的时候,我们究竟在说什么?**其实,这里的执行权限指的是**进入该文件夹的权限**。如果一个文件夹没有执行权限,那么我们就无法进入该文件夹,也就无法访问该文件夹下的文件,即使有对应权限。 回到这个问题,我们可以看到,我们仓库存放的文件夹的权限很有可能是没有对应读写或进入权限。所以修改办法也很简单,改权限。 ### 解决方案 修改权限的方法有以下几种: - 直接把对应库及其下的所有文件/文件夹对所有其他用户赋予读写执行权限(权限使用`chmod -R 777 test.git`)。缺点很明显: - 不安全,任何人都可以访问该文件夹下的文件,尤其是 root 和 nginx 用户,前者权限太高,一旦出错足以破坏一切,后者容易导致危险(如强制停止或内存泄漏时) - 不方便。每次创建库都需要手动修改仓库及文件的权限,十分不便 - 不为 git 仓库设立单独用户,直接由原有用户或 root 用户接管一切。缺点: - 如果使用原有用户,仓库会和其他文件混在一起,不方便管理,且容易误操作 - 如果使用 root 用户,权限太高,而且一般 root 不会设置为 ssh 登陆,这就意味着想简单创建一个库操作比较复杂 - 设立单独用户,将 nginx 的工作用户(一般为 www-data 或 root,在`/etc/nginx/nginx.conf`文件第一行规定)添加到该用户所在组。这种方式避免了文件混杂不方便管理的问题,也一定程度上避免了权限过高问题(注意,权限问题依然存在),可以说是一个比较好的解决办法。 **这也就是上文我推荐为 git 仓库托管设立单独用户的原因。**因而,最后我选择了第三种方案。具体操作如下: ```bash usermod -aG git root usermod -aG git www-data ``` 此时再进行测试,应该就可以正常使用了。 # cgit 拥抱图形化 到了 http(s)服务这里,我们在命令行里进行 git 操作的需求已经基本得到了满足。但是,生命不息,折腾不止,我们发现有一个能图形化显示仓库的界面、并且要仍然能在命令行里进行仓库操作,最是舒坦。 - GitList 的界面看起来不错,而且能展示源码、clone 链接之类的,整体非常像 github 的界面,可惜使用的是我不会的 php 语言,而且没有找到详细一些的安装使用教程 - cgit 是一个用纯 C 语言开发的一个 git 裸库展示,虽然界面看起来比较古早,但功能简单、强大,能展示源码、自由切换分支、方便地查看提交历史(diss 一下 github,github 查看提交历史看起来真的很不方便很不直观)。诸如[Linux 内核](https://git.kernel.org)等项目都在使用。 选择了 cgit,我找到了又一位大佬的[博客](https://blog.dejavu.moe/posts/hosting-minimal-git-server-with-cgit),非常详细。需要注意的是,**我们仍然需要一个专门的用户。** ## 依赖 `nginx`/`git`/`vim`等工具不必赘述,还有一些依赖项需要安装: ```bash # 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 仓库中获取最新的代码,而非直接安装: ```bash git clone https://git.zx2c4.com/cgit cd cgit git submodule init git submodule update ``` 在仓库目录下创建`cgit.conf`文件,用来存放 cgit 构建时可以覆盖的配置: ```bash sed -n '3,31p' Makefile > cgit.conf ``` 我们可以按需编辑之: ```plaintext 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 = 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) ``` 编译 && 安装: ```bash # 如果不需要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 认证文件换成自己的: ```conf # /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`中写: ```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 服务: ```bash sudo nginx -s reload ``` _看吧,一个个仓库,向我们列队走来!_ ## cgit 高级配置 可以安装一些包,用于 cgit 的代码高亮、Markdown 渲染、Gravatar 头像渲染等: ```bash 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](https://git.zx2c4.com/cgit/tree/cgitrc.5.txt)。 ```plaintext # /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](https://pygments.org/styles/)的代码高亮。其默认使用的高亮是 pastie,我们可以根据自己的喜好修改高亮风格。 首先,看看有哪些可用的高亮风格。 ```bash # 查看可用的高亮风格 pygmentize -L styles # 编辑我们使用的代码高亮脚本 sudo vim /usr/local/share/cgit/filters/syntax-highlighting.py ``` ### Gravatar 头像 在上述配置中,我们使用了 Gravatar 头像。[Gravatar](https://cn.gravatar.com/) 是一个全球通用的头像服务,根据你使用的邮箱(而非用户身份)来为你提供头像。换言之,只要你的邮箱注册了 Gravatar,那么你在任何一个支持 Gravatar 的网站上都可以使用你的 Gravatar 头像。这对于我们这种不想做登录、又想展示用户的网站来说,是一个很好的选择。 - [中文官网](https://cn.gravatar.com/) - [英文官网](https://gravatar.com/) 由于 Gravatar 中文官网访问比较慢(不知道为什么,英文官网我挂了梯子还上不去),我们可以使用国内的镜像服务。这里有一篇常用镜像服务的[博客](https://luoxx.top/archives/gravatar-mirror-2022)。我使用的是[Cravatar](https://cravatar.cn/)。 使用流程都是基本一致的,在这个网站上注册账号,上传头像,然后根据网站提供的 API 来获取。一般方式为`https://域名.com/avatar/邮箱的md5值`。在我们使用的脚本`/usr/local/share/cgit/filters/email-gravatar.lua`中,将原有的域名替换为我们使用的域名即可。 cgit 的[官网](https://git.zx2c4.com/cgit/)上不仅能实现 Gravatar 头像,还能在鼠标移动到头像上的时候以大图显示。这不是原生功能,而是需要动 lua 脚本自己实现。原博客大佬在 cgit 的[邮件列表](https://lists.zx2c4.com/pipermail/cgit/2014-March/002036.html)找到了实现方式。 ```bash sudo vim /usr/local/share/cgit/filters/email-libravatar-korg.lua ``` 在脚本中写入以下内容: ```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 -- -- 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("" .. "" .. "" .. "" .. buffer) return 0 end function filter_write(str) buffer = buffer .. str end ``` 只有 lua 还不够,我们需要将以下内容添加到`/var/www/cgit/cgit.css`中: ```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`文件,我们会看到以下内容: ```bash #!/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 我们托管的仓库。大功告成! # 其他存在的问题 除了上述问题已经解决之外,还有一些问题依然存在: - 不管是 nginx 还是 git,在使用 http 上传的时候都会有一定的**缓冲区限制,如果上传文件过大或累计多个 commit 才上传,很可能被拒收导致上传失败**。这个问题在 github 上也存在,但是 github 的缓冲区限制比较大,一般不会出现这个问题;我们的服务器配置比较低,所以这个问题就比较严重了。解决办法是在 nginx 的配置文件中添加`client_max_body_size 100m;`,这样就可以将缓冲区限制扩大到 100M,一般来说足够了。 - 即使解决了缓冲区大小,偶尔也会被拒收,原因尚未查清 - **尚未能通过访问特定链接来实现 git 仓库的创建。**据说是用 nginx 调用脚本,但暂时没弄出来 这篇博客前前后后有二十多天了,有空再折腾吧。看电视去也~