Skip to content

Repository files navigation

VideoCMS

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.

Estado atual

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.

Requisitos

  • 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.

Execução

Gere uma chave externa de 32 bytes e mantenha-a apenas no ambiente:

export CMS_SECRET_KEY="$(openssl rand -base64 32)"
make dev

Acesse 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/cms

O backend serve web/dist quando a UI React foi compilada; caso Node não exista, serve o fallback funcional de web/static.

Docker

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-cms

Compose:

docker compose up -d

Logs:

docker compose logs -f cms

Stop, preservando o volume:

docker compose down

A 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.

Stack para Portainer usando GHCR

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.

Desenvolvimento

make backend
make backend-cross
make frontend
make test
make lint
make build
make docker-build-multi

make 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.

Configuração

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.

Arquitetura

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:

Descoberta e ONVIF

  • ws-discovery: probe multicast de NetworkVideoTransmitter, 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, GetCapabilities e GetProfiles;
  • ONVIF usa WS-Security UsernameToken PasswordDigest. Media1 cobre profiles, stream URI e snapshot URI. Media2 ainda não foi implementado.

RTSP, streaming e codecs

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.

Segurança

  • não coloque credenciais em rtsp://usuario:senha@host/path;
  • não exponha o CMS fora de uma LAN confiável: a interface AccessMiddleware está 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.

API principal

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.

SQLite

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.db

Restore deve ser feito com a aplicação parada, conforme deployment.

Testes

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.

Troubleshooting

  • Credenciais rejeitadas: defina uma CMS_SECRET_KEY base64 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.

Limitações e roadmap

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.

About

No description, website, or topics provided.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages