Refatora infraestrutura de dados, build e provisionamento de streams

---

- Movido streams.json para /app/data/streams/streams.json; volume do compose mapeado especificamente em /app/data/streams, deixando logs fora do volume persistido;
- Adicionado scripts/reprovision.mjs que regenera stream.conf e tokens VNC a partir dos templates da imagem no startup, garantindo que updates de container não exijam recriar o volume;
- Removido autologin.template.sh por-stream; substituído por scripts/autologin.sh global na imagem, com variáveis passadas via environment= no supervisor conf (com valores entre aspas
para compatibilidade com valores vazios);
- Logs de processos por stream movidos de /app/data/streams/{id}/ para /app/data/logs/{id}/;
- Adicionada função recreateStream em supervisor.ts e rota POST /api/streams/[id]/recreate; botão "Recreate" adicionado ao menu do card para limpar chrome-profile e re-provisionar;
- Adicionado auto-disparo de captureThumb no GET /api/streams/[id]/thumb quando thumb.jpg não existe e nenhuma captura está em andamento;
- Dockerfile: adicionado --mount=type=cache para /var/cache/apt e /var/lib/apt/lists (não /var/lib/apt inteiro para evitar corrupção de estado); removido --no-cache do Makefile; remoção
de pacotes limitada a curl gnupg para evitar cascata em dependências do chromium/novnc;
- Migração automática de streams.json do caminho antigo adicionada ao entrypoint.sh;

---
This commit is contained in:
2026-04-25 15:08:25 -03:00
parent f6879781c1
commit 40824c08a4
13 changed files with 174 additions and 82 deletions
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
[ -z "${LOGIN_USER:-}" ] && exit 0
sleep "${STREAM_DELAY:-0}"
CURRENT_URL=$(node -e "
const http = require('http');
http.get('http://localhost:${DEBUG_PORT}/json', res => {
let d = '';
res.on('data', c => d += c);
res.on('end', () => {
try {
const tabs = JSON.parse(d);
const page = tabs.find(t => t.type === 'page');
process.stdout.write(page ? page.url : '');
} catch { process.stdout.write(''); }
});
}).on('error', () => process.stdout.write(''));
" 2>/dev/null)
if [ -n "$CURRENT_URL" ] && ! echo "$CURRENT_URL" | grep -qiE '/(login|signin|sign-in|auth|sso|oauth)'; then
exit 0
fi
DISPLAY=${DISPLAY} xdotool search --sync --onlyvisible --class chromium windowfocus windowraise
sleep 1
DISPLAY=${DISPLAY} xdotool type --clearmodifiers --delay 50 "${LOGIN_USER}"
DISPLAY=${DISPLAY} xdotool key Tab
sleep 0.3
DISPLAY=${DISPLAY} xdotool type --clearmodifiers --delay 50 "${LOGIN_PASS}"
DISPLAY=${DISPLAY} xdotool key Return
-37
View File
@@ -1,37 +0,0 @@
#!/bin/bash
# Auto-generated by API — do not edit manually
# Stream: {{STREAM_ID}}
[ -z "{{USER}}" ] && exit 0
sleep {{STREAM_DELAY}}
# Query Chrome DevTools Protocol to detect current page URL
CURRENT_URL=$(node -e "
const http = require('http');
http.get('http://localhost:{{DEBUG_PORT}}/json', res => {
let d = '';
res.on('data', c => d += c);
res.on('end', () => {
try {
const tabs = JSON.parse(d);
const page = tabs.find(t => t.type === 'page');
process.stdout.write(page ? page.url : '');
} catch { process.stdout.write(''); }
});
}).on('error', () => process.stdout.write(''));
" 2>/dev/null)
# If we got a URL and it doesn't look like a login page, skip autologin
if [ -n "$CURRENT_URL" ] && ! echo "$CURRENT_URL" | grep -qiE '/(login|signin|sign-in|auth|sso|oauth)'; then
exit 0
fi
DISPLAY={{DISPLAY}} xdotool search --sync --onlyvisible --class chromium windowfocus windowraise
sleep 1
DISPLAY={{DISPLAY}} xdotool type --clearmodifiers --delay 50 "{{USER}}"
DISPLAY={{DISPLAY}} xdotool key Tab
sleep 0.3
DISPLAY={{DISPLAY}} xdotool type --clearmodifiers --delay 50 "{{PASS}}"
DISPLAY={{DISPLAY}} xdotool key Return
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env node
// Regenerates stream.conf and VNC token files from current image templates.
// Runs at container startup (before supervisord) so configs always match the image.
import fs from 'fs'
import path from 'path'
const DATA_DIR = process.env.DATA_DIR ?? '/app/data'
const STREAMS_FILE = path.join(DATA_DIR, 'streams', 'streams.json')
const STREAMS_DIR = path.join(DATA_DIR, 'streams')
const VNC_TOKENS_DIR = path.join(DATA_DIR, 'vnc-tokens')
const LOGS_DIR = path.join(DATA_DIR, 'logs')
const CONF_TPL = '/opt/scripts/stream.template.conf'
if (!fs.existsSync(STREAMS_FILE) || !fs.existsSync(CONF_TPL)) process.exit(0)
const streams = JSON.parse(fs.readFileSync(STREAMS_FILE, 'utf-8'))
if (streams.length === 0) process.exit(0)
const confTpl = fs.readFileSync(CONF_TPL, 'utf-8')
function render(tpl, vars) {
return tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => String(vars[k] ?? ''))
}
for (const stream of streams) {
const dir = path.join(STREAMS_DIR, stream.id)
fs.mkdirSync(path.join(dir, 'chrome-profile'), { recursive: true })
fs.mkdirSync(path.join(LOGS_DIR, stream.id), { recursive: true })
fs.mkdirSync(VNC_TOKENS_DIR, { recursive: true })
const vars = {
STREAM_ID: stream.id,
DISPLAY: stream.display,
RESOLUTION: stream.resolution,
CHROME_SIZE: stream.resolution.replace('x', ','),
STREAM_URL: stream.url,
DEBUG_PORT: stream.debugPort,
VNC_PORT: stream.vncPort,
STREAM_DELAY: stream.delay,
FPS: stream.fps,
PRESET: stream.preset,
TUNE: stream.tune,
GOP: stream.gop,
BITRATE: stream.bitrate,
BUFSIZE: stream.bufsize,
SCALE: String(stream.scale).replace('x', ':'),
THREADS: stream.threads ?? 0,
USER: stream.user ?? '',
PASS: stream.pass ?? '',
}
fs.writeFileSync(path.join(dir, 'stream.conf'), render(confTpl, vars), 'utf-8')
fs.writeFileSync(
path.join(VNC_TOKENS_DIR, `${stream.id}.cfg`),
`${stream.id}: localhost:${stream.vncPort}\n`,
'utf-8'
)
console.log(`[reprovision] ${stream.id}`)
}
console.log(`[reprovision] done (${streams.length} stream(s))`)
+12 -12
View File
@@ -5,8 +5,8 @@
command=Xvfb {{DISPLAY}} -screen 0 {{RESOLUTION}}x24 -ac
autorestart=true
priority=10
stdout_logfile=/app/data/streams/{{STREAM_ID}}/xvfb.log
stderr_logfile=/app/data/streams/{{STREAM_ID}}/xvfb.log
stdout_logfile=/app/data/logs/{{STREAM_ID}}/xvfb.log
stderr_logfile=/app/data/logs/{{STREAM_ID}}/xvfb.log
[program:chromium-{{STREAM_ID}}]
command=bash -c "rm -rf \
@@ -34,25 +34,25 @@ environment=DISPLAY={{DISPLAY}}
autorestart=true
priority=20
startsecs=5
stdout_logfile=/app/data/streams/{{STREAM_ID}}/chromium.log
stderr_logfile=/app/data/streams/{{STREAM_ID}}/chromium.log
stdout_logfile=/app/data/logs/{{STREAM_ID}}/chromium.log
stderr_logfile=/app/data/logs/{{STREAM_ID}}/chromium.log
[program:autologin-{{STREAM_ID}}]
command=/app/data/streams/{{STREAM_ID}}/autologin.sh
command=/opt/scripts/autologin.sh
autorestart=false
priority=30
startsecs=0
environment=DISPLAY={{DISPLAY}}
stdout_logfile=/app/data/streams/{{STREAM_ID}}/autologin.log
stderr_logfile=/app/data/streams/{{STREAM_ID}}/autologin.log
environment=DISPLAY="{{DISPLAY}}",LOGIN_USER="{{USER}}",LOGIN_PASS="{{PASS}}",DEBUG_PORT="{{DEBUG_PORT}}",STREAM_DELAY="{{STREAM_DELAY}}"
stdout_logfile=/app/data/logs/{{STREAM_ID}}/autologin.log
stderr_logfile=/app/data/logs/{{STREAM_ID}}/autologin.log
[program:x11vnc-{{STREAM_ID}}]
environment=DISPLAY={{DISPLAY}}
command=bash -c "while [ ! -e /tmp/.X11-unix/X$(echo $DISPLAY | cut -d: -f2 | cut -d. -f1) ]; do sleep 0.2; done; exec x11vnc -nopw -listen 0.0.0.0 -rfbport {{VNC_PORT}} -xkb -forever -shared -threads"
autorestart=true
priority=40
stdout_logfile=/app/data/streams/{{STREAM_ID}}/vnc.log
stderr_logfile=/app/data/streams/{{STREAM_ID}}/vnc.log
stdout_logfile=/app/data/logs/{{STREAM_ID}}/vnc.log
stderr_logfile=/app/data/logs/{{STREAM_ID}}/vnc.log
[program:ffmpeg-{{STREAM_ID}}]
command=bash -c "sleep {{STREAM_DELAY}} && ffmpeg \
@@ -85,5 +85,5 @@ command=bash -c "sleep {{STREAM_DELAY}} && ffmpeg \
autorestart=true
startretries=999
priority=60
stdout_logfile=/app/data/streams/{{STREAM_ID}}/ffmpeg.log
stderr_logfile=/app/data/streams/{{STREAM_ID}}/ffmpeg.log
stdout_logfile=/app/data/logs/{{STREAM_ID}}/ffmpeg.log
stderr_logfile=/app/data/logs/{{STREAM_ID}}/ffmpeg.log