Pages

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式的在线合并代码等,适用于企业内部;有社区版免费提供使用。
不过前面也说了,对于个人用来说,不需要数据库支持、丰富的功能以及酷炫的界面。只需要达成,并且方便易用就达到目的了。
-------------

安装配置Cgit

安装Cgit

git clone https://git.zx2c4.com/cgit
cd cgit
make get-git
make
make install

合并gitolite-admin.git 和 cgitrc

  • gitolite-admin.git用来管理gitolite的库
  • /etc/cgitrc是cgit的配置文件
rm /etc/cgitrc
ln -s /home/git/.gitolite/local/cgitrc /etc/cgitrc

然后在gitolite-admin库里建立local/cgitrc文件

使Cgit显示Gitolite库

配置local/cgitrc

enable-git-config=1
enable-gitweb-owner=1
remove-suffix=1
project-list=/home/git/projects.list
scan-path=/home/git/repositories/

设置库属性

repo testing
    RW+     =   @all
    R           =   gitweb
    desc = For testing purpose.
    owner = Jian Zhou
    config cgit.clone-url = clone-url

设置权限

更改.gitolite.rc文件

21c21
<     UMASK                           =>  0077,
---
>     UMASK                           =>  0027,
24c24
<     GIT_CONFIG_KEYS                 =>  '',
---
>     GIT_CONFIG_KEYS                 =>  '.*',
chmod -R g+rX repositories

添加www-data到git组

usermod -aG git www-data

cgitrc全部内容

max-repo-count=50
# Enable caching of up to 1000 output entries
cache-size=1000
section-from-path=1
virtual-root=/

cache-root=/root/cache/cgit

# Specify some default clone urls using macro expansion
#clone-url=git://foo.org/$CGIT_REPO_URL git@foo.org:$CGIT_REPO_URL

# Specify the css url
css=/cgit.css


# Show owner on index page
enable-index-owner=1


# Allow http transport git clone
enable-http-clone=1


# Show extra links for each repository on the index page
enable-index-links=1


# Enable blame page and create links to it from tree page
enable-blame=1


# Enable ASCII art commit history graph on the log pages
enable-commit-graph=1


# Show number of affected files per commit on the log pages
enable-log-filecount=1


# Show number of added/removed lines per commit on the log pages
enable-log-linecount=1


# Sort branches by date
branch-sort=age


# Add a cgit favicon
favicon=/favicon.ico


# Use a custom logo
logo=/cgit.png


# Enable statistics per week, month and quarter
max-stats=quarter


# Set the title and heading of the repository index page
root-title=Kule Yang's privete git repositories


# Set a subheading for the repository index page
root-desc=tracking the foobar development


# Include some more info about example.com on the index page
root-readme=/var/www/htdocs/about.html


# Allow download of tar.gz, tar.bz2 and zip-files
snapshots=tar.gz tar.bz2 zip



##
## List of common mimetypes
##

mimetype.gif=image/gif
mimetype.html=text/html
mimetype.jpg=image/jpeg
mimetype.jpeg=image/jpeg
mimetype.pdf=application/pdf
mimetype.png=image/png
mimetype.svg=image/svg+xml


# Highlight source code with python pygments-based highlighter
source-filter=/usr/local/lib/cgit/filters/syntax-highlighting.py

# Format markdown, restructuredtext, manpages, text files, and html files
# through the right converters
about-filter=/usr/local/lib/cgit/filters/about-formatting.sh

##
## Search for these files in the root of the default branch of repositories
## for coming up with the about page:
##
readme=:README.md
readme=:readme.md
readme=:README.mkd
readme=:readme.mkd
readme=:README.rst
readme=:readme.rst
readme=:README.html
readme=:readme.html
readme=:README.htm
readme=:readme.htm
readme=:README.txt
readme=:readme.txt
readme=:README
readme=:readme
readme=:INSTALL.md
readme=:install.md
readme=:INSTALL.mkd
readme=:install.mkd
readme=:INSTALL.rst
readme=:install.rst
readme=:INSTALL.html
readme=:install.html
readme=:INSTALL.htm
readme=:install.htm
readme=:INSTALL.txt
readme=:install.txt
readme=:INSTALL
readme=:install


##
## List of repositories.
## PS: Any repositories listed when section is unset will not be
##     displayed under a section heading
## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
##      and included like this:
##        include=/etc/cgitrepos
##

enable-git-config=1
enable-gitweb-owner=1
remove-suffix=0
project-list=/home/git/projects.list
scan-path=/home/git/repositories/


# repo.url=foo
# repo.path=/pub/git/foo.git
# repo.desc=the master foo repository
# repo.owner=fooman@example.com
# repo.readme=info/web/about.html
#
#
# repo.url=bar
# repo.path=/pub/git/bar.git
# repo.desc=the bars for your foo
# repo.owner=barman@example.com
# repo.readme=info/web/about.html


# The next repositories will be displayed under the 'extras' heading
# section=extras
#
#
# repo.url=baz
# repo.path=/pub/git/baz.git
# repo.desc=a set of extensions for bar users
#
# repo.url=wiz
# repo.path=/pub/git/wiz.git
# repo.desc=the wizard of foo


# Add some mirrored repositories
# section=mirrors
#
#
# repo.url=git
# repo.path=/pub/git/git.git
# repo.desc=the dscm
#
#
# repo.url=linux
# repo.path=/pub/git/linux.git
# repo.desc=the kernel

# Disable adhoc downloads of this repo
# repo.snapshots=0

# Disable line-counts for this repo
# repo.enable-log-linecount=0

# Restrict the max statistics period for this repo
# repo.max-stats=month