11 KiB
Transcoder para Lidarr — AGENTS.md
Visão Geral
Aplicação Go em Docker que converte álbuns FLAC para MP3 320 kbps CBR. Pode ser acionada automaticamente por webhooks do Lidarr ou manualmente pela interface web.
Stack
| Camada | Escolha |
|---|---|
| Linguagem | Go 1.25.x |
| HTTP Router | chi (go-chi/chi/v5) |
| BD | SQLite (modernc.org/sqlite) |
| Templates | html/template + htmx |
| CSS | Tailwind CSS v4 + DaisyUI |
| Logging | log/slog (stdlib) + handler SQLite |
| IDs | github.com/google/uuid |
| Audio | ffmpeg + ffprobe |
| Distribuição | Docker (Alpine Linux) |
Estrutura de Diretórios
cmd/
server/ # entrypoint
internal/
config/ # carrega env, merge com SQLite overrides
scheduler/ # work window gate
webhook/ # handler do webhook Lidarr
queue/ # job queue (channels)
worker/ # worker pool
transcoder/ # wrapper ffmpeg
validator/ # validação pós-conversão (ffprobe)
lidarr/ # cliente API REST do Lidarr
logger/ # logs estruturados → SQLite
web/ # handlers + templates da interface web
models/ # tipos compartilhados (Job, Log, Config)
store/ # repositórios SQLite + migrations
scanner/ # library scanner (navegação + scan recursivo)
ntfy/ # cliente de notificações ntfy
web/
styles/ # input.css + bundles DaisyUI (fonte)
static/ # app.css (gerado), htmx.min.js
templates/ # templates HTML (embutidos via //go:embed)
data/ # volume Docker para o SQLite
Arquitetura (Fluxo)
Lidarr (webhook) ──► HTTP Server ──┐
├──► Job Queue ──► Scheduler ──► Worker Pool ──► ffmpeg ──► ffprobe ──► Lidarr API
Web UI (manual) ──► Scanner ─────┘ │
└──► SQLite (jobs + logs + config)
Configuração
Variáveis de ambiente (defaults). Sobrescritas por overrides no SQLite via /settings na web.
| Variável | Default | Descrição |
|---|---|---|
PORT |
8080 |
Porta do servidor HTTP |
WORKERS |
4 |
Workers simultâneos |
FFMPEG_BIN |
/usr/bin/ffmpeg |
Caminho do ffmpeg |
FFPROBE_BIN |
/usr/bin/ffprobe |
Caminho do ffprobe |
LOG_LEVEL |
INFO |
Nível de log |
DRY_RUN |
false |
Modo dry run (não converte, não apaga) |
LIDARR_URL |
— | URL base do Lidarr |
LIDARR_API_KEY |
— | API key do Lidarr |
WEBHOOK_SECRET |
— | Segredo para validar webhooks |
MUSIC_PATH |
/music |
Diretório com os arquivos de música |
DB_PATH |
/data/coda.db |
Caminho do arquivo SQLite |
TIMEZONE |
UTC |
Timezone para janela de trabalho |
WORK_WINDOW_ENABLED |
false |
Habilita janela de trabalho |
WORK_WINDOW_START |
08:00 |
Início da janela (HH:MM) |
WORK_WINDOW_END |
18:00 |
Fim da janela (HH:MM) |
NTFY_ENABLED |
false |
Habilita notificações ntfy |
NTFY_URL |
— | URL base do servidor ntfy |
NTFY_TOPIC |
— | Tópico para publicar notificações |
NTFY_TOKEN |
— | Token de autenticação (opcional) |
Componentes Principais
Store (internal/store/)
Repositórios SQLite para jobs, logs e config. Migrações automáticas no startup.
store.New(dbPath)→ inicializa BD, roda migrationsstore.JobRepository— CRUD + consulta de tarefasstore.LogRepository— escrita e consulta paginada de logsstore.ConfigRepository— persistência de overrides
Scheduler (internal/scheduler/)
Gate que libera ou retém tarefas conforme a janela de trabalho.
- Se desabilitado, passa tudo direto.
- Se habilitado, só despacha dentro de
[WORK_WINDOW_START, WORK_WINDOW_END]no timezone configurado. - Tarefa em andamento quando a janela fecha: termina normalmente, as próximas aguardam.
Config Service (internal/config/)
- Carrega env no startup.
- Merge com overrides do SQLite (SQLite vence).
- Expõe
Get()eSet(key, value)em runtime. - Propaga mudanças para componentes afetados.
Scanner (internal/scanner/)
- Navegação por árvore de diretórios sob
MUSIC_PATH. - Scan recursivo: detecta FLACs, agrupa por diretório (álbum), retorna lista.
- Endpoints REST usados pela página
/library.
Ntfy (internal/ntfy/)
- Cliente de notificações para servidor ntfy (self-hosted ou público).
- Envia notificação de sucesso/falha ao final de cada job.
- Respeita
DRY_RUN; token opcional viaAuthorization: Bearer.
Web UI (internal/web/)
Páginas:
/— dashboard: status, fila, workers, janela de trabalho/jobs— tabela de tarefas (data, artista, álbum, duração, status, origem)/logs— visualização de logs (do SQLite)/library— navegação + scan de FLACs, botão de conversão/settings— edição de configuração/health— health check
Banco de Dados (SQLite)
Tabelas principais (criadas por migrations automáticas):
CREATE TABLE jobs (
id TEXT PRIMARY KEY,
artist TEXT,
album TEXT,
path TEXT,
status TEXT, -- received / processing / done / error
source TEXT, -- webhook / manual
created_at DATETIME,
finished_at DATETIME,
duration_ms INTEGER,
error_msg TEXT
);
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_id TEXT,
level TEXT,
message TEXT,
timestamp DATETIME
);
CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT
);
Docker
# Stage 1: generate CSS + fetch static assets (Tailwind + DaisyUI + htmx, no Node)
FROM alpine:3.20 AS assets
RUN apk add --no-cache curl
WORKDIR /src
ARG TAILWIND_VERSION=v4.1.14
ARG HTMX_VERSION=2.0.4
COPY web/ web/
RUN mkdir -p tools web/static \
&& curl -sLo tools/tailwindcss https://github.com/tailwindlabs/tailwindcss/releases/download/${TAILWIND_VERSION}/tailwindcss-linux-x64-musl \
&& chmod +x tools/tailwindcss \
&& curl -sLo tools/daisyui.mjs https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.mjs \
&& curl -sLo web/static/htmx.min.js https://unpkg.com/htmx.org@${HTMX_VERSION}/dist/htmx.min.js \
&& ./tools/tailwindcss -i web/styles/input.css -o web/static/app.css --minify
# Stage 2: build the Go binary (embeds web/static + templates)
FROM golang:1.25-alpine AS builder
WORKDIR /src
COPY . .
COPY --from=assets /src/web/static/app.css web/static/app.css
COPY --from=assets /src/web/static/htmx.min.js web/static/htmx.min.js
RUN go build -o /server ./cmd/server
# Stage 3: runtime
FROM alpine:3.20
RUN apk add --no-cache ffmpeg
COPY --from=builder /server /server
VOLUME ["/music", "/data"]
EXPOSE 8080
ENTRYPOINT ["/server"]
Volumes obrigatórios:
/music— bind mount com os arquivos de música (mesmo path usado pelo Lidarr)/data— volume persistente para o SQLite (coda.db)
Desenvolvimento
Desenvolvimento Local (sem Docker)
Fluxo de dev usa Makefile. Ferramentas baixadas (Tailwind CLI + bundle DaisyUI) ficam em tools/ e o htmx em web/static/htmx.min.js — tudo no .gitignore. As variáveis de ambiente são carregadas de um .env (o make setup copia de .env.example).
Pré-requisitos (Fedora):
# Go 1.25+ — baixar de https://go.dev/dl (o dnf pode ter versão mais antiga)
# ffmpeg com libmp3lame (via RPM Fusion):
sudo dnf install -y https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
sudo dnf install -y ffmpeg
# SQLite NÃO precisa ser instalado (modernc.org/sqlite é Go puro, sem CGO)
Primeira vez:
make setup # baixa Tailwind + DaisyUI + htmx, roda go mod tidy, cria .env
# edite o .env (MUSIC_PATH, DB_PATH etc.)
mkdir -p data testdata/music
Rodar:
make run # dev: assets servidos do disco (build tag `dev`), hot-reload de templates
make run-prod # produção: assets embutidos via //go:embed
make css-watch # em outro terminal, regenera o CSS ao salvar (dev)
Em dev (-tags dev) os templates e estáticos são lidos do disco (os.DirFS), então editar HTML reflete no reload da página. O CSS embutido não recarrega sozinho — use make css-watch. Em produção tudo é embutido no binário.
Comandos
make setup # baixa ferramentas + deps + cria .env
make css # gera web/static/app.css (obrigatório antes de build/embed)
make run # roda em dev (build tag dev, assets do disco)
make run-prod # roda com assets embutidos
make build # binário de produção em bin/server
make test # go test ./...
make lint # go vet ./...
make clean # remove bin/ e app.css
Convenções de Código
- Sem comentários em código (código auto-documentado).
- Erros são propagados com
fmt.Errorf("context: %w", err). - Logs usam
log/slogcom handler SQLite (logger interno do pacoteinternal/logger). - Config é acessada via
config.Get(...)— nunca leia env vars diretamente. - Handlers HTTP seguem o padrão
func(w http.ResponseWriter, r *http.Request)— compatível com chi. - Rotas são registradas com chi router (
chi.NewRouter(),r.Get(...),r.Post(...), etc.). - SQL queries usam
database/sqlcommodernc.org/sqlite. - Migrações são executadas em
store.New()antes do servidor iniciar. - Interações dinâmicas na UI usam htmx (atributos
hx-*nos elementos HTML). - UI estilizada com classes DaisyUI (
btn,card,modal) + utilitários Tailwind. web/static/app.cssé gerado pelo Tailwind CLI — nunca editar à mão.web/styles/input.cssdefine@source "../templates"para o purge detectar as classes usadas.- Estáticos e templates são embutidos no binário via
//go:embed(binário único). - Testes usam
testingpadrão +_test.gojunto ao código.
Fluxo para Adicionar Funcionalidade
- Se necessário, adicione tabela/coluna em
internal/store/migrations.go - Implemente a lógica no pacote apropriado
- Exponha via handler HTTP em
internal/web/ - Adicione template/rota na web UI
- Teste com
go test ./...