My Unnecessary Adventure in (Re)Building a Media Sever
Tags: docker, mediaserver, ubuntu, jellyfin
Published: 2020-10-30 22:13:50

Welcome! Strap in because this is gonna be a long ride.

To preface, I had an existing media server that I broke some permissions and reinstalling was the only option. I had an existing RAID 0 setup with lots of data I did not want to lose, plus a standalone SSD. So, without further adieu, here we go!

## Installing Ubuntu Server

I chose Ubuntu Server because Ubuntu has a great deal of community support and I have no need for a desktop GUI.

Download the ISO from [here](https://ubuntu.com/download/server). You may have to choose 'Option 3' to actually get a download. At the time of writing, I'm using 20.04.1, so take that into account.

My tool of choice for burning ISOs is [balenaEtcher](https://www.balena.io/etcher/).

Plug in the USB, boot to it, and follow setup. Make sure you enable the SSH server AND NO LVM ON THE SSD. Check to make sure the full capacity of the SSD is mounted to `/` and then you're good to go. Finish the setup, remove the USB, and reboot. Now we'll switch to SSH.

## Configuring SSH

Port 22 sucks, but we need to login with it. Connect to the server on port 22 with the credentials you added during install.

Open the port in the firewall

```
sudo ufw allow 8364/tcp
```

Open the SSH config file and change the port used

```
sudo nano /etc/ssh/sshd_config
```

Find the line containing `Port 22`, uncomment, and change to `Port 8364`. Save (`ctrl x`). 

Restart the SSH server and then reconnect on your client.

```
sudo systemctl restart ssh
```

## Drive Mounting

Find the UUID of the drive:
```
sudo blkid
```

Add your drive to `/etc/fstab` (BE CAREFUL, THIS CAN BRICK STUFF):
```
sudo nano /etc/fstab
```

E.g:
```
UUID=ccc56606-385e-4a16-89c4-7f1513ab1641 /home ext4 defaults 0 0
```

Reboot the system and your drive your be properly mounted.

## Docker

First, update your system:
```
sudo apt update
sudo apt upgrade
```

Instead of repeating things, go follow the official Docker tutorial [here](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). Just follow the 'Install using the repository' and 'Install Docker Engine' sections.

Then, install Docker Compose by following the tutorial [here](https://docs.docker.com/compose/install/).

The next part is taken mostly from [Smart Home Beginner](https://www.smarthomebeginner.com/docker-home-media-server-2018-basic/#Add_Linux_User_to_Docker_Group). 

We will add the current user to the docker group to simplify some things in the future.
```
sudo gpasswd -a $USER docker
```

Activate the changes using the following:
```
newgrp docker
```

## Portainer

Portainer is our docker management solution. Yes, I have serious issues with it but currently don't have an alternative. Maybe this will change in the future, but here we are.

Run the following commands to get Portainer up and running:

```
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
docker run -d -p 9001:9001 --name portainer_agent --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes portainer/agent
```

Open a browser and go to your computer's IP with the port 9000. For me, that's `http://192.168.1.50:9000`. Chose a username and password, then create user.

Choose "Docker" and press "Connect".

Now that you are in the web panel, click on 'local' in the middle, and then the 'Stacks' tab on the left. Click 'Add stack' and give it a name such as "main_stack". Paste in the following config and adjust it to fit your needs.

```yml
---
version: "2.3"
services:

  organizr:
    image: linuxserver/organizr
    container_name: organizr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${USERDIR}/organizr:/config
    ports:
      - 9983:80
    restart: unless-stopped
    
  watchtower:
    container_name: watchtower
    restart: always
    image: containrrr/watchtower
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --schedule "0 0 4 * * *" --cleanup
    
  radarr:
    image: linuxserver/radarr
    container_name: radarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${USERDIR}/radarr:/config
      - ${USERDIR}/media/movies:/movies
      - ${USERDIR}/downloads:/downloads
    ports:
      - 7878:7878
    restart: unless-stopped
    
  sonarr:
    image: linuxserver/sonarr
    container_name: sonarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${USERDIR}/sonarr:/config
      - ${USERDIR}/media/tv:/tv
      - ${USERDIR}/downloads:/downloads
    ports:
      - 8989:8989
    restart: unless-stopped
    
  duckdns:
    image: linuxserver/duckdns
    container_name: duckdns
    environment:
      - TZ=${TZ}
      - SUBDOMAINS=kyleserver
      - TOKEN=326ab158-e842-4d0e-aa20-233e0f7e51e3
    restart: unless-stopped
    
  minecraft-creative:
    container_name: minecraft-creative
    image: itzg/minecraft-server
    environment:
      EULA: "true"
      TYPE: PAPER
      VERSION: 1.16.2
      CONSOLE: "false"
      LEVEL_TYPE: "flat"
    ports:
      - 25566:25565
    volumes:
      - ${USERDIR}/minecraft-creative:/data
    restart: unless-stopped
    
  minecraft-survival:
    container_name: minecraft-survival
    image: itzg/minecraft-server
    environment:
      EULA: "true"
      TYPE: PAPER
      VERSION: 1.16.2
      CONSOLE: "false"
      MEMORY: 2G
    ports:
      - 25565:25565
    volumes:
      - ${USERDIR}/minecraft-survival:/data
    restart: unless-stopped
    
  qbittorrent:
    image: linuxserver/qbittorrent:14.2.5.99202004250119-7015-2c65b79ubuntu18.04.1-ls93
    container_name: qbittorrent
    network_mode: "service:gluetun"
    volumes:
      - ${USERDIR}/docker/qbittorrent:/config
      - ${USERDIR}/downloads:/downloads
    restart: unless-stopped
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - UMASK_SET=002
      - WEBUI_PORT=4545
      
  gluetun:
    image: qmcgaw/private-internet-access
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    network_mode: bridge
    ports:
      - 8888:8888
      - 8388:8388
      - 8388:8388/udp # Shadowsocks
      - 8001:8001
      - 4545:4545
      - 6881:6881
      - 6881:6881/udp
    volumes:
      - ${USERDIR}/gluetun:/gluetun
    environment:
      - VPNSP=surfshark
      - TZ=${TZ}
      - USER=USERNAME
      - PASSWORD=PASSWORD
      - REGION=Canada Toronto
    restart: unless-stopped
```

Next, run `id` and note your uid and pid. We will input these as PUID and PGID in the "Environment" section.
```
PUID=1000
PGID=1000
TZ="America/Toronto"
USERDIR="/home/user"
```

Add any environment variables that you need NOW as I don't believe these can be changed in the future.

Click 'Deploy the stack'.

Currently, qbittorrent has an issue with mismatched torrent names, that's why it's using a custom image version. If it gets resolved in the future, remove everything after the colon.

These are simply the containers I use for everything I do. The 'gluetun' container connects to Surfshark VPN and routes the qbittorrent container's traffic through, allowing a normal container to connect to a VPN. This can be done with any container by moving its ports to gluetun and setting its `network_mode` to "service:gluetun". After that, go and check each container to make sure it's working.

## Jellyfin

Welp, this is gonna be a fun one. If you don't want to do hardware transcoding with a GPU, simply add a standard container to the above compose file. Otherwise, come along for this hell of a ride. There is no guarantee that some of these steps aren't redundant as I have no clue how any of this works. But, ideally, everything will work by the end of this.

Firstly, we need to install the NVIDIA drivers. I'm using a GTX 1050ti but it should work for any NVIDIA card 10-series or newer.

Run these commands first to disable Nouveau:
```
sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf"
sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf"
```

What is Nouveau and why are we disabling it? How dare you ask! Subsequently, I have no clue but it's what we gotta do.

Reboot.

Install DMKS:
```
sudo apt install dkms
```

Follow the instructions [here](https://github.com/keylase/nvidia-patch) to install the drivers.
Additional Notes:
* Update the version number in the other commands
* Each command needs the be run with `sudo`

Click 'Yes' for everything, and after exiting the GUI, reboot, then run the test commands.

Install ffmpeg:
```
sudo apt install ffmpeg
```

Next, we need to setup the NVIDIA Container Toolkit. Follow the official instructions [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#setting-up-nvidia-container-toolkit).

Now the GPU should be good to go! Next, we need to actually setup Jellyfin. Going back to SSH, go to your home dir (`cd`).

Create a new docker compose file called `docker-compose.yml` and fill it with the following:
```yml
version: "2.3"
services:
  jellyfin:
    image: linuxserver/jellyfin
    container_name: jellyfin
    network_mode: "host"
    runtime: nvidia
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Toronto
      - NVIDIA_VISIBLE_DEVICES=all
    volumes:
      - /home/user/jellyfin:/config
      - /home/user/media/tv:/data/tvshows
      - /home/user/media/movies:/data/movies
    ports:
      - 8080:8096
      - 7359:7359/udp
    restart: unless-stopped
```

Run `docker-compose up -d` and Jellyfin will be running. Counterintuitvly, go to port 8096 on your server's IP and login. 

### Change Jellyfin Settings
* Go to Settings > Advanced > Networking. Change "Local HTTP port number" and "Public HTTP port number" to 8080. 
* Go to Server > Playback and under "Hardware acceleration" choose 'Nvidia NVENC'. Select which files you want to decode.
* Change 'ffmpeg path' to `/usr/bin`

Restart Jellyfin from Portainer, and you're good to go!

## Conclusion

It was a lot of work, but hopefully everything works. Remember, DuckDuckGo and StackOverflow are your friends. If something doesn't work, let me know below and I'll do my best to help.

Good luck and stay curious!


## Sources

* https://linuxize.com/post/how-to-change-ssh-port-in-linux/
* https://fossbytes.com/how-to-auto-mount-partitions-on-boot-in-linux-easily/
* https://www.smarthomebeginner.com/docker-home-media-server-2018-basic/#Add_Linux_User_to_Docker_Group
* https://askubuntu.com/questions/477551/how-can-i-use-docker-without-sudo
* https://linuxconfig.org/how-to-disable-blacklist-nouveau-nvidia-driver-on-ubuntu-20-04-focal-fossa-linux