Começar
O instalador baixa artefatos da release, gera os segredos locais e o certificado TLS, prepara o Compose e inicia Valkyris e MediaMTX.
curl -fsSL https://valkyris.vercel.app/install.sh | shRequisitos
- ✓Linux em amd64 ou arm64
- ✓Docker Engine com Docker Compose v2
- ✓Android 8.0 (SDK 26) ou mais recente
- ✓Câmera ONVIF Profile S com RTSP
- ✓Servidor, celular e câmera acessíveis por LAN ou VPN
Mantenha as portas ONVIF e RTSP acessíveis apenas na rede privada. Para acesso externo, conecte o celular por VPN; não publique a câmera na internet.
Arquitetura
O backend é o limite de segurança. O app nunca recebe a senha da câmera nem segredos de adaptadores locais. Câmeras passam pelo MediaMTX; a iluminação usa uma fronteira de driver independente de fabricante. Credenciais ficam cifradas com AES-256-GCM; tokens são persistidos somente como hash.
Preparar a câmera
- 1Conecte ao Wi-Fi
Na TC40, use o Tapo uma vez para concluir a configuração inicial.
- 2Crie a Camera Account
Use uma credencial exclusiva nas configurações avançadas. Não é a senha da sua conta TP-Link.
- 3Confirme a rede local
O servidor precisa alcançar a câmera pelas portas ONVIF e RTSP na rede privada.
- 4Cadastre no app
Informe nome, ícone, IP, usuário e senha. O RTSP principal da Tapo é montado automaticamente; use o campo avançado apenas para sobrescrever.
O cadastro é persistido imediatamente. A tela da câmera acompanha queued, probing, stream, ready ou failed em tempo real, e o último erro sanitizado permanece salvo para diagnóstico.
Iluminação inteligente
O modelo comum de energia, brilho, temperatura do branco e RGB permanece no aplicativo e no painel, mas o núcleo não inclui integração proprietária com contas de fabricante. Adaptadores locais padronizados podem ser adicionados atrás da fronteira de driver sem redesenhar as interfaces.
Registros antigos não são apagados automaticamente durante essa transição. Um administrador ainda pode removê-los pelo aplicativo.
Android
Instale o APK da release mais recente. No primeiro acesso, informe a URL HTTPS pela qual o celular alcança o servidor e crie a conta com usuário e senha. O primeiro dispositivo vira administrador.
Outros celulares
Em Ajustes, o administrador cria um convite temporário e de uso único. O QR é montado dentro do app com a URL já conhecida; o backend não possui página pública de pareamento.
Notificações nativas
O APK usa Firebase Cloud Messaging diretamente e não exige aplicativo auxiliar. Após o login, o token deste celular é registrado automaticamente; o Android recebe um payload mínimo cifrado e o Valkyris cria a notificação ou o alarme nativo.
- 1Configure o APK
Adicione com.ferforastieri.valkyris ao projeto Firebase, baixe google-services.json e grave seu base64 no secret FIREBASE_ANDROID_CONFIG_BASE64 do GitHub.
- 2Configure o servidor
Gere uma chave JSON em Configurações do projeto → Contas de serviço. No sheet de alertas do app, envie esse JSON: ele é validado e cifrado no SQLite do seu servidor.
- 3Conceda a permissão
Abra o APK da nova release, faça login e permita notificações. O registro no FCM acontece automaticamente.
A API aceita a mesma configuração para integrações administrativas em PUT /api/v1/settings/push, usando serviceAccountBase64. A conta nunca é retornada pela API.
Painel web no seu servidor
Abra /app/ na URL HTTPS da instalação e use seu usuário e senha. O painel consulta câmeras ao vivo, controla as lâmpadas cadastradas, exibe eventos, família, percursos e informações do servidor. Cadastro e configuração dos dispositivos continuam no Android.
A sessão usa cookie Secure, HttpOnly e SameSite=Strict, válido por 30 dias e renovado durante o uso. Sair ou trocar a senha revoga a sessão. O painel vem na imagem Docker e não exige Node nem deploy na Vercel. Dados são consultados a cada 15 segundos com a aba visível.
WebRTC · Cloudflare Tunnel
Android e painel usam os mesmos endpoints WHEP. O túnel leva HTTPS e a negociação, mas áudio e vídeo precisam alcançar o MediaMTX por ICE. Libere 8189 UDP/TCP na rede privada e configure VALKYRIS_WEBRTC_HOSTS com endereços LAN/VPN alcançáveis. O domínio do túnel não é endereço de mídia. STUN pode auxiliar conexão direta; NAT restritivo exige VPN, rota pública de mídia ou TURN configurado.
O player não substitui WebRTC por imagem ou HLS. A espera ICE de oito segundos não encerra a conexão Android quando já existem candidatos utilizáveis. Um HTTP 201 na negociação não comprova reprodução: verifique frames recebidos.
Localização, eventos e movimento
O Android integra fontes de localização com Fused Location Provider. Uma entrada ou saída exige três leituras por pelo menos dois minutos, além da margem de precisão. Leitura inconclusiva ou retorno cancela a confirmação. O servidor pede amostras enquanto há confirmação pendente, sem preencher o histórico com pontos estacionários.
Alertas mostram pessoa e área, e não são enviados ao próprio usuário que se deslocou. O histórico tem pontos numerados, horário e precisão; os mapas aproximam pessoas próximas. A precisão é estimada, e falta de rede ou sinal pode adiar alertas. Instale o APK atualizado em cada celular.
Nas regras da câmera, movimento persistente permite selecionar uma região da imagem, duração, sensibilidade e dias/horários, inclusive 22:00–06:00. Mantenha a câmera fixa e redesenhe a região após mover PTZ. A análise detecta movimento na região, não identifica o bebê, postura, respiração ou perigo médico; não substitui supervisão.
Eventos são separados em localização, áudio e câmera. Detalhes mostram pessoa/área e posição quando disponíveis; clipes indicam se estão processando, prontos ou indisponíveis.
Configuração
VALKYRIS_WEBRTC_HOSTSIP LAN/VPNEndereços de mídia anunciados, separados por vírgula.VALKYRIS_WEB_DIR/opt/valkyris/webBuild estático do painel no backend.VALKYRIS_LISTEN:8443Endereço interno do serviço HTTPS.VALKYRIS_DATA_DIR/dataVolume persistente.VALKYRIS_DATABASE/data/valkyris.dbSQLite.VALKYRIS_TLS_CERT/data/tls/server.crtCertificado TLS.VALKYRIS_TLS_KEY/data/tls/server.keyChave TLS.VALKYRIS_MASTER_KEY_FILE/data/secrets/master.keyChave mestra de criptografia.VALKYRIS_MEDIA_APIhttp://mediamtx:9997API interna do MediaMTX.VALKYRIS_MEDIA_RTSPrtsp://mediamtx:8554Stream interno para monitoramento e detectores.VALKYRIS_MEDIA_WEBRTChttp://mediamtx:8889Origem WHEP interna; a mídia WebRTC segue direto ao celular após ICE.VALKYRIS_MEDIA_PLAYBACKhttp://mediamtx:9996API interna para gerar clipes recentes.VALKYRIS_RELEASE_APIapi.github.com/…/releases/latestRelease estável consultada.VALKYRIS_FIREBASE_CREDENTIALS_FILE/data/secrets/firebase-service-account.jsonAlternativa legada: arquivo de conta de serviço FCM. O app usa o armazenamento cifrado do SQLite.O arquivo mediamtx.yml deve existir como arquivo antes de subir o Compose.
API HTTP
Bearer no Android e integrações; o painel usa cookie HttpOnly e X-Valkyris-Viewer: 1. Rotas públicas dispensam sessão. A base é /api/v1. Respostas JSON usam o envelope success, message e data; erros usam success, message e error.
curl -k https://SEU_SERVIDOR:8443/api/v1/cameras \
-H 'Authorization: Bearer SEU_TOKEN'Referência de endpoints
OpenAPI YAML/healthPúblicoSaúde do servidor.
/openapi.yamlPúblicoContrato OpenAPI 3.1 servido pelo backend.
/api/v1/auth/statusPúblicoInforma se o primeiro administrador já existe.
/api/v1/admin/bootstrapPúblicoCria a conta com usuário e senha e o primeiro administrador, uma única vez.
/api/v1/loginPúblicoAutentica uma conta pessoal sem recriar o perfil.
/api/v1/pairPúblicoConsome um convite de uso único.
/api/v1/pairing-sessionsAdminCria um convite temporário para outro celular.
/api/v1/camerasAutenticadoLista câmeras ou inicia o cadastro assíncrono.
/api/v1/cameras/{id}AutenticadoEdita ou remove uma câmera; uma mudança de conexão reinicia sua configuração.
/api/v1/camera-operations/{id}AutenticadoAcompanha a descoberta ONVIF e a configuração de mídia.
/api/v1/cameras/{id}/ptzAutenticadoMove, para ou aplica zoom quando suportado.
/api/v1/lightsAutenticadoLista a iluminação fornecida pelo adaptador local instalado.
/api/v1/lights/{id}Autenticado / Admin ao removerConsulta ou remove uma iluminação.
/api/v1/lights/{id}/stateAutenticadoLiga, desliga e altera brilho, branco ou cor.
/api/v1/cameras/{id}/snapshotAutenticadoRetorna um frame JPEG do stream local atual.
/api/v1/cameras/{id}/recordingAutenticadoBaixa os últimos 10 segundos do buffer em MP4.
/api/v1/cameras/{id}/live/webrtc/whepAutenticadoNegocia e encerra o stream WebRTC autenticado.
/api/v1/detectorsAutenticadoLista os tipos normalizados de detecção.
/api/v1/rulesAutenticadoLista ou cria regras de automação.
/api/v1/rules/{id}AutenticadoEdita ou remove uma regra.
/api/v1/usersAutenticadoLista os perfis da família vinculados aos dispositivos pareados.
/api/v1/users/{id}/historyAutenticadoRetorna o histórico de localização de um usuário.
/api/v1/me/locationAutenticadoRegistra a localização do usuário vinculado ao dispositivo atual.
/api/v1/placesAutenticado / Admin ao alterarLista áreas ou cria uma área de alerta.
/api/v1/places/{id}AdminEdita ou remove uma área.
/api/v1/meAutenticadoConsulta ou atualiza o perfil do próprio dispositivo.
/api/v1/me/passwordUsuárioAltera a própria senha e revoga as outras sessões.
/api/v1/viewer-sessionSessão webRestaura ou revoga a sessão do painel.
/api/v1/eventsAutenticadoLista eventos recentes.
/api/v1/events/{id}AutenticadoRetorna metadados de um evento.
/api/v1/events/{id}/acknowledgeAutenticadoReconhece o evento e interrompe o alarme.
/api/v1/events/acknowledge-allAutenticadoReconhece todos os eventos pendentes.
/api/v1/events/{id}/snapshotAutenticadoRetorna o JPEG preservado no evento.
/api/v1/events/{id}/clipAutenticadoTransmite o clipe MP4 do evento.
/api/v1/devices/pushAutenticadoRegistra o token FCM e o segredo do payload cifrado.
/api/v1/settings/pushAdmin ao alterarConsulta a configuração FCM ou envia a conta de serviço Base64, cifrada no SQLite.
/api/v1/settings/retentionAdmin ao alterarLê ou altera os limites de retenção.
/api/v1/detectionsAutenticadoRecebe uma detecção normalizada de integração local.
/api/v1/system/updateAutenticadoConsulta a release e o link do APK, sem executar atualizações.
/api/v1/realtimeAutenticadoWebSocket para mudanças de câmeras, iluminação e eventos.
Atualizações
Quando uma release estável mais nova existir, o app e o web exibem um toast, sem atualizar automaticamente. Nas configurações do app, o usuário pode iniciar o download do APK assinado do GitHub; o Android solicita confirmação para instalar.
SQLite, clipes e snapshots permanecem no volume valkyris-data. A API apenas consulta versões; não executa atualizações do servidor.
Na série 2.x, a API permanece /api/v1 e a migração preserva dados. Execute o instalador novamente no host para atualizar o backend, o web, Compose e MediaMTX. O APK precisa ser instalado em cada celular com confirmação do Android. A consulta de releases tem cache de até 15 minutos.
Segurança e backup
A API limita por minuto: 3000 requisições globais e 1200 por endereço; autenticação tem 30 globais e 10 por endereço; snapshots/gravações têm 60 por sessão/endereço, e abertura WHEP tem 12. Respostas 429 incluem Retry-After. O backend não confia em X-Forwarded-For ou CF-Connecting-IP do cliente; usuários de um proxy compartilham o orçamento. Os contadores são locais ao processo.
Para backup, use a API de backup SQLite ou pare a stack antes de copiar o volume. Preserve banco, WAL quando aplicável, chave mestra, certificados, mídia, .env, Compose e mediamtx.yml. Copiar apenas o .db em execução pode gerar backup incompleto.
- Não encaminhe 554, 2020, 8888 ou 9997 no roteador.
- Use TLS confiável por proxy reverso ou instale a CA privada no celular.
- Use Tailscale, WireGuard ou outra VPN para acesso fora de casa.
- Faça backup do volume valkyris-data e do .env juntos, com acesso restrito.
Diagnóstico
docker compose ps
docker compose logs --tail=200 valkyris
docker compose logs --tail=200 mediamtx
curl -k https://localhost:8443/healthAbra a câmera no app: a etapa e o erro persistido indicam se a falha ocorreu no ONVIF ou no stream.
Verifique primeiro se MediaMTX está saudável e se mediamtx.yml foi montado como arquivo.
Confira snapshot, perfil ONVIF escolhido e conectividade RTSP entre o host e a câmera.