Total Pageviews

Sunday, 28 August 2016

可自己搭建的github.com的替代品cgit

cgit Quick Facts

  • web interface (cgi) for Git repositories, written in C
  • licensed under GPLv2
  • discussions, patches etc. go to the list (signuparchivegmane).
  • common questions are answered in the FAQ.

Features

  • basic repository browsing (logs, diffs, trees...)
  • caching of generated HTML
  • cloneable URLs (implements dumb HTTP transport)
  • commit feeds (atom format)
  • discovery of Git repositories
  • on-the-fly archives for tags and commits
  • plugin support for e.g. syntax highlighting
  • side-by-side diffs
  • simple time/author statistics
  • simple virtual hosting support (macro expansion)
  • understands GitWeb project-lists
  • understands gitweb.owner in Git config files
  • has extensive filtering framework using scripts or a built-in lua interpreter

Source Code

  • download current or past releases
  • clone the repo:
  • git clone https://git.zx2c4.com/cgit
  • see the README for build instructions。
from https://git.zx2c4.com/cgit/about/
------------------

搭建私用Git仓库


TL;DR结论:利用现成的工具,以最小的成本来实现私用Git仓库。
对于目前世面上有的现成服务与软件来说,他们会有这样一些不合适的情况:
  • 国外网站,访问速度不理想(例如GitHub,BitBucket,SourceForge等)
  • 没有免费的私有仓库支持(例如GitHub)
  • 私有内容放在别人服务器上不放心
  • 自己搭建服务时太繁琐,又依赖数据库,又依赖外部服务(例如GitLab等),占用不必要的资源
  • 用的软件虽然是开源的,但是所使用技术不熟悉,无法按需求修改(例如gitolite)
综合这些情况,某分享一下自己是如何搭建私用Git仓库的。

原理

我们知道,在使用未经修改的SSH协议时,需要指定远程主机的绝对路径。例如
git@example.com:/var/git/repos/group/project/repo.git
这样对使用还是相当不便的。
而git在推送(push)代码时,根据手册中的描述,其实是调用远程的git-receive-pack命令来实现接收上传内容。在最常见的SSH协议中,可以通过密钥对认证来判断访问是否有权限;除此以外,根据SSH密钥对认证authorized_keys文件内容格式,可以定制运行的命令,借以修改传递给git-receive-pack命令的参数
对于查看而言,可以使用基于git与C实现的cgit来完成。

实现思路

在这里,将问题分为几个方面
  • 上传下载访问时的权限认证
    • 我们通过使用SSH协议的密钥对来解决这个问题
  • 去除不必要的绝对路径,便于仓库目录、文件的管理
    • 借用authorized_keys文件格式中允许使用的命令来修改最终使用的路径
  • 私有仓库与公开仓库的访问权限
    • 由于cgit本身没有提供访问控制,所以得自己想法。虽然某没有需要隐藏的私人仓库,不过可以分开两个cgit分别读取不同目录,并在前端的Nginx或Apache上做2个目录,后者添加访问控制来解决;另外这里也有一个解决思路,在cgit的CGI脚本前再套一个。

准备工作

:某使用的环境是Gentoo Linux,在实际使用时可按照自己所使用发行版或其他环境作出相应调整。
首先由于我们使用的是SSH协议(更确切地说,使用OpenSSH的实现),故需要指定一个用户,这里假定为git
# 不需要单独再设立一个git组,只需要uid即可
$ useradd git -g nogroup
接着,我们需要设立一个存放代码仓库的路径,假定为/opt/git,并把它作为git用户的HOME;这其中.ssh目录存放authorized_keysrepos来存放代码仓库。整个目录结构类似如下
/opt/git
  .ssh
    authorized_keys
  repos
    ...
如果需要展示页面的话,请自行安装cgit,以及衷爱的web服务器。

搭建与维护

authorized_keys的写法

command="/path/to/git-shell",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ...
在第一列(列之间以空白字符分割),指定一些参数,具体含义可以翻sshd(8)手册;这里比较重要的使用command=...来指定真正运行的命令。这里指向我们自己写的git-shell
以后添加其他公钥时,都把第一列的参数一样的复制一遍。

编写git-shell

某将git-shell的源代码公开在自己的Git仓库里,可以直接获取。
这里解释一下重要的部分,各位可以选择任何自己衷爱的语言或工具来实现同样的功能。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
import re
import shlex
import sys
import subprocess



def main():

    # 这里的 SSH_ORIGINAL_COMMAND 内容便是原始调用的远程命令

    command_line = os.environ.get('SSH_ORIGINAL_COMMAND')

    try:
        shell(command_line)
    except Exception as e:
        print('', file=sys.stderr)
        print(e, file=sys.stderr)
        print('', file=sys.stderr)


def shell(command_line):
    if not command_line:
        raise ValueError('No command line provided')

    args = shlex.split(command_line)
    command = args[0]

    repos_root = '/opt/git/repos'

    print(args, file=sys.stderr)

    # 仅允许这三个命令
    if command in {'git-receive-pack', 'git-upload-pack',
'git-upload-archive'}:

        # 去除路径末尾的 .git
        args[1] = re.sub(r'\/+', '/',
                         '{}/{}.git'.format(repos_root,
args[1].replace('.git', '')))

        print(args, file=sys.stderr)

        if not os.path.isdir(args[1]):
     # 如果是一个新仓库,先初始化
            init_git_repo(args[1])

        print(args, file=sys.stderr)

        os.execvp(command, args)


def init_git_repo(repo_dir):
    os.makedirs(repo_dir)
    p = subprocess.Popen('git init --bare', stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE, cwd=repo_dir, shell=True)
    stdout, stderr = p.communicate()
    export_daemon_ok(repo_dir)


def export_daemon_ok(repo_dir):

    # 这个文件为 git 协议访问作准备
    _f = os.path.join(repo_dir, 'git-daemon-export-ok')
    with open(_f, 'a') as f:
        os.utime(_f)


if __name__ == '__main__':
    sys.exit(main())
使用自己脚本的好处还在于:路径不仅仅支持像GitHub等那些的假设,username/repo.git两层结构。而是真正像一个目录那样来管理

git协议访问

这个安装git-daemon服务即可,设置其根目录到仓库根目录(或者公开仓库的根目录,因为我们不希望别人可以访问到私有仓库
$ /etc/init.d/git-daemon
$ rc-update add git-daemon
访问时使用
$ git clone git://your.host.name/path/to/repo.git

搭建cgit与web服务器

安装cgit,其实它就是一个C实现的CGI程序
$ emerge -v www-apps/cgit
并在配置文件里做必要的修改
### /etc/cgitrc

scan-path=/opt/git/repos
virtual-root=/
并在web服务器中配置(这里以Nginx为例)
### /etc/nginx/conf.d/cgit.conf
server {
    listen 80;
    server_name git.your.host.name;

    root /usr/share/webapps/cgit/0.10.1/htdocs;


    location ~* ^.+(cgit.(css|png)|favicon.ico|robots.txt) {
        root /usr/share/webapps/cgit/0.10.1/htdocs;
    }

    location / {
        try_files $uri @cgit;
    }

    location @cgit {
        gzip off;
        include uwsgi_params;
        uwsgi_modifier1 9;
        uwsgi_pass unix:/var/run/uwsgi_cgit.sock;
    }
}

测试访问

至此,一个私用的Git仓库便搭建完毕,访问的时候使用:
# SSH协议
ssh://your.host.name/path/to/repo.git

# Git协议
git://your.host.name/path/to/repo.git

# 查看cgit
http://cgit.your.host.name/
除了git-daemon以外,sshd与web服务器这些服务进程在服务器上一般是必备的,所以不至于占用过多的不必要的资源。

其他方案

正文所述的Git仓库授权只通过SSH本身的密钥对完成简单的“是否有效”的认证。如果需要更加完善的支持,可以考虑其他方案,例如:
  • gitolite :原理同样的,实现了更为丰富的认证模式,支持组织、仓库组管理等等。
  • GitLab :一个仿照GitHub功能开发的开源项目(先已有商业化公司),有完整的用户系统,界面美观,支持MergeRequest式的在线合并代码等,适用于企业内部;有社区版免费提供使用。
不过前面也说了,对于个人用来说,不需要数据库支持、丰富的功能以及酷炫的界面。只需要达成,并且方便易用就达到目的了。