SimpNas server with docker - home assistant, radicale , jellyfin & esphome (tutorial)
#1
Outdated Tutorial: Please use as a guide only


Hi all,



I've been meaning to write this tutorial since last year , now right out of the gate I will say I am far from an expert.

This is how I set up my NAS Server and docker containers , but I will gladly accept any critique of my setup and advice to better secure or improve the overall setup.



To start with I did try OpenMediaVault, I got everything how I wanted it, however I felt that OpenMediaVault was too bloated for my needs and a little complicated in some of its options. Whilst on the Armbian forum ,seeking advice for getting the fan to work properly for my server (yes I did try tuxd3v 's ATS fan configuration but this didn't work after using a later kernel to get my sata card to work  (more on that later)).

I discovered SimpNAS , I posted a thread on this forum for all who are interested . SimpNAS was exactly what I was looking for , simple, elegant and easy .



So to begin I setup OpenMediaVault and long story short I had issues with the pine64 supplied Sata card , I wont go in to detail here as there are many threads that describe this issue, here is some slight insight

So I bought  a marvell 88SE9230 Sata card , which so far has been absolutely amazing , rock solid no matter what I throw at it, but to use said card I had to use a newer kernel which as described in the thread resulted in me not being able to use ATS to control the fan.



Now down to business , I have a rockpro64 with NAS case , tall heatsink , case fan, a marvell 88SE9230 Sata card, 2 SSD drives and a very decent SD card , the software is Armbian , with SimpNAS and docker installed and 6 containers  -  Nginx,  Portainer,  Homeassistant , Radicale caldav server , Jellyfin media server and EspHome for my home automation gadgets. home assistant and radicale are exposed to internet so they are accessible from outside my local network .



so here are my instructions :

  1. burn Armbian Buster server to SD Card using imager of choice I typically use Gnome disks , which is simple to use .
  2. after booting ssh to armbian with root and password 1234
  3. Change root password
  4. Do not add new user when logged in as root
  5. apt update && apt upgrade (reboot)
  6. add ssh keys to root home to ease login with out password (optional )
  7.  type Armbian-config  and then select options > System > Other > install latest kernel (5.8+) (server will reboot)
  8.  type echo 0 >  /sys/devices/platform/pwm-fan/hwmon/hwmon2/pwm1  this is to turn off the fan which is continously running on the newer kernel .
  9. edit /etc/rc.local file and add fan settings for startup, I chose these, which allow the fan to run continously but at a whisper quiet setting more info here
     


Code:
rmmod pwm-fan
echo 0 > /sys/class/pwm/pwmchip1/export
echo 110 > /sys/class/pwm/pwmchip1/pwm0/duty_cycle
echo 500 > /sys/class/pwm/pwmchip1/pwm0/period
echo 1 > /sys/class/pwm/pwmchip1/pwm0/enable


      10. add udev rule so drives will show up nano /etc/udev/rules.d/99-marvell.rules.

Code:
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x1b4b", ATTR{device}=="0x9230", RUN+="/bin/bash -c 'echo %k > /sys/bus/pci/drivers/ahci/bind'"



      11.  reboot then type lsblk  to see if drives are discovered

      12. now install SimpNAS

Code:
wget https://simpnas.com/install.sh; bash install.sh

         
13. follow setup wizard on url shown in terminal at the end of install .

          14. login into SimpNAS admin panel using username administrator and password set during wizard

          15. in admin panel Add Volumes to add the drives in  SimpNAS

          16. add users in user section

          17. add shares to drives

          18. at this point you now have your NAS setup , you can upload all the files you need to  via samba

          19. now onto portainer run this to get portainer installed and running,  Docker is installed when SimpNAS is.



Code:
docker volume create portainer_data

docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce


         20. now pull the images for the other containers, you can do this via the portainer UI (hence why I installed it first or via command line ) I like to do this now as it can take some time to pull the images ,although they will be installed with docker-compose later, if you prefer doing it all at once and don't want to know how docker works .



Code:
docker pull homeassistant/home-assistant:stable tomsquest/docker-radicale esphome/esphome nginx:latest




        21. now to install docker compose which will help later with renewing  lets encrypt certificates via certbot . first we need a few packages

Code:
apt install python3 python3-pip python3-setuptools python3-dev libffi-dev


       22. then using the newly installed pip3 install  wheel and docker-compose



Code:
pip3 install wheel

pip3 install docker-compose


23.now lets create a docker-compose.yml file to start our containers and provide all the info they need to get running. When docker compose is run it will create all the necessary directories and files based on this docker-compose file .

Code:
version: '3.7'
services:
  nginx:
    depends_on:
      - radicale
      - homeassistant
    image: nginx:latest
    container_name: nginx_reverse_proxy
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /root/nginx.conf:/etc/nginx/nginx.conf
      - /root/nginx/error.log:/etc/nginx/error.log
      - /root/nginx/access.log:/etc/nginx/access.log
      - /etc/letsencrypt/:/etc/letsencrypt/
      - /root/nginx/dhparams.pem:/etc/nginx/dhparams.pem
    restart: unless-stopped
    network_mode: host

  radicale:
    image: tomsquest/docker-radicale
    container_name: radicale
    ports:
      - 127.0.0.1:5232:5232
    init: true
    read_only: true
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - SETUID
      - SETGID
      - CHOWN
      - KILL
    healthcheck:
      test: curl -f http://127.0.0.1:5232 || exit 1
      interval: 30s
      retries: 3
    restart: unless-stopped
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /root/radicale/data:/data
      - /root/radicale/config:/config:ro
      - /root/radicale/users:/etc/radicale/users
      - /root/radicale/log:/var/log/radicale/log
  homeassistant:
    container_name: home-assistant
    image: homeassistant/home-assistant:stable
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /root/homeassistant:/config
    environment:
      - TZ=Europe/London
    restart: unless-stopped
    network_mode: host
  esphome:
    image: esphome/esphome
    volumes:
      - /root/esphome/config:/config:rw
      - /etc/localtime:/etc/localtime:ro
    network_mode: host
    restart: always
         24. create nginx.conf file in root home directory this is needed for the nginx container to access the other containers and to have a few needed settings for secure access( edit the duckdns address to suit the url you create on duckdns.org , you will need to create an account and register for a maximum of 5 subdomains) (this if free)



Code:
user www-data;
worker_rlimit_core 500M;
worker_processes 1;

events {

  worker_connections 1024;

}

http {
  map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
  }
  error_log /etc/nginx/error.log warn;
  access_log /etc/nginx/access.log;
  ssl_dhparam /etc/nginx/dhparams.pem;
  ssl_prefer_server_ciphers on;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_protocols TLSv1.2 TLSv1.3;
  add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
  ssl_stapling on;
  ssl_stapling_verify on;
  ssl_certificate /etc/letsencrypt/live/yourRadicaleAddress.duckdns.org/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/yourRadicaleAddress.duckdns.org/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/yourRadicaleAddress.duckdns.org/chain.pem;
  ssl_session_cache shared:SSL:10m;
  proxy_buffering off;

  server {
    listen 80;
    server_name yourRadicaleAddress.duckdns.org;
    return 301 https://$server_name$request_uri;   
  }
 
  server {
    listen 443 ssl http2;
    server_name yourRadicaleAddress.duckdns.org;

    location /radicale/ {
    proxy_pass           http://localhost:5232/;
    proxy_set_header     X-Script-Name /radicale;
    proxy_set_header     X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass_header    Authorization;
    }
  }

  server {
    listen 80;
    server_name yourHomeassistantAddress.duckdns.org;
    return 301 https://$server_name$request_uri;
  }

  server {
    listen 443 ssl http2;
    server_name yourHomeassistantAddress.duckdns.org;

    location / {
        proxy_pass http://localhost:8123;
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     }

     location /api/websocket {
        proxy_pass http://localhost:8123/api/websocket;
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

     }
  }
}
       25. in the root home directory create an nginx directory, then a cert directory and then create a dhparam file this is used by the nginx container

Code:
openssl dhparam -out /root/nginx/cert/dhparam.pem -dsaparam 2048
      26. create a radicale directory , a config directory and add a config file for the radicale container



Code:
[server]
hosts = 0.0.0.0:5232

max_connections = 15
# 100 Megabyte
max_content_length = 100000000
# 30 seconds
timeout = 20

[auth]
# Average delay after failed login attempts in seconds
delay = 600

type = htpasswd
htpasswd_filename = /etc/radicale/users
# encryption method used in the htpasswd file
htpasswd_encryption = md5


[storage]
filesystem_folder = /data/collections
           27. now we need to install apache2-utils so we can use htpasswd to create a password file for users of Radicale . ( I only use Radicale for calendar sync i.e only one user in this case user calendar, but it can be used for multiple users with calendars , address books and to-do lists for each .)



Code:
apt install apache2-utils

htpasswd -c /root/radicale/users calendar


           28. now in order for radicale to work a radicale user and group needs to be created , more info can be gleamed from the provider of the Radicale docker container , here



Code:
addgroup --gid 2999 radicale

adduser --gid 2999 --uid 2999 --shell /bin/false --disabled-password --no-create-home radicale


           29. we are almost there now , just a few more steps and some optional ones at the end . Time to install fail2ban and certbot ( certbot obtains a lets encrypt certificate for us and will renew the cert via a cronjob specified later .)



Code:
apt install fail2ban certbot
         30. A few necessary adjustments need to be made to fail2ban , lots of articles are available on the on the internet so I wont touch on them here.u Currently I have it working for home assistant but not Radicale , as Radicale doesn't provide logging easily (logging is how fail2ban block unwanted log ins and block the IP address). It is in the works however  and hopefully will be available soon.



Code:
cp jail.conf jail.local

nano jail.local
    I tend to edit this section of jail.local to ignore my local network so as not to ban the IP Address of the machine I use to SSH into the Rockpro64 , this is because SSH  Jail is enabled by default on fail2ban .

Code:
# "ignoreip" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban
# will not ban a host which matches an address in this list. Several addresses
# can be defined using space (and/or comma) separator.
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
          31. I now add 2 files as specified in the home assistant documentation on fail2ban to use the logging from home assistant and what to look(filter) for in the logging .



Code:
nano /etc/fail2ban/jail.d/hass.conf


[hass-iptables]
enabled = true
chain = DOCKER-USER
filter = hass
action = iptables-allports[name=HASS]
logpath = /root/homeassistant/home-assistant.log
bantime = 3600
maxretry = 3


Code:
nano /etc/fail2ban/filter.d/hass.conf


[INCLUDES]
before = common.conf

[Definition]
failregex = ^%(__prefix_line)s.*Login attempt or request with invalid authentication from <HOST>.*$
ignoreregex =

[Init]
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S


         32. A word of warning , if you are starting home assistant from new( as in no existing configuration file yet) I would disable fail2ban for now the reason is once restarted it will complain that there is no log located at /root/homeassistant/home-assistant.log . As stated in home assistants documentation linked above you need to have

Code:
logger:
  logs:
    homeassistant.components.http.ban: warning


added to your configuration file  for this to work. If you have an existing home assistant configuration you can copy this across now to a home assistant directory located in the root home directory . add the above logger to the config file and then use the touch command to create the home-assistant.log file . Fail2ban can then be restarted or started if disabled and it wont complain about a missing file and will work when the conatiner is started and begins logging to said file .



Code:
systemctl restart fail2ban.service
( change above to start instead of restart if stopped , or change to enable if previously disabled and then start )



now check to see if the jail is up and working



Code:
fail2ban-client status hass-iptables


         33. Ok finally certbot , to get certbot to work port 80 and port 443 need to be open on your router and directed to the Rockpro64 IP address (I would also highly recommend that a static IP is set for the rockpro64 in your routers settings also )



lets begin by getting a lets encrypt certificate for the duckdns subdomains associated with our home assisatnt and radicale services.



Code:
certbot certonly --standalone -d yourRadicaleAddress.duckdns.org -d yourHomeassistantAddress.duckdns.org
this will retrieve a certificate and store it for you ready for the nginx container to use .



now certbot can renew this certificate  for all associated subdomains when they have less than 30 days left until expiration this is done with a cronjob



Code:
crontab -e
 add this to the now open file



Code:
@daily certbot renew --pre-hook "docker-compose -f /root/docker-compose.yml down" --post-hook "docker-compose -f /root/docker-compose.yml up -d"


      34. and finally lets start the containers and run them in the background .



Code:
docker-compose up -d
      35. if successful you will see the new containers up and running in portainer at the rockpro64 IP address port 9000 (for example 192.168.1.120:9000)

now you should be able to access the home assistant container at https://yourHomeassisantAddress.duckdns.org or locally at 192.168.1.120:8123(notice port 8123)

you can access the Radicale container  via https://yourRadicaleAddress.duckdns.org/radicale/ (notice the extension /radicale/ this was specified in the nginx.conf file earlier.) I would suggest  closing the ports on teh router for the time being and accessing home assistant via its local IP address until you have a password and the  setup complete then reopen the ports to access outside of you local network . this is of course not necessary if you have an existing configuration with home assistant and are transitioning to the Rockpro64 server .





      36. the next few steps are optional , I installed Jellyfin  as media server , I did this through the admin panel of SimpNAS under apps  this creates a docker container for you and removes some of the steps to get it to access your media files correctly . of course you can install it manually like we did with the other containers and even use docker-compose by adding the container to the docker-compose.yml file , please note that when certbot renews the certificate it stops all the containers in this file  before renewing the certs this is so port 80 and 443 are open for it to use  and then it restarts the containers again. this will not happen to Jellyfin if installed manually or via the SimpNAS apps

     37 . to top it all off, for those who want the firewall blocking ports on their local network to and from the rockpro64 , this is a little over kill as only port 80 and 443 are open to the outside world via the router, but I understand that some people like to add rules for local traffic to prevent spread locally of a virus or similar between machines  on that network .

I say this because it quite difficult to get a firewall working properly as docker provides it own set of rules to control the firewall and when you add rules of your own it might not work well with dockers rules if they work at all , so as suggested by a great article I found regarding this issue . here is a solution





firstly create a iptables.conf file in /etc/ directory



Code:
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:FILTERS - [0:0]
:DOCKER-USER - [0:0]

-F INPUT
-F DOCKER-USER
-F FILTERS

# BASIC Allow
-A INPUT -i lo -j ACCEPT

# Chain to FILTERS
-A INPUT -j FILTERS
-A DOCKER-USER -i eth0 -j FILTERS

# COMMON FIREWALL RULES

# ALLOW something
-A FILTERS -p all -j ACCEPT -m conntrack --ctstate ESTABLISHED,RELATED
-A FILTERS -p icmp --source 192.168.1.0/24 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 22 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 81 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 82 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 139 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 445 -j ACCEPT
-A FILTERS -p tcp --dport 80 -j ACCEPT
-A FILTERS -p tcp --dport 443 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 8123 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 8096 -j ACCEPT
-A FILTERS -p udp --source 192.168.1.0/24 --dport 1900 -j ACCEPT
-A FILTERS -p udp --source 192.168.1.0/24 --dport 7359 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 8000 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 9000 -j ACCEPT
-A FILTERS -p tcp --source 192.168.1.0/24 --dport 6052 -j ACCEPT
-A FILTERS -p udp --source 192.168.1.0/24 --dport 5353 -j ACCEPT
# DENY something


###################################################################
### special cases for servers
### please modify by the server



### end special cases
############################################################

# FINAL REJECT
# Optional logging
#-A FILTERS -m limit --limit 5/min -j LOG --log-prefix "iptables_INPUT_denied: " --log-level 7
-A FILTERS -p all -j REJECT

COMMIT




and add any rules you want after allow something, those above are just examples using the ports most of the services we have installed, use .



next add these new rules from the file to the kernel to implement them



Code:
iptables-restore -n /etc/iptables.conf


and then   create a service that loads this file on startup .



Code:
nano /etc/systemd/system/iptables.service


then add this



Code:
[Unit]
Description=Restore iptables firewall rules
Before=network-pre.target

[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore -n /etc/iptables.conf

[Install]
WantedBy=multi-user.target
then enable and start the service



Code:
systemctl enable iptables.service
systemctl start iptables.service






I think I've covered most of the setup process for my install , of course you will need to configure the service to suit your individual needs and for that I turn you to there excellent documentation



Jellyfin



Radicale



HomeAssistant



Esphome



Docker



SimpNAS



Duckdns











if you have any questions or feedback for me I will help where I can and edit the tutorial based on giving accurate information .
  Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Smart TV & server storz 1 1,785 01-19-2024, 01:59 PM
Last Post: dairymilkbatman
  Home Server with RAID 1 SATA M2 SSDs runyor 7 3,866 09-26-2023, 09:14 AM
Last Post: wdt
  Umbrel Server Shaeroden 0 1,643 05-04-2022, 06:14 PM
Last Post: Shaeroden
  SkiffOS server with Docker ,NAS, Home assistant, Jellyfin, Snikket XMPP (tutorial) GreyLinux 12 12,567 04-04-2022, 05:26 AM
Last Post: GreyLinux
  Tutorial:How to Configure ATS Fan control tuxd3v 26 58,441 11-06-2020, 08:56 PM
Last Post: g_t_j
  Serial Connection Tutorial: FTDI 232RL hmuller 0 3,791 10-23-2020, 11:56 AM
Last Post: hmuller
  [WIP] backup server (4 sata HDDs + ZFS) rolando 8 13,424 06-30-2020, 10:58 AM
Last Post: dml-pc
  OMV + VPN server + Pi Hole? odhinnhrafn 3 8,733 03-16-2020, 05:01 PM
Last Post: mabs
Video ARMBand an easily configured Home Media Server gabrielsr 2 6,762 07-22-2019, 04:56 AM
Last Post: stuartiannaylor
Wink Tutorial[ Seagate Disks ]: Optimize Life Span/Power Managment tuxd3v 14 27,057 06-19-2019, 04:40 AM
Last Post: Neo2018

Forum Jump:


Users browsing this thread: 1 Guest(s)