npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pacforge

v0.2.1

Published

Orquestrador e painel self-hosted para múltiplas instâncias do PocketBase.

Readme

⚒️ PacForge

PacForge é um orquestrador e painel de gerenciamento self-hosted para múltiplas instâncias do PocketBase. Projetado para ser leve, seguro e com uma UX excepcional, permite que desenvolvedores gerenciem bancos de dados BaaS com o mesmo conforto de plataformas SaaS, mas rodando em sua própria infraestrutura.

License: MIT Node.js Version Tests


✨ Destaques

  • Node.js nativo — sem React, sem Vue, sem build steps complexos. Apenas http, child_process, fs e better-sqlite3.
  • Multi-instância — cada projeto PocketBase roda como um processo isolado em 127.0.0.1:<porta_aleatória>, nunca exposto diretamente à internet.
  • Reverse proxy transparente — roteamento por subdomínio (app1.seudominio.com) ou por path (/i/app1/) para dev local sem DNS wildcard.
  • Download automático do PocketBase — na primeira execução, o binário é baixado automaticamente do GitHub releases, com detecção de OS/arquitetura.
  • Auto-restart com backoff exponencial — se uma instância crashear, ela é reiniciada automaticamente (1s, 2s, 4s, 8s, 16s), até 5 tentativas.
  • Painel SaaS-like — dark mode com glassmorphism, terminal embutido (xterm.js) com logs em tempo real via WebSocket, toasts, modais e micro-interações.
  • Seguro por padrão — bcrypt para senhas, JWT com expiração curta (15 min), rate limiting no login (5 tentativas / 15 min), regex estrita para nomes de instância.
  • CLI completopacforge start, instances:create, admin:create, etc. Para administração via SSH/CI sem expor o painel.

📑 Sumário

  1. Início Rápido
  2. Arquitetura
  3. CLI Reference
  4. API Reference
  5. Configuração
  6. Deploy
  7. Desenvolvimento
  8. Testing & Quality Assurance
  9. Troubleshooting & FAQ
  10. Contributing
  11. License

🚀 Início Rápido

Pré-requisitos

  • Node.js >= 18 (recomendado 20+)
  • unzip instalado no sistema (para extrair o binário PocketBase no primeiro run)
  • Linux, macOS ou Windows

Instalação e primeiro run

# 1. Clone o repo
git clone https://github.com/pacdt/pacforge.git
cd pacforge

# 2. Instale dependências
npm install

# 3. Build do frontend (Tailwind CSS + cópia dos JS)
npm run build

# 4. Crie um usuário admin para o painel
node bin/cli.js admin:create --email [email protected] --password S3nh4F0rt3

# 5. Inicie o Master Process em background
node bin/cli.js start --daemon

# 6. Abra o painel no navegador
#    http://localhost:4001

Criando sua primeira instância

# Via CLI
node bin/cli.js instances:create meu-app
# → Instance created: meu-app (port 38211)
#   URL: http://meu-app.localhost:4001
#   Admin UI: http://meu-app.localhost:4001/_/

# Ou via painel web: botão "New Instance" no canto superior direito

Acesse http://meu-app.localhost:4001/_/ para configurar o superusuário do PocketBase dessa instância (ou use o botão "Reset Superuser" no card do painel).


🏗️ Arquitetura

O PacForge opera como um Master Process em Node.js que gerencia múltiplos Child Processes do binário PocketBase.

flowchart TD
    subgraph Master ["PacForge Master (Node.js)"]
        R["Router<br>(Proxy)"]
        PM["Process<br>Manager"]
        AP["Admin Panel<br>(HTTP + WS)"]

        HS["HTTP Server<br>(Port 4001)"]
        SS["State Store<br>(SQLite)"]
        TUI["Tailwind UI<br>(Vanilla JS)"]

        R ==> HS
        PM ==> SS
        AP ==> TUI
    end

    PB1[/"PocketBase Proc<br/>(Inst: app1)<br/>127.0.0.1:32111"\]
    PB2[/"PocketBase Proc<br/>(Inst: app2)<br/>127.0.0.1:32122"\]

    HS -.-> PB1
    SS -.-> PB2

    classDef default fill:#1e293b,stroke:#475569,stroke-width:2px,color:#f8fafc,rx:8px,ry:8px;
    classDef master fill:#0f172a,stroke:#334155,stroke-width:2px,color:#e2e8f0;
    classDef proc fill:#0369a1,stroke:#0284c7,stroke-width:2px,color:#ffffff,rx:12px,ry:12px;

    class Master master;
    class PB1,PB2 proc;

Como funciona o roteamento

O Master Process escuta em PACFORGE_PORT (default 4001). Ele analisa o Host header:

| Host | Path | Ação | | ------------------------ | ------------- | ----------------------------------------------- | | localhost:4001 | / | Serve o painel HTML | | localhost:4001 | /api/* | API REST interna | | localhost:4001 | /auth/* | Rotas de autenticação | | localhost:4001 | /ws/logs/* | WebSocket para terminal de logs | | localhost:4001 | /i/<name>/* | Path-based fallback: proxy para a instância | | meu-app.localhost:4001 | /* | Subdomain: proxy para a instância meu-app | | meu-app.seudominio.com | /* | Subdomain: proxy para a instância meu-app |

Segurança: as instâncias do PocketBase NUNCA escutam em 0.0.0.0. Elas rodam estritamente em 127.0.0.1:<porta_aleatoria>. O único exposto à internet é o Master Process.

Estrutura de arquivos

pacforge/
├── bin/
│   └── cli.js              # Entry point CLI (start, stop, admin:create, ...)
├── src/
│   ├── master.js           # Servidor HTTP + WebSocket + API REST
│   ├── processManager.js   # Spawn/kill/restart do PocketBase + auto-restart
│   ├── router.js           # Proxy reverso por subdomínio + path-based
│   ├── database.js         # Camada SQLite (State Store)
│   ├── security.js         # bcrypt + JWT + regex + rate limiter
│   └── logger.js           # Logger com níveis (debug/info/warn/error)
├── frontend/
│   ├── src/
│   │   ├── main.js         # Lógica principal do painel (login, dashboard)
│   │   ├── components.js   # Builders de DOM (cards, modais, toasts)
│   │   └── styles.css      # Tailwind directives + custom CSS
│   └── dist/               # CSS compilado (gerado pelo build)
├── public/
│   ├── index.html          # HTML do painel
│   └── assets/             # CSS + JS copiados pelo build (servidos estaticamente)
├── test/
│   ├── security.test.js    # Unit tests: bcrypt, JWT, regex, rate limiter
│   ├── database.test.js    # Unit tests: SQLite schema e CRUD
│   ├── router.test.js      # Unit tests: host parsing, classify, path matching
│   ├── processManager.test.js # Integration: download PB, provision/destroy
│   └── master.test.js      # Integration: HTTP server, API, proxy reverso
├── scripts/
│   └── build-frontend.js   # Copia frontend/src/*.js → public/assets/
├── deploy/
│   └── pacforge.service    # systemd unit file
├── Dockerfile              # Multi-stage build
├── .eslintrc.json          # ESLint config (standard)
├── .prettierrc.json        # Prettier config
├── tailwind.config.js      # Tailwind config (cores, fontes, animações)
└── package.json

State Store (SQLite)

O PacForge mantém um banco SQLite em ~/.pacdt/pacforge.db com 3 tabelas:

  • users — usuários autorizados a acessar o painel (email, password_hash bcrypt, role)
  • instances — metadados das instâncias PocketBase (id, name, port, status, pid)
  • settings — config global (jwt_secret, etc.)

Os dados das instâncias PocketBase em si (collections, records, auth) ficam em ~/.pacforge/data/<name>/pb_data/ — o PacForge nunca toca nesse diretório exceto para deletá-lo quando você pede para remover a instância.


🖥️ CLI Reference

pacforge <command> [options]

Servidor

| Comando | Descrição | | ---------------- | ---------------------------------------------------- | | start | Inicia o Master Process em foreground | | start --daemon | Inicia como daemon em background (escreve PID file) | | stop | Para um daemon em execução (SIGTERM, depois SIGKILL) | | status | Mostra status do daemon (PID, porta, apex) |

Usuários do painel

| Comando | Descrição | | ------------------------------------- | ------------------------ | | admin:create --email E --password P | Cria um usuário admin | | admin:list | Lista usuários do painel | | admin:delete --email E | Remove um usuário |

Instâncias

| Comando | Descrição | | ----------------------------------- | ----------------------------------------------------- | | instances:list | Lista todas as instâncias (name, status, port, pid) | | instances:create <name> | Cria e inicia uma nova instância | | instances:delete <name> [--force] | Para e deleta permanentemente a instância + dados | | instances:start <name> | Inicia uma instância parada | | instances:stop <name> | Para uma instância em execução | | instances:restart <name> | Reinicia uma instância |

Binário PocketBase

| Comando | Descrição | | ------------- | --------------------------------------- | | pb:download | Força re-download do binário PocketBase |

Exemplos

# Criar usuário admin
pacforge admin:create --email [email protected] --password S3nh4F0rt3

# Iniciar como daemon
pacforge start --daemon

# Criar instância via CLI (útil para CI/SSH)
pacforge instances:create blog-prod
# → Instance created: blog-prod (port 38211)
#   URL: http://blog-prod.localhost:4001

# Listar
pacforge instances:list
# → Instances:
#     blog-prod                   running    port=38211 pid=12345  http://blog-prod.localhost:4001

# Deletar (com confirmação)
pacforge instances:delete blog-prod --force

🔌 API Reference

Todas as rotas (exceto /auth/login) exigem header Authorization: Bearer <JWT>.

Autenticação

POST /auth/login

Autentica e retorna um JWT (válido por 15 minutos).

POST /auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "securepassword"
}
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "expiresIn": 900,
  "user": { "email": "[email protected]", "role": "admin" }
}

Rate limited: 5 tentativas por IP a cada 15 minutos.

GET /auth/me

Retorna o usuário autenticado pelo token atual.

Instâncias

GET /api/instances

Lista todas as instâncias com status em tempo real, uso de disco e URL pública.

[
  {
    "id": "uuid-1234",
    "name": "my-app",
    "status": "running",
    "port": 32111,
    "pid": 12345,
    "size": "12.3M",
    "url": "http://my-app.localhost:4001",
    "createdAt": "2026-08-05 10:00:00",
    "updatedAt": "2026-08-05 10:30:00"
  }
]

POST /api/instances

Cria e provisiona uma nova instância. O nome deve passar pela regex ^[a-z0-9](?!.*--)[a-z0-9-]*[a-z0-9]$ (2–32 chars, sem hifens duplos).

POST /api/instances
Authorization: Bearer <token>
Content-Type: application/json

{ "name": "new-project" }
{
  "id": "uuid-5678",
  "name": "new-project",
  "status": "running",
  "port": 32122
}

DELETE /api/instances/:name

Para o processo, apaga a pasta pb_data e remove o registro do banco. Irreversível.

DELETE /api/instances/new-project
Authorization: Bearer <token>

204 No Content

Gerenciamento de Processos

| Endpoint | Método | Descrição | | ---------------------------------- | ------ | -------------------------------------- | | /api/instances/:name/start | POST | Inicia o processo (se parado) | | /api/instances/:name/stop | POST | Envia SIGTERM (depois SIGKILL após 5s) | | /api/instances/:name/restart | POST | Stop + start | | /api/instances/:name/logs?tail=N | GET | Histórico de logs (últimas N linhas) |

Administração do PocketBase

POST /api/instances/:name/admin

Cria ou atualiza o superusuário do PocketBase via CLI do binário (pocketbase superuser upsert).

POST /api/instances/my-app/admin
Authorization: Bearer <token>
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "anothersecurepassword"
}
{ "ok": true, "message": "Superuser upserted successfully." }

WebSocket: Terminal de logs

WS /ws/logs/<instance-name>?token=<JWT>

Streaming ao vivo de stdout + stderr do PocketBase. Histórico das últimas 1000 linhas é replayado ao conectar. Usado pelo terminal embutido (xterm.js) no painel.


⚙️ Configuração

Todas as configurações são via variáveis de ambiente. Veja .env.example para o template completo.

| Variável | Descrição | Padrão | | -------------------------- | ------------------------------------------------------- | ------------- | | PACFORGE_PORT | Porta do Master Process (painel + proxy) | 4001 | | PACFORGE_HOST | Host/interface para bind (0.0.0.0 para expor) | 0.0.0.0 | | APEX_DOMAIN | Domínio base para roteamento de subdomínios | localhost | | PACFORGE_HOME | Diretório base de dados (SQLite + binário + instâncias) | ~/.pacforge | | PB_VERSION | Versão do binário PocketBase | 0.22.14 | | JWT_SECRET | Chave JWT (auto-gerada se vazio, persistida no SQLite) | (auto) | | LOG_LEVEL | debug | info | warn | error | info | | ENABLE_HTTPS | Habilita HTTPS com Let's Encrypt (em produção) | false | | PACFORGE_DEV_AUTH_BYPASS | Bypass de auth em dev (NUNCA usar em prod) | false |

Estrutura de diretórios em PACFORGE_HOME

~/.pacforge/
├── pacforge.db            # State Store SQLite
├── pacforge.db-journal    # WAL journal (auto)
├── pacforge.pid           # PID file (quando rodando como daemon)
├── bin/
│   └── pocketbase         # Binário PB baixado automaticamente
└── data/
    ├── meu-app/           # Dados da instância "meu-app"
    │   ├── pb_data/       # Banco SQLite do PocketBase (NUNCA tocar manualmente)
    │   └── ...            # Outros arquivos do PB
    └── outro-app/
        └── pb_data/

🐳 Deploy

Opção 1: NPM (desenvolvedores)

npm install -g pacforge
pacforge admin:create --email [email protected] --password S3nh4F0rt3
pacforge start --daemon

Opção 2: Docker

# Build
docker build -t pacforge .

# Run com volume persistente
docker run -d \
  --name pacforge \
  -p 4001:4001 \
  -v pacforge-data:/data \
  -e APEX_DOMAIN=seudominio.com \
  -e JWT_SECRET=$(openssl rand -hex 32) \
  pacforge

# Criar usuário admin (após container estar rodando)
docker exec -it pacforge node bin/cli.js admin:create --email [email protected] --password S3nh4F0rt3

O Dockerfile é multi-stage: build do Tailwind/JS em um estágio, runtime enxuto em outro. Usa tini como init para tratar SIGTERM corretamente.

Opção 3: Systemd (bare metal Linux)

# Instalar globalmente
sudo npm install -g pacforge

# Criar usuário do sistema
sudo useradd -r -s /bin/false -d /var/lib/pacforge pacforge
sudo mkdir -p /var/lib/pacforge
sudo chown pacforge:pacforge /var/lib/pacforge

# Instalar service
sudo cp deploy/pacforge.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now pacforge

# Ver logs
sudo journalctl -u pacforge -f

O service file inclui hardening systemd: NoNewPrivileges, ProtectSystem=full, ProtectKernelTunables, etc.

Opção 4: Atrás de Nginx/Caddy (recomendado em produção)

Em produção, recomendamos colocar o PacForge atrás de um reverse proxy (Nginx, Caddy, Traefik) que trata TLS e passa os headers corretamente:

server {
    listen 443 ssl http2;
    server_name *.seudominio.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:4001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400;
    }
}

🛠️ Desenvolvimento

Setup

git clone https://github.com/pacdt/pacforge.git
cd pacforge
npm install
npm run build  # build inicial do CSS + JS

Modo dev com hot reload

# Terminal 1: watch do Tailwind CSS
npm run build:css:dev

# Terminal 2: master com --watch (reinicia em mudanças no src/)
npm run dev

Scripts NPM

| Script | Descrição | | ------------------- | --------------------------------------------------- | | npm start | Inicia o master em foreground | | npm run dev | Inicia com node --watch (hot reload do backend) | | npm run build | Build completo: Tailwind CSS + cópia dos JS | | npm run build:css | Apenas Tailwind (compila + minifica) | | npm run build:js | Apenas copia frontend/src/*.jspublic/assets/ | | npm test | Roda todos os testes (unit + integration) | | npm run lint | ESLint | | npm run format | Prettier |

Estrutura do frontend

O frontend é Vanilla JS com ES modules — sem bundler. O scripts/build-frontend.js apenas copia frontend/src/*.js para public/assets/ (renomeando main.jsapp.js para casar com o <script src> do HTML).

Bibliotecas externas (xterm.js) são carregadas via CDN no index.html. Se você precisa de offline total, baixe os arquivos do CDN para public/assets/vendor/ e ajuste as tags <script>/<link>.


🧪 Testing & Quality Assurance

O projeto usa o Node.js Native Test Runner (node:test), sem dependências externas de teste.

Rodar os testes

# Todos os testes
npm test

# Apenas unit tests (rápido, sem download de binário)
node --test test/security.test.js test/database.test.js test/router.test.js

# Apenas integration tests (baixa PocketBase real na primeira execução)
node --test test/processManager.test.js test/master.test.js

# Watch mode
npm run test:watch

Cobertura dos testes

| Arquivo | Tipo | Cobertura | | ----------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | test/security.test.js | Unit | isValidName, bcrypt hash/verify, JWT issue/verify, extractBearer, RateLimiter, safeEqual | | test/database.test.js | Unit | Schema init, user CRUD, instance CRUD, settings upsert, JWT secret persistence | | test/router.test.js | Unit | parseHost (apex, subdomain, localhost, case), matchPathInstance, classify | | test/processManager.test.js | Integration | getFreePort, detectPlatform, download do binário real, provision + destroy lifecycle, LogBus pub/sub | | test/master.test.js | Integration | Boot do servidor, login JWT, auth middleware (401 sem token), create/list/stop/delete instance, reverse proxy alcançando PocketBase real, rate limiting |

Resultado atual: 56 testes, 56 passando.

Linting e formatação

  • ESLint com config standard
  • Prettier com semi: false, singleQuote: true
  • Husky + lint-staged rodam ESLint + Prettier nos arquivos staged antes de cada commit
npm run lint       # check
npm run lint:fix   # auto-fix
npm run format     # prettier em tudo

🩺 Troubleshooting & FAQ

Q: Criei uma instância, mas ao acessar http://app.localhost:4001/_/ recebo "Bad Gateway".

R: O Master Process não conseguiu se comunicar com o processo interno do PocketBase. Possíveis causas:

  1. Binário sem permissão de execução — rode chmod +x ~/.pacforge/bin/pocketbase
  2. Porta interna colidiu e o processo crashou — verifique os logs no terminal embutido do painel
  3. Download do binário falhou — rode pacforge pb:download para forçar re-download
  4. unzip não instalado — instale via apt install unzip (Linux) ou brew install unzip (macOS)

Q: Como faço para atualizar a versão do PocketBase de uma instância?

R:

  1. Pare o PacForge: pacforge stop
  2. Altere PB_VERSION no ambiente (ex: PB_VERSION=0.23.0)
  3. Force re-download: pacforge pb:download
  4. Inicie o PacForge: pacforge start --daemon
  5. Reinicie cada instância pelo painel (ou pacforge instances:restart <name>)

O PocketBase lida com migrações automáticas de banco ao iniciar com um binário mais novo.

Q: Posso usar o PacForge atrás de um Nginx ou Caddy?

R: Sim, e é recomendado em produção. Veja a seção Deploy → Opção 4. O PacForge usa o header Host para rotear para a instância correta, então o Nginx precisa passar proxy_set_header Host $host;.

Q: Os dados das minhas instâncias somem quando reinicio o servidor.

R: Verifique:

  • Docker sem volume: mapeie -v pacforge-data:/data (ou o PACFORGE_HOME que você configurou)
  • systemd: confirme que ReadWritePaths=/var/lib/pacforge está no service file
  • Pasta ~/.pacforge/data sendo limpa: algum serviço de limpeza do sistema (tmpwatch, etc.) pode estar removendo. Adicione a pasta à lista de exclusões.

O PacForge nunca deleta pb_data a menos que você explicitamente clique em "Delete" no painel ou rode instances:delete.

Q: Esqueci a senha do admin do painel.

R: Crie um novo admin via CLI:

pacforge admin:create --email [email protected] --password NovaSenha123
# Ou delete o antigo:
pacforge admin:delete --email [email protected]

Q: Como rodar em desenvolvimento sem auth?

R: Defina PACFORGE_DEV_AUTH_BYPASS=true e NODE_ENV=development. NUNCA use isso em produção.

Q: O WebSocket do terminal não conecta.

R: Verifique:

  1. O token JWT não expirou (15 min). Faça login novamente.
  2. Se atrás de Nginx, confirme proxy_http_version 1.1 e headers Upgrade/Connection (veja exemplo acima).
  3. Browser console deve mostrar erro específico.

Q: Como mudar a porta de uma instância existente?

R: Não é suportado diretamente. As portas são alocadas automaticamente. Para "resetar":

  1. Pare a instância: pacforge instances:stop <name>
  2. Delete e recrie: pacforge instances:delete <name> --force && pacforge instances:create <name>

(os dados serão perdidos — faça backup antes se necessário)


🤝 Contributing

Contribuições são bem-vindas! Veja CONTRIBUTING.md para detalhes. Em resumo:

  1. Fork + branch: git checkout -b feat/nome-da-feature
  2. Siga Conventional Commits: feat:, fix:, docs:, refactor:, test:, chore:
  3. Mantenha o footprint mínimo de dependências (regra 14.2 da spec)
  4. Todo async/await deve ter try/catch
  5. Use o logger interno, não console.log direto
  6. Adicione testes para novas features
  7. Abra um PR detalhando o quê e o porquê

📄 License

MIT © PacForge Contributors