Immersive Web, Docker and problem with symbolic links for SSL certificates.
Tags: webhidapi, immersiveweb, smartcity, webdev
Published: 2022-06-10 09:53:19

## Interactive Wall at GAM in Turin

As an **Immersive Web Developer** from time to time, there is a need to play the role of **DevOps engineer**. Mostly because the prototype of my research project needed to be deployed to production cloud infrastructure **([5G EVE](https://www.5g-eve.eu/))**.

Despite the complexity of the overall 5G research, my web application is relatively weak, called **"Interactive Wall"** is designed for a multi-device experience with blazing fast animation purposes WebGL canvas ([PixiJS](https://pixijs.io/)). Depending on the provided parameters there are two types of UIs: 
1. For students (kids or other pupils) - (multiplayer)
2. The teacher(s) (or other guardians) - (single user)

The first one is displayed on the wall via projector, the second can be opened via QR code on the mobile device, wearable or any other desktop running Chromium-based browsers. Realtime communication between both interfaces is established via [WebSocket](https://github.com/websockets/ws) server.


![Immersive Web](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/soend0j23ozzh82p6wvv.gif)

The GIF above showcases a WebSocket connection between 2 tabs of the browser, mobile and wearable device. The latency between them is about and above 30ms but depends on the hardware and network characteristics. We were aiming to measure all the advantages of having 20 times faster connections than 4G.

## Connecting to uncommon HID devices.

Paired and connected via Bluetooth thanks to [WebHID API](https://developer.mozilla.org/en-US/docs/Web/API/WebHID_API) - old good and forgotten almighty Nintendo Wiimote. Its camera for direction reports is calibrated by IR Bar, and all the motion, acceleration and input data from 11 buttons are sent via Bluetooth to the browser. 

_What is actually WebHID?_
> <sup>"HID consists of two fundamental concepts: reports and report descriptors. Reports are the data that is exchanged between a device and a software client. The report descriptor describes the format and meaning of data that the device supports.
> An HID (Human Interface Device) is a type of device that takes input from or provides output to humans. It also refers to the HID protocol, a standard for bi-directional communication between a host and a device that is designed to simplify the installation procedure. The HID protocol was originally developed for USB devices, but has since been implemented over many other protocols, including Bluetooth." - https://web.dev/hid/</sup>

I was able to use simultaneously more than 4 controllers. Originally developed for one Wiimote by a former colleague at Samsung [Kevin Picchi](https://picchikevin.github.io/wiimote-webhid/). I have developed a multiuser version for [5G Tours consortium](http://5gtours.eu/). It's a really long story. 

## Portable Deployment
In general, the browser UI can be statically provided separately from the WebSocket server but for the sake of portability, decided to pack everything all together as [Node.JS](https://nodejs.org/) project, perfect for scalable web applications. Portable deployment should be easy with [Docker](https://www.docker.com/resources/what-container/). There is super simple [tutorial how to do bake nodejs-docker-webapp](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/). Basic use of Docker is free and you can install it on Mac, Windows and Linux. You need to write a relatively simple Dockerfile.

```
# The name of the image for your container
FROM node:16

# Create app directory, copy configuration assets
WORKDIR /usr/src/app
COPY package*.json ./

# Install npm
RUN npm install

# Bundle app source
COPY . .

# Optional exposed app ports
EXPOSE 8080

# Available commands from CLI
CMD [ "node", "index-wall.js" ]

```

To build the image run this command from the folder where your Dockerfile and assets are located. 
``$ docker build . -t flaboy/node-wall``
flaboy/node-wall is the given name of image, when it's created you can push and pull this like git repo to the docker.io repository.

``$ docker push flaboy/node-wall``

If you need to share image file this is the way you can export tarball:
``$ docker save flaboy/node-wall > node-vr.tar``

Later someone can load the archived image as follows:
``$ docker load < node-vr.tar``

Deploying such an image seems to be super simple. On the server with a fully qualified domain and web server and reverse proxy [Nginx](https://nginx.org/)).

The only little problem is those files required by server web application via HTTPS, ie SSL certificates. Where they are located? It depends on how you managed such certificates. I use [letsEncrypt](https://letsencrypt.org/) Certbot. 

Your browser lock at the location bar says:

![This is what you see](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w82kljpi0a0537o4jsmt.png)

These chains of trust hierarchy technically have this structure:

![Chain of Trust](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40mikmhsum3c7lqtjetv.png)

# Let's Encrypt Certbot hints
Installation:
``$ sudo apt install certbot python3-certbot-nginx``

Get your domain SSL certificates:
``$ sudo certbot --nginx -d your.domain.name``

The architecture is organized by certbot in this folder: ``/etc/letsencrypt``

![/etc/letsencrypt/](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64w5o61mtw93o3yvla8q.png)

And the domain name specific files are saved to ``/etc/letsencrypt/archive/your.domain.name``

So these files are archived under the following numbers (because certbot already renewed cert files so far several times). The latest ones are exposed to the ``/etc/letsencrypt/live/`` folder. as symbolic links ([symlinks](https://man7.org/linux/man-pages/man2/symlink.2.html)) without numbers in their names.

> <sup>- Symbolic links are interpreted at run time as if the contents of the link had been substituted into the path being followed to find a file or directory. - Symbolic links may contain ..  path components, which (if used at the start of the link) refer to the parent directories of that in which the link resides. - A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent one; the latter case is known as a dangling link..</sup>
 
```
root [letsencrypt] $ more /etc/letsencrypt/live/your.domain.name/README 
This directory contains your keys and certificates.

`privkey.pem`  : the private key for your certificate.
`fullchain.pem`: the certificate file used in most server software.
`chain.pem`    : used for OCSP stapling in Nginx >=1.3.7.
`cert.pem`     : will break many server configurations, and should not be used
                 without reading further documentation (see link below).

WARNING: DO NOT MOVE OR RENAME THESE FILES!
         Certbot expects these files to remain in this location in order
         to function properly!

We recommend not moving these files. For more information, see the Certbot
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
``` 

Coming back to the Docker. We have pulled the image and we would love to run the web app. The problem is that the container needs to read SSL certificates (exposed as symbolic links) from the outside of the Docker virtual container. They are required to run the web app securely via HTTPS. The paths to the key and certificate are provided in the main Node.js app file: ``index-wall.js``  

```
const server = https.createServer({
    key: fs.readFileSync('/ssl/privkey.pem'),
    cert: fs.readFileSync('/ssl/fullchain.pem')
}, app);
```

Here is how it can not be run with mounted volumes -v ( --volume). 

``$ docker run -v /etc/letsencrypt/live/your.domain.name:/ssl -d -p 4444:8080 flaboy/node-wall node index-wall.js``

Docker unfortunately is not able to read symlinks from provided volumes (-v). You can check the error logs this way:
``$ docker logs containerid``
```
root [letsencrypt] $ docker logs 8d2b81a25359
node:internal/fs/utils:345
    throw err;
    ^

Error: ENOENT: no such file or directory, open '/ssl/privkey.pem'
    at Object.openSync (node:fs:585:3)
    at Object.readFileSync (node:fs:453:35)
    at Object.<anonymous> (/usr/src/app/index-wall.js:18:13)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47 {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: '/ssl/privkey.pem'
}
```
# You need to use bind mounts

_**What are bind mounts?**_ As per [Docker documentation](https://docs.docker.com/storage/bind-mounts/), we can read that:
> [...] Bind mounts have limited functionality compared to volumes. When you use a bind mount, a file or directory on the host machine is mounted into a container. The file or directory is referenced by its absolute path on the host machine. By contrast, when you use a volume, a new directory is created within Docker’s storage directory on the host machine, and Docker manages that directory’s contents.

Detailed instruction on the bind mounts syntax:
>**--mount**: Consists of multiple key-value pairs, separated by commas and each consisting of a ``<key>=<value>`` tuple. - The --mount syntax is more verbose than ``-v`` or ``--volume``, but the order of the keys is not significant, and the value of the flag is easier to understand.
- The type of the mount, which can be bind, volume, or tmpfs. [...] The type is always bind.
- The source of the mount. For bind mounts, this is the path to the file or directory on the Docker daemon host. May be specified as source or src.
- The destination takes as its value the path where the file or directory is mounted in the container. May be specified as destination, dst, or target.
- The readonly option, if present, causes the bind mount to be mounted into the container as read-only.
- The bind-propagation option, if present, changes the bind propagation. May be one of rprivate, private, rshared, shared, rslave, slave.
- The --mount flag does not support z or Z options for modifying selinux labels.

So let's try to mount 2 cert files (**sources**):
``/etc/letsencrypt/live/your.domain.name/privkey.pem``
and ``/etc/letsencrypt/live/your.domain.name/fullchain.pem`` as they would exist inside docker container inside ``/ssl/`` folder (**target**).

``$ docker run --mount type=bind,source=/etc/letsencrypt/live/your.domain.name/privkey.pem,target=/ssl/privkey.pem --mount type=bind,source=/etc/letsencrypt/live/your.domain.name/fullchain.pem,target=/ssl/fullchain.pem -d -p 4444:8080 flaboy/node-wall node index-wall.js``

Is the app running?
``$ docker ps -a``
```
9990433c624d   flaboy/node-wall   "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes                 0.0.0.0:4444->8080/tcp, :::4444->8080/tcp   hardcore_nash
```

## Yes! That's all people!

Here is my video on how it did not work properly with -v
[![Here is how it did not work with -v](https://img.youtube.com/vi/doS0L6nBeKg/0.jpg)](https://www.youtube.com/watch?v=doS0L6nBeKg)

And here is working properly with the latest SSL
[![And here is working properly SSL](https://img.youtube.com/vi/1rRZRmLt7s4/0.jpg)](https://www.youtube.com/watch?v=1rRZRmLt7s4)

If you would love to play with [Interactive Wall](https://xr.workwork.fun:4444), it's deployed to: https://xr.workwork.fun:4444

The app works on Mac, and Windows (you might need also Nintendo 
 emulator **[Dolphin](https://dolphin-emu.org/))**. You just need a Chromium-based browser (Edge, Brave, Chrome), also get your **original Wiimote(s) Plus** (cheap, the third party rather does not work). Do not forget to charge your batteries. Infrared direction calibrator **Mayflash W010 Dolphin Bar** is necessary, get it from the [Amazon](https://www.amazon.co.uk/Mayflash-W010-Dolphin-Bar-Wireless/dp/B00HZWEB74). The WebHID API and amount of Bluetooth devices might be limited on Windows but Bluetooth standard might allow connecting lots of devices so having multiple input devices connected to the web browser interface at the same time is an amazing opportunity for multiplayer experiences without any game-server involved. _Just think about this a little bit!_

The Students UI
https://xr.workwork.fun:4444

The Teachers UI (open from provided QR code)
https://xr.workwork.fun:4444/?u=teacher&p=pass1

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/teopgxe6dta37kbkx5op.png)

More about **5GTours** project you can find in D4.4 public document on [http://5gtours.eu/deliverables/](http://5gtours.eu/deliverables/)