ikiwiki в режиме CGI

Оглавление

Введение

В прошлой статье была рассмотрена настройка Ikiwiki в качестве генератора статических сайтов.

Ikiwiki можно настроить для работы в режиме CGI, который позволяет редактировать содержимое страниц из веб-браузера. CGI-скрипт используется только для обновления содержимого страниц, поэтому не влияет на скорость отдачи HTML-страниц. Для достижения максимальной скорости отдачи страниц можно воспользоваться каким-нибудь мультиплексирующим веб-сервером, поддерживающим системные вызовы kqueue, epoll и sendfile: nginx, lighttpd, thttpd, mathopd, boa.

Донастройка ikiwiki

Для редактирования страниц через веб-браузер при сборке ikiwiki из pkgsrc нужно включить опцию cgi, прописав опции в файле /etc/mk.conf:

PKG_OPTIONS.ikiwiki=            cgi -cvs -git -ikiwiki-amazon-s3 -ikiwiki-highlight -ikiwiki-search -ikiwiki-sudo -imagemagick -l10n -python -svn -w3m -p5-Text-Markdown-Discount -p5-Text-Markdown p5-Text-MultiMarkdown

Если ikiwiki уже установлена, то можно переустановить её с новыми опциями следующим образом:

# cd /usr/pkgsrc/www/ikiwiki
# make clean
# make
# pkg_delete ikiwiki
# make install

Если ikiwiki ещё не установлена, то собрать и установить можно следующим образом:

# cd /usr/pkgsrc/www/ikiwiki
# make install

Рассмотрим опции, влияющие на работу ikiwiki в режиме CGI.

Опция Значение по умолчанию Описание
adminuser [] Список пользователей-администраторов. Администратор может редактировать заблокированную страницу, которую уже открыл на редактирование обычный пользователь. Если включен плагин websetup, то администраоры будут иметь доступ к его функциям, а обычные пользователи - нет.
cgiurl Пустая строка Ссылка на CGI-скрипт ikiwiki. Я выставил ссылку https://stupin.su/wiki/index.cgi
reverse_proxy 0 По умолчанию ссылка на CGI-скрипт прописывается в HTML-страницах полностью. Если выставлено значение 1, то в HTML-страницах прописывается относительная ссылка. У меня ikiwki работает в виртуальной машине, а доступ снаружи осуществляется через обратный прокси, поэтому я выставил значение 1.
cgi_wrapper Пустая строка Путь к CGI-скрипту. Стоит прописать сюда полный путь к файлу. Я задал имя файла /home/wiki/dst/index.cgi
cgi_wrappermode 06755 Права доступа к CGI-скрипту. Я выставил значение 0700: без SUID-бита, читать, писать и исполнять скрипт имеет право только его владелец.
account_creation_password s3cr1t Пароль, который необходимо ввести новому пользователю для регистрации. Стоит поменять этот пароль, чтобы заблокировать возможность регистрации новых пользователей. При необходимости этот пароль можно сообщить человеку, желающему зарегистрироваться, а затем снова поменять.

После изменения настроек нужно вызвать ikiwiki с указанием имени файла конфигурации:

 $ ikiwiki --setup src/.ikiwiki/ikiwiki.setup

ikiwiki сгенерирует CGI-скрипт, в который будут вшиты эти настройки. При последующем изменении настроек не забывайте снова выполнять эту команду, чтобы новые настройки вступали в силу.

CGI с использованием uwsgi

Установка и настройка uwsgi

Для запуска ikiwiki в режиме CGI я решил воспользоваться сервером приложений uwsgi. Пропишем в файл /etc/mk.conf опции для его сборки:

PKG_OPTIONS.py-uwsgi=           -debug -openssl -pcre -uuid -uwsgi-sse_offload -yaml -jansson -yajl -expat -libxml2

Установим uwsgi:

# cd /usr/pkgsrc/www/py-uwsgi
# make install

В pkgsrc нет примера rc-файла, но это не проблема. За основу можно взять любой уже существующий rc-файл. Я взял за основу rc-файл openntpd. Создадим файл /etc/rc.d/uwsgi для запуска uwsgi:

#!/bin/sh

# PROVIDE: uwsgi
# REQUIRE: DAEMON
# BEFORE:  LOGIN

. /etc/rc.subr

name="uwsgi"
rcvar="uwsgi"
command="/usr/pkg/bin/uwsgi-3.8"
required_files="/usr/pkg/etc/uwsgi.ini"

load_rc_config $name
run_rc_command "$1"

Создадим файл конфигурации /usr/pkg/etc/uwsgi.ini:

[uwsgi]

procname = uwsgi-cgi
procname-master = uwsgi-cgi-master

master-as-root = yes
uid = wiki
gid = users
chdir = /home/wiki/

cgi-mode = yes
plugins = cgi
cgi = /home/wiki/dst/index.cgi
cgi-timeout = 120
socket = /var/run/uwsgi.sock
pidfile = /var/run/uwsgi.pid

processes = 1

Включим uwsgi и пропишем опции для его запуска в файле /etc/rc.conf:

uwsgi=YES
uwsgi_flags="-d /var/log/uwsgi.log --ini /usr/pkg/etc/uwsgi.ini"

Осталось запустить uwsgi:

# /etc/rc.d/uwsgi start
Starting uwsgi.
[uWSGI] getting INI configuration from /usr/pkg/etc/uwsgi.ini
!!! UNABLE to load uWSGI plugin: Cannot open "./cgi_plugin.so" !!!

Несмотря на предупреждение uwsgi о невозможности открыть плагин CGI, плагин CGI работает, т.к. скомпонован с uwsgi статически.

Донастройка nginx

Если nginx уже установлен, то нужно включить опцию uwsgi, пересобрать и переустановить его. Пропишем в файл /etc/mk.conf нужные нам опции сборки nginx:

PKG_OPTIONS.nginx=              -array-var -auth-request -cache-purge -dav -debug -echo -encrypted-session -flv -form-input -geoip -gtools -gzip -headers-more -http2 -image-filter -luajit -mail-proxy -memcache -naxsi -njs -pcre -perl -push -realip -rtmp -secure-link -set-misc -slice -ssl -status -stream-ssl-preread -sub uwsgi

Пересобрать и переустановить nginx можно следующим образом:

# cd /usr/pkgsrc/www/nginx
# make clean
# make
# pkg_delete nginx
# make install

Если nginx ещё не установлен, то установить его можно вот так:

# cd /usr/pkgsrc/www/nginx
# make install

Добавим в секцию server файла /usr/pkg/etc/nginx/nginx.conf к уже имеющимся настройкам дополнительную секцию с правилами обработки CGI-скрипта:

location /wiki/ {
  alias /home/wiki/dst/;
  index index.html;
}

location = /wiki/index.cgi {
  uwsgi_pass unix:/var/run/uwsgi.sock;
  include uwsgi_params;

  uwsgi_modifier1 9;
  uwsgi_param SCRIPT_FILENAME /home/wiki/dst/index.cgi;
}

Включим nginx в файле /etc/rc.conf, если это ещё не было сделано:

nginx=YES

Осталось перезапустить nginx, чтобы запустить новый двоичный файл nginx и задействовать новый файл конфигурации:

# /etc/rc.d/nginx restart

uwsgi из pkgsrc тянет с собой не нужную нам зависимость - язык Python. Интересно, что в этом проекте для сборки программы на языке Си используются не привычная утилита make и Makefile'ы, а скрипты, написанные на Python. Несмотря на то, что язык Python мне знаком, скрипты сборки показались мне довольно запутанными и я не смог доработать pkgsrc так, чтобы uwsgi собирался без Python и множества плагинов, не нужных для работы ikiwiki.

CGI с использованием spawn-fcgi и fcgiwrap

fcgiwrap - это программа, которая принимает запросы через Unix-сокет по протоколу FastCGI и вызывает для их обслуживания CGI-скрипт. Она может как открывать Unix-сокет с указанным именем сама, так и принимать идентификатор Unix-сокета через стандартный ввод.

spawn-fcgi - это программа, которая создаёт Unix-сокет, принимает через него запросы по протоколу FastCGI, а для их обслуживания порождает указанные FastCGI-процессы. Для Unix-сокета можно указать владельца, группу и права доступа. Для FastCGI-процессов можно указать пользователя и группу, от имени которых они будут работать. FastCGI-процессы можно запускать в указанном рабочем каталоге и/или указанной chroot-среде.

Установка и настройка spawn-fcgi и fcgiwrap

Установить обе программы можно из системы pkgsrc:

# cd /usr/pkgsrc/www/fcgiwrap
# make install
# cd /usr/pkgsrc/www/spawn-fcgi
# make install

Скрипт инициализации spawnfcgi из пакета spawn-fcgi не позволяет указать для процесса spawn-fcgi все возможные настройки и не умеет корректно работать в случаях, когда для обслуживания запросов из Unix-сокета используется несколько процессов. Я практически полностью переписал этот скрипт, так что он принял следующий вид:

#!/bin/sh
#
# $NetBSD: spawnfcgi.sh,v 1.4 2011/07/25 11:36:29 imil Exp $
#
# PROVIDE: spawnfcgi
# REQUIRE: DAEMON

. /etc/rc.subr

name="spawnfcgi"
rcvar=$name
command="/usr/pkg/bin/spawn-fcgi"
start_cmd="spawnfcgi_start"
stop_cmd="spawnfcgi_stop"
status_cmd="spawnfcgi_status"
pidfile_base="/var/run/spawnfcgi-"

spawnfcgi_start()
{
        rv=0
        for job in "" $spawnfcgi_jobs; do
                pidfile=${pidfile_base}${job}.pid
                [ -z $job ] && continue
                if [ -f $pidfile ] ; then
                        (cat $pidfile ; echo) | while read pid ; do
                                kill -0 $pid
                                if $! ; then
                                        echo "${name}/${job} at PID $pid is already running"
                                        rv=1
                                        continue
                                fi
                        done
                fi
                job_command=$(eval echo \$${name}_${job}_command)
                job_args=$(eval echo \$${name}_${job}_args)
                echo "Starting ${name}/$job."
                $command ${job_args} -P $pidfile -- ${job_command}
        done
        return $rv
}

spawnfcgi_stop()
{
        rv=0
        for job in "" $spawnfcgi_jobs; do
                pidfile=${pidfile_base}${job}.pid
                [ -z $job ] && continue
                if [ ! -f $pidfile ] ; then
                        echo "${name}/${job} is not running"
                        continue
                fi
                (cat $pidfile ; echo) | while read pid ; do
                        echo -n "${name}/${job} at PID $pid"
                        kill -TERM $pid
                        if $! ; then
                                wait_for_pids $pid
                                echo " is stopped"
                        else
                                echo " is not running"
                                rv=1
                        fi
                done
                rm $pidfile
        done
        return $rv
}

spawnfcgi_status()
{
        rv=0
        for job in "" $spawnfcgi_jobs; do
                pidfile=${pidfile_base}${job}.pid
                [ -z $job ] && continue
                if [ ! -f $pidfile ] ; then
                        echo "${name}/${job} is not running"
                        continue
                fi
                (cat $pidfile ; echo) | while read pid ; do
                        echo -n "${name}/${job} at PID $pid"
                        kill -0 $pid
                        if $! ; then
                                echo " is running"
                        else
                                echo " is not running"
                                rv=1
                        fi
                done
        done
        return $rv
}

load_rc_config $name
run_rc_command $1

Настроим запуск spawn-fcgi, вписав в файл /etc/rc.conf следующие настройки:

spawnfcgi=YES
spawnfcgi_jobs="ikiwiki"
spawnfcgi_ikiwiki_args="-u wiki -g users -s /var/run/ikiwiki.sock -U nginx -G nginx -M 0600 -d /home/wiki/dst/ -F 4"
spawnfcgi_ikiwiki_command="/usr/pkg/sbin/fcgiwrap"

Запустим spawn-fcgi при помощи следующей команды:

# /etc/rc.d/spawnfcgi start

Для ослуживания запросов будет запущено 4 процесса fcgiwrap под пользователем wiki и группой users, которые будут ослуживать запросы на Unix-сокете /var/run/ikiwiki.sock. Владельцем сокета будет пользователь nginx и группа nginx, правами чтения и записи в сокет будет обладать только сам пользователь nginx. Текущим каталогом для процессов будет каталог /home/wiki/dst/

Донастройка nginx

В отличие от конфигурации с использованием uwsgi, в случае со spawn-fcgi пересобирать nginx не требуется, т.к. поддержка протокола FastCGI в nginx уже имеется.

Добавим в секцию server файла /usr/pkg/etc/nginx/nginx.conf к уже имеющимся настройкам дополнительную секцию с правилами обработки CGI-скрипта:

location /wiki/ {
  alias /home/wiki/dst/;
  index index.html;
}

location = /wiki/index.cgi {
  fastcgi_pass unix:/var/run/ikiwiki.sock;
  include /usr/pkg/etc/nginx/fastcgi_params;
  fastcgi_param SCRIPT_FILENAME /home/wiki/dst/index.cgi;
}

Осталось перезагрузить nginx, чтобы задействовать новый файл конфигурации:

# /etc/rc.d/nginx restart

Автор spawn-fcgi, похоже, расчитывал на то, что эта программа будет запускаться программой multiwatch, которая умеет изменять количество запущенных процессов spawn-fcgi без их предварительной остановки, как это происходит при использовании скрипта spawncgi, а также умеет перезапускать внезапно завершившиеся процессы. К сожалению, в pkgsrc нет программы mulitwatch. Впрочем, она зависит от библиотеки glib и мне кажется, что в такой программе можно было обойтись и без неё.

CGI с использованием thttpd

Настроим Ikiwiki под управлением веб-сервера thttpd. В отличие от nginx, thttpd умеет самостоятельно запускать CGI-скрипты. Из недостатков можно назвать лишь, пожалуй, отсутствие поддержки протокола HTTPS.

Устанавилваем thttpd из pkgsrc:

# cd /usr/pkgsrc/www/thttpd
# make install

Копируем скрипт инициализации:

# cp /usr/pkg/share/examples/rc.d/thttpd /etc/rc.d/

Прописываем настройки в файл конфигурации /usr/pkg/etc/thttpd.conf:

logfile=/var/log/thttpd.log
pidfile=/var/run/thttpd.pid
port=8080
dir=/home/wiki/dst/
cgipat=index.cgi
user=wiki

Запускаем thttpd:

# /etc/rc.d/thttpd start

Включаем запуск thttpd в файле /etc/rc.conf:

thttpd=YES

CGI с использованием mathopd

Mathopd тоже имеет встроенную поддержку CGI, но тоже не имеет поддержки HTTPS. Существенный плюс по сравнению с thttpd - поддержка авторизации и псевдонимов.

Для установки mathopd понадобится pkgsrc-wip. Его развёртывание описано на странице Система pkgsrc.

Устанавилваем mathopd из pkgsrc:

# cd /usr/pkgsrc/wip/mathopd
# make install

Копируем скрипт инициализации:

# cp /usr/pkg/share/examples/rc.d/mathopd /etc/rc.d/

Прописываем настройки в файл конфигурации /usr/pkg/etc/mathopd.conf:

ErrorLog /var/log/mathopd/mathopd/error_log
Log /var/log/mathopd/mathopd/access_log.%Y%m%d
LogGMT Off

StayRoot On
User mathopd
Umask 022
PIDFile /var/run/mathopd.pid

Tuning {
        AcceptMulti On
        Clobber On

        NumConnections 64
        BufSize 12288
        InputBufSize 2048
        ScriptBufSize 4096
        NumHeaders 100

        ScriptTimeout 10
        Timeout 10
        Wait 10
}

LogFormat {
        Ctime
        Method
        Uri
        QueryString
        Version
        Status
        BytesRead
        Referer
        UserAgent
}

Control {
        Admin vladimir@stupin.su

        AllowDotfiles Off
        SanitizePath On
        UserDirectory Off

        Types {
                image/gif { .gif }
                image/png {
                        .png
                        .ico
                }
                image/jpeg { .jpg }
                text/css { .css }
                text/html { .html }
                text/plain { .sh }
                application/octet-stream { * }
        }

        Specials {
                CGI { .cgi }
        }

        ExtraHeaders {
                "Cache-Control: max-age=3600"
        }

        IndexNames {
                index.html
        }
}

Server {
        Address 0.0.0.0
        Port 80
        Backlog 128

        Virtual {
                AnyHost

                Control {
                        Alias /wiki/
                        Location /home/wiki/dst/
                }

                Control {
                        Alias /wiki/private/
                        Location /home/wiki/dst/private/

                        Realm "private content"
                        EncryptedUserFile On
                        UserFile /home/wiki/src/.ikiwiki/private_users
                }

                Control {
                        Alias /wiki/ufanet/
                        Location /home/wiki/dst/ufanet/

                        Realm "limited access"
                        EncryptedUserFile On
                        UserFile /home/wiki/src/.ikiwiki/ufanet_users
                }

                Control {
                        Alias /wiki/index.cgi
                        Location /home/wiki/dst/index.cgi
                        PathArgs On

                        RunScriptsAsOwner Off
                        ScriptUser wiki
                }
        }
}

Секции Control одного уровня просматриваются веб-сервером снизу вверх, так что более специфичные секции нужно помещать ниже. В противном случае сработает более общая секция, которая при просмотре снизу вверх встретится веб-серверу первой.

Запускаем mathopd:

# /etc/rc.d/mathopd start

Включаем запуск mathopd в файле /etc/rc.conf:

mathopd=YES