Versão: v2.0.0 · 2026-07-03 Status: Backup (full+incremental+individual+S3 off-site) + Delegated + Sharing + Monitor funcionais.
Painel admin leve (Go binary, ~30MB) pra Carbonio CE — escrito pra rodar junto com o Carbonio no mesmo host (precisa chamar zmmailbox/zmprov que vivem em /opt/zextras/bin/).
| Módulo | URL | Status |
|---|---|---|
| Dashboard | /dashboard.html |
✅ funcional |
| Monitor | /monitor.html |
✅ funcional (Prometheus + JSON) |
| Sharing | /sharing.html |
✅ funcional |
| Delegated | /delegated.html |
✅ funcional (full-setup one-shot) |
| Backup | /backup.html |
✅ funcional — 5 abas: Executar / Individual / Sessões / Restaurar / Logs |
O nchub chama binários do Carbonio (zmmailbox, zmprov, ldapsearch) que vivem em /opt/zextras/bin/. Esses binários precisam rodar como o user zextras (uid 996) porque:
- Os mailbox stores ficam em
/opt/zextras/store/0/<user>/com ownershipzextras:zextras - O
zmmailboxprecisa abrir esses arquivos com permissão de escrita - O
zmprovprecisa de permissão pra escrever no LDAP
| Modo | Como o daemon roda | Como chama zmmailbox |
Quando usar |
|---|---|---|---|
| Bare-metal (recomendado) | root no host | su - zextras -c "..." interno |
Servidor dedicado Carbonio |
| Docker (alternativo) | uid 996 (zextras) no container |
direto (já é zextras) | Ambiente isolado / reproduzível |
Bare-metal setuid |
zextras direto |
direto | Mais raro, evita o su |
O nchub usa o modo bare-metal com su - zextras -c "..." — mesmo padrão de qualquer software Carbonio nativo (Zextras Suite, etc).
curl -L https://raw.githubusercontent.com/nextcoreti/nchub/main/scripts/install.sh | sudo bashO script:
- Detecta o Carbonio automaticamente
- Detecta o admin (
zextras@<hostname>por padrão) - Pede a senha do admin (input oculto)
- Baixa o binary (da release do GitHub, ou compila do source se não tiver release)
- Instala em
/opt/nchub/ - Sobe o daemon
- Valida que tá respondendo
- Oferece instalar systemd unit pra boot automático
Pré-requisitos no servidor Carbonio:
- Carbonio CE já instalado e funcional
- Go instalado (só se não tiver release no GitHub) —
apt install golangouyum install golang - Conexão com a internet (pra baixar do GitHub)
Pra rodar diretamente no servidor Carbonio, sem Docker:
# Pré-requisito: Carbonio CE já instalado e funcional
# (zmmailbox, zmprov, LDAP em :389, admin user criado)
# 1. Build (no seu Mac ou CI)
cd nchub
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w" -o dist/nchubd-v2.0.0 ./cmd/nchubd/
# 2. Copia pro servidor
scp dist/nchubd-v2.0.0 root@CARBONIO:/tmp/
# 3. No servidor, instala
ssh root@CARBONIO
VERSION=v2.0.0
mkdir -p /opt/nchub/versions/$VERSION
mv /tmp/nchubd-v2.0.0 /opt/nchub/versions/$VERSION/nchubd
chmod 755 /opt/nchub/versions/$VERSION/nchubd
ln -sf /opt/nchub/versions/$VERSION/nchubd /opt/nchubd
mkdir -p /opt/nchub/backup
# 4. Detecta o admin Carbonio (ajusta pro SEU servidor)
# Tipicamente: zextras@<hostname> com a senha que você definiu no install
HOSTNAME=$(hostname -f)
ADMIN_USER="zextras@${HOSTNAME%%.*}.com" # ajuste aqui
# 5. Sobe o daemon
nohup /opt/nchubd \
-addr=:1955 \
-interval=120s \
-db=/var/lib/nchub/history.db \
-carbonio-url=https://127.0.0.1:7071 \
-carbonio-mailbox-url=https://127.0.0.1:8443 \
-carbonio-user="$ADMIN_USER" \
-carbonio-pass='SENHA_ADMIN_CARBONIO' \
</dev/null >/var/log/nchubd.log 2>&1 &
# 6. Valida
sleep 3
curl -s http://localhost:1955/healthz
# Saída esperada: ok
tail /var/log/nchubd.logNÃO commita a senha no git nem em dotfiles! Use:
- Variável de ambiente
NCHUB_CARBONIO_PASS=... - Ou arquivo
~/.config/nchub/passwdcom perm 600 - Ou Vault / 1Password CLI
Pro usuário final (no servidor Carbonio):
sudo nchubd-update # atualiza pra última versão do GitHub
sudo nchubd-update v2.3.0 # atualiza pra uma versão específica
sudo nchubd-update --rollback # volta pra versão anterior se der ruimO comando nchubd-update é instalado em /usr/local/bin/ durante o install.sh. Ele:
- Detecta versão atual e versão alvo
- Baixa o binary (da release do GitHub, ou compila do source se não tiver release)
- Para o daemon, swap do symlink, restart
- Valida
/healthz— se não responder, faz rollback automático - Mantém a versão anterior em
versions/<v>/nchubd(pra rollback manual)
Pro dev (no seu Mac/CI):
cd nchub
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w" -o dist/nchubd-v2.3.0 ./cmd/nchubd/
sshpass -p 'SENHA' scp dist/nchubd-v2.3.0 root@CARBONIO:/opt/nchub/versions/v2.3.0/nchubd
ssh root@CARBONIO "
pkill -9 nchubd; sleep 1
ln -sf /opt/nchub/versions/v2.3.0/nchubd /opt/nchubd
systemctl restart nchubd || nohup /opt/nchubd [flags] &
"Crie uma release no GitHub (gh release create v2.3.0 dist/nchubd-v2.3.0) pra que o nchubd-update consiga baixar o binary direto (sem precisar compilar).
Não use Docker se você tem um servidor Carbonio dedicado. O modo bare-metal é mais simples e mais debugável. Docker só vale a pena em cenários específicos listados abaixo.
Quando considerar Docker:
- Você roda múltiplas instâncias do nchub num mesmo host (multi-tenant SaaS)
- Você quer CI/CD reproduzível com imagem versionada no registry
- O host NÃO é o servidor Carbonio (cenário raro, exige NFS/SMB pros mailbox stores)
Pré-requisito: o container precisa do bind mount do Carbonio porque chama zmmailbox/zmprov do host. Não é Docker "puro" — é container com acesso controlado ao host.
# 1. Clone o repo
git clone git@github.com:nextcoreti/nchub.git
cd nchub
# 2. Descobre o uid do zextras no host (default 996, mas confirme)
id -u zextras
# Saída: 996 (ou outro — use esse número)
# 3. Cria o diretório de backup persistente
sudo mkdir -p /opt/nchub/backup
sudo chown 996:996 /opt/nchub/backup
# 4. Edita docker-compose.yml e ajusta:
# - user: "996:996" (se uid do zextras for diferente)
# - NCHUB_CARBONIO_USER=seu_admin@seudominio.com
# - NCHUB_CARBONIO_PASS='sua_senha'
# - ports: 1955:1955 (ou outra se 1955 já tá em uso)
# 5. Sobe
docker compose up -d
# 6. Valida
docker compose ps
curl -s http://localhost:1955/healthz
docker compose logs nchubd | tail -20services:
nchubd:
user: "996:996" # roda como zextras (dono dos mailbox stores)
read_only: true # filesystem read-only
cap_drop: [ALL] # sem capabilities desnecessárias
cap_add: [NET_BIND_SERVICE] # só bind de porta
volumes:
- /opt/zextras:/opt/zextras:ro # binários Carbonio (read-only)
- /opt/nchub/backup:/opt/nchub/backup # backup persistente
- /etc/passwd:/etc/passwd:ro # user zextras precisa existir
- /etc/group:/etc/group:ro
- /proc:/proc:ro # métricas CPU/mem do hostPor que user: "996:996"? Pra o processo dentro do container ser o mesmo uid do zextras no host. Como o /opt/zextras é bind mount (mesmo filesystem), os arquivos aparecem com o uid 996 → o zmmailbox consegue abrir.
Segurança do container:
- ✅ Read-only filesystem (impossível escrever fora dos volumes)
- ✅ Sem capabilities extras (não pode mount/iptables/etc)
- ✅
no-new-privileges(não pode escalar privilégio) - ✅
apparmor=nchubd(perfil AppArmor restritivo)
| Aspecto | Bare-metal | Docker |
|---|---|---|
| Onde roda o daemon | /opt/nchubd (symlink) |
container nchubd |
Como chama zmmailbox |
su - zextras -c "..." (root → zextras) |
direto (já é zextras) |
| Permissões | root no host | zextras (uid 996) no container |
| Bind mount necessário | não | sim (/opt/zextras ro) |
| Backup persistente | /opt/nchub/backup no host |
/opt/nchub/backup (volume) |
| Rollback | troca symlink + restart | docker compose down && up com tag anterior |
| Mais seguro | não (root total no host) | sim (read-only, no caps) |
| Endpoint | Descrição |
|---|---|
GET /healthz |
Health check |
GET /metrics |
Prometheus metrics |
GET /dashboard.html |
Dashboard |
GET /backup.html |
Painel backup (5 abas) |
GET /api/v1/backup/sessions |
Lista sessões (full+incremental+individual) |
POST /api/v1/backup/start |
Inicia backup full/incremental {type, domain?} |
POST /api/v1/backup/single |
Backup individual {email} |
GET /api/v1/backup/single/list |
Lista backups individuais |
GET /api/v1/backup/single/download?id=X |
Baixa TGZ de 1 backup individual |
GET /api/v1/backup/single/zip?id=X |
Baixa TGZ+LDIF juntos (ZIP) |
POST /api/v1/backup/single/restore |
Restaura 1 backup pra conta destino {sessionID, sourceEmail, targetEmail, type} |
GET /api/v1/backup/session?id=X |
Detalhes de uma sessão |
POST /api/v1/backup/restore |
Restaura sessão (separate/over) |
GET/PUT /api/v1/backup/config |
Config agendamento (cron + keep_days) |
GET /api/v1/logs?n=50 |
Tail do log |
GET /api/v1/delegated/full-setup |
Aplica grants + cartBlanche |
nchubd (Go binary, ~30MB)
├── cmd/nchubd/
│ ├── main.go # daemon + HTTP + embed.FS
│ ├── backup.html # 5 abas: Executar / Individual / Sessões / Restaurar / Logs
│ ├── delegated.html # painel delegated
│ ├── sharing.html
│ ├── dashboard.html / monitor.html
│ ├── sidebar.html
│ └── nchub.css
├── internal/
│ ├── carbonio/ # SOAP + zmprov fallback
│ ├── delegated/ # SetDelegated, GetRights, cartBlanche
│ ├── sharing/ # COS / grants de domínios
│ ├── monitor/ # collector Prometheus
│ ├── backup/ # full+incremental+individual, scheduler, rotation, restore
│ ├── s3storage/ # aws-sdk-go-v2 (S3 / iDrive E2 / Backblaze / R2 / Wasabi)
│ └── storage/ # SQLite (history.db + sessions.sqlite3)
├── Dockerfile # multi-stage Go → Alpine
├── docker-compose.yml # bind mount Carbonio + volume backup
└── docs/ # 2FA-IMPLEMENTATION-PLAN.md, etc
- Full: mailbox TGZ (
zmmailbox getRestURL) + LDAP LDIF por conta - Incremental:
query=after:"YYYY-MM-DD HH:MM:SS"no mesmo getRestURL - Scheduler: roda 1x/min, decide full/incremental via cron parser (crones separados: full + incremental)
- Individual (sob demanda): aba "👤 Individual" — escolhe 1 conta, faz backup full, lista de backups anteriores com download TGZ/ZIP
- Restore: 2 modos —
over(sobrescreve conta destino) eseparate(cria conta nova no LDAP se não existir) - Rotação: deleta fulls >
full_keep_days(mantém 1) + incrementais >incremental_keep_days - Cleanup: ao subir, marca FAILED sessões IN PROGRESS órfãs
- Config: cron + keep_days + ativo/pausado via PUT /api/v1/backup/config
Storage remoto S3-compatible pra disaster recovery (servidor queimou, ransomware, etc). Não use storage local (mesmo HD) — é o mesmo que fazer backup na própria pasta. S3 é pra OFF-SITE.
Providers suportados (via aws-sdk-go-v2):
- AWS S3
- Backblaze B2 (S3-compatible API, ~$0.006/GB/mês — 4x mais barato que AWS)
- Cloudflare R2 (egress grátis)
- Wasabi (sem cobrança de egress)
- DigitalOcean Spaces
- iDrive E2 (validado em produção — endpoint
s3j2.da.idrivee2-20.com) - Qualquer S3-compatible (MinIO, etc)
Endpoints (7):
GET/PUT /api/v1/backup/s3/configPOST /api/v1/backup/s3/test— testa credenciais, lista bucketsPOST /api/v1/backup/s3/sync?session=X— upload de 1 sessãoGET /api/v1/backup/s3/list— lista objetos no bucketPOST /api/v1/backup/s3/restore?session=X— download pro localPOST /api/v1/backup/s3/delete?session=X— apaga sessão
Hook auto-sync: se auto_sync=true na config, todo backup que termina com sucesso sobe pro S3 em background (goroutine separada, não bloqueia).
Configuração típica (Backblaze B2 — mais barato):
- Endpoint:
https://s3.us-west-001.backblazeb2.com - Region:
us-west-001 - Bucket:
nchub-acme-backups - Access Key: Application Key (criar no painel B2 com scope restrito)
- Storage Class: STANDARD (hot) ou Standard-IA (após 30+ dias)
- Auto-sync: ON
Storage classes AWS S3 (tier automático):
STANDARD— $0.023/GB/mês, acesso imediatoSTANDARD_IA— $0.0125/GB/mês, mínimo 30 diasGLACIER— $0.004/GB/mês, restore em minutosDEEP_ARCHIVE— $0.00099/GB/mês, restore 12h
| Nome | Tipo | Descrição |
|---|---|---|
nchub_up |
gauge | Collector respondendo |
nchub_cpu_percent |
gauge | CPU% atual |
nchub_memory_bytes{type="used|total"} |
gauge | Memória |
nchub_disk_bytes{type="used|total",mount="/"} |
gauge | Disco |
nchub_load_avg |
gauge | Load 1min |
nchub_mail_queue |
gauge | Mensagens na fila |
nchub_services_total{status="running|down"} |
gauge | Serviços Carbonio |
nchub_accounts_total |
gauge | Contas detectadas |
nchub_mail_sent_24h |
gauge | Emails enviados 24h |
nchub_mail_received_24h |
gauge | Emails recebidos 24h |
nchub_mail_rejected_24h |
gauge | Emails rejeitados 24h |
nchub_last_collect_timestamp |
gauge | Última coleta |
- ✅ Container read-only (
read_only: true) - ✅
cap_drop: [ALL]+cap_add: [NET_BIND_SERVICE] - ✅
no-new-privileges - ✅ Binário com
chattr +i(imutável, bare-metal) - ✅ Build com
-ldflags="-s -w"(strip symbols) - ✅
-trimpath(path limpo) - ✅ CGO_ENABLED=0 (static binary)
- ✅ Bind mount
/opt/zextrasread-only (Docker) - ✅ Roda como
zextras(uid 996) no Docker (sem root)
- Módulo Backup (full + incremental + individual)
- Scheduler + rotation
- Restore Separate/Over
- Restore individual com escolha de destino
- Storage S3 / Backblaze / R2 / Wasabi / iDrive E2
- Módulo Fail2ban policies
- WebAuthn / 2FA no login
- API REST autenticada
- License server + HW fingerprint