Self-Hosting con una Raspberry Pi: Parte 1

Hace un tiempo me picó el bichito del self-hosting. Como ciertos datos a hostear son sensibles o pueden dar problemas de derechos de autor al tratar de usar algún servicio de hosting externo, requeriremos un servidor en casa, para lo cual una Raspberry Pi es perfecta. En mi caso, usé una Raspberry Pi 4 con un disco externo de estado sólido (SSD).

Sistema Operativo

Para el sistema operativo, instalaremos la versión AArch64 de Arch Linux ARM siguiendo la guía de instalación.

Luego de tener la Raspberry encendida, tocará actualizar con

pacman -Syu

Configuración Inicial

En lo que sigue, los términos entre <cuñas> denotarán las partes de los comandos o archivos que deberán reemplazarse por los datos propios de nuestra configuración.

  1. Configuraremos la zona horario usando timedatectl set-timezone <Zona/Horaria>. Podremos ver cuáles zonas hay disponibles mirando los contenidos bajo /usr/share/zoneinfo. Por ejemplo, haciendo ls /usr/share/zoneinfo/America, podemos ver que existe America/Santiago.

  2. Actualizaremos el reloj con timedatectl set-ntp true.

  3. Descomentaremos el idioma que usaremos en /etc/locale.gen removiendo el caracter # de la línea correspondiente. Esto lo haremos con nuestro editor de texto favorito (por ejemplo ejectuando nano /etc/locale.gen y borrando el # de en_US.UTF-8).

    • Si no has usado nano antes, se guarda con Ctrl+O y se sale con Ctrl+X.
  4. Ejecutaremos locale-gen y luego localectl set-locale LANG=<idioma>.UTF-8, donde <idioma> es el que elegimos en el paso anterior.

  5. Le pondremos nombre a nuestro servidor con hostnamectl set-hostname <nombre>.

  6. Editaremos /etc/hosts para que se vea como sigue

    127.0.0.1	localhost.localdomain	<nombre>	localhost
    ::1			localhost.localdomain	<nombre>	localhost
    
  7. Configuraremos una IP estática para nuestra Raspberry editando el archivo /etc/systemd/network/eth.network1 (suponiendo que conectamos nuestra Raspberry por cable de red). Esto será útil más adelante cuando queramos hacer que nuestro servidor sea accesible desde fuera de la red local.

    [Match]
    Name=eth*
    
    [Network]
    Address=<IP Raspberry>
    Gateway=<IP Router>
    DNS=208.67.220.220
    
  8. Opcionalmente, haremos que nuestro servidor sea descubrible por nombre en nuestra red local, de modo de poder conectarnos usando el <nombre> que elegimos antes. Para ello, ejecutaremos

    systemctl enable systemd-resolved.service
    systemctl start systemd-resolved.service
    
  9. También de forma opcional, activaremos los colores en pacman, editando el archivo /etc/pacman.conf y descomentando la linea Color.

Configuración de usuario

Ahora crearemos un usuario distinto a root y al por defecto alarm. Pero primero configuraremos sudo para poder ejecutar comandos que requieran privilegios sin necesidad de ingresar con root.

  1. Instalaremos sudo con pacman -S sudo.
  2. Ejecutaremos EDITOR=nano visudo y descomentaremos la línea %wheel ALL=(ALL) ALL. Al guardar y salir de nano, los usuarios en el grupo wheel tendrán permiso para usar sudo.

Ahora, crearemos nuestro nuevo usuario.

  1. Ejecutaremos
    useradd -m -G wheel <usuario>
    
  2. Crearemos su contraseña con
    passwd <usuario>
    
  3. Finalmente, borraremos el usuario por defecto
    userdel alarm
    

Ahora probablemente queramos reiniciar la Raspberry si es que no lo hemos hecho ya. Para ello, ejecutaremos

systemctl reboot

Servidor de música: Navidrome

Antes de seguir, es probablemente buena idea configurar nuestro editor de texto favorito y apuntar la variable de entorno $EDITOR a este. Para ello, basta agregar al archivo .bashrc la línea

export EDITOR=<editor>

También instalaremos un gestor de AUR, para poder instalar paquetes desde el repositorio de usuarios. En este caso, instalaremos paru2 ejecutando

sudo pacman -S --needed base-devel
git clone https://aur.archlinux.org/paru-bin.git
cd paru-bin
makepkg -si

Y ahora procederemos a instalar nuestro servidor de música. En este caso, elegimos Navidrome3, el que instalaremos con

paru -S navidrome-bin

Luego, ejecutaremos sudoedit /etc/navidrome/navidrome.toml y dejaremos el archivo como sigue

# additional configuration options can be seen at
# https://www.navidrome.org/docs/usage/configuration-options/#available-options
# Address = "localhost"
MusicFolder = "/var/lib/Music"
# CoverArtPriority = "cover.*, folder.*, front.*, embedded"
#BaseURL = ""
#LastFM.ApiKey = ""
#LastFM.Secret = ""
#Spotify.ID = ""
#Spotify.Secret = ""

A continuación, iniciaremos Navidrome

sudo systemctl enable navidrome.service
sudo systemctl start navidrome.service

Desde un navegador, iremos a http://<IP Raspberry>:4533 o http://<nombre>:4533, en donde nos encontraremos una pantalla para crear el usuario administrador en Navidrome. Una vez hecho esto, podemos crear el resto de los usuarios que necesitemos.

Exponiendo nuestro servidor al internet

En esta parte de la guía, haremos que se pueda acceder a nuestro servidor desde cualquier parte del mundo. Para ello necesitaremos que nuestro servicio de internet nos provea de una IP estática o usar algún servicio de DNS dinámico.

DNS dinámico

Como servicio de DNS dinámico usaremos Duck DNS4. Luego de ingresar con alguna de nuestras cuentas (la de GitHub en mi casi), crearemos un dominio con el botón add domain. Para actualizar nuestra IP en Duck DNS, instalaremos godns con

paru -S godns

La configuración se encuentra en /etc/conf.d/godns.json y deberá verse como sigue

{
  "provider": "DuckDNS",
  "password": "",
  "login_token": "<token>",
  "domains": [
    {
      "domain_name": "www.duckdns.org",
      "sub_domains": [ "<dominio>" ]
    }
  ],
  "ip_urls": [
    "https://api.ipify.org",
    "https://myip.biturl.top",
    "https://ip4.seeip.org",
    "https://ipecho.net/plain",
    "https://api-ipv4.ip.sb/ip",
    "https://api.ip.sb/ip"
  ],
  "ip_type": "IPv4",
  "interval": 300,
  "resolver": "8.8.8.8",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
  "ip_interface": "eth0",
  "socks5_proxy": "",
  "use_proxy": false,
  "debug_info": false,
}

Donde <token> lo podemos encontrar en la página de inicio de Duck DNS una vez ingresamos a nuestra cuenta. Finalmente, iniciamos el servicio con

sudo systemctl enable godns.service
sudo systemctl start godns.service

Redireccionamiento de puertos

Con lo anterior, hicimos que un dominio web dirija a la IP de nuestra red, pero nuestro router bloqueará las conexiones. Ahora debemos hacer que el tráfico se redirija a nuestra Raspberry. Para ello, buscaremos la configuración de puertos del panel de administración del router y agregaremos reglas para los puertos 80 (HTTP) y 443 (HTTPS).

Proxy inverso

Si nos fijamos en el paso anterior, solo abrimos los puertos 80 y 443 hacia el exterior, pero Navidrome estaba usando el puerto 4533. Para remediar esto, y evitarnos tener que estar abriendo puertos cada vez que instalamos un servicio nuevo, usaremos un proxy inverso. En general, la mayoría de los servidores web, como Apache, Nginx y otros, tienen la posibilidad de usarse como proxy inverso, pero en este caso usaremos Caddy5, pues es ligero y viene con la configuración de certificados para HTTPS incorporada. Para instalarlo, ejecutaremos

sudo pacman -S caddy

Al archivo de configuración /etc/caddy/Caddyfile agregaremos la siguiente sección

navidrome.<dominio>.duckdns.org:443 {
	reverse_proxy localhost:4533
}

Finalmente, iniciaremos el servicio con

sudo systemctl enable caddy.service
sudo systemctl start caddy.service

Como última medida de seguridad, haremos los siguiente cambios en en el archivo /etc/navidrome/navidrome.toml, para que Navidrome solo se pueda acceder a través de la dirección que elegimos arriba:

- # Address = "localhost"
+ Address = "localhost"

Una vez editado el archivo, reiniciaremos el servicio de Navidrome con

sudo systemctl restart navidrome.service

Servidor de notas: Joplin

Para instalar e iniciar el servidor de Joplin6 necesitaremos docker y docker-compose, los que instalaremos con

sudo pacman -S docker docker-compose

Además, agregaremos a nuestro usuario al grupo docker

sudo usermod -a -G docker <usuario>

e iniciaremos el servicio con

sudo systemctl enable docker.service
sudo systemctl start docker.service

Ahora, crearemos un directorio para almacenar el archivo de configuración

mkdir joplin-server
cd joplin-server

El archivo docker-compose.yaml se verá como sigue

version: '3'
services:
  db:
    restart: unless-stopped
    image: postgres:14
    ports:
      - "5432:5432"
    volumes:
      - /foo/bar/joplin-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=<contraseña>
      - POSTGRES_USER=joplin
      - POSTGRES_DB=joplin
  app:
    environment:
      - APP_BASE_URL=https://joplin.<dominio>.duckdns.org/
      - APP_PORT=22300
      - POSTGRES_PASSWORD=<contraseña>
      - POSTGRES_DATABASE=joplin
      - POSTGRES_USER=joplin
      - POSTGRES_PORT=5432
      - POSTGRES_HOST=db
      - DB_CLIENT=pg
    restart: unless-stopped
    image: etechonomy/joplin-server:latest
    ports:
      - "22300:22300"
    depends_on:
      - db

Para iniciar el servidor, ejecutaremos

docker compose up -d

También agregaremos la siguiente sección a /etc/caddy/Caddyfile

joplin.<dominio>.duckdns.org:443 {
	reverse_proxy localhost:22300
}

Ahora reiniciaremos Caddy para poder acceder al servidor de Joplin. Como primera acción, debemos ingresar con las credenciales admin@localhost y admin para cambiarlas inmediatamente y crear los usuarios para iniciar la sincronización de notas.