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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
|
<!-- git 远程存储 -->
> 争名夺利几时休,早起迟眠不自由。
>
> 骑着驴骡思骏马,官居宰相望王侯。
自打在服务器上完成了代码环境的配置,我就在想,要是有一个自己的远程 git 存储库该多好?
# gitlab
我按照网上的教程,我更新了清华镜像源,下载安装 gitlab。孰料一次一次在安装过程卡死,而且情况十分严重,CPU
占用率长期维持在 50%以上,内存更是飙升到 90%,不仅安装进行不下去了,甚至一切操作都无法执行,包括<kbd>Ctrl+C</kbd>想要杀死当前进程的请求也会石沉大海。没办法,只得强制重启。
如是反复若干次,最终死心,将 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 配置与说明
最初的设置是这样的:
```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 错误:
```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 代码了,这也不是我们想要的。那么,我们应该怎么做呢?更好的做法是这样的:
```nginx
$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:
```plaintext
[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:
```plaintext
[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:
```plaintext
[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 的配置文件进行修改:
```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](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 = <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)
```
编译 && 安装:
```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 认证文件换成自己的:
```nginx
# /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`中写:
```nginx
# /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
-- <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`中:
```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 调用脚本,但暂时没弄出来
这篇博客前前后后有二十多天了,有空再折腾吧。看电视去也~
<!-- 2024.1.3 -->
|