coda/AGENTS.md

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 migrations
  • store.JobRepository — CRUD + consulta de tarefas
  • store.LogRepository — escrita e consulta paginada de logs
  • store.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() e Set(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 via Authorization: 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/slog com handler SQLite (logger interno do pacote internal/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/sql com modernc.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.css define @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 testing padrão + _test.go junto ao código.

Fluxo para Adicionar Funcionalidade

  1. Se necessário, adicione tabela/coluna em internal/store/migrations.go
  2. Implemente a lógica no pacote apropriado
  3. Exponha via handler HTTP em internal/web/
  4. Adicione template/rota na web UI
  5. Teste com go test ./...