by Maniack Crudelis

Into this post we're going to see how to deploy a YunoHost server into an existing YunoHost server by using LXC in order to configure the guest YunoHost to connect throught ProtonVPN.

The idea is to isolate a service behind a VPN, but still using the convenience of YunoHost. Without affecting the whole server and all the services it provides.

Install LXC

First things first, let's install LXC:

sudo apt update
sudo apt install lxc lxctl

Host network configuration

Allow the kernel to forward traffic:

echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/lxc_ynh.conf
sudo sysctl -p /etc/sysctl.d/lxc_ynh.conf

Add a bridge to route the traffic between the host and the LXC guest:

sudo nano /etc/network/interfaces.d/lxc_ynh
auto lxc_ynh
    iface lxc_ynh inet static
    bridge_ports none
    bridge_fd 0
    bridge_maxwait 0
    up iptables -A FORWARD -i lxc_ynh -o eth0 -j ACCEPT
    up iptables -A FORWARD -i eth0 -o lxc_ynh -j ACCEPT
    up iptables -t nat -A POSTROUTING -s -j MASQUERADE

Then start the bridge:

sudo ifup lxc_ynh --interfaces=/etc/network/interfaces.d/lxc_ynh

Create the container

Create a minimalist Debian container:

sudo lxc-create -n lxc_ynh -t debian -- -r buster

Container configuration

The container configuration will be done into the file /var/lib/lxc/lxc_ynh/config

sudo nano /var/lib/lxc/lxc_ynh/config

Remove the line lxc.net.0.type = empty
And add the following ones:

# Network
lxc.net.0.type = veth
lxc.net.0.flags = up
lxc.net.0.link = lxc_ynh
lxc.net.0.name = eth0
lxc.net.0.hwaddr = 00:FF:AA:00:00:01
lxc.net.0.ipv4.address =
lxc.net.0.ipv4.gateway =

# Autostart
lxc.start.auto = 1

First steps with the container

Start the LXC container:

sudo lxc-start -n lxc_ynh -d

And update the apt cache (That's also a way to check if the network is working):

sudo lxc-attach -n lxc_ynh -- apt-get update
If the container fails to resolve domain names, you likely have to change the dns for the next steps.

Go to the file /var/lib/lxc/lxc_ynh/rootfs/etc/resolv.conf and replace by a dns address you want, your gateway is usually a good choice.

Now that the network is working into your container, install the packages needed for a fully working Debian:

sudo lxc-attach -n lxc_ynh -- apt-get install -y aptitude sudo git ssh openssh-server
sudo lxc-attach -n lxc_ynh -- aptitude install -y ~pstandard ~prequired ~pimportant

You now have a perfectly working Debian into a LXC container.
So, now let's go directly into the container:

sudo lxc-attach -n lxc_ynh -- /bin/bash
This is one of the best way to go into your container to have a proper terminal to work. Could be useful to keep that for further operations.

Install YunoHost into the container

Now that we're into the container, you'll notice that the prompt has changed to root@lxc_ynh, we're going to use that syntax for every command to use into the container.

And first, we're going to install YunoHost:

root@lxc_ynh:/# git clone https://github.com/YunoHost/install_script /tmp/install_script
root@lxc_ynh:/# cd /tmp/install_script; ./install_yunohost -a
root@lxc_ynh:/# yunohost tools postinstall
root@lxc_ynh:/# yunohost user create MYUSER
Change MYUSER by the name you want for your user.

To ease the usage of a Let's Encrypt certificate, we're going to let the host handle it.
First we're going to share the certificates between the host and the guest by mounting the directory into the container.
In order to do so, we're going back to the file /var/lib/lxc/lxc_ynh/config

sudo nano /var/lib/lxc/lxc_ynh/config
Please notice that there isn't the prompt root@lxc_ynh as said before. This command has to be executed into the host, not the container.

And add these few lines

# Mount between host and guest
# SSL Certificates
lxc.mount.entry=/etc/yunohost/certs etc/yunohost/certs none ro,bind 0 0

Yet, after a restart of the container, ssl-cert will probably have a problem to read the certificate because of the mapping of the groups between the host and the LXC guest.
And, as the certificates are not readable except by root and ssl-cert, ldap will fail to read it...

Start by checking the permissions

root@lxc_ynh:/# ls -l /etc/yunohost/certs/

All files should belong to root:ssl-cert. If not, you have an issue with the gid of ssl-cert. To fix that, we will modify the gid to have the same than the host.

First, find the gid of ssl-cert into the host

cat /etc/group | grep ssl-cert

Then find the current group using that gid into the guest

root@lxc_ynh:/# cat /etc/group | grep 'gid ssl-cert'

Change the gid of the group impersonating ssl-cert gid and change the ownership of its files.

root@lxc_ynh:/# groupmod -g 'free gid' 'impersonating group'
root@lxc_ynh:/# find / -group 'free gid' -exec chgrp -h 'impersonating group' {} \;

Then give ssl-cert its correct gid so it would be the same as the host.

root@lxc_ynh:/# groupmod -g 'gid ssl-cert' ssl-cert

Now ssl-cert will be able to read correctly the certificates, as its gid is the same as the host.

Configure the host to access the container

Into the admin panel of your host YunoHost, add the domain you have chosen for your guest YunoHost during the post install just before.

If has to be the same, otherwise, the redirection and the certificate won't match

And install a Let's Encrypt certificate for this new domain.

Now, we need to redirect the request to this domain toward the container.
To do that, we're going to use the easy way by installing the app redirect with the following configuration:

Choose a domain for your redirect: The domain you used for your YunoHost into the container.
Choose a path for your redirect:   /
Redirect destination path:
Redirect type:                     Proxy, insivible [...]. Everybody will be able to access it.
Don't worry about the warning, we're not going to install anything else for that domain into this YunoHost. Everything else will be installed into the guest YunoHost.
If you're not using YunoHost, you should configure the domain yourself and handle the certificate.
For the redirection to the container, you'll need a nginx proxy pass instruction.
Here the nginx config set while using the app redirect_ynh:

location / {
   proxy_redirect    off;
   proxy_set_header  Host $host;
   proxy_set_header  X-Real-IP $remote_addr;
   proxy_set_header  X-Forwarded-Proto $scheme;
   proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header  X-Forwarded-Host $server_name;
   proxy_set_header  X-Forwarded-Port $server_port;

   proxy_http_version 1.1;
   proxy_set_header Upgrade $http_upgrade;
   proxy_set_header Connection "upgrade";

Enjoy it

In order to mount the certificate, and shake up a little bit all the stuff, let's restart the container:

sudo lxc-stop -n lxc_ynh
sudo lxc-start -n lxc_ynh -d

Now, from a not to bothering browser (be careful with any cache you may have) you should be able to reach your guest YunoHost from the domain name you've chosen.

It can be useful to go into private browsing to avoid any cache that would got you some trouble.
While you should reach the portal of your guest YunoHost, you won't be able to reach the admin panel.
Fortunately, you still have the CLI command available.
I hope to find a way to fix that issue...

Configure the container to support a VPN connection

The container is unable to connect to a VPN because it doesn't have a TUN interface.
We're going to add that interface, by adding those lines to the config file /var/lib/lxc/lxc_ynh/config:

# VPN specific configuration
lxc.autodev = 1
lxc.cgroup.devices.allow = c 10:200 rwm
lxc.hook.autodev = sh -c "cd ${LXC_ROOTFS_MOUNT}/dev; mkdir -p net; test -e net/tun || mknod net/tun c 10 200"

Then restart the container:

sudo lxc-stop -n lxc_ynh
sudo lxc-start -n lxc_ynh -d

Now that the container can have a VPN client, let's install the open source CLI client for ProtonVPN into the container:

root@lxc_ynh:/# apt install openvpn dialog python3-pip python3-setuptools iptables-persistent
root@lxc_ynh:/# sudo pip3 install protonvpn-cli

Configure the VPN:

root@lxc_ynh:/# sudo protonvpn init
Note that you need sudo here to run the cli ProtonVPN app.

The VPN client is configured, but not yet connected. You can run the command protonvpn status to see that you're not connected to the VPN.
So first, connect to the VPN:

root@lxc_ynh:/# sudo protonvpn connect -f
root@lxc_ynh:/# sudo protonvpn status
Find all the available commands of the app here.

Your guest YunoHost, into its LXC container is now protected behind the VPN.
But as soon as you will restart the container, the VPN will be disconnected.
To prevent that situation, we're going to create a systemd config for the ProtonVPN app.
Since a documentation already exist, let's follow it: https://github.com/ProtonVPN/protonvpn-cli-ng/blob/master/USAGE.md#via-systemd-service

This systemd config has to be added into the LXC container.

You're all set

Everything should be ok now.
You can restart your container to make sure everything is working and you're still connected to the VPN.

And now it's time to install the services you want into your YunoHost https://yunohost.org/#/apps

If you want to use a multimedia app, it can be useful to mount your /home/yunohost.multimedia into the LXC container to share the media between both the host and the guest.

Create first the directory:
sudo mkdir /var/lib/lxc/lxc_ynh/rootfs/home/yunohost.multimedia
And add a mount line into /var/lib/lxc/lxc_ynh/config:
lxc.mount.entry=/home/yunohost.multimedia home/yunohost.multimedia none bind 0 0
Restart the container and that's ok.