DHIS2 demo on a raspberry pi

hello,

Just in case it interest other people, I created a script to have DHIS2 on a rapberrypi 4, it works ONLY with the arm64 rasbianOS

It also provide a wifi AP so no need for a router.

I develop it as a backup solution for a DHIS2 training, but the DHIS2 instance run well on my machine Docker so I didn’t used it. During my testing, it works but it is not a real server so the speed is not top notch.

I couldn’t attached the sh file, so find the content here

#!/bin/bash
###############################################################################
# DHIS2 build script for rapberry pi
################################################################################

DHIS_VERSION='2.36.4'
POSTGRES_VERSION='13'
TOMCAT='9'
JDK=11
# dev version is used because 36.4 should be avoided
DHIS2URL=https://releases.dhis2.org/2.36/dev/dhis2-dev-2.36.war
DHIS2URLDemo=https://databases.dhis2.org/sierra-leone/2.36.4/dhis2-db-sierra-leone.sql.gz
# Create the file repository configuration:
echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list

# Import the repository signing key:
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
apt-get purge motion -y
apt-get update
apt-get upgrade
apt-get install openjdk-${JDK}-jre software-properties-common unzip nginx tomcat${TOMCAT} fail2ban pg-activity htop certbot postgresql-${POSTGRES_VERSION} postgresql-${POSTGRES_VERSION}-postgis-3 postgresql-contrib-${POSTGRES_VERSION} -y

################################################################################

DHIS2_HOME='/home/pi/config'
CATALINA_HOME="/usr/share/tomcat${TOMCAT}"
TOMCAT_HOME="/etc/tomcat${TOMCAT}"
CATALINA_BASE="/var/lib/tomcat${TOMCAT}"
DHIS2CONF="$DHIS2_HOME/dhis.conf"
BACKUP_SCRIPT='/opt/database_backup.sh'
BACKUP_DIR='/var/backups/dhis2'
MEMORY=$(awk '/MemTotal:/ { print $2 }' /proc/meminfo)
PG_CONF='/etc/postgresql/'$POSTGRES_VERSION'/main/postgresql.conf'
PG_HBA_CONF='/etc/postgresql/'$POSTGRES_VERSION'/main/pg_hba.conf'
PG_IDENT_CONF='/etc/postgresql/'$POSTGRES_VERSION'/main/pg_ident.conf'

NGINXCONF='/etc/nginx/nginx.conf'
# Shell colors
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m' # Reset to No Color

################################################################################
# Intro and checks
if [ "$(id -u)" != "0" ]; then
  echo -e "${RED}You need to be root! Follow instructions in README.${NC}"
  exit 1
fi

BACKUP_SETUP=false


if [ $MEMORY -gt 7000000 ]; then
  EXOSCALE='large'
elif [ $MEMORY -gt 4000000 ]; then
  EXOSCALE='medium'
elif [ $MEMORY -gt 2000000 ]; then
  EXOSCALE='small'
else
  echo -e "${RED}Memory is too small for DHIS2 (min. 2GB RAM). Exiting build.${NC}" 1>&2
  #exit 1
  EXOSCALE='xsmall'
fi



mkdir -p $DHIS2_HOME $CATALINA_HOME/bin $CATALINA_HOME/webapps $(dirname "${PG_CONF}")

echo -e "${CYAN}Build info${NC}"
echo -e "DHIS2 Version:        $DHIS_VERSION"
echo -e "URL:                  $URL"
echo -e "Let's Encrypt E-Mail: $MAILADDRESS"
        echo -e "Project:              $PROJ"
echo -e "Backups:              $BACKUP_SETUP"
echo -e "Instance size:    $EXOSCALE"
sleep 15
################################################################################



# PostgreSQL config
# Set up Database including Password and put it into /root/dhis_info
echo '+++ PostgreSQL...'
sleep 3
#create the user if dhis2 file doesn-t exist
if [ ! -e "$DHIS2CONF" ]; then
        DHIS_DB_PW=$(date +%s | sha256sum | base64 | head -c 32 ; echo)
        touch /root/dhis_info
        cat > /root/dhis_info <<EOF
URL:                         $URL
Project:                     $PROJ
IP:                          $(curl -s icanhazip.com)
Build date:                  $(date '+%Y-%m-%d %H:%M:%S %Z')
PostgresSQL DB Name:         dhis2
PostgresSQL DB Username:     dhis
PostgresSQL DB Password:     $DHIS_DB_PW
EOF

        chmod 0600 /root/dhis_info

        sudo -iu postgres psql -U postgres -c "CREATE ROLE dhis WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '$DHIS_DB_PW';" -q
        sudo -iu postgres psql -U postgres -c "CREATE DATABASE dhis2 OWNER dhis;" -q
        sudo -iu postgres psql -U postgres -d dhis2 -c "CREATE EXTENSION IF NOT EXISTS postgis;" -q
		        mkdir -p $DHIS2_HOME
		cat > $DHIS2CONF <<EOF
connection.dialect = org.hibernate.dialect.PostgreSQLDialect
connection.driver_class = org.postgresql.Driver
connection.url = jdbc:postgresql:dhis2
connection.username = dhis
connection.password = $DHIS_DB_PW
connection.schema = update
EOF
        chown pi:tomcat $DHIS2_HOME -R
        chmod 0660 $DHIS2_HOME/dhis.conf
fi

cat > $PG_HBA_CONF <<EOF
# Allow root to login as postgres or dhis DB user without the need of entering a password.
local   all             all                                     peer map=local_users
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5
EOF

cat > $PG_IDENT_CONF <<EOF
# MAPNAME      SYSTEM-USERNAME    PG-USERNAME
local_users    postgres           postgres
local_users    root               postgres
local_users    root               pi
EOF

if [[ $EXOSCALE == 'large' ]]; then
        cat >> $PG_CONF <<EOF
# dhis2 tuning
synchronous_commit = off
wal_writer_delay = 10000ms
max_connections = 200
shared_buffers = 1GB
effective_cache_size = 4GB
work_mem = 20MB
maintenance_work_mem = 256MB
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.8
wal_buffers = 16MB
default_statistics_target = 100
EOF
elif [[ $EXOSCALE == 'medium' ]]; then
        cat >> $PG_CONF <<EOF
# dhis2 tuning
synchronous_commit = off
wal_writer_delay = 10000ms
max_connections = 200
shared_buffers = 512MB
effective_cache_size = 1536MB
work_mem = 10MB
maintenance_work_mem = 128MB
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.8
wal_buffers = 16MB
default_statistics_target = 100
EOF
elif [[ $EXOSCALE == 'small' ]]; then
        cat >> $PG_CONF <<EOF
# dhis2 tuning
synchronous_commit = off
wal_writer_delay = 10000ms
max_connections = 200
shared_buffers = 256MB
effective_cache_size = 768MB
work_mem = 4MB
maintenance_work_mem = 64MB
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.8
wal_buffers = 7864kB
default_statistics_target = 100
EOF
elif [[ $EXOSCALE == 'xsmall' ]]; then
        cat >> $PG_CONF <<EOF
# dhis2 tuning
synchronous_commit = off
wal_writer_delay = 10000ms
max_connections = 200
shared_buffers = 256MB
effective_cache_size = 256MB
work_mem = 4MB
maintenance_work_mem = 64MB
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.8
wal_buffers = 7864kB
default_statistics_target = 100
EOF
fi

service postgresql restart

################################################################################
# Tomcat & DHIS2 war file
echo '+++ Apache Tomcat & DHIS2 WAR file...'
sleep 3

 cat > $CATALINA_HOME/etc/server.xml <<'EOF'
 <?xml version='1.0' encoding='utf-8'?>
 <Server port="8005" shutdown="SHUTDOWN">
   <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
   <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
   <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
   <Service name="Catalina">
     <Connector port="8080" URIEncoding="UTF-8" relaxedQueryChars ="<>[\\]^{|}"/>
     <Engine name="Catalina" defaultHost="localhost">
       <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"/>
       <!-- use real IP when behind local proxy-->
       <Valve
         className="org.apache.catalina.valves.RemoteIpValve"
         remoteIpHeader="X-Forwarded-For"
         requestAttributesEnabled="true"
         internalProxies="127\.0\.0\.1"/>
       <Valve
         className="org.apache.catalina.valves.AccessLogValve"
         directory="logs"
         prefix="localhost_access_log."
         suffix=".txt"
         pattern="%h %l %u %t &quot;%r&quot; %s %b &quot;%{Referer}i&quot; &quot;%{User-Agent}i&quot; &quot;%{org.apache.catalina.AccessLog.RemoteAddr}r&quot;"/>
     </Engine>
   </Service>
 </Server>
EOF

# Java settings depending on instance memory total size
if [[ $EXOSCALE == 'large' ]] ; then
  JOPTS='-Xmx2840m -Xms1700m'
elif [[ $EXOSCALE == 'medium' ]] ; then
  JOPTS='-Xmx1700m -Xms1200m'
elif [[ $EXOSCALE == 'small' ]] ; then
  JOPTS='-Xmx1000m -Xms600m'
elif [[ $EXOSCALE == 'xsmall' ]] ; then
  JOPTS='-Xmx600m -Xms300m'
fi

cat >> $CATALINA_HOME/bin/setenv.sh <<EOF
# -Djava.security.egd=file:/dev/./urandom -> makes dhis2 startup faster
#export JAVA_HOME='/usr/lib/jvm/java-8-openjdk-amd64/'
export JAVA_OPTS='-Djava.security.egd=file:/dev/./urandom $JOPTS'
export DHIS2_HOME='$DHIS2_HOME'
EOF


rm -rf $CATALINA_BASE/webapps/ROOT*
wget  $DHIS2URL -O $CATALINA_BASE/webapps/ROOT.war

################################################################################
# download demo data
echo '+++ Download demo data...'
wget  $DHIS2URLDemo -O $DHIS2_HOME/dhis2-db-sierra-leone.sql.gz
gunzip $DHIS2_HOME/dhis2-db-sierra-leone.sql.gz

sudo -u postgres psql -d dhis2 -U dhis -f $DHIS2_HOME/demodata.sql.gz

################################################################################
# Let's Encrypt SSL and Nginx
echo '+++ Nginx and Lets Encrypt...'
sleep 3
service nginx stop
if [ ! -f /etc/ssl/certs/dhparam.pem ]; then
  openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
fi

cp $NGINXCONF $NGINXCONF.default
cat > $NGINXCONF << 'EOF'
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
  worker_connections 1024;
}

http {
  root          _CATALINA_HOME_/webapps/ROOT;

  include       mime.types;
  default_type  application/octet-stream;

  gzip on; # Enables compression, incl Web API content-types
  gzip_types
    "application/json;charset=utf-8" application/json
    "application/javascript;charset=utf-8" application/javascript text/javascript
    "application/xml;charset=utf-8" application/xml text/xml
    "text/css;charset=utf-8" text/css
    "text/plain;charset=utf-8" text/plain;

  server {
    listen 80 default_server;
    listen [::]:80 default_server;


    add_header  X-Frame-Options DENY;

    # Disallow Search Engine Crawling
    location = /robots.txt {
      add_header  Content-Type  text/plain;
      return 200 "User-agent: *\nDisallow: /\n";
    }

    # File Max Upload size
    client_max_body_size 10M;

    # Serve static files
    location ~ (\.js|\.css|\.gif|\.woff|\.ttf|\.eot|\.ico|(/dhis-web-commons/|/images/|/icons/).*\.png)$ {
      add_header  Cache-Control public;
      expires     14d;
    }

    # Proxy pass to servlet container
    location / {
      proxy_pass                http://localhost:8080;
      proxy_redirect            off;
      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_set_header          X-Forwarded-Proto  https;
      proxy_buffer_size         128k;
      proxy_buffers             8 128k;
      proxy_busy_buffers_size   256k;
    }
  }
}
EOF
# Replace domain.com and tomcat home folder in nginx.conf
sed -i 's|_DOMAIN_|'$URL'|g' $NGINXCONF
sed -i 's|_CATALINA_HOME_|'$CATALINA_BASE'|g' $NGINXCONF
nginx -t
service nginx start
nginx -s reload



################################################################################
# DHIS2 conf
echo '+++ DHIS2 config...'
sleep 3



################################################################################
# Security upgrades
echo '+++ Ubuntu unattended security upgrades...'
cat > /etc/apt/apt.conf.d/20auto-upgrades <<EOF
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
EOF
#ensure the tomcat and nginx have acess
rm -rf $CATALINA_BASE/webapps/ROOT
chown -R tomcat:www-data $CATALINA_HOME
chown -R tomcat:www-data $CATALINA_BASE
chown -R tomcat:pi /home/pi/config
################################################################################
# Configuring Wifi Ap
# https://thepi.io/how-to-use-your-raspberry-pi-as-a-wireless-access-point/
# Install for the WIFI Access point
apt-get install hostapd dnsmasq -y
# Configure a static IP for the wlan0 interface
cat > /etc/dhcpcd.conf <<EOF
nohook wpa_supplicant
interface wlan0
static ip_address=192.168.50.10/24
static routers=192.168.50.10
EOF

# Configure the DHCP server (dnsmasq)
# mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig
cat >  /etc/dnsmasq.conf <<EOF
expand-hosts
interface=wlan0
domain-needed
bogus-priv
dhcp-range=192.168.50.150,192.168.50.200,255.255.255.0,12h
EOF


# Configure the access point host software
cat >  /etc/hostapd/hostapd.conf <<EOF
interface=wlan0
driver=nl80211
ssid=DHIS2_wifi
hw_mode=g
channel=6
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=1234567890
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
EOF

cat >  /etc/default/hostapd <<EOF
DAEMON_CONF="/etc/hostapd/hostapd.conf"
EOF

cat >  /etc/host <<EOF
192.168.50.10 dhis2
EOF

#unmask hostapd service
systemctl unmask hostapd

# unlock the wifi device
rfkill unblock 0

#  Set up traffic forwarding
#sed -ie "/#net\.ipv4\.ip_forward=1/#net.ipv4.ip_forward=1/" /etc/sysctl.conf
#iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
#sh -c "iptables-save > /etc/iptables.ipv4.nat"
#iptables-restore < /etc/iptables.ipv4.nat


#Enable internet connection
#apt-get install bridge-utils
#brctl addbr br0
#rctl addif br0 eth0
# echo "auto br0" > /etc/network/interfacesvi 
# echo "iface br0 inet manual" > /etc/network/interfaces
# echo "bridge_ports eth0 wlan0" > /etc/network/interfaces
################################################################################
# Finishing
echo "FQDN=$URL" >> /etc/environment







echo -e "${RED}++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++${NC}"
echo '+++ Setup finished!'
echo -e "+++ ${CYAN}Check README for final steps.${NC}"
echo -e "+++ ${RED}Rebooting in 10 seconds...${NC} Log back in with ssh httu@$URL"
echo -e "... ${RED}10${NC}"
sleep 5
echo -e "... ${RED}5${NC}"
sleep 2
echo -e "... ${RED}3${NC}"
sleep 1
echo -e "... ${RED}2${NC}"
sleep 1
echo -e "... ${RED}1${NC}"
sleep 1
/sbin/shutdown -r no
10 Likes

Wow! Very nice @pmpdelcroix !! :heart_eyes:

1 Like

Thanks for sharing @pmpdelcroix!

1 Like

Reviewed it a bit more thoughtfully and some quick ideas in case someone wants to develop this further:

  1. Put all the VARS are top or be able to provide them by a flag while running the script (./DHIS2_raspi --vars). I see, for example, that the WiFi AP and password could be better not hardcoded
  2. The wlan0 you are referring to… is it always like this? in some of my devices I don’t get that name of the interface, I think it depends on the device/driver. But it could be easily detected with some commands (i.e. nmcli device | grep wifi | awk '{ print $1 }')
  3. Increase a bit the security, for example making sure the firewall allows connection to the HTTP(s) port (as maybe the machine is previously secured. Add in IPTABLES or implement IPTABLES

But very nice job, @pmpdelcroix !

1 Like

Hello @jaime.bosque

  1. that could be nice, I hardcoded it because I use the card for education only so having the same pass avoid issue during the training, if I have time I will have a look.

  2. Normally on Raspbian the wifi always has the same name, if someone wants to use the script for another board then it could make sense to have a more generic device name but the other wifi card must support AP mode which is not given.

  3. I understand you point of view but I don’t agree for hpps because the points of using a Rpi for DHIS2 is to avoid using internet during training (not enough “compute” to host a prod server), having a non-valid SSL certificate will confuse the trainee because of the “Insecure website” message that most browser will display (some of them barely touch a PC before). Regarding the IP tables, it doesn’t seem to be required on a freshly installed raspbian (I did the minimum :slight_smile: ).

br

1 Like

Hi again @pmpdelcroix ! :slight_smile:

Just a couple of comments to clarify my message, I think I wrote that with my mind in the holidays already, hehe

  1. AFAIK the name of the wireless interface does not come by the OS itself but by the drivers. This means that if your device uses another card you might get a different name. For example if you would plug a USB atheros card, I think it would get a name like ath0 instead of wlan0

  2. In this case I was not referring specifically to HTTPS SSL certificates (although thinking about it now it would make sense to avoid people having the security alert, but this would include some overhead as it would be required to use selfisigned certificates -probably not recommended- or having control over the DNS -might not be possible-). I was more referring to implementing a firewall to allow connections only to HTTP ports and blocking everything else. In case this machine is being used for something else you might want to avoid people messing with it.

In any case there was just some ideas and thoughts for maybe your or other’s future developments :slight_smile:

Happy New Year!

1 Like

Thanks and happy new year,

I didn’t thought of adding another card on the raspberry pi :slight_smile: I’ll have a look

I completely misunderstood your IPTABLES message, clearly, people messing with the system is a risk, I’ll have a look too

br

2 Likes