CMS/VMS web para descobrir, cadastrar, diagnosticar, organizar e visualizar câmeras IP em redes locais autorizadas. O projeto privilegia ONVIF e capabilities reais, mantém credenciais separadas das URLs e limita qualquer scanner às redes permitidas.
O MVP fornece API Go, SQLite com migrations, UI React/TypeScript compilada localmente, CRUD de câmeras, jobs de descoberta incrementais, WS-Discovery, scanner privado, SSE, consultas ONVIF, snapshots, HTTP/MJPEG compartilhado, diagnósticos, layouts e grid de até 32 posições. RTSP é testado no nível de protocolo, mas reprodução RTSP no navegador depende de um backend de remux ainda não incluído.
- Go 1.22 ou superior para desenvolvimento local; a imagem oficial compila com Go 1.26.6;
- Node.js 20.19+, 22.12+ ou 24 LTS e npm para compilar a UI React (opcional para usar o fallback
web/static); a imagem usa Node 24.19.0 no estágio de build; - SQLite é embutido via driver Go, sem daemon externo;
- FFmpeg não é exigido no MVP. Será dependência opcional quando um backend de remux/transcoding for adicionado.
Nenhuma dependência deve ser instalada globalmente pelo projeto. Os caches Go usados pelo Makefile ficam em .cache/ dentro do repositório.
Gere uma chave externa de 32 bytes e mantenha-a apenas no ambiente:
export CMS_SECRET_KEY="$(openssl rand -base64 32)"
make devAcesse http://localhost:15000. Sem CMS_SECRET_KEY o servidor inicia para inspeção e cadastros sem senha, mas rejeita de forma segura qualquer tentativa de persistir credenciais.
Para produção local:
make build
# CMS_SECRET_KEY deve estar definida no ambiente e ser preservada entre reinícios.
./bin/cmsO backend serve web/dist quando a UI React foi compilada; caso Node não exista, serve o fallback funcional de web/static.
Build:
docker build -t camera-cms .Run:
docker run \
--name camera-cms \
-p 15000:15000 \
-v camera-cms-data:/app/data \
--env-file .env \
camera-cmsCompose:
docker compose up -dLogs:
docker compose logs -f cmsStop, preservando o volume:
docker compose downA imagem é multi-stage e multi-arquitetura (linux/amd64 e linux/arm64), não contém Node/Go no runtime, executa como usuário cms e usa /app/data como volume persistente. O Docker seleciona automaticamente a variante correta no pull. Consulte docs/multi-architecture.md para builds locais e matriz de validação, e docs/deployment.md antes de backup, restore, atualização ou configuração de networking para WS-Discovery.
A imagem pública de desenvolvimento gerada pelo GitHub Actions está disponível em ghcr.io/facrf/videocmscodex:main. Ela não é release imutável; confira digest, tags e validações em docs/ghcr.md antes do deploy.
Para instalação pelo Portainer via Web editor, use os stacks e o passo a passo em deploy/README.md. Há um stack bridge padrão e outro host-network específico para Docker Engine Linux quando multicast ONVIF exigir acesso direto à LAN.
No Web editor do Portainer, use o YAML abaixo. A imagem publicada pelo workflow deste repositório está no GitHub Container Registry em ghcr.io/facrf/videocmscodex:main:
services:
cms:
image: ghcr.io/facrf/videocmscodex:main
container_name: camera-cms
restart: unless-stopped
user: "10001:10001"
ports:
- "15000:15000"
volumes:
- cms-data:/app/data
environment:
CMS_PORT: "15000"
CMS_DB_PATH: /app/data/cms.db
CMS_SECRET_KEY: "${CMS_SECRET_KEY}"
CMS_SCAN_ENABLED: "${CMS_SCAN_ENABLED:-true}"
CMS_ALLOWED_NETWORKS: "${CMS_ALLOWED_NETWORKS:-}"
TZ: "${TZ:-UTC}"
healthcheck:
test: ["CMD", "/app/cms", "healthcheck"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
volumes:
cms-data:Cadastre CMS_SECRET_KEY em Environment variables no Portainer com uma chave base64 de 32 bytes; não grave a chave no YAML. A tag main é mutável e indicada para avaliação. Em produção, prefira uma tag de versão já publicada, por exemplo ghcr.io/facrf/videocmscodex:0.1.0.
make backend
make backend-cross
make frontend
make test
make lint
make build
make docker-build-multimake backend-cross produz binários Linux AMD64/ARM64. Com Buildx disponível, make docker-build-multi gera um archive OCI com as duas plataformas em dist/. make clean remove somente os artefatos locais bin/, coverage/, dist/, web/dist/, web/node_modules/ e .cache/ dentro deste projeto.
Copie os valores de .env.example para o ambiente (o processo não carrega .env automaticamente):
| Variável | Padrão | Finalidade |
|---|---|---|
CMS_PORT |
15000 |
Porta HTTP |
CMS_DB_PATH |
./data/cms.db |
Banco SQLite |
CMS_SECRET_KEY |
vazio | Chave AES-256-GCM em base64 |
CMS_SCAN_ENABLED |
true |
Habilita discovery |
CMS_SCAN_MAX_CONCURRENCY |
32 |
Limite de probes concorrentes |
CMS_SCAN_TIMEOUT |
2s |
Timeout por conexão |
CMS_ALLOWED_NETWORKS |
vazio | CIDRs extras, separados por vírgula |
CMS_CAMERA_HEALTH_INTERVAL |
30s |
Intervalo do monitor leve |
CMS_LOG_LEVEL |
info |
debug, info, warn ou error |
TZ |
UTC |
Timezone opcional do processo/logs; timestamps persistidos permanecem em UTC |
As redes RFC1918 (10/8, 172.16/12, 192.168/16) são permitidas por padrão. CIDRs extras são explícitos; scans maiores que /20, IPs públicos, loopback, multicast, unspecified e link-local são bloqueados.
O servidor usa net/http, injeção simples de stores/managers e shutdown gracioso. SQLite opera com foreign keys, busy timeout, WAL e uma conexão de escrita para previsibilidade. Migrations em migrations/ são embutidas no executável e aplicadas uma única vez.
Credenciais são cifradas com AES-256-GCM; usuário, host e path permanecem separados. URLs retornadas por ONVIF são sanitizadas antes de sair do pacote. Logs são JSON e há uma função central de redaction testada. Consulte docs/architecture.md.
Documentação detalhada:
- deployment;
- configuration;
- discovery;
- streaming;
- security;
- troubleshooting;
- development;
- suporte multi-arquitetura;
- análise técnica e recomendações;
- GHCR e processo de release;
- protocols e OpenAPI.
ws-discovery: probe multicast deNetworkVideoTransmitter, sem cadastro automático;scan: CIDR privado explícito, interface opcional, concorrência limitada, cancelamento, timeout, progresso, deduplicação e portas candidatas controladas;- fingerprint: handshake RTSP seguro e probe SOAP ONVIF; porta aberta isolada não identifica uma câmera;
- probe posterior: recebe credenciais apenas no corpo, consulta
GetDeviceInformation,GetCapabilitieseGetProfiles; - ONVIF usa WS-Security UsernameToken PasswordDigest. Media1 cobre profiles, stream URI e snapshot URI. Media2 ainda não foi implementado.
RTSP nunca é devolvido ao browser com credenciais. O ONVIF client remove userinfo das URIs. O StreamManager implementa sessão compartilhada, métricas seguras, viewers, profile, grace period e lifecycle (1 câmera → 1 ingest → N viewers). Desconexões usam backoff exponencial cancelável de 500 ms a 30 s. O backend HTTP/MJPEG é funcional; o backend de remux RTSP continua sendo um extension point.
HTTP/MJPEG multipart configurado como URL HTTP(S) no campo de stream é servido por /api/cameras/:id/live através do transporte com proteção SSRF. Um upstream é compartilhado por até 128 viewers; cada fila possui limite e viewers lentos são desconectados para preservar backpressure. Snapshots usam /api/cameras/:id/snapshot.
H.264, H.265, MJPEG e o codec de áudio aparecem como metadados quando informados pelo profile ONVIF; o MVP não decodifica nem transcodifica. H.265 não é prometido como reprodução direta no navegador. O desempenho de 32 tiles depende de codec, bitrate, resolução, hardware, GPU, navegador e rede.
- não coloque credenciais em
rtsp://usuario:senha@host/path; - não exponha o CMS fora de uma LAN confiável: a interface
AccessMiddlewareestá pronta, mas autenticação de usuários ainda não foi implementada; - redirects HTTP são revalidados e limitados;
- DNS e cada IP resolvido passam pela allowlist antes do dial;
- scanner não testa senhas, não explora vulnerabilidades e não acessa redes públicas por padrão;
- audit events guardam apenas tipo, entidade, ID, metadata segura e timestamp.
Health por componente e metadados de build em GET /api/health; versão isolada em GET /api/version; câmeras paginadas/filtráveis em /api/cameras; teste, capabilities, diagnóstico, organização, snapshot e live em subrotas da câmera; discovery em /api/discovery; layouts em /api/layouts; grupos/tags em /api/groups e /api/tags; eventos SSE em /api/events. Erros usam {error:{code,message,detail?}}. A especificação versionada está em docs/openapi.yaml.
O banco padrão é data/cms.db e não entra no Git. Não altere migrations aplicadas; crie uma nova migration numerada. As tabelas iniciais incluem câmeras, layouts/items, grupos/tags e auditoria.
Backup consistente sem sobrescrita usa SQLite VACUUM INTO:
./bin/cms backup ./data/backup-2026-08-18.dbRestore deve ser feito com a aplicação parada, conforme deployment.
Os testes não dependem de hardware. internal/testutil/fakecamera simula ONVIF e um servidor MJPEG local é acessado por dialer injetado depois da validação de IP privado. A suíte cobre configuração, migrations, CRUD, filtros, restart/persistência, layouts, identidade, criptografia, logs, SSRF/redirects/DNS, URLs, ONVIF/RTSP, discovery, backpressure, reconexão e ingest compartilhado. go test -race ./... também é suportado.
- Credenciais rejeitadas: defina uma
CMS_SECRET_KEYbase64 de exatamente 32 bytes e mantenha a mesma chave entre reinícios. - Node/npm ausente: o fallback abre normalmente; instale Node 20+ por meios administrados fora deste projeto para compilar React.
- Câmera bloqueada: confirme que todos os IPs resolvidos pertencem às redes permitidas.
- ONVIF authentication required: confira relógio da câmera/servidor e as credenciais; PasswordDigest depende de timestamp.
- Snapshot 401: alguns snapshots usam HTTP Digest proprietário; o proxy atual envia Basic no GET, embora o SOAP ONVIF use WS-Security.
- RTSP não reproduz: esperado sem backend de remux. Nenhum FFmpeg é instalado automaticamente.
Ainda não há remux RTSP/HLS/WebRTC, HTTP Digest para snapshot, Media2, PTZ operacional, APIs proprietárias, autenticação de usuários, gravação, playback, analytics, IA, multi-tenant, cluster ou HA. Dahua e Intelbras têm adapters de identificação estrita e reutilizam ONVIF; não há URLs proprietárias presumidas. Hikvision não possui suporte específico nem validação em hardware; uma possível interoperabilidade depende exclusivamente dos padrões ONVIF/RTSP implementados.