Table of Contents
LiquidFeedback
References:
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 … An area is …
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
Verify if this checklist is complete.
- Create another database.
- Create another Apache VirtualHost and declare the configuration filename in
WEBMCP_CONFIG_NAME
. - Create another config file in
/usr/local/share/liquid_feedback_frontend/config/
configuring at leastabsolute_base_url
anddatabase
credentials. - 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 Admin → Units → Create 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.