Linux

Roteador Linux DIY - Parte 6 - Nextcloud e Jellyfin

Esta é a sexta parte de uma série de várias partes que descreve como construir seu próprio roteador Linux.

Índice


Introdução

Nas partes anteriores, instalamos o sistema operacional, configuramos a funcionalidade de internet do gateway usando PPPoE e configuramos o Firewall e o Unbound como servidores DNS.
Agora é hora de expandir as capacidades desta máquina adicionando serviços como o Nextcloud e o Jellyfin.

Jellyfin, Nextcloud
Jellyfin e Nextcloud

O que é o Nextcloud

Existem muitos serviços de nuvem para armazenamento de arquivos na internet. No entanto, eles tendem a ser caros se você precisar de espaço de armazenamento, e há preocupações com a privacidade, como o uso do conteúdo armazenado para publicidade, por exemplo. Como o Nextcloud é uma solução de nuvem privada, você pode armazenar seus dados em qualquer lugar no seu armazenamento. Com o auxílio do aplicativo Nextcloud, você pode sincronizar arquivos, como vídeos e fotos, do seu celular para o Nextcloud.

O que é o Jellyfin

Existem muitos serviços de streaming de mídia sob demanda, como Netflix, Prime Video, Looke e assim por diante. Isso significa que há muitas contas para pagar. Há também a questão de algum conteúdo que você queria assistir desaparecer da plataforma. Isso ocorre porque você tem acesso ao conteúdo enquanto paga por ele, mas não é o proprietário do conteúdo em si. Eles podem ser removidos do catálogo quando o contrato de licença com o produtor termina.
Então, por que não ter seu próprio conteúdo e rodar seu próprio servidor de mídia sob demanda? O Jellyfin faz exatamente isso para você.


Configurando o Armazenamento

Tanto o Jellyfin quanto o Nextcloud armazenam e acessam arquivos. Poderíamos criar pastas para eles, mas é melhor configurar o armazenamento corretamente para fazer backup dos dados de forma adequada. O ZFS facilitou bastante a criação dos Datasets pretendidos para cada um deles.

Execute com sudo:

Supondo que o nome do pool de armazenamento de dados seja zdata.

ZDATA=zdata

Criando o Dataset para o Armazenamento do Nextcloud

zfs create ${ZDATA}/containers/podman/storage/volumes/nextcloud-html
zfs create ${ZDATA}/containers/podman/storage/volumes/nextcloud-db
chown -R podman:podman /mnt/${ZDATA}/containers/podman/storage/volumes/nextcloud-*

Criando Outro Dataset para Arquivos de Mídia

zfs create -o canmount=off -o mountpoint=/srv ${ZDATA}/srv
zfs create ${ZDATA}/srv/media

Ingress

Cada serviço roda em sua própria porta HTTP. Para disponibilizar esses serviços na Internet, o ideal é configurar um Serviço de Ingress. O Ingress é um proxy reverso NGINX para consolidar todos os serviços no protocolo HTTPS na porta 443. Se você quiser disponibilizar esses serviços na Internet, precisa ter um domínio FQDN e criar subdomínios nele, já que ter um endereço IPv4 público também é bom. Então, se você não tem um domínio, precisa comprar um para usá-lo. Está bem barato atualmente. Existem até opções gratuitas. Se você não tem um endereço IP publicamente disponível, pode usar um VPS na nuvem para atuar como um proxy e se conectar a você. A Oracle, por exemplo, oferece um VPS gratuito vitalício que você pode conferir, para configurar uma VPN Wireguard e configurar uma conexão entre seu VPS e seu Gateway. Há um artigo aqui sobre Wireguard

Configurando Subdomínios

No painel de administração do domínio, você deve adicionar duas entradas DNS para seu IPv4 (entrada A) com seu endereço IP público. O nextcloud.exemplo.com e o jellyfin.exemplo.com, sendo exemplo.com seu FDQN. Se você não tem um IP fixo, mas sim um IP que muda entre conexões, pode usar o CloudDNS que oferece um daemon para atualizar as entradas DNS dinamicamente quando o IP mudar.

Rede Podman para Ingress

Assim como o Nextcloud e o Jellyfin, nosso Ingress viverá em um Pod do Podman. O Ingress precisa ser capaz de se comunicar com os pods do Nextcloud e do Jellyfin. Então, vamos criar uma rede para eles.

Execute como usuário podman:

podman network create ingress-net

Pod de Ingress

Configuração para o pod NGINX do Podman atuar como nosso serviço de Ingress.

  1. Crie o volume ingress-conf:
podman volume create ingress-conf
  1. Crie uma configuração básica para o NGINX: /mnt/zdata/containers/podman/storage/volumes/ingress-conf/_data/default_server.conf
server {
    listen 80 default_server;
    server_name _;

    location ~ /.well-known/acme-challenge/ {
      root /var/www/;
    }
}
  1. Crie o arquivo de implantação do ingress: /home/podman/deployments/ingress.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: ingress
  name: ingress
spec:
  networks:
    - name: ingress-net
  containers:
    - name: nginx
      image: docker.io/library/nginx:1.27.2-alpine
      ports:
      - containerPort: 80
        hostPort: 1080
      - containerPort: 443
        hostPort: 1443
      volumeMounts:
      - mountPath: /etc/localtime
        name: etc-localtime-host
      - mountPath: /etc/nginx/conf.d
        name: ingress-conf-pvc
      - mountPath: /var/www
        name: ingress-www-pvc
      - mountPath: /etc/letsencrypt 
        name: certificates-pvc
  restartPolicy: Always
  volumes:
  - name: etc-localtime-host
    hostPath:
      path: /etc/localtime
      type: File
  - name: ingress-conf-pvc
    persistentVolumeClaim:
      claimName: ingress-conf
  - name: ingress-www-pvc
    persistentVolumeClaim:
      claimName: ingress-www
  - name: certificates-pvc
    persistentVolumeClaim:
      claimName: certificates
  1. Inicie o Pod de Ingress:
podman kube play --log-level info --network ingress-net --replace /home/podman/deployments/ingress.yaml 
  1. Habilite seu arquivo de serviço systemd:
systemctl --user enable [email protected] --now

O pod Ingress cria volumes adicionais, como ingress-www e certificates, que serão usados para validar os Certificados SSL, a serem criados no próximo passo. Você pode verificar sua criação executando podman volume list.


Firewall

Como o pod Ingress é executado sem privilégios de root, ele não pode abrir portas abaixo de 1024. Como HTTP e HTTPS estão abaixo desse valor, o serviço de ingress será configurado para abrir as portas 1080 e 1443, e redirecionar o tráfego de entrada das portas 80 e 443 para 1080 e 1443, respectivamente.

Adicione essas cadeias e regras para o Ingress conforme necessário.

/etc/nixos/nftables/services.nft

...
  chain ingress_input {
    tcp dport 1080 ct state { new, established } counter accept comment "Ingress HTTP"
    tcp dport 1443 ct state { new, established } counter accept comment "Ingress HTTPS"
  }
...

/etc/nixos/nftables/zones.nft

  chain LAN_INPUT {
    jump ingress_input
    ...
  }
  ...
  chain WAN_INPUT {
    jump ingress_input
    ...
  }

/etc/nixos/nftables/nat_chains.nft

  ...
  chain ingress_redirect {
    ip daddr { $ip_lan, $ip_guest, $ip_iot } tcp dport  80 redirect to 1080
    ip daddr { $ip_lan, $ip_guest, $ip_iot } tcp dport 443 redirect to 1443
  }

  chain ingress_redirect_wan {
    tcp dport  80 redirect to 1080
    tcp dport 443 redirect to 1443
  }
  ...

/etc/nixos/nftables/nat_zones.nft

  chain LAN_PREROUTING {
    jump ingress_redirect
    ...
  }
  ...
  chain WAN_PREROUTING {
    jump ingress_redirect_wan
  }

Reconstruir a configuração do NixOS

nixos-rebuild switch

Let's Encrypt

O Let's Encrypt é um serviço gratuito que fornece Certificados SSL. Ele utiliza uma ferramenta chamada certbot para renovar nossos certificados.

Esses certificados expiram em um curto período. Portanto, ter uma unidade systemd para renovar o serviço mensalmente impede que seus domínios tenham seus certificados expirados. Substitua a lista DOMAINS pelos seus domínios, e EMAIL pelo seu endereço de e-mail.

  1. Crie a unidade systemd: /home/podman/.config/systemd/user/certbot.service
Description=Renovação do Lets encrypt com Certbot
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
Environment="DOMAINS=unifi.example.com,nextcloud.example.com,jellyfin.example.com"
Environment="[email protected]"
ExecStart=/bin/sh -c '/run/current-system/sw/bin/podman run --rm \
  -v ingress-www:/var/www \
  -v certificates:/etc/letsencrypt \
  --log-level info \
  docker.io/certbot/certbot:v3.0.0 \
  certonly --agree-tos --non-interactive -v \
  --webroot -w /var/www --force-renewal \
  --email ${EMAIL} \
  --domains ${DOMAINS} && systemctl --user restart [email protected]'
  1. Crie uma unidade timer: /home/podman/.config/systemd/user/certbot.timer

Este timer irá disparar o evento de renovação uma vez por mês.

[Unit]
Description=Renovar certificados usando certbot mensalmente.

[Timer]
OnCalendar=monthly
Persistent=true

[Install]
WantedBy=timers.target
  1. Habilite e inicie o certbot.service:

Verifique os logs para ver se o registro foi bem-sucedido.

systemctl --user daemon-reload
systemctl --user enable certbot.timer
systemctl --user start certbot.service
journalctl --user -eu certbot.service
...
Certificado recebido com sucesso.
O certificado está salvo em: /etc/letsencrypt/live/example.com/fullchain.pem
A chave está salva em:         /etc/letsencrypt/live/example.com/privkey.pem
Este certificado expira em 2025-02-10.
PRÓXIMOS PASSOS:
- O certificado precisará ser renovado antes de expirar. O Certbot pode renovar automaticamente o certificado em segundo plano, mas você pode precisar tomar medidas para habilitar essa funcionalidade. Veja https://certbot.org/renewal-setup para instruções.
  1. Atualize a configuração do Ingress:

Use o caminho de configuração fornecido pela saída do serviço certbot.

/mnt/zdata/containers/podman/storage/volumes/ingress-conf/_data/default_server.conf

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 

server {
    listen 80 default_server;
    server_name _;

    location / {
        return 301 https://$host$request_uri;
    }

    location ~ /.well-known/acme-challenge/ {
      root /var/www/;
    }
}
  1. Reinicie o pod ingress:
systemctl --user restart [email protected]

Nextcloud

Agora que temos o Ingress pronto, podemos começar a criar o serviço Nextcloud.

Segredos

Crie um segredo para o serviço Nextcloud. Este segredo será usado para armazenar a senha do banco de dados do Nextcloud. Utilize o mesmo script que fizemos para o Unifi Network anteriormente.

  1. Crie o arquivo de segredos:
cd /home/podman/deployments/
export MARIADB_ROOT_PASSWORD="$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;)"
export MYSQL_PASSWORD="$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;)"

cat << EOF > nextcloud-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: nextcloud-secret
data:
  mariadbRootPassword: $(echo -n ${MARIADB_ROOT_PASSWORD} | base64)
  mysqlPassword: $(echo -n ${MYSQL_PASSWORD} | base64)
EOF

echo "Arquivo de segredo criado com o nome nextcloud-secret.yaml"
  1. Implante o arquivo de segredos criado:
podman kube play /home/podman/deployments/nextcloud-secret.yaml
  1. Verifique o segredo recém-criado:
podman secret list
ID                         NAME               DRIVER      CREATED             UPDATED
b22f3338bbdcec1ecd2044933  nextcloud-secret   file        Há um minuto atrás  Há um minuto atrás
  1. Exclua o arquivo secret.yaml:

É uma boa prática excluir o arquivo de segredo após a implantação. Esteja ciente de que você não poderá recuperar seu conteúdo no futuro.

rm -f /home/podman/deployments/nextcloud-secret.yaml

YAML para o Nextcloud

Crie o arquivo yaml para implantar o Nextcloud no Podman

/home/podman/deployments/nextcloud.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nextcloud
  name: nextcloud

spec:
  restartPolicy: Always
  containers:
    - image: docker.io/nextcloud:28.0.4
      name: server
      resources:
        limits:
          memory: 300Mi
          ephemeral-storage: 1000Mi
        requests:
          cpu: 20.0
          memory: 50Mi
          ephemeral-storage: 50Mi
      volumeMounts:
      - mountPath: /var/www/html
        name: nextcloud-html-pvc
      env:
      - name: MYSQL_DATABASE
        value: nextcloud
      - name: MYSQL_HOST
        value: nextcloud-db
      - name: MYSQL_USER
        value: nextcloud
      - name: MYSQL_PASSWORD
        valueFrom:
          secretKeyRef:
            name: nextcloud-secret
            key: mysqlPassword

    - image: docker.io/mariadb:11.5.2
      name: db
      resources:
        limits:
          memory: 500Mi
          ephemeral-storage: 500Mi
        requests:
          cpu: 1.0
          memory: 100Mi
          ephemeral-storage: 100Mi
      volumeMounts:
      - mountPath: /var/lib/mysql
        name: nextcloud-db-pvc
      env:
      - name: MYSQL_DATABASE
        value: nextcloud
      - name: MYSQL_USER
        value: nextcloud
      - name: MYSQL_PASSWORD
        valueFrom:
          secretKeyRef:
            name: nextcloud-secret
            key: mysqlPassword
      - name: MARIADB_ROOT_PASSWORD
        valueFrom:
          secretKeyRef:
            name: nextcloud-secret
            key: mariadbRootPassword

  volumes:
  - name: nextcloud-html-pvc
    persistentVolumeClaim:
      claimName: nextcloud-html
  - name: nextcloud-db-pvc
    persistentVolumeClaim:
      claimName: nextcloud-db

Este arquivo yaml criará um serviço Nextcloud com um banco de dados MariaDB.

Os volumes nextcloud-data e nextcloud-html são colocados nos datasets criados no início deste artigo.

Iniciar o Pod do Nextcloud

Como fizemos para o Ingress, inicie o pod com o seguinte comando:

podman kube play --log-level info --network ingress-net --replace /home/podman/deployments/nextcloud.yaml 

Habilite o serviço systemd do Nextcloud:

systemctl --user enable --now [email protected]

Certainly! Here's the translation of the last part of your article into Brazilian Portuguese:


Jellyfin

Crie o arquivo jellyfin.yaml com o seguinte conteúdo:

/home/podman/deployments/jellyfin.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: jellyfin
  name: jellyfin
spec:
  restartPolicy: Always
  containers:
    - image: docker.io/jellyfin/jellyfin:10.9.1
      name: jellyfin
      resources:
        limits:
          memory: 500Mi
          ephemeral-storage: 500Mi
        requests:
          cpu: 1.0
          memory: 100Mi
          ephemeral-storage: 100Mi
      volumeMounts:
        - mountPath: /config
          name: jellyfin-config-pvc
        - mountPath: /cache
          name: jellyfin-cache-pvc
        - mountPath: /media
          name: srv-media-host
  volumes:
    - name: jellyfin-config-pvc
      persistentVolumeClaim:
        claimName: jellyfin-config
    - name: jellyfin-cache-pvc
      persistentVolumeClaim:
        claimName: jellyfin-cache
    - name: srv-media-host
      hostPath:
        path: /srv/media

Inicie o Pod do JellyFin e habilite seu serviço systemd:

podman kube play --log-level info --network ingress-net --replace /home/podman/deployments/jellyfin.yaml 

Habilite o serviço systemd:

systemctl --user enable --now [email protected]

Configurar Ingresses

Nossos serviços estão em execução. Vamos configurar os Ingresses para os seguintes subdomínios:

  • Nextcloud: nextcloud.example.com.
  • Jellyfin: jellyfin.example.com.

1. Crie o arquivo de configuração do **Nextcloud**

/mnt/zdata/containers/podman/storage/volumes/ingress-conf/_data/nextcloud.conf

server {
  set $upstream http://nextcloud;
  listen 443 ssl;
  server_name nextcloud.example.com;
  root /var/www/html;
  client_max_body_size 10G;
  client_body_buffer_size 400M;
  location / {
    proxy_pass $upstream;
  }
}
  • client_max_body_size: Esta diretiva define o tamanho máximo permitido do corpo da requisição do cliente. Definimos como 10GB para permitir o upload de arquivos grandes.
  • client_body_buffer_size: Esta diretiva define o tamanho do buffer para leitura do corpo da requisição. Definimos como 400MB para permitir o upload de arquivos grandes.

2. Crie o arquivo de configuração do **Jellyfin**

/mnt/zdata/containers/podman/storage/volumes/ingress-conf/_data/jellyfin.conf

server {
  set $upstream http://jellyfin:8096;
  listen 443 ssl;
  server_name jellyfin.example.com;
  location / {
    proxy_pass $upstream;
  }
}

3. Crie um arquivo de configuração para o Unifi Network

Como já temos o Unifi Network Application configurado no servidor, podemos criar um Ingress para ele.

/mnt/zdata/containers/podman/storage/volumes/ingress-conf/_data/unifi.conf

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}
server {
  listen 443 ssl;
  server_name unifi.example.com;
  set $upstream unifi:8443;

  location / {
    proxy_pass     https://$upstream;
    proxy_redirect https://$upstream https://$server_name;

    proxy_cache off;
    proxy_store off;
    proxy_buffering off;
    proxy_http_version 1.1;
    proxy_read_timeout 36000s;

    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Referer "";

    client_max_body_size 0;
  }
}

Você pode opcionalmente remover a porta de encaminhamento para 8443/tcp do arquivo yaml do pod. Para fazer isso, basta remover as seguintes linhas:

/home/podman/deployments/unifi.yaml

...
spec:
  enableServiceLinks: false
  restartPolicy: Always
  containers:
  ...
  ports:
  ...
  # Remova estas linhas:
  - containerPort: 8443
      hostPort: 8443
      protocol: TCP
  ...

Reimplante o Unifi Network Application adicionando-o à rede ingress-net como fizemos com os outros Pods.

/home/podman/.config/systemd/user/podman-unifi.service

podman kube play --log-level info --network ingress-net --replace /home/podman/deployments/unifi.yaml 

4. Configure o resolvedor

Para que o NGINX alcance os serviços, é necessário configurar um resolvedor. Para fazer isso, siga os passos abaixo:

  1. Verifique a configuração do gateway da ingress-net digitando:
podman network inspect ingress-net \
  --format 'Gateway: {{ range .Subnets }}{{.Gateway}}{{end}}'
Gateway: 10.89.1.1
  1. Crie o resolvedor com o Endereço IP obtido:

/mnt/zdata/containers/podman/storage/volumes/ingress-conf/_data/resolver.conf

resolver 10.89.1.1 valid=30s;

6. Configure o Unbound para resolver os nomes de host localmente

Meu domínio está configurado no Cloudflare. Para resolver meus DNS locais, eu precisaria recuperar as entradas DNS do Cloudflare e acessar esses serviços via meu IP Público pela Internet. Isso não é necessário, pois posso resolver os endereços localmente. Para fazer isso, vamos atualizar a configuração do Unbound para resolver esses endereços localmente editando o local.conf

/mnt/zdata/containers/podman/storage/volumes/unbound-conf/_data/local.conf

server:
  ...
  # Adicione as linhas abaixo. Deixe o restante como está.
  local-data: "unifi.example.com. IN A 10.1.78.1"
  local-data: "nextcloud.example.com. IN A 10.1.78.1"
  local-data: "jellyfin.example.com. IN A 10.1.78.1"

Reinicie o Ingress:

systemctl --user restart [email protected]

Conclusão

Agora que temos nossos serviços em execução, podemos acessá-los a partir do nosso navegador. Podemos acessar o Nextcloud em nextcloud.example.com e o Jellyfin em jellyfin.example.com. Configure os serviços, crie contas e comece a usá-los.
No próximo post, instalaremos Servidores de Arquivos e configuraremos a interface web do Cockpit para gerenciar nossos serviços.

keywords: macmini • roteador • linux • nixos • pppoe • unbound • podman • docker

This article in other languages