MiniDLNA Manager: um app para levar os filmes do PC para a TV da sala
Como uma maratona da Marvel virou um aplicativo GTK4 para gerenciar o MiniDLNA no Linux, com helper privilegiado via Polkit, ACLs, override do systemd e compatibilidade com libadwaita 1.1.
- python
- linux
- gtk
- projetos
Há uns dois meses eu e minha esposa entramos em uma saga: assistir a todos os filmes e séries da Marvel, em ordem, até o filme que estreia em dezembro de 2026. Ele vai marcar o começo do reboot do MCU, e a ideia é chegar lá com tudo em dia.
Temos assinatura do Disney+, mas o plano tem propaganda demais, e maratona com intervalo comercial perde a graça rápido. Então voltei aos métodos arcaicos e baixei os episódios por torrent. Aí apareceu o problema de verdade: os arquivos estavam no meu PC, e a gente assiste na TV da sala.
Servindo mídia pela rede com DLNA
A TV já sabe ler conteúdo da rede local por DLNA, um padrão antigo e ainda muito suportado. Basta ter um servidor de mídia no PC anunciando os arquivos, e a TV encontra sozinha. Um dos servidores mais simples para isso no Linux é o MiniDLNA (também chamado de ReadyMedia): você aponta as pastas em um arquivo de configuração, sobe o serviço e pronto.
Isso é a teoria. Configurar o MiniDLNA na mão significa editar o /etc/minidlna.conf como root, reiniciar o serviço pelo systemctl e ler o log pelo journalctl para descobrir por que a TV mostra as pastas vazias. E a resposta costuma estar em dois detalhes:
- O
minidlnadroda com um usuário próprio, ominidlna. Uma pasta dentro do seu/homenormalmente tem permissão 700 ou 750, então esse usuário não consegue nem entrar nela. - O serviço do systemd vem com
ProtectHomeligado, que esconde o/homedo processo por completo. Enquanto isso estiver ativo, nenhuma permissão nos arquivos resolve.
Fazer isso uma vez é aceitável, mas trocar de pasta ou descobrir por que a TV não achava o servidor significava repetir o ritual no terminal. Então escrevi uma interface para ele.
O MiniDLNA Manager
O MiniDLNA Manager é um aplicativo desktop para Linux, escrito em Python com GTK4 e libadwaita, que cuida do MiniDLNA inteiro por uma janela com três abas.
A aba de status mostra se o serviço está ativo, tem os botões de iniciar, parar e reiniciar, um interruptor para iniciar junto com o sistema e as últimas linhas do log:

A aba de configuração troca a edição do minidlna.conf por um formulário: nome do servidor, porta, interface de rede, nível de log por categoria e os diretórios de mídia, escolhidos pelo seletor de pastas do sistema. Tudo é validado antes de ser gravado (a porta, por exemplo, precisa estar livre), e depois de salvar o app oferece reiniciar o serviço para aplicar a mudança:

A aba de dispositivos mostra quantos arquivos de áudio, vídeo e imagem o servidor indexou e quais aparelhos estão conectados:

Se o MiniDLNA nem estiver instalado, o app detecta e oferece instalar pela própria interface, usando o gerenciador de pacotes da distribuição (apt, dnf, pacman ou zypper).
Como ele funciona por dentro
A interface nunca roda como root
Quase tudo que o app faz precisa de privilégio: mexer no systemctl, gravar em /etc e instalar pacotes. O caminho fácil seria rodar a janela inteira com sudo, e foi exatamente o que eu quis evitar. Uma interface gráfica é muito código para rodar como root.
A solução foi separar um helper pequeno, que é o único trecho que roda com privilégio. A interface chama esse helper pelo pkexec, e o Polkit pede a senha de administrador antes de deixar ele executar. O helper aceita só uma lista fixa de subcomandos (start, stop, restart, enable, disable, write-config, install-package e ensure-home-access) e nunca executa um comando vindo de fora. Até o caminho do arquivo de configuração é fixo no código, em vez de chegar como argumento.
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="minidlna-manager-helper")
subparsers = parser.add_subparsers(dest="action", required=True)
subparsers.add_parser("is-installed")
subparsers.add_parser("install-package")
subparsers.add_parser("write-config")
subparsers.add_parser("ensure-home-access")
for action in ("start", "stop", "restart", "enable", "disable"):
subparsers.add_parser(action)
return parser
A regra do Polkit usa auth_admin_keep, então a senha é pedida uma vez e vale por alguns minutos, em vez de aparecer a cada clique. Consultar o estado do serviço e ler o log não precisa de privilégio nenhum, e por isso essas partes nem passam pelo helper.
A configuração é gravada em um arquivo temporário na mesma pasta e depois trocada de lugar com os.replace. Se algo falhar no meio, o minidlna.conf antigo continua intacto. O arquivo novo também herda a permissão do anterior, porque o mkstemp cria arquivos com 0600 e o minidlnad, rodando com o próprio usuário, deixaria de conseguir ler a configuração.
Liberando as pastas do /home
Os dois obstáculos do começo do post são resolvidos em partes separadas.
Para o ProtectHome, o helper cria um drop-in do systemd, que é a forma padrão de ajustar um serviço sem editar o arquivo que veio no pacote. Ele troca a proteção total por somente leitura, então o daemon passa a enxergar o /home, mas continua sem poder escrever nele:
# /etc/systemd/system/minidlna.service.d/minidlna-manager-protecthome.conf
[Service]
ProtectHome=read-only
Para as permissões, o app usa ACLs em vez de abrir as pastas para todo mundo. Quando você escolhe um diretório de mídia, ele dá ao usuário minidlna permissão de atravessar cada pasta do caminho até ali e permissão de leitura na pasta escolhida, incluindo os arquivos que chegarem depois:
setfacl -m u:minidlna:x /home/usuario # e cada pasta intermediária
setfacl -R -m u:minidlna:rx /home/usuario/Videos/Marvel
setfacl -R -d -m u:minidlna:rx /home/usuario/Videos/Marvel
Isso só vale para pastas dentro do seu próprio diretório pessoal e nunca mexe na permissão de “outros”. Como é o seu usuário ajustando os próprios arquivos, essa parte nem precisa de senha.
Descobrindo quem está conectado
O MiniDLNA não tem um comando que liste os aparelhos conectados. A única fonte dessa informação é uma página HTML de status que o próprio minidlnad serve em http://127.0.0.1:8200/status, com duas tabelas sem id nem classe. A aba de dispositivos faz um GET nessa página e lê as tabelas com o HTMLParser da biblioteca padrão, pela ordem em que aparecem: a primeira tem a contagem de arquivos, a segunda tem os clientes.
Rodando em distribuições mais antigas
Escrevi a interface contra o libadwaita atual, e a versão 0.2.0 simplesmente não abria no Ubuntu 22.04 e no Linux Mint 21, que vêm com GTK 4.6 e libadwaita 1.1. Vários widgets que eu usava, como Adw.EntryRow, Adw.SwitchRow e Gtk.FileDialog, só existem em versões mais novas.
A correção, na 0.2.1, foi um módulo de compatibilidade. Ele testa se cada widget existe, em vez de comparar números de versão, porque uma distribuição pode ter feito backport de um recurso. Quando o widget existe, o app usa o nativo; quando não existe, monta um equivalente com os componentes do libadwaita 1.0:
HAVE_ENTRY_ROW = hasattr(Adw, "EntryRow") # 1.2
HAVE_BANNER = hasattr(Adw, "Banner") # 1.3
HAVE_SWITCH_ROW = hasattr(Adw, "SwitchRow") # 1.4
HAVE_TOOLBAR_VIEW = hasattr(Adw, "ToolbarView") # 1.4
HAVE_FILE_DIALOG = hasattr(Gtk, "FileDialog") # GTK 4.10
Um teste de import não pega esse tipo de erro, porque o widget só é acessado quando a janela é montada. Por isso a CI instala o .deb e abre a janela de verdade em quatro imagens: Ubuntu 22.04, Debian 12, Ubuntu 24.04 e Debian 13. Elas cobrem desde o caso em que todo fallback está em uso até o caso em que tudo é nativo. Além disso, a CI roda o ruff, os 139 testes com pytest e o lintian no pacote.
Instalando
Os pacotes prontos ficam na página de releases. No Debian, Ubuntu e Mint:
sudo apt install ./minidlna-manager_*_all.deb
No Arch e no Manjaro:
sudo pacman -U minidlna-manager-*.pkg.tar.zst
O pacote .deb declara as versões mínimas de GTK e libadwaita, então o apt recusa a instalação em um sistema sem suporte em vez de instalar um app que não abre. Ainda não há Flatpak; para outras distribuições, o README explica como rodar a partir do código-fonte.
Enquanto isso, a maratona segue. O código é MIT e está no GitHub, e issues e sugestões são bem-vindas.