Moving from dokuwiki to Gollum wiki

Migrating from Dokuwiki to Gollum

Intro

Last year at $job, I joined another sysadmin team. This team used dokuwiki for writing documentations.

I am not a fan of wiki system, because I am writing most of my documentations in markdown and, then, I am generating static html files using gitbook or mkdocs. All my docs are versioned with git, so I am able to search history of a file.

Main problem here is not to use git or not (that is required), but:

  • people who like wiki won't use any other system, and they are accustomed to a wysiwyg system,
  • people (like me) who like to manage their documentation in markdown won't use any wiki format, and usually do not like wysiwyg.

So, what do we do now ?

There are many options, but the 1st choice should be to use a modern wiki system that handle correctly markdown format and permit flat file storage that can be used with a git backend.

I discovered some wiki systems that tackle correctly those points:

  • gitit (but that is written in haskell),
  • gollum (but that is written in ruby),
  • wikijs (but it is a nodejs application).

According to Github, wikijs is the most popular wiki of that 3 ones. After some tests, it was a well designed application, and fulfills almost all of our requirements. Dockuwiki to wikijs is possible with this project. Moreover it is already present in the roadmap for the future versions 3.X. See this post for more informations about it. So, I think it is possible to get a nice wiki up after some work on pages and changing the links to new medias paths, specially in future releases of wikijs.

I also tested gollum, which was our second choice. Less popular than wikijs but most than gitit. Please also consider that gitit LDAP provisionning seems to be experimental: it is present in this commit but it was for a previous release.

I thought for a long time that Gollum won't satisfy our needs, which requires at least LDAP authentication and the management of user permissions on pages.

However, I found some interesting projects around gollum, including omnigollum based on ruby omniauth library. Gollum looks like a classic nice wiki. IMHO, it is prettier than dokuwiki, even if there is not much differences.

Gollum installation

I found some really interesting readings on Internet for this purpose. Here are 2 articles quite useful IMHO:

That being said, bellow are the steps I did to install my gollum instance (ubuntu 20.04):

# from my history
sudo apt update
# basic packages
sudo apt install -y sudo vim gnupg curl build-essential git make cmake zlib1g-dev libicu-dev libidn11-dev pkg-config libssl-dev openssl
sudo apt install -y python-docutils python3-docutils
sudo apt install -y ruby-full ruby-dev asciidoc

sudo gem install omniauth -v 1.8.1

sudo gem install gollum org-ruby omnigollum github-markup wikicloth RedCloth github-markdown asciidoctor bundle gollum-auth omniauth-ldap 

sudo adduser --shell /bin/bash --gecos 'Gollum application' gollum

Then, as the Gollum user:

su - gollum
git config --global user.name "gollum user"
git config --global user.email "<user>@<domain>"
mkdir wiki && cd wiki

# First we need to create the repository on github/gitlab/...
# Then, we need to add this public key to a user [1] in github/gitlab to allow remote SSH push
# [1] a service account would be a better option.
ssh-keygen -o -t rsa -b 4096 -C "gollum@gollum"
cat /home/gollum/.ssh/id_rsa.pub

git init .

# creating some hooks...
cat<<EOF |tee ~gollum/wiki/.git/hooks/post-commit
#!/usr/bin/env bash
git push origin main
DATE=$(date +"%Y%m%d at %HH%M")
echo -e "${DATE}\nUpdating repository..." >> /var/log/gollum_commit.log
exit
EOF

cat<<EOF |tee ~gollum/wiki/.git/hooks/init
#!/usr/bin/env bash
git pull origin main
exit
EOF

# some verifications
gollum --versions
gem list
ssh -T git@<repository server>

exit

Back to our sudoer user:

chsh -s /usr/bin/git-shell gollum

# creating basic Gollum files
sudo mkdir /etc/gollum
# basic configuration
sudo cp /var/lib/gems/2.7.0/gems/gollum-5.2.3/config.rb /etc/gollum/config.rb

cat<<EOF | sudo tee /etc/systemd/system/gollum.service
[Unit]
Description=Gollum wiki server
After=network.target
After=syslog.target
[Service]
Type=simple
User=gollum
Group=gollum
WorkingDirectory=/home/gollum/wiki/
ExecStart=/usr/local/bin/gollum --live-preview --config "/etc/gollum/config.rb"
Restart=on-abort
[Install]
WantedBy=multi-user.target
EOF

# Testing gollum binary
which gollum
# checking gollum user
getent passwd gollum
gollum /home/gollum/wiki
ss -tunelp | grep 4567

# testing service
sudo systemctl daemon-reload
sudo service start gollum

# checking logs
sudo journalctl -xn150 -f -u gollum.service

Configuring Gollum

Now, we need to modify the config.rb file to fit our needs (LDAP with authorized users, addressing hooks, and adding valid formats for editing):

# Example gollum config with omnigollum authentication
# gollum ../wiki --config config.rb
#
# or run from source with
#
# bundle exec bin/gollum ../wiki/ --config config.rb

# Remove const to avoid
# warning: already initialized constant FORMAT_NAMES
#
# only remove if it's defined.
# constant Gollum::Page::FORMAT_NAMES not defined (NameError)
Gollum::Page.send :remove_const, :FORMAT_NAMES if defined? Gollum::Page::FORMAT_NAMES
# limit to one format
Gollum::Page::FORMAT_NAMES = {:markdown => "Markdown", :rest => "reStructuredText", :asciidoc => "AsciiDoc", :mediawiki => "MediaWiki", :textile   => "Textile"}

=begin
Valid formats are:
{ :markdown  => "Markdown",
  :textile   => "Textile",
  :rdoc      => "RDoc",
  :org       => "Org-mode",
  :creole    => "Creole",
  :rest      => "reStructuredText",
  :asciidoc  => "AsciiDoc",
  :mediawiki => "MediaWiki",
  :pod       => "Pod" }
=end

# Specify the path to the Wiki.
gollum_path = '/home/gollum/wiki'
Precious::App.set(:gollum_path, gollum_path)

# Specify the wiki options.
wiki_options = {
  :ref => "main",
  :live_preview => false,
  :allow_uploads => true,
  :allow_editing => true,
  :css => false,
  :js => false,
  :emoji => true,
  :show_all => true,
  :h1_title => true,
  :user_icons => 'gravatar',
  :per_page_uploads => true
}
Precious::App.set(:wiki_options, wiki_options)

# Set as Sinatra environment as production (no stack traces)
Precious::App.set(:environment, :production)

# Setup Omniauth via Omnigollum.
require 'omnigollum'
require 'omniauth-ldap'

# adding gon-sinatra to retrieve session info in js
require 'gon-sinatra'

options = {
  # OmniAuth::Builder block is passed as a proc
  :providers => Proc.new do
    provider :ldap,
      :title => 'Active Directory Login',
      :host => 'IP.IP.IP.IP',
      :port => 389,
      :method => :plain,
      :base => 'dc=domain,dc=tld',
      :uid => 'sAMAccountName',
      :bind_dn => 'admin@domain.tld',
      :password => 's3cret'
  end,
  :dummy_auth => false,
  # Make the wiki private under any private or editing page
  # from https://github.com/arr2036/omnigollum/issues/45
  :protected_routes => [
      '/private/*',
      '/private',
      '/revert/*',
      '/revert',
      '/create/*',
      '/create',
      '/edit/*',
      '/edit',
      '/login/*',
      '/login',
      '/rename/*',
      '/rename/',
      '/upload/*',
      '/upload/',
      '/delete/*',
      '/delete'
  ],
  # Specify committer name as just the user name
  :author_format => Proc.new { |user| user.name },
  # Specify committer e-mail as just the user e-mail
  :author_email => Proc.new { |user| user.email },
  #:authorized_users => nil,
  :authorized_users => ENV["OMNIGOLLUM_AUTHORIZED_USERS"].split(","),
}

# :omnigollum options *must* be set before the Omnigollum extension is registered
Precious::App.set(:omnigollum, options)
Precious::App.register Omnigollum::Sinatra

# hook for initialization
Gollum::Hook.register(:post_wiki_initialize, :hook_id) do |wiki|
  system('/home/gollum/wiki/.git/hooks/init')
end
Gollum::Hook.unregister(:post_wiki_initialize, :hook_id)


# hook for web edition
Gollum::Hook.register(:post_commit, :hook_id) do |committer, sha1|
#  system('/home/gollum/wiki/.git/hooks/post-commit')
   system('git pull origin main')
   system('git push origin main')
end

Now, I need to add the list of authorized user in a /etc/gollum/.env file:

OMNIGOLLUM_AUTHORIZED_USERS=foo,bar,joe

After this, you need to tell your service file to source it. Add the following in the [Service] section :

EnvironmentFile=/etc/gollum/.env

Then, we will modify gollum itself to aget a login and a logout button + a way to check permsissions when a action is done.

Here are the modifications I have done to do so (deeply inspired by this commit from the omnigollum author). Corresponding files are located in /var/lib/gems/2.7.0/gems/gollum-5.2.3/lib/gollum.

After those modifications, restart gollum.

sudo systemctl daemon-reload
sudo systemctl restart gollum

Modifying Gollum to work with gitlab-ci

First, adjust your ssh config file for the gollum user in git:

cat ~gollum/.ssh/config
Host <Gitlab Server>
    User <service account>
    Hostname <Gitlab Server>
    PreferredAuthentications publickey
    IdentityFile ~gollum/.ssh/id_rsa
    IdentitiesOnly yes

This section is specific to Gitlab users. We will follow gitlab install.

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get install gitlab-runner
SERVER=<gitlab server>
PORT=443
CERTIFICATE=/etc/gitlab-runner/certs/${SERVER}.crt
# Create the certificates hierarchy expected by gitlab
sudo mkdir -p $(dirname "$CERTIFICATE")
# Get the certificate in PEM format and store it
openssl s_client -connect ${SERVER}:${PORT} -showcerts </dev/null 2>/dev/null | sed -e '/-----BEGIN/,/-----END/!d' | sudo tee "$CERTIFICATE" >/dev/null

# Here, you need to register your runner first !
# https://docs.gitlab.com/runner/register/index.html

sudo gitlab-runner register --non-interactive --executor shell --tls-ca-file="$CERTIFICATE" --url "https://${SERVER}/" --registration-token "<your gitlab token>" --tag-list "gollum,wiki" --description "gollum wiki autodeploy" --locked="false"

My .gitlab-ci looks like this:

stages:
  - test
  - deploy

# tag is needed to select the right gilab-runner
# https://www.bitslovers.com/gitlab-runner-tags/

test:
  tags:
    - gollum
  script:
    - whoami
    - gollum --versions

deploy:
  tags:
    - gollum
  timeout: 5 minutes
  script:
    - cd /home/gollum/wiki
    - /usr/bin/git pull
    - chown -R gollum:gollum /home/gollum/wiki
  only: # Only run on main branch
    variables:
      - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Nginx configuration

We need a fqdn for this machine. Let's say you manage your DNS zone and that you can add a A record for our gollum machine "gollum.domain.tld".

sudo apt install -y nginx certbot
sudo mkdir -p /var/www/html/acme-challenge/
sudo chown www-data:www-data /var/www/html/acme-challenge

# for now, we comment all ssl sections
cat<<EOF |sudo tee /etc/nginx/sites-enabled/default
server {
	listen 80 default_server;
	#listen 443 ssl default_server;
	root /var/www/html;
	server_name gollum.domain.tld;
	location / {
        proxy_pass              http://127.0.0.1:4567;
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_connect_timeout   150;
        proxy_send_timeout      100;
        proxy_read_timeout      100;
        proxy_buffers           4 32k;
        client_max_body_size    500m;
        client_body_buffer_size 128k;
	}
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html/acme-challenge/;
        allow all;
        default_type "text/plain";
    }
    #ssl_certificate     /etc/letsencrypt/live/gollum.domain.tld/fullchain.pem;
    #ssl_certificate_key /etc/letsencrypt/live/gollum.domain.tld/privkey.pem;
    #ssl_session_timeout 5m;
    #ssl_ciphers  EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    #ssl_protocols TLSv1.2;
    #ssl_prefer_server_ciphers on;
    access_log  /var/log/nginx/gollum.access.log;
    error_log   /var/log/nginx/gollum.error.log;
}
EOF

sudo nginx -t
sudo service nginx restart
sudo journalctl -xn150 -u nginx.service

Let's encrypt

I have done a Let's encrypt bash script some time ago. I will download it and execute it. It is mainly based upon certbot.

sudo mkdir /root/crons/
sudo wget -O /root/crons/letsencrypt https://gist.githubusercontent.com/remyd1/35fdbcb740fa4fdec1a15727ad7e743c/raw/589cec3af3fed440fbb0d38c493cae2fd44440a0/letsencrypt.sh
sudo chmod u+x /root/crons/letsencrypt
sudo /root/crons/letsencrypt initial

Now we can uncomment ssl sections in /etc/nginx/sites-enabled/default, then, restart the service.

sudo nginx -t
sudo service nginx restart

If it works, you can add a cron to manage let's encrypt certificates:

crontab -e

And add:

@weekly /root/crons/letsencrypt

Convert from dokuwiki

Now, that's the least attractive part. We need to convert each dokuwiki file to a markdown compatible format.

Let's retrieve the dokuwiki pages:

# fix / adapt permissions, then
scp -r user@dokuwiki:/path/to/dokuwiki/data/pages .

We will use the famous pandoc software.

Here is how you can convert a single wiki file to markdown:

cd pages
pandoc -f dokuwiki -t markdown test.txt -o test.md

And to convert all of your files:

find . -type f -name "*.txt" -exec pandoc -f dokuwiki -t markdown {} -o {}.md \;

Then, we can copy all of this into gollum:

# creating back directory structure
find . -type d | xargs -I{} mkdir /path/to/gollum/{} 2>/dev/null
find . -type f -name "*.md" -exec cp {} /path/to/gollum/{}

Issues that need to be adressed

  1. For now, even if you are not login, you can still go to overview, even from /private path. So, do not name your files with some confidential data. If you click on the files, then you have to authenticate. Anyway, if I think it is necessary, I will create a .htpasswd file. Adding /overview/private in the protected routes does not change anything.

  2. Considering the part I modified to manage permissions in gollum code itself, particularly in lib/gollum/app.rb, another option would be to have two wiki running gollum:

  • one with --no-edit mode,

  • another one in read/write.

    The other options would be to use a CAS server or HTTP auth.

    Main issue with a specific commit is to have a code up to date with the official gollum current code. PR is not an option as the main author does not want this kind of auth code in the core of gollum.

  1. Maybe I could try to track users who made some modification through the Gollum frontend in git with the right committer name, and not a system account. See here for more informations.

Other useful resources

Comparison of wiki systems:

Gollum