User Tools

Site Tools


doc:appunti:linux:sa:liquidfeedback

LiquidFeedback

Installation on Debian Wheezy

Our goal is to install LiquidFeedback on a Debian Wheezy, using an Apache VirtualHost and Postgres 9.1 with login/password authentication. We also try to follow the Filesystem Hierarchy Standard for installation directories, etc.

Install the required packages (database, libraries to compile lqfb core tools, LUA language for the frontend, image manipulation tools, etc.):

apt-get install postgresql libpq-dev imagemagick lua5.1 liblua5.1-0-dev ghc libghc6-parsec3-dev

Components

Core The core consists of a database scheme for the PostgreSQL database, including the algorithms for delegations, feedback and the voting procedure implemented as SQL views and database procedures written in PL/pgSQL. It includes also an external tool to be executed as a cronjob.
Frontend The frontend is implemented in Lua using the web application framework WebMCP.
WebMCP Web application framework written in Lua and C. Instead of using the classical Model-View-Controller (MVC) concept, WebMCP makes use of a so-called Model-View-Action concept.
RocketWiki It is a small parser written in Haskell which translates a wiki dialect to HTML. The special “LiquidFeedback Edition” disallow the use of images.

Download the following archives and save them into a directory (we used /usr/local/download/lqfb/):

Core tools, WebMCP, RocketWiki and Frontend

Compile the core tools

cd /usr/local/src/
tar zxvf /usr/local/download/lqfb/liquid_feedback_core-v2.1.0.tar.gz
cd liquid_feedback_core-v2.1.0/
make

Install core tools

mkdir -p /usr/local/lib/liquid_feedback_core
cp core.sql lf_update /usr/local/lib/liquid_feedback_core

Install WebMCP

cd /usr/local/src/
tar zxvf /usr/local/download/lqfb/webmcp-v1.2.5.tar.gz
cd webmcp-v1.2.5/
vi Makefile.options
make
mkdir -p /usr/local/lib/webmcp
cp -RL framework/* /usr/local/lib/webmcp/

Install RocketWiki LqFb-Edition

cd /usr/local/src/
tar zxvf /usr/local/download/lqfb/rocketwiki-lqfb-v0.4.tar.gz
cd rocketwiki-lqfb-v0.4/
make
mkdir -p /usr/local/lib/rocketwiki-lqfb
cp rocketwiki-lqfb rocketwiki-lqfb-compat /usr/local/lib/rocketwiki-lqfb/

Install LiquidFeedback-Frontend v2.1.2

cd /usr/local/src/
tar zxvf /usr/local/download/lqfb/liquid_feedback_frontend-v2.1.2.tar.gz
cd liquid_feedback_frontend-v2.1.2/
mv liquid_feedback_frontend-v2.1.2 /usr/local/share/liquid_feedback_frontend
chown www-data /usr/local/share/liquid_feedback_frontend/tmp/
cd /usr/local/share/liquid_feedback_frontend/locale/
PATH=/usr/local/lib/rocketwiki-lqfb:$PATH make

Compile binary for fast delivery of member images

cd /usr/local/share/liquid_feedback_frontend/fastpath/
vi getpic.c
# Edit: #define GETPIC_CONNINFO "dbname=liquid_feedback"
# Edit: #define GETPIC_DEFAULT_AVATAR "/usr/local/share/liquid_feedback_frontend/static/avatar.jpg"
make

Create the frontend configuration

cd /usr/local/share/liquid_feedback_frontend/config
cp example.lua campibisenzio5stelle.lua
chgrp www-data campibisenzio5stelle.lua
chmod 640 campibisenzio5stelle.lua

The configuration file must be readable by the web server process, but not readable by others, because it can contains database credentials and other sensitive data.

The configuration name (campibisenzio5stelle in the example) must be referenced by the web server via the WEBMCP_CONFIG_NAME environment variable (see below).

Then you must adjust the configuration of the Frontend, this is an example of values changed from defaults:

config.instance_name = "Movimento 5 Stelle Campi Bisenzio"
config.app_service_provider = "Movimento 5 Stelle Campi Bisenzio<br/>Campi Bisenzio<br/>Italia"
config.absolute_base_url = "http://lqfb.campibisenzio5stelle.it/lf/"
config.database = { host='localhost', engine='postgresql', dbname='liquid_feedback', user='liquid_feedback', password='MySecret' }
  rocketwiki= "/usr/local/lib/rocketwiki-lqfb/rocketwiki-lqfb",
  compat = "/usr/local/lib/rocketwiki-lqfb/rocketwiki-lqfb-compat"
config.default_lang = "it"
config.mail_envelope_from = "lqfb@campibisenzio5stelle.it"
config.mail_from = { name = "LiquidFeedback", address = "lqfb@campibisenzio5stelle.it" }

Database (the Core)

Create database user and database

su - postgres
psql
CREATE USER liquid_feedback PASSWORD 'MySecret';
CREATE DATABASE liquid_feedback OWNER liquid_feedback 
    LC_COLLATE = 'it_IT.UTF-8' LC_CTYPE = 'it_IT.UTF-8' TEMPLATE template0;

Initialize the database

cd /usr/local/lib/liquid_feedback_core
psql -v ON_ERROR_STOP=1 -U liquid_feedback -W -h localhost -f core.sql liquid_feedback
psql -U liquid_feedback -W -h localhost liquid_feedback
INSERT INTO system_setting (member_ttl) VALUES ('1 year');
INSERT INTO contingent (polling, time_frame, text_entry_limit, initiative_limit) VALUES (FALSE, '1 hour', 20, 6);
INSERT INTO contingent (polling, time_frame, text_entry_limit, initiative_limit) VALUES (FALSE, '1 day', 80, 12);
INSERT INTO policy (INDEX, name, admission_time, discussion_time, verification_time, voting_time,
    issue_quorum_num, issue_quorum_den, initiative_quorum_num, initiative_quorum_den)
  VALUES (1, 'Default policy', '8 days', '15 days', '8 days', '15 days', 10, 100, 10, 100);
INSERT INTO unit (name) VALUES ('M5S Campi Bisenzio');
INSERT INTO area (unit_id, name) VALUES (1, 'Default area');
INSERT INTO allowed_policy (area_id, policy_id, default_policy) VALUES (1, 1, TRUE);

Here we created a single unit named M5S Campi Bisenzio which contains a single area Default area. A unit is … FIXME An area is … FIXME

Create the admin account

We register the admin account assigning to it an invite code. The invite is a secret code to be used only once, the user will be asked to enter a password, an email address, etc.

INSERT INTO member (login, name, admin, invite_code) VALUES ('admin', 'Administrator', TRUE, 'vieniqua');

lf_update daemon

The lf_update core program must be run regulary:

cd /usr/local/lib/liquid_feedback_core
./lf_update "host=localhost dbname=liquid_feedback user=liquid_feedback password=MySecret"

We must prepare a script that runs in an endless loop, /usr/local/sbin/lf_updated:

#!/bin/sh
 
PIDFILE="/var/run/lf_updated.pid"
PID=$$
 
if [ -f "${PIDFILE}" ] && kill -CONT $( cat "${PIDFILE}" ); then
  echo "lf_updated is already running."
  exit 1
fi
 
echo "${PID}" > "${PIDFILE}"
 
while true; do
  nice /usr/local/lib/liquid_feedback_core/lf_update \
      "host=localhost dbname=liquid_feedback user=liquid_feedback password=MySecret" 2>&1 \
      | logger -t "lf_updated"
  sleep 5
done

The script contains database credentials, so protect it:

chown root:root /usr/local/sbin/lf_updated
chmod 750 /usr/local/sbin/lf_updated

Then preare a script that start/stop the daemon, /etc/init.d/lf_updated:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          lf_updated
# Required-Start:    $syslog
# Required-Stop:     $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: lf_updated
# Description:       Calls LiquidFeedback lf_update regulary
### END INIT INFO
 
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="lf_updated"
NAME=lf_updated
DAEMON=/usr/local/sbin/lf_updated
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
 
. /lib/lsb/init-functions
 
do_start()
{
        start-stop-daemon -b --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null || return 1
        start-stop-daemon -b --start --quiet --pidfile $PIDFILE --exec $DAEMON -- $DAEMON_ARGS || return 2
}
 
do_stop()
{
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        rm -f $PIDFILE
        return "$RETVAL"
}
 
case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
       ;;
  restart|force-reload)
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac
 
:

Activate the start/stop script at bootstrap:

chmod 755 /etc/init.d/lf_updated
insserv lf_updated

The daemon logs to syslog with the lf_updated tag.

Configuring Apache

Enable required modules:

a2enmod rewrite
a2enmod actions

Create an Apache configuration snippet, e.g. in /etc/lqfb/apache.conf:

Alias       /lf/static   /usr/local/share/liquid_feedback_frontend/static
Alias       /lf/fastpath /usr/local/share/liquid_feedback_frontend/fastpath
ScriptAlias /lf/         /usr/local/lib/webmcp/cgi-bin/

RewriteEngine on
#RewriteLog /var/log/apache2/rewrite.log
#RewriteLogLevel 3

RewriteRule ^/$ /lf/ [R]
RewriteRule ^/lf/static/(.*)$ /lf/static/$1 [L,PT]

RewriteCond %{QUERY_STRING} (.*)?
RewriteRule ^/lf/$ \
    /lf/webmcp-wrapper.lua?_webmcp_urldepth=0&_webmcp_module=index&_webmcp_view=index&%1 [PT]

RewriteCond %{QUERY_STRING} (.*)?
RewriteRule ^/lf/([^/]+)/$ \
    /lf/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_view=index&%1 [PT]

RewriteCond %{QUERY_STRING} (.*)?
RewriteRule ^/lf/([^/]+)/([^/\.]+)$ \
    /lf/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_action=$2&%1 [PT]

RewriteCond %{QUERY_STRING} (.*)?
RewriteRule ^/lf/([^/]+)/([^/\.]+)\.([^/]+)$ \
    /lf/webmcp-wrapper.lua?_webmcp_urldepth=1&_webmcp_module=$1&_webmcp_view=$2&_webmcp_suffix=$3&%1 [PT]

RewriteCond %{QUERY_STRING} (.*)?
RewriteRule ^/lf/([^/]+)/([^/]+)/([^/\.]+)\.([^/]+)$ \
    /lf/webmcp-wrapper.lua?_webmcp_urldepth=2&_webmcp_module=$1&_webmcp_view=$2&_webmcp_id=$3&_webmcp_suffix=$4&%1 [PT]

# Allow CGI execution for the webmcp CGI interface
<Directory "/usr/local/lib/webmcp/cgi-bin">
    AllowOverride None
    Options ExecCGI -MultiViews
    Order allow,deny
   Allow from all
</Directory>

Any Apache VirtualHost can include that configuration:

<VirtualHost *:443>
    SSLEngine on
    SSLCertificateFile ssl/lqfb.campibisenzio5stelle.it.pem
    ServerName lqfb.campibisenzio5stelle.it
    ServerAlias lqfb1.campibisenzio5stelle.it
    DocumentRoot /var/www/
    ErrorLog ${APACHE_LOG_DIR}/lqfb.campibisenzio5stelle.it/error.log
    CustomLog ${APACHE_LOG_DIR}/lqfb.campibisenzio5stelle.it/access.log combined

    # Configure environment for LiquidFeedback application
    Include /etc/lqfb/apache.conf
    <Location />
        SetEnv LANG 'it_IT.UTF-8'
        SetEnv WEBMCP_APP_BASEPATH '/usr/local/share/liquid_feedback_frontend/'
        SetEnv WEBMCP_CONFIG_NAME 'campibisenzio5stelle'
    </Location>

</VirtualHost>

Configure the mail subsystem

Install an MTA software like Exim4 or Postfix and configure it so that the system can send mail to the internet. This is required to send the invite code to new members.

Mail messages generated by LiquidFeedback will have www-data@mailname as sender address, where mailname is the content of /etc/mailname.

You can change the LiquidFeedback frontend configuration /usr/local/share/liquid_feedback_frontend/config/campibisenzio5stelle.lua:

config.mail_envelope_from = "lqfb@campibisenzio5stelle.it"
config.mail_from = { name = "LiquidFeedback", address = "lqfb@campibisenzio5stelle.it" }

Another way to map the sender to another address is by configuring the MTA. With Postfix you can add into /etc/postfix/main.cf:

# Rewrite some sender addresses.
sender_canonical_maps = hash:/etc/postfix/sender_canonical_maps

Then create the /etc/postfix/sender_canonical_maps and compile it with postmap:

www-data        lqfb@campibisenzio5stelle.it

Install a second instance of LiquidFeedback on the same host

FIXME Verify if this checklist is complete.

  1. Create another database.
  2. Create another Apache VirtualHost and declare the configuration filename in WEBMCP_CONFIG_NAME.
  3. Create another config file in /usr/local/share/liquid_feedback_frontend/config/ configuring at least absolute_base_url and database credentials.
  4. Add or update the lf_updated daemon to run on each database instance.

LiquidFeedback administration

Eng Ita Note
unit sezione Administrators allow each user partecipate (or not) to the existing units.
area area An unit can contain one or more areas. An user can partecipate to an area and he can delegate the entire area to someone else.
issue
policy

An user with the admin right can login and click on the admin link.

The first task for an administrator is to create some units containing some areas, where users can partecipate. From AdminUnitsCreate new unit or Edit areas.

Then the administrator create invite codes for new users. When creating a new user the admin define the login name (the identification), an email address, the admin right and the units where he can partecipate.

doc/appunti/linux/sa/liquidfeedback.txt · Last modified: 2012/11/11 13:58 by niccolo