A instância no ar
A instância mantida pelo autor roda a mesma imagem que qualquer self-host — o
docker-compose.yml do repositório, num VPS, atrás de um Caddy com TLS. Este documento é o que
ela tem a mais em relação ao INSTALACAO.md, que é o procedimento base e vale aqui inteiro.
O que já funciona
Seção intitulada “O que já funciona”-
Integração contínua em
.github/workflows/ci.yml: a cada push namaine a cada pull request rodampnpm lint,pnpm typecheck,pnpm test, os testes de ação contra um Postgres de verdade,pnpm test:e2e(Chromium, desktop e celular), e a imagem Docker é construída e posta de pé contra um banco novo — migração, página pública com RDKit no servidor, motor comimmutable, script de promoção. O relatório do Playwright sobe como artefato. -
Imagem publicada em
ghcr.io/gabrielmello1407/rotamera cada etiquetav*(.github/workflows/image.yml):git tag v0.3.0git push --tagsSai com as etiquetas
0.3.0,0.3elatest. O pacote nasce privado no GHCR: depois da primeira publicação, torne-o público nas configurações do pacote, no GitHub — sem isso,docker compose pullde quem não tem conta recebedenied.
O que a instância no ar tem a mais
Seção intitulada “O que a instância no ar tem a mais”
1. Domínio
Seção intitulada “1. Domínio”Ainda não confirmado no registrador. Ver a ressalva em DECISOES.md D-07: DNS sem registro
não é o mesmo que disponível no registrador, e disponível no registrador não é o mesmo que livre
no INPI. Enquanto não houver endereço, o README.md não promete link.
2. Caddy na frente, com o motor pré-comprimido
Seção intitulada “2. Caddy na frente, com o motor pré-comprimido”O prebuild grava .br e .gz ao lado de cada arquivo de public/chem/, e é isso que decide a
primeira aula de uma turma inteira — medido, não estimado:
| Arquivo | Original | gzip | brotli |
|---|---|---|---|
RDKit_minimal.wasm |
6753 KB | 1998 KB (−70%) | 1437 KB (−79%) |
ocl-resources.json |
1320 KB | 457 KB | 375 KB |
RDKit_minimal.js |
125 KB | 29 KB | 26 KB |
O servidor do Next comprime em gzip na hora. Para servir o brotli pronto, o Caddy precisa dos arquivos no disco do host — eles saem da própria imagem:
docker compose cp app:/app/apps/web/public/chem ./chemE o Caddyfile:
rotamer.exemplo.br { encode zstd gzip
handle_path /chem/* { root * /srv/rotamer/chem file_server { precompressed br gzip } header Cache-Control "public, max-age=31536000, immutable" }
reverse_proxy 127.0.0.1:3000}Repita o cp a cada atualização que mude a versão do RDKit ou do OpenChemLib — o prebuild
imprime as duas versões na construção.
O immutable resolve a revisita mesmo sem proxy: 605 ms com o motor em cache, contra 6,9 s na
primeira visita. Numa turma de trinta alunos na mesma sala, é a diferença entre a aula começar
e a aula esperar.
3. Backup no cron, com ensaio marcado
Seção intitulada “3. Backup no cron, com ensaio marcado”O VPS tem o cliente do Postgres instalado, e os scripts do repositório rodam do host contra
127.0.0.1:5432, que é onde o compose expõe o banco:
# uma vez por dia, às 3h, pelo crontab do usuário que roda o compose0 3 * * * DATABASE_URL='postgresql://rotamer:<senha>@127.0.0.1:5432/rotamer' BACKUP_REMOTE='usuario@outra-maquina:/backups/rotamer/' /srv/rotamer/scripts/backup-db.sh >> /var/log/rotamer-backup.log 2>&1O script despeja em formato próprio do Postgres, confere que o arquivo abre, manda a cópia para fora da máquina e apaga o que passou de 30 dias. Qualquer passo que falhe aborta com erro — o cron manda o e-mail, e silêncio deixa de ser sinal de que deu certo.
O ensaio, uma vez por mês e sempre depois de mudar o schema:
createdb -h 127.0.0.1 -U rotamer rotamer_ensaio # uma vez; o pg_restore não cria bancoDATABASE_URL='postgresql://rotamer:<senha>@127.0.0.1:5432/rotamer' ./scripts/restore-db.sh /var/backups/rotamer/rotamer-<carimbo>.dumpRestaura em rotamer_ensaio, nunca por cima da produção — para isso é preciso pedir com
RESTORE_CONFIRMO=sim. No fim, o script imprime as três contagens que dizem se o backup vale
alguma coisa: perfis, tentativas e batismos. Backup que nunca foi restaurado é arquivo, não
garantia (D-11).
4. Atualização
Seção intitulada “4. Atualização”cd /srv/rotamerDATABASE_URL='postgresql://rotamer:<senha>@127.0.0.1:5432/rotamer' ./scripts/backup-db.shgit pulldocker compose pulldocker compose up -dAs migrações rodam na subida do container, antes de ele abrir a porta. A ordem é sempre esta:
backup, código novo, subir. Se a migração falhar, o app não sobe e o log diz por quê
(docker compose logs app); o Postgres continua de pé com os dados intactos.
5. Telemetria
Seção intitulada “5. Telemetria”Umami auto-hospedado, em outro compose no mesmo VPS, atrás do mesmo Caddy. Existe porque sessão
de observação sem instrumento vira anedota: dá para ver uma pessoa travar, não dá para saber se
ela é a regra. Liga-se com as duas variáveis do .env; o que é medido, e o que nunca sai, está
em INSTALACAO.md.
6. Contas de professor e de administrador
Seção intitulada “6. Contas de professor e de administrador”Como em qualquer instância: pelo script, dentro do container.
docker compose exec app node scripts/promote-teacher.mjs ana@escola.br --escola "EE Dom Pedro II"docker compose exec app node scripts/promote-teacher.mjs ana@escola.br --escola "EE Dom Pedro II" --administradorQuem dá aula sem escola preenchida não emite nada — o código só vale para alguém da mesma escola, e o script recusa a promoção quando o campo está vazio (D-19).
Na instância no ar, o comando a rodar é o segundo: um administrador por escola. Daí em diante quem coordena promove os próprios professores pela tela, sem passar por aqui — e continua sem poder criar outro administrador, que é o que mantém o controle da instância com quem tem a máquina (D-29).
7. O que olhar quando algo cai
Seção intitulada “7. O que olhar quando algo cai”docker compose ps— o app mostrahealthyquando/marcaresponde.docker compose logs --tail 200 app— migração, subida do Next, erros de rota.- Se a tela ficar em
carregando o motor, o primeiro suspeito é/chem/*: a imagem foi construída sem oprebuild, ou ohandle_pathdo Caddy aponta para uma pasta vazia. - O WASM carrega depois da primeira pintura, dentro do worker. A meta de 3 s para o primeiro desenho num celular fraco em 3G depende de isso continuar assim.
Custos, e o que acontece se não couberem
Seção intitulada “Custos, e o que acontece se não couberem”Um VPS pequeno, um domínio e a chave do Gemini para o tutor. Se um dia não couberem, a instância
encolhe — o tutor desliga primeiro, a instância some por último — e o código não fecha (D-28).
Quem precisa de garantia de disponibilidade sobe a própria instância: é para isso que o
INSTALACAO.md existe.