Matteo Mattei

Hello, my name is Matteo Mattei and this is my personal website. I am computer engineer with a long experience in Linux system administration and web software development.

linkedin rss twitter google+ github facebook

How to build a web kiosk with Raspberry Pi and make the SD read-only.

Download latest Raspbian Lite distribution (Jessie in my case) from the official Raspberry website.

Then unzip the file and flash the image in the SD card:

sudo dd if=2016-02-09-raspbian-jessie-lite.img of=/dev/mmcblk0 bs=1M

Power up the RPi2 (or RPi1) with the SD plugged in and log in:

user: pi password: raspberry

Now configure the network (wired static ip in my case) editing /etc/dhcpcd.conf and adding the following lines at the bottom:

# my custom static settings
interface eth0
static ip_address=192.168.1.130/24
static routers=192.168.1.254
static domain_name_servers=192.168.1.254

Configure Kiosk

Execute raspi-config to cutomize some settings:

sudo raspi-config

In particular,

  • Set localization (keyboard and system locale)
  • Enable SSH
  • Expand root partition
  • Set autologin on console (B2)

At the end reboot your raspberry pi so that the new filesystem size will take effect.

Login again, update the system and install all needed software:

sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get install midori matchbox-window-manager xserver-xorg x11-xserver-utils unclutter xinit

Now add tty group to pi user because pi needs to handle /dev/ttyX devices and adjust permissions accordingly at every system startup:

gpasswd -a pi tty
sed -i '/^exit 0/c\chmod g+rw /dev/tty?\nexit 0' /etc/rc.local

Now create a startup script:

vi /home/pi/startkiosk.sh

#!/bin/bash

# disable DPMS (Energy Star) features.
xset -dpms

# disable screen saver
xset s off

# don't blank the video device
xset s noblank

# disable mouse pointer
unclutter &

# run window manager
matchbox-window-manager -use_cursor no -use_titlebar no  &

# run browser
midori -e Fullscreen -a http://www.google.com

and make it executable:

chmod +x /home/pi/startkiosk.sh

Then add the following lines at the end of /home/pi/.bashrc:

if [ -z "${SSH_TTY}" ]; then
  xinit ~/startkiosk.sh
fi

This three lines allow starting X only when we login as pi directly but not from SSH.

Make the SD read-only

Backup your current /etc/fstab and create a new one with the following content:

proc            /proc           proc    defaults          0       0
/dev/mmcblk0p1  /boot           vfat    ro                0       2
/dev/mmcblk0p2  /               ext4    ro                0       1
tmpfs           /tmp            tmpfs   defaults,noatime,mode=1777      0       0
tmpfs           /var/log        tmpfs   defaults,noatime,mode=0755      0       0
tmpfs           /var/lib/systemd tmpfs   defaults,noatime,mode=0755      0       0
tmpfs           /run            tmpfs   defaults,noatime,mode=0755      0       0

Then create a little script to help you to change the read-write/read-only mode of the filesystem everytime you need:

vi /home/pi/mountfs.sh

#!/bin/bash

case "${1}" in
        rw)
                sudo mount -o remount,rw /
                echo "Filesystem mounted in READ-WRITE mode"
                ;;
        ro)
                sudo mount -o remount,ro /
                echo "Filesystem mounted in READ-ONLY mode"
                ;;
        *)
                if [ -n "$(mount | grep mmcblk0p2 | grep -o 'rw')" ]
                then
                        echo "Filesystem is mounted in READ-WRITE mode"
                else
                        echo "Filesystem is mounted in READ-ONLY mode"
                fi
                echo "Usage ${0} [rw|ro]"
                ;;
esac

That’s it, reboot your RPi and enjoy your safe web kiosk!


How to create self contained executables in Python

This is a very quick guide on how to create self contained Python executables (for all platforms). First of all install PyInstaller (I am using pip3 because I work with Python 3.x):

sudo pip3 install pyinstaller

Now install upx for a better compression:

sudo apt-get install upx

Now you are ready to create your self-contained executable:

pyinstaller \
    --onefile \
    --noconfirm \
    --noconsole \
    --clean \
    --log-level=WARN \
    --key=MySuperSecretPassword \
    --strip \
    myscript.py

Resulting executable will be placed inside dist folder and it will be called myscript. This is what each parameter does:

  • --onefile allows to create a single self contained binary.
  • --noconfirm replaces output directory without asking for confirmation.
  • --noconsole should be used in GUI application with no console. --console should be used otherwise.
  • --clean cleans PyInstaller cache and remove temporary files.
  • --log-level=WARN shows only warnings and errors during build.
  • --key=yourkey uses the given key to encrypt the Python bytecode (yes it’s secure!).
  • --strip removes debug information to executable and shared libraries.

For windows, if you want to add also the icon to the resulting exe file you can add this additional parameter:

--icon-file=myapplication.ico

The application have only to be recompiled on every platform you want to release your application for. I know, the resulting binary will be a little heavy (~24MB for a PySide GUI application on Linux) but we have to consider that it contains the interpreter itself and all the needed libraries!!!


Configure production web sever with Debian 8 Jessie

This is the list of the services we are going to install and configure:

  • Apache
  • PHP-FPM
  • MySQL
  • Postfix
  • PhpMyAdmin
  • Shorewall
  • Nodejs
  • Couchdb

First of all configure ssh key access to your server. Make sure to have a rsa key-pair in ~/.ssh/ folder in your pc. Assuming xxx.xxx.xxx.xxx is the public ip address of your server, execute the following command to copy the public key:

ssh-copy-id root@xxx.xxx.xxx.xxx

Setup root shell configuration and update the system:

cp /etc/skel/.bashrc /root/.bashrc
apt-get update
apt-get upgrade
apt-get dist-upgrade

Configure a fully qualified domain name (FQDN) in /etc/hosts:

127.0.0.1       localhost.localdomain localhost
xxx.xxx.xxx.xxx srv1.mycompany.com srv1

where xxx.xxx.xxx.xxx is the public IP address assigned by your provider.

Install MySQL server and client:

apt-get install mysql-server mysql-client

New password for the MySQL "root" user: xxx
Repeat password for the MySQL "root" user: xxx

[...]

[Warning] Using unique option prefix key_buffer instead of key_buffer_size is deprecated and will be removed in a future release. Please use the full name instead.

Correct the above MySQL configuration warning:

sed -i "{s/^key_buffer/key_buffer_size/g}" /etc/mysql/my.cnf

Enable MySQL slow query logging:

sed -i "{s/^#slow_query_log_file /slow_query_log_file /g}" /etc/mysql/my.cnf
sed -i "{s/^#slow_query_log /slow_query_log /g}" /etc/mysql/my.cnf
sed -i "{s/^#long_query_time /long_query_time /g}" /etc/mysql/my.cnf
sed -i "{s/^#log_queries_not_using_indexes/log_queries_not_using_indexes/g}" /etc/mysql/my.cnf

Restart MySQL server:

systemctl restart mysql

Install Apache (mpm-worker is needed for using PHP-FPM):

apt-get install apache2-mpm-worker

Install PHP-FPM and related PHP extensions:

apt-get install libapache2-mod-fastcgi php5-fpm php5 php-pear php5-readline php5-gd php5-mysql php5-mcrypt mcrypt php5-imagick imagemagick php5-curl

Configure Apache server properly:

sed -i "{s#^Timeout 300#Timeout 45#g}" /etc/apache2/apache2.conf
sed -i "{s#^KeepAliveTimeout 5#KeepAliveTimeout 15#g}" /etc/apache2/apache2.conf
sed -i "{s#^ServerTokens OS#ServerTokens Minimal#g}" /etc/apache2/conf-enabled/security.conf
sed -i "{s#^ServerSignature On#ServerSignature Off#g}" /etc/apache2/conf-enabled/security.conf

Enable needed Apache modules and restart Apache server:

a2enmod actions fastcgi alias proxy prxy_http ssl
service apache2 restart

Configure correct umask for PHP-FPM and restart the daemon:

sed -i "{s#^\[Service\]#[Service]\nUMask=0022#g}" /etc/systemd/system/multi-user.target.wants/php5-fpm.service

systemctl daemon-reload
systemctl restart php5-fpm.service

Install and configure PhpMyAdmin:

apt-get install phpmyadmin

Web server to reconfigure automatically: [apache2]
Configure database for phpmyadmin with dbconfig-common? [Yes]
Password of the database's administrative user: [xxx]
MySQL application password for phpmyadmin: [xxx]
Password confirmation: [xxx]

Make MySQL more secure:

mysql_secure_installation

Enter current password for root (enter for none): [xxx]
Change the root password? [Y/n] [n]
Remove anonymous users? [Y/n] [Y]
Disallow root login remotely? [Y/n] [Y]
Remove test database and access to it? [Y/n] [Y]
Reload privilege tables now? [Y/n] [Y]

Create a group for SFTP and configure SFTP in a secure manner with chroot directory:

addgroup sftponly

cat <<EOF >> /etc/ssh/sshd_config
Match Group sftponly
   ChrootDirectory %h
   ForceCommand internal-sftp -u 0022
   AllowTcpForwarding no
   AllowAgentForwarding no
   PermitTunnel no
   X11Forwarding no
EOF

service ssh restart

Install and configure firewall:

apt-get install shorewall

cd /usr/share/doc/shorewall/examples/one-interface
cp interfaces /etc/shorewall/interfaces
cp policy /etc/shorewall/policy
cp rules /etc/shorewall/rules
cp zones /etc/shorewall/zones

echo "HTTP/ACCEPT net \$FW" >> /etc/shorewall/rules
echo "HTTPS/ACCEPT net \$FW" >> /etc/shorewall/rules
echo "SSH/ACCEPT  net \$FW" >> /etc/shorewall/rules

sed -i "{s#^startup=0#startup=1#g}" /etc/default/shorewall

systemctl restart shorewall

Install and configure Postfix:

apt-get install postfix heirloom-mailx

General type of mail configuration: [Internet Site]
System mail name: [your FQDN as reported by hostname -f]

Install CouchDb dependencies:

apt-get install libmozjs185-1.0 libmozjs185-dev build-essential curl erlang-nox erlang-dev libnspr4 libnspr4-0d libnspr4-dev libcurl4-openssl-dev curl libicu-dev

Now create CouchDb account:

useradd -d /var/lib/couchdb couchdb
mkdir -p /usr/local/{lib,etc}/couchdb /usr/local/var/{lib,log,run}/couchdb /var/lib/couchdb
chown -R couchdb:couchdb /usr/local/{lib,etc}/couchdb /usr/local/var/{lib,log,run}/couchdb
chmod -R g+rw /usr/local/{lib,etc}/couchdb /usr/local/var/{lib,log,run}/couchdb

Download and install CouchDb:

cd /root
wget http://apache.panu.it/couchdb/source/1.6.1/apache-couchdb-1.6.1.tar.gz
tar xzf apache-couchdb-1.6.1.tar.gz
cd apache-couchdb-1.6.1
./configure --prefix=/usr/local --with-js-lib=/usr/lib --with-js-include=/usr/include/js --enable-init
make && make install

Configure startup script for CouchDB and run it:

chown couchdb:couchdb /usr/local/etc/couchdb/local.ini
ln -s /usr/local/etc/init.d/couchdb /etc/init.d/couchdb
/etc/init.d/couchdb start
update-rc.d couchdb defaults

Configure Apache Proxy to forward all requests on a particular domain to localhost:5984 (CouchDb) over SSL. To do it, create a new virtual host for your db URL creating /etc/apache2/site-available/db.mysite.com.conf with the following content:

<VirtualHost *:443>
    ServerAdmin info@matteomattei.com
    ServerName db.mysite.com

    ErrorLog /var/www/vhosts/db.mysite.com/logs/error.log
    CustomLog /var/www/vhosts/db.mysite.com/logs/access.log combined

    SSLEngine On
    SSLCertificateFile      /etc/ssl/certs/ssl-cert-snakeoil.pem
    SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key

    <FilesMatch "\.(cgi|shtml|phtml|php)$">
        SSLOptions +StdEnvVars
    </FilesMatch>
    <Directory /usr/lib/cgi-bin>
        SSLOptions +StdEnvVars
    </Directory>
    BrowserMatch "MSIE [2-6]" \
        nokeepalive ssl-unclean-shutdown \
        downgrade-1.0 force-response-1.0
    BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown

    AllowEncodedSlashes On
    ProxyRequests Off
    ProxyPreserveHost On
    KeepAlive On
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    ProxyPass / http://localhost:5984/ nocanon
    ProxyPassReverse / http://localhost:5984/
</VirtualHost>

Create the folder /var/www/vhosts/db.mysite.com/logs for storing logs and restart Apache server:

mkdir -p /var/www/vhosts/db.mysite.com/logs
service apache2 restart

Now your couchdb futon interface should be available at https://db.mysite.com/_utils. Remember however to setup authentication in /usr/local/etc/couchdb/local.ini in order to limit the db access to only authenticated users.

Install now the latest version of nodejs:

apt-get install -y curl
curl -sL https://deb.nodesource.com/setup_5.x | bash -
apt-get install -y nodejs

Install pm2 node process manager globally:

npm install pm2 -g

HTML5 Ajax file upload

Very often I have to write HTML forms for uploading files and since I would like to have something responsive without a full page reload I decided to write a little script as example for the next time I will need something similar. The following example should be compatible with every browser but the actual dynamic part is only available on new browsers that support FormData object from XMLHttpRequest2. Old browsers should be able to upload the files using the old (standard) way with just few changes in the server side part.

The following example creates a form for uploading PDF files (checks at line 34) and show the list of uploaded files inside the <div> element with class result.

The main components of the script are the following:

  • FormData object from XMLHttpRequest2.
  • multiple attribute of file input HTML field.
  • FileReader object from the new File API.

Remember to apply all needed checks in the PHP server side script.

Please note the TransferCompleteCallback() at line 10 that can be used to get the content of the files once they are transferred. This might be useful for example to directly render uploaded images to the screen without an additional request to the server to get them back.


Mount NFS share using Virtualbox under NAT

I am using Ubuntu 14.04 (but it is valid also with other Linux distributions) that runs under Virtualbox. My network interface is set as NAT and I am running nfs version 4 (NFSv4).

Every time I try to mount a remote share I always obtain the following error:

matteo@vm:~$ sudo mount -t nfs xxx.xxx.xxx.xxx:/opt/share /mnt/remote
mount.nfs: access denied by server while mounting xxx.xxx.xxx.xxx:/opt/share

And in the kernel log (dmesg) I see this:

[ 1351.443078] RPC: server xxx.xxx.xxx.xxx requires stronger authentication.

The issue here is that the NFS client is trying to use UDP protocol to access the remote host. You can verify it by capturing the network traffic with wireshark (or tcpdump). In this case is sufficient to force the usage of the TCP protocol when you mount the share:

matteo@vm:~$ sudo mount -t nfs xxx.xxx.xxx.xxx:/opt/share /mnt/remote -o proto=tcp
matteo@vm:~$

Another option is to change your network adapter in Virtualbox from NAT to BRIDGE.

That’s all for the moment.


Use Python to print labels with Dymo LabelWriter 450 Turbo

Dymo is a leading company for label printing. Altought several things can be done using their DYMO Label v.8 Software, I found that there was no way to set a specific date in a label layout so that every time I need to print a label I have the current date plus 30 days. I read the manual and I found that to do it we have to use a variable field but this is something that has to be handled using an external program.

So I wrote a little python tool that runs on Windows 7 and that does exactly what I need. Make sure to install pywin32 since we are going to use COM apis.

This is my example label (my.label) and the related Python code:

As you can see I have two fields in the XML with IsVariable set to True and I have assigned to them the name TEXT1 and TEXT2. Now, all the logic happens between line 30 and line 41 of the Python script.

  • I initialize the Dispatch for the printer, configure the label I want to use (mylabel) and then I select the printer [lines 30-34].
  • Then I apply my rules to the variables TEXT1 and TEXT2 [lines 36-37].
  • And finally I launch the print job [lines 39-41].

That’s all! With only few lines of Python code we are able to print a label using our Dymo LabelWriter 450 Turbo printer.

Note: the full code is available on GitHub including the scripts to build a self-contained executable.