MVP no ar · vps_4 · 30/07/2026

TBC Suite

Substituto self-hosted de Jira + JSM + Tempo + Planner. Roda na nossa infraestrutura, sem custo por usuário e sem plugin de terceiros — o que falta a gente escreve.

RedmineProjetos, épicos, tarefas, subtarefas, horas, Gantt, wiki≈ Jira Software + Tempo
Quadro KanbanDrag-and-drop, drawer com comentários e anexos, filtros≈ quadro do Jira
GLPI Service DeskPortal do cliente, SLA, e-mail→ticket, base de conhecimento≈ Jira Service Management
PlannerCalendário de alocação: o coordenador planeja, o dev vê e confirma≈ Tempo Planner
Ciclo completoChamado GLPI #4 ↔ issue #28 com comentários e anexosdemo real executada

O que já roda de verdade

Nada aqui é maquete — cada linha foi executada neste ambiente.

CapacidadeEvidência
Hierarquia épico → tarefa → subtarefaÉpico FSWTBC-4991 (5 tarefas) e FSWTBC-4620 (15 subtarefas) importados do Jira real via API, com status e link de voltapronto
Quadro kanbanAutentica pela sessão do Redmine (ninguém digita chave), move status por drag-and-drop, drawer lê comentários e anexospronto
Portal do clienteUsuário cliente.fujioka abriu o chamado pelo portal self-service do GLPI, com anexopronto
Comentários bidirecionaisComentário do cliente virou nota na issue; resposta da engenharia voltou como followup no chamadopronto
Anexos bidirecionaiserro-pdv-se1.txt (cliente → engenharia) e analise-tecnica-fuj028.txt (engenharia → cliente)pronto
Status sincronizadoIssue movida para Em Homologação no Redmine → chamado do cliente foi para Pendente automaticamentepronto
Identidade visualTema TBC com as cores oficiais da TOTVS (#753399, do design system PO-UI), interface em pt-BRpronto
Planner (alocação)Calendário pessoa × dia: o coordenador aloca, o dev vê só o que é dele e confirma ou recusa. Mostra carga da semana e avisa quando passa da jornadapronto
Apontamento de horasNativo do Redmine (time_entries) + custom fields — mesma modelagem do vínculo Tempo↔PSA de hojea validar
Ponte com o PSASincronizar time_entries com o Dynamics; é do que o faturamento dependego/no-go

O ciclo que rodou

Chamado #4 do cliente Fujioka, do portal até a devolutiva — sem ninguém copiar nada entre sistemas.

sequenceDiagram
    autonumber
    actor C as Cliente (portal)
    participant G as GLPI
    participant P as Ponte (2 min)
    participant R as Redmine
    actor T as Time TBC
    C->>G: Abre chamado + anexa log do erro
    P->>R: Cria issue #28 com descrição e anexo
    P->>G: Confirma registro com link da issue
    C->>G: Comenta "aconteceu em 14 vendas"
    P->>R: Vira comentário na issue
    T->>R: Responde, anexa análise técnica
    T->>R: Move para Em Homologação (no quadro)
    P->>G: Publica resposta + anexo no chamado
    P->>G: Chamado passa para Pendente
    G-->>C: Cliente vê tudo no portal

Arquitetura

flowchart LR
    subgraph vps [vps_4 Hostinger · Docker]
        GLPI[GLPI 11
portal · SLA · e-mail→ticket · KB] RM[Redmine 6.1
projetos · horas · wiki · Gantt] BOARD[Quadro Kanban
SPA sobre a REST API] HUB[tbc_sync
ponte + automações] end CLIENTE((Clientes)) -->|portal · e-mail| GLPI TIME((Time TBC)) --> RM & BOARD GLPI <-->|REST API| HUB HUB <-->|REST API| RM HUB -.->|a construir| PSA[(Dynamics 365 PSA
faturamento)] JIRA[(Jira Cloud)] -.->|importador API→API| RM

Como a ponte foi feita

Sem plugin, sem fornecedor: um script Python de ~200 linhas em /root/redmine-mvp/tbc_sync.py, cron a cada 2 minutos.

OpçãoLatênciaSituação
Poller REST — consulta as duas APIs e concilia por id (à prova de eco: a ponte registra o que ela mesma escreve)até 2 minem uso
Webhook nativo do GLPI — dispara na hora do eventosegundospróximo
Micro-plugin no Redmine — ~50 linhas Ruby, after_save → webhook (tempo real na volta)segundosfase 2
Motor de automação — regras YAML (gatilho → condição → ação): SLA, auto-resposta, transições, aviso no chatfase 2

Segurança — o que foi atacado e corrigido

Um agente revisor com contexto zerado tentou quebrar o ambiente. O que ele achou virou correção.

AchadoCorreção
XSS no quadro kanban — o título da tarefa era injetado via innerHTML; um assunto com <img onerror> executava script no navegador de quem abrisse o quadroQuadro reescrito: todo dado de usuário entra por textContent. Reteste com o mesmo payload confirma que agora vira texto escapadocorrigido
Auto-registro aberto — qualquer pessoa na internet criava conta no Redmine em /registerAuto-registro desabilitado (/register devolve 404) e login passou a ser obrigatório (anônimo é redirecionado)corrigido
Contas padrão do GLPI (tech, normal, post-only) e senha glpi/glpiContas desativadas e senha trocada — as quatro combinações padrão foram testadas e todas falhamcorrigido
Sem backupBackup diário às 03:17: dump dos dois bancos + volumes, retenção de 7 dias. Restauração testada: os dumps foram restaurados em bancos descartáveis e conferidos (24 issues e 2 projetos no Redmine; 1 chamado, 6 usuários e 5 documentos no GLPI; 1.050 arquivos no volume)corrigido
Sem HSTS e segredos legíveis por outros usuários do hostHSTS ativo nos dois domínios; .env e sync.env em modo 600corrigido
Superfície expostaVerificado de fora: só 80/443 respondem. Portas de aplicação e de banco (8090, 8091, 5433, 6380, 3306) estão fechadas. Certificado renova sozinho pelo timer do certbotconfirmado

O que ainda não está pronto

Honestidade importa mais que demo bonita.

PendênciaImpacto
Ponte de horas com o PSA (Dynamics)É o go/no-go do projeto: o fechamento mensal depende dela. Precisa rodar em paralelo ao Tempo por um ciclo inteiro antes de qualquer corte.
SLA configurado no GLPIO GLPI tem SLA nativo, mas ainda não parametrizamos as regras da TBC.
E-mail → ticketNativo nos dois; falta apontar a caixa postal e testar com um cliente real.
Importação completa do JiraImportamos 1 épico + 20 issues para provar o caminho. Faltam as 6.400 restantes, automações, dashboards e filtros salvos.
Backup fora da vps_4Hoje o backup mora no mesmo servidor que ele protege. Se a VPS morrer, morre junto — falta enviar para o Drive ou outra VPS.