Mesa com notebook, monitor mostrando a biblioteca do Navidrome, fone de ouvido e o vinil Favourite Worst Nightmare do Arctic Monkeys

Introdução

Faz tempo que eu queria tirar minhas músicas de pastas espalhadas do meu notebook e ter algo parecido com um Spotify, só que meu. E eu finalmente resolvi tirar isso do papel.

A motivação é que, basicamente, os arquivos são meus, a maior parte vinda de download codes que acompanham alguma mídia física que comprei, e também o meu próprio catálogo do Bandcamp que muitas vezes não se encontra em nenhum streaming por aí, a qualidade sempre que possível é em FLAC (lossless) e nenhum disco ‘some’ da biblioteca por problemas entre a gravadora dos artistas e o serviço de streaming (cof, cof, Spotify).

Assim consigo ter tudo em um só lugar, disponível offline em casa (pelo endereço local) e também pela internet pelo meu celular ou computador em qualquer lugar do mundo.

Em troca, tem um pouco de esforço para a configuração, o que também chega a ser divertido, e é isso que eu conto aqui em passo a passo.

Por que o Navidrome?

Bom, o Navidrome é uma solução opensource que só lida com música. E só isso foi suficiente para mim. É o foco principal dele, e está sempre bem atualizado (aqui é possível acompanhar os releases: https://github.com/navidrome/navidrome/releases/). É um binário único em Go que roda bem tranquilo containerizado em uma VM pequena e lê as tags dos arquivos.

Também contou a favor ter uma interface web bonitinha e com cores personalizáveis e scrobble nativo pro Last.fm.

A infraestrutura

Em casa eu tenho um Proxmox rodando num nó chamado hyrule. Dentro dele, fiz uma VM com Debian 13. E dentro dela, fiz o setup do Navidrome utilizando Docker - escolhi rodar em Docker devido à facilidade e também porque a mesma VM será utilizada em projectos self-hosted futuramente.

Proxmox VE com a VM 104 (Media) rodando Debian 13

Deixei o /home em um volume separado por ser onde mora o acervo e ele pode (e deve) crescer sem lotar o disco do sistema.

Subindo o Navidrome

O Navidrome precisa de bem pouca coisa. Uma pasta gravável pro banco e o cache, que dentro do container é o /data, e a pasta de música em /music, que pode ser só leitura.

Coloquei também um SCANSCHEDULE de 1h, mas também é possível fazer isso de imediato pela webUI depois de logar.

O docker-compose.yml ficou assim:

services:
  navidrome:
    image: deluan/navidrome:latest
    user: 1000:1000
    ports:
      - "4533:4533"
    restart: unless-stopped
    environment:
      ND_LOGLEVEL: info
      ND_SCANSCHEDULE: 1h
    volumes:
      - "/opt/navidrome/data:/data"
      - "/home/giovanna/music:/music:ro"

(a pasta de dados tem que pertencer ao mesmo UID que está no user: do container, senão ele não consegue gravar o banco)

sudo mkdir -p /opt/navidrome/data
sudo chown 1000:1000 /opt/navidrome/data
cd /opt/navidrome && sudo docker compose up -d

Primeiro start do Navidrome 0.64.2 criando o banco

docker ps com o container de pé na porta 4533

Com o container no ar, é só abrir http://media.home:4533 e criar as credenciais de admin. Depois disso, é por esta tela que se entra:

Tela de login do Navidrome na rede local, com a URL destacada e as credenciais ocultas

Organizando o acervo

Vale saber que o Navidrome monta a biblioteca pelas tags dos arquivos, e não pelos nomes das pastas. Então a arrumação começou pelas tags, o que demanda um pouco de trabalho manual se você, assim como eu, preza por uma biblioteca bem organizadinha com Artista, Ano, Capa, tudo nos conformes.

Para isso é possível tanto usar python com a lib mutagen que edita tags de MP3, FLAC e outros formatos, ou alguma solução como o Puddletag.

Artista/Álbum (Ano)/NN - Título.ext
Artista/Álbum (Ano)/D-NN - Título.ext   (álbuns com mais de um disco)
Artista/Álbum (Ano)/cover.jpg

Pra mandar tudo pro servidor usei tar por ssh.

cd organizado && tar cf - . | ssh media.home 'tar xf - -C ~/music'

Streaming já funcionando:

Álbum organizado no Navidrome, com ano, capa e faixas em FLAC

Fiscalização Last.fm

Eu uso o Last há anos e não queria deixar de ‘scrobblar’ ouvindo essa biblioteca, afinal, a fiscalização Last.fm nunca deixa de trabalhar.

O Navidrome suporta a conexão com o Last.fm usando de uma conta de API que pode ser feita de forma bem simples pelo perfil da plataforma.

Scrobble desativado porque a chave de API não foi configurada

Conta de API criada no Last.fm, com a API key e o shared secret ocultos

Depois de habilitar no meu perfil, usei um .env com as credenciais da API para não soltar no compose:

# /opt/navidrome/.env  (chmod 600)
ND_LASTFM_APIKEY=
ND_LASTFM_SECRET=

No compose do Navidrome basta habilitar o Last e apontar para o arquivo com as keys.

    environment:
      ND_LASTFM_ENABLED: "true"
    env_file:
      - .env

Depois de reiniciar o container, é só voltar em Personal, ligar Scrobble to Last.fm e autorizar o Navidrome na janelinha do Last.fm. Essa configuração fica então habilitada em minha conta do Navi e linkada ao meu Last.

Last.fm vinculado e scrobble ativado na tela Personal do Navidrome

(1) Last.fm vinculado e (2) scrobble ativado.

Então só validei no web player do Navidrome o scrobble em tempo real lá no meu Last.fm:

Navidrome tocando Roy's Our Boy, do Charmer, ao lado do Last.fm mostrando a mesma faixa como Scrobbling now

(1) Tocando no Navidrome e (2) aparecendo no Last.fm como “Scrobbling now”.

Acesso de fora de casa

Eu queria ouvir minhas músicas também pela internet mas estando atrás de um NAT. A solução foi juntar o Tailscale com um domínio que eu já tinha no Cloudflare.

Hoje o Navidrome responde em https://music.noiseops.net com certificado válido, mas só para os meus dispositivos cadastrados no Tailscale. :)

Abaixo coloco um desenho para explicar melhor essa parte:

Diagrama do acesso remoto: celular ou notebook com Tailscale acessa o Caddy na VM media, que repassa para o Navidrome; o Cloudflare DNS resolve o nome e o Caddy obtém o certificado do Let's Encrypt

O truque é que o nome aponta pro IP do servidor dentro do Tailscale, um endereço que só existe na minha tailnet. Para conseguir o certificado sem expor nada, o Caddy prova ao Let’s Encrypt que o domínio é meu criando um registro TXT pela API do Cloudflare. É o chamado desafio DNS-01, e ele dispensa qualquer acesso vindo da internet.

Tailscale na VM

A instalação é um comando só, e o tailscale up mostra um link pra aprovar a máquina na sua conta.

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

Cloudflare

No painel do Cloudflare criei um token de API a partir do modelo Edit zone DNS, liberado só pra zona do meu domínio noiseops.net. Depois adicionei um registro A chamado music apontando pro IP do servidor no Tailscale, marcado como DNS only.

Registro A music no Cloudflare como DNS only, com o IP do Tailscale oculto

Caddy

O Caddy entrou no mesmo compose do Navidrome. A imagem oficial não vem com o módulo do Cloudflare, então precisei de um Dockerfile pequeno que compila o Caddy com ele usando o xcaddy.

# caddy/Dockerfile
FROM caddy:2-builder AS builder
RUN xcaddy build --with github.com/caddy-dns/cloudflare

FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

O Caddyfile inteiro tem oito linhas.

music.noiseops.net {
	tls {
		dns cloudflare {env.CF_API_TOKEN}
		resolvers 1.1.1.1
	}
	encode zstd gzip
	reverse_proxy navidrome:4533
}

E este é o serviço novo no compose.

  caddy:
    build: ./caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    env_file:
      - .caddy.env # CF_API_TOKEN, chmod 600
    volumes:
      - "./Caddyfile:/etc/caddy/Caddyfile:ro"
      - "./caddy/data:/data"
      - "./caddy/config:/config"
    depends_on:
      - navidrome

O token do Cloudflare ficou num .caddy.env separado do .env do Last.fm. Assim cada container recebe só o conteúdo de que precisa. A renovação do certificado o Caddy faz sozinho.

Tela de login do Navidrome em music.noiseops.net, com HTTPS e a URL destacada

Conclusão

No fim, a parte que deu mais trabalho não foi o Docker nem o proxy, mas sim organizar as tags de todas as músicas, para poder dormir em paz.

Vale a pena gastar esse tempo antes de subir as músicas, porque é isso que define se a biblioteca vai parecer organizada ou não. O resto são dois arquivos de configuração, alguns comandos no terminal e muuuita documentação.

Como o Caddy já está rodando, colocar outro serviço no mesmo servidor agora é também mais fácil. É só subir o container, criar mais um bloco no Caddyfile e mais um registro no Cloudflare. O próximo da fila provavelmente vai ser uma biblioteca de livros, ou talvez um servidor de fotos. :D