最近准备搭建一个wiki,看了维基百科上的wiki软件列表后觉得可用的实在太少。我希望架设一个轻量级的wiki,至少能满足以下条件:

其实GitHub自带的wiki系统能很好满足我的需求。后来看到GitHub用的wiki叫做Gollum,简单试用了一下发现效果很不错,能满足绝大部分需求,并且还有额外赠送的基于git的存储。

其实Gollum本身的安装很简单,一个gem install就能搞定,不过想支持OAuth2认证以及服务器配置比较麻烦。所以下面着重从这两方面介绍。

OAuth2认证

要想做OAuth2认证首先要选一家认证服务,我选择了Google。其他OAuth2用到的包可能会稍有不同。

登录到Google Developer Console,新建一个Project,然后打开该Project,从左侧菜单中选择”APIs & auth” → “APIs”,启用”Google+ API”。后面的认证需要用到这个API。

之后选择”APIs & auth” → “Credentials”,单击”Create new Client ID”,然后填入以下值:

  • Application type: Web application
  • Authorized JavaScript origins: http://服务器域名
  • Authorized redirect URIs: http://服务器域名/__omnigollum__/auth/google_oauth2/callback

建好之后从中找出Client IDClient secret两个值,稍后配置Gollum时会用到。

接下来要安装Gollum。我在这里没有使用rvm,因为最初安装时遇到一点问题怀疑是rvm导致的(后证实与rvm无关)从而选择了全局安装ruby。下次安装时可以尝试下rvm。首先安装必要的gems:

sudo gem install gollum omniauth omnigollum omnigollum-google-oauth2

其中:

  • gollum:wiki系统
  • omniauth:通用的认证中间件
  • omnigollum:连接gollumomniauth的桥梁
  • omniauth-googleo-oauth2:为omniauth提供Google OAuth2的认证策略

为了方便配置,我们要将gollum的主目录放在/opt/gollum,并为其单独建一个用户,用这个用户来启动gollum进程。这也是rvm建议的做法。

$ sudo useradd -d /opt/gollum gollum
$ sudo mkdir -p /opt/gollum
$ sudo chown gollum:gollum /opt/gollum

切换到gollum用户:

$ sudo su - gollum

创建git仓库,作为Gollum的存储: $ cd /opt/gollum $ mkdir wiki $ cd wiki $ git init

创建其他必要的目录: $ cd /opt/gollum $ mkdir log run

我们稍后会使用unicorn来运行gollum,所以需要为unicorn创建Rack配置文件/opt/gollum/config.ru

#!/usr/bin/env ruby

require 'rubygems'
require 'gollum/app'

# Specify the path to the Wiki.
gollum_path = File.join(File.expand_path(File.dirname(__FILE__)), 'wiki')
puts gollum_path
Precious::App.set(:gollum_path, gollum_path)

# Specify markup language
Precious::App.set(:default_markup, :markdown)

# Specify the wiki options
wiki_options = {
  :live_preview => false,     # 禁用实时预览
  :allow_uploads => true,     # 允许文件上传
  :per_page_uploads => true,  # 文件上传到每页的子目录中
  :h1_title => true,          # 页面中的首个h1作为页面标题
  :allow_editing => true      # 允许所有人编辑
}
Precious::App.set(:wiki_options, wiki_options)

# Add Google OAuth2 authentication to all urls
require 'omnigollum'
require 'omniauth/strategies/google_oauth2'

GOOGLE_CLIENT_ID = '<Google提供的Client ID>'
GOOGLE_CLIENT_SECRET = '<Google提供的Client Secret>'

options = {
  :providers => Proc.new do
    provider :google_oauth2, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, {
      :scope => 'email,profile',
      :hd => '允许登录的用户的域',
      :redirect_uri => 'http://服务器域名/__omnigollum__/auth/google_oauth2/callback'
    }
  end,

  :dummy_auth => false,
  :protected_routes => ['/*'],
  :author_format => Proc.new { |user| user.name },
  :author_email => Proc.new { |user| user.email }
}

Precious::App.set(:omnigollum, options)
Precious::App.register Omnigollum::Sinatra

# limit to some formats
Gollum::Page::FORMAT_NAMES = {
  :markdown  => "Markdown",
  :mediawiki => "MediaWiki",
  :rdoc      => "RDoc"
}


# Run
run Precious::App

上述配置中的:providers部分是OAuth2的认证,文档参考omni-google-oauth2的文档wiki_options是Gollum本身的认证,详细配置可以参考Gollum的文档

运行Gollum

我们将使用nginx + unicorn运行Gollum,用supervisord做系统监视。

首先创建unicorn的配置文件/opt/gollum/unicorn.conf.rb

app_directory = "/opt/gollum"
worker_processes 2
working_directory app_directory
listen "unix:#{app_directory}/run/gollum.sock", :backlog => 1024
timeout 60
user 'gollum', 'gollum'
File.umask(027)
preload_app true
pid "#{app_directory}/run/gollum.pid"
stderr_path "#{app_directory}/log/gollum-stderr.log"
stdout_path "#{app_directory}/log/gollum-stdout.log"

然后配置nginx(/etc/nginx/conf.d/gollum.conf,基于CentOS 6):

upstream unicorn {
    server unix:/opt/gollum/run/gollum.sock;
}

server {
    listen 80;
    server_name     服务器域名;
    access_log      /opt/gollum/log/nginx_access_log;
    error_log       /opt/gollum/log/nginx_error_log info;

    location / {
	proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_redirect   off;
        proxy_pass       http://unicorn;
    }
}

试着启动服务器:

$ cd /opt/gollum
$ unicorn -c unicorn.conf.rb

到这一步应该可以通过浏览器访问wiki了。

接下来配置supervisord。首先安装supervisord:

$ sudo pip install supervisor

创建/etc/supervisord.conf

[unix_http_server]
file=/var/tmp/supervisor.sock


[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB       ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10          ; (num of main logfile rotation backups;default 10)
loglevel=debug               ; (logging level;default info; others: debug,warn)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false              ; (start in foreground if true;default false)
minfds=1024                 ; (min. avail startup file descriptors;default 1024)
minprocs=200                ; (min. avail process descriptors;default 200)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface


[supervisorctl]
serverurl=unix:///var/tmp/supervisor.sock ; use a unix:// URL  for a unix socket

[include]
files=/etc/supervisord.d/*.conf

创建目录/etc/supervisord.d/并创建以下配置文件/etc/supervisord.d/gollum.conf

[program:gollum]
directory=/opt/gollum
command=/usr/local/bin/unicorn -c unicorn.conf.rb
user=gollum
umask=022
autostart=true
autorestart=true
startretries=3
redirect_stderr=true

创建initscript以启动supervisord(/etc/init.d/supervisord):

#!/bin/bash
#
# supervisord   Startup script for the Supervisor process control system
#
# Author:       Mike McGrath <mmcgrath@redhat.com> (based off yumupdatesd)
#               Jason Koppe <jkoppe@indeed.com> adjusted to read sysconfig,
#                   use supervisord tools to start/stop, conditionally wait
#                   for child processes to shutdown, and startup later
#               Erwan Queffelec <erwan.queffelec@gmail.com>
#                   make script LSB-compliant
#
# chkconfig:    345 83 04
# description: Supervisor is a client/server system that allows \
#   its users to monitor and control a number of processes on \
#   UNIX-like operating systems.
# processname: supervisord
# config: /etc/supervisord.conf
# config: /etc/sysconfig/supervisord
# pidfile: /var/run/supervisord.pid
#
### BEGIN INIT INFO
# Provides: supervisord
# Required-Start: $all
# Required-Stop: $all
# Short-Description: start and stop Supervisor process control system
# Description: Supervisor is a client/server system that allows
#   its users to monitor and control a number of processes on
#   UNIX-like operating systems.
### END INIT INFO

# Source function library
. /etc/rc.d/init.d/functions

# Source system settings
if [ -f /etc/sysconfig/supervisord ]; then
    . /etc/sysconfig/supervisord
fi

# Path to the supervisorctl script, server binary,
# and short-form for messages.
supervisorctl=/usr/bin/supervisorctl
supervisord=${SUPERVISORD-/usr/bin/supervisord}
prog=supervisord
pidfile=${PIDFILE-/var/run/supervisord.pid}
lockfile=${LOCKFILE-/var/lock/subsys/supervisord}
STOP_TIMEOUT=${STOP_TIMEOUT-60}
OPTIONS="${OPTIONS--c /etc/supervisord.conf}"
RETVAL=0

start() {
    echo -n $"Starting $prog: "
    daemon --pidfile=${pidfile} $supervisord $OPTIONS
    RETVAL=$?
    echo
    if [ $RETVAL -eq 0 ]; then
        touch ${lockfile}
        $supervisorctl $OPTIONS status
    fi
    return $RETVAL
}

stop() {
    echo -n $"Stopping $prog: "
    killproc -p ${pidfile} -d ${STOP_TIMEOUT} $supervisord
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && rm -rf ${lockfile} ${pidfile}
}

reload() {
    echo -n $"Reloading $prog: "
    LSB=1 killproc -p $pidfile $supervisord -HUP
    RETVAL=$?
    echo
    if [ $RETVAL -eq 7 ]; then
        failure $"$prog reload"
    else
        $supervisorctl $OPTIONS status
    fi
}

restart() {
    stop
    start
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status -p ${pidfile} $supervisord
        RETVAL=$?
        [ $RETVAL -eq 0 ] && $supervisorctl $OPTIONS status
        ;;
    restart)
        restart
        ;;
    condrestart|try-restart)
        if status -p ${pidfile} $supervisord >&/dev/null; then
          stop
          start
        fi
        ;;
    force-reload|reload)
        reload
        ;;
    *)
        echo $"Usage: $prog {start|stop|restart|condrestart|try-restart|force-reload|reload}"
        RETVAL=2
esac

exit $RETVAL

最后启动supervisord:

$ sudo service supervisord start
$ sudo supervisorctl status