Guide to EMG Portal, the JavaScript edition

Introduction

For various reasons, we have rewritten most of the original EMG Portal from scratch, this time in TypeScript. Some functionality has been added, and some is left as future work. On the server side we now use node.js and the Express framework, and on the client side we use React. The application is distributed as a Docker image.

Preparations

In order to run this application, you need a server (physical or virtual, it does not matter) running a recent version of a 64 bit Linux. It needs to have the Docker engine installed. We assume that the EMG server is already installed.

Create a base directory for the application, for example /home/portal.

Create a file portal.env in the base directory, and add information about your environment.

The SMTP_SENDER value is used as the sender address for outgoing emails, such as account invitations. It must be an address which you are allowed to use with the given SMTP server.

The INTERNAL_PORT value (here 9001, but you can select anything that suits you) should be the port of an incoming HTTPS connector, using USERDB. The INTERNAL_USERNAME value should be the name of a user in the “emguser” table, having usergroup=”INTERNAL”.

SMTP_HOST=smtp.example.com
SMTP_PORT=25
SMTP_USERNAME=username
SMTP_PASSWORD=password
SMTP_SENDER="EMG Portal <support@example.se>"

INTERNAL_HOST=emg.example.se
INTERNAL_PORT=9001
INTERNAL_USERNAME=emguser
INTERNAL_PASSWORD=secret

MySQL

If you will run MySQL in its own Docker container on the same server, you need to create a network. We will call this network “portal-net”. So, put this in /etc/rc.local, or some other script which will be run when the machine starts.

docker network inspect portal-net | grep Subnet || \
docker network create -o com.docker.network.bridge.enable_icc=true portal-net

The “docker run” command used for the MySQL container, will then need the two flags below. These connect the MySQL container to the “portal-net” network, and makes it reachable by the name “portal-mysql”.

--network portal-net
--name portal-mysql

You need to create a database and a database user, using commands such as the ones below. Please replace “dbuser” to something more meaningful, replace “secret” with a proper password, and adjust the hostnames as needed for your environment.

CREATE DATABASE portal CHARACTER SET utf8;
CREATE USER dbuser@"%" IDENTIFIED BY 'secret';
ALTER USER 'dbuser'@'%' IDENTIFIED WITH mysql_native_password BY 'secret';
GRANT ALL PRIVILEGES ON portal.* TO dbuser@"%";
CREATE USER dbuser@127.0.0.1 IDENTIFIED BY 'secret';
ALTER USER 'dbuser'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY 'secret';
GRANT ALL PRIVILEGES ON portal.* TO dbuser@127.0.0.1;
FLUSH PRIVILEGES;

Finally, add the line below to the portal.env file created earlier, adjusted as needed.

EMG_PORTAL_DB=mysql://dbuser:secret@portal-mysql:3306/portal

If you’re using MySQL in Docker, don’t forget to bind /var/lib/mysql to a directory on your host in the docker run command, to ensure that the database contents is not lost when the container stops or is restarted.

--mount type=bind,source=`pwd`/mysqldata,target=/var/lib/mysql

Please check the MySQL documentations for additional options you may need.

Scripts

We will then create a handful of scripts. The first one is pull.sh, which fetches the most recent version of the application. The “prune” command removes unused Docker images from disk.

#!/bin/sh
docker image prune -f
repo=icab/repo
version=0.2
docker pull $repo:emg-portal-$version
docker pull $repo:emg-portal-migrations-$version
docker image prune -f
exit 0

Next, we need the run-portal.sh script. This script first runs any database migrations, and then the Portal application. Port 5000 is used for the communication from the web server, and the “sessions” directory contains the session cache. This makes it possible to restart the application without forcing the users to login again.

#!/bin/sh
repo=icab/repo
version=0.2
image=emg-portal-$version
migrations=emg-portal-migrations-$version
cd /home/portal
docker run --rm -i \
  --network portal-net \
  --env-file portal.env \
  --name portal-migrations \
  $repo:$migrations
mkdir -p sessions
docker run --rm -i \
  --network portal-net \
  --env-file portal.env \
  --name portal \
  -p 127.0.0.1:5000:5000 \
  --mount type=bind,source=`pwd`/sessions,target=/app/server/sessions \
  $repo:$image

To ensure that the Portal is always restarted it if is stopped, we use the following script, loop-portal.sh.

!/bin/sh
while true
do
  date
  sleep 5
  ./run-portal.sh
done

That script, in turn, is run by “with-lock.sh“, shown below. This script uses the flock command to ensure that only one copy of the script given as its parameter is running at any given time.

#!/bin/bash
script=$1
filename=`basename $script .sh`
dir=$(cd `dirname $0` && pwd)
lockname=$dir/$filename.lock
(
  cd $dir
  flock -xn 200
  RETVAL=$?
  if [ $RETVAL -eq 1 ] ; then
    exit 1
  else
    trap 'rm $lockname' 0
    $script
  fi
) 200>$lockname

Finally, we add a line to the crontab. As usual, adjust the paths as needed.

* * * * * /home/portal/with-lock.sh /home/portal/loop-portal.sh

After at most one minute, you can run “docker ps“, and the portal should appear in the list. You will also get a file called loop-portal.lock.

The command “docker logs portal | less” shows the log entries from when the server started. Please run “docker help logs” to see the current list of options to adjust this output.

Web server configuration

The application now listens to requests on localhost:5000. To make it reachable from the outside, you need a forward from your web server. Here we will use the nginx server. Create the file portal.conf, with the following contents, adjusted with the proper hostname, and copy it to /etc/nginx/conf.d. Then restart nginx.

server {
        listen 80 default_server;
        return 301 https://portal.example.se$request_uri;
}
server {
        server_name portal.example.se;
        root /tmp;
        location / {
                proxy_pass http://localhost:5000;
        }
        # Certbot stuff added here.
}

The “Certbot” line is for Let’s Encrypt, where you can get regularly updated TLS certificates for free. After installing its command line tool, run a command such as the one below.

letsencrypt -d portal.example.se --nginx -m user@example.se

Database

The first user must be added manually. It is a record in the emgp_user table, where you set the columns created=now(), username=”user@example.se”, password=”secret”, and role=”OWNER”. As usual, please replace the username and password.

You should now be able to open the application in your web browser, adding anything else you need.

Version control

We strongly recommend putting all these files under version control, for example git.