diff --git a/CLAUDE.md b/CLAUDE.md index ebfa76c..3904d22 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ 3. **Periods** usam formato `YYYY-MM` (ex: `"2025-11"`). Utils em `src/shared/utils/period/`. 4. **Moeda**: R$ com 2 decimais. DB: `numeric(12, 2)`. Utils em `src/shared/utils/currency.ts`. 5. **Revalidation**: usar `revalidateForEntity("entity")` de `src/shared/lib/actions/helpers.ts` apos mutations. -6. **Versionamento**: registrar mudancas no `CHANGELOG.md` seguindo Keep a Changelog, também altere o `package.json` e `readme.md`. +6. **Versionamento**: registrar mudancas no `CHANGELOG.md` seguindo Keep a Changelog, também altere o `package.json` e `readme.md` (Badges do README.md). 7. **Comunicacao**: responder em portugues clara e direta com o time. 8. **Commit messages**: agrupar por natureza. em pt-br. seguindo o padrao do sistema. 9. **README.md**: sempre que fizer alteracoes significativas, atualize o README.md. @@ -85,6 +85,7 @@ src/ │ │ ├── insights/ │ │ ├── calendar/ │ │ ├── inbox/ +│ │ ├── attachments/ │ │ ├── changelog/ │ │ ├── reports/ │ │ │ ├── category-trends/ @@ -111,6 +112,7 @@ src/ │ ├── insights/ │ ├── calendar/ │ ├── inbox/ +│ ├── attachments/ │ ├── reports/ │ └── settings/ ├── shared/ @@ -307,18 +309,29 @@ export async function fetchData(userId: string, period: string) { --- -## Response Style +## Security Rules -Quando o time pedir avaliacao de plano ou feature: +Regras aplicadas automaticamente ao gerar codigo. -1. Responder em portugues simples. -2. Listar 3-5 problemas principais. -3. Fechar com decisao pratica: - - aprova agora - - nao aprova agora - - o que ajustar antes de comecar codigo +### Secrets +Nunca colocar API keys, credenciais de banco ou tokens em codigo frontend. Evitar variaveis prefixadas com `NEXT_PUBLIC_` para dados sensiveis — estas sao bundladas no cliente. Usar variaveis server-side apenas. `.env` deve estar no `.gitignore` antes do primeiro commit. `.env.example` deve ter apenas placeholders. -Exemplo: +### Autenticacao & Autorizacao +Toda rota protegida em `src/app/api/` requer `getUser()` ou `getOptionalUserSession()` antes de qualquer logica, retornando 401 para nao autenticados. Rotas com IDs de recursos devem verificar ownership: `eq(table.userId, userId)`. Rotas admin devem checar role e retornar 403 para nao-admins. Session cookies em Better Auth ja tem `httpOnly`, `secure` e `sameSite` configurados — nao alterar. -- "Nao aprovaria para comecar codigo imediatamente." -- "Primeiro ajustaria o doc com estes 5 pontos." +### Input & Output +Usar Drizzle ORM (parametrizado por padrao) — nunca concatenar input de usuario em SQL. Validar todo input com Zod antes de usar. Upload de arquivos: usar whitelist de MIME types (`ALLOWED_MIME_TYPES`), presigned URLs para S3, token de upload assinado com verificacao pos-upload. Nunca usar `dangerouslySetInnerHTML` com conteudo de usuario. + +### Headers & CSP +CSP definida em `src/proxy.ts` via middleware — alterar la, nao em `next.config.ts`. Headers de seguranca (HSTS, X-Frame-Options, etc.) definidos em `next.config.ts`. Nao remover nem enfraquecer essas configuracoes. + +### Rate Limiting +Login: 5 tentativas/min. Signup: 3 tentativas/min. API tokens: 100 req/min (inbox), 20 req/min (batch). Configurado em `src/shared/lib/auth/config.ts` e nas rotas de inbox. Nao remover. + +### Tratamento de Erros +Erros nao devem expor stack traces, paths ou nomes de bibliotecas ao cliente. Usar mensagens genericas: `"Algo deu errado"`. Logar detalhes apenas no servidor com `console.error()`. + +### Dependencias +Verificar pacotes novos sugeridos pela IA em npmjs.com antes de instalar. Red flags: menos de 1.000 downloads/semana, publicado nos ultimos 30 dias, nome muito parecido com pacote popular. Rodar `pnpm audit` periodicamente. + +--- diff --git a/README.md b/README.md index 8decd56..10e2cde 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ > **⚠️ Não há versão online hospedada.** Você precisa clonar o repositório e rodar localmente ou no seu próprio servidor. -[![Version](https://img.shields.io/badge/version-2.3.6-blue?style=flat-square)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-2.3.7-blue?style=flat-square)](CHANGELOG.md) [![Next.js](https://img.shields.io/badge/Next.js-black?style=flat-square&logo=next.js)](https://nextjs.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/) [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-blue?style=flat-square&logo=postgresql)](https://www.postgresql.org/) @@ -23,15 +23,21 @@ Dashboard Preview

+

+ Lançamentos +

+ --- ## 📖 Índice - [Sobre o Projeto](#-sobre-o-projeto) - [Instalação via Script](#-instalação-via-script) + - [Preparar o servidor (Ubuntu 24.04)](#-preparar-o-servidor-ubuntu-2404) - [Início Rápido (manual)](#-início-rápido) - [Scripts Disponíveis](#-scripts-disponíveis) - [Docker](#-docker) +- [Backup](#-backup) - [Storage S3 Compatível](#-storage-s3-compatível) - [Variáveis de Ambiente](#-variáveis-de-ambiente) - [Arquitetura](#-arquitetura) @@ -53,7 +59,7 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ **1. Não há versão hospedada online** — Este projeto é self-hosted. Você precisa rodar no seu próprio computador ou servidor. -**2. Não há Open Finance** — Não há conexão automática com bancos. Você pode registrar transações manualmente ou importar extratos nos formatos OFX e XLS/XLSX. +**2. Não há Open Finance** — Não há conexão automática com bancos. Você pode registrar transações manualmente, usar o app companion para capturar notificações bancárias ou importar extratos nos formatos OFX e XLS/XLSX. **3. Requer disciplina** — O OpenMonetis funciona melhor para quem tem disciplina de registrar os gastos regularmente, quer controle total sobre seus dados e gosta de entender exatamente onde o dinheiro está indo. @@ -77,7 +83,11 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ 📅 **Calendário financeiro** — Visualize todos os lançamentos em um calendário mensal. -📲 **OpenMonetis Companion** — App Android que captura notificações bancárias (Nubank, Itaú, Bradesco, Inter, C6 e outros) e envia como pré-lançamentos para revisão. [Repositório](https://github.com/felipegcoutinho/openmonetis-companion). +📲 **OpenMonetis Companion** — App Android que captura notificações bancárias (Nubank, Itaú, Bradesco, Inter, C6 e outros) e envia automaticamente como pré-lançamentos para revisão — sem digitar nada. [Repositório](https://github.com/felipegcoutinho/openmonetis-companion). + +

+ OpenMonetis Companion +

⚙️ **Personalização** — Tema dark/light e modo privacidade. @@ -97,6 +107,31 @@ A ideia é simples: ter um lugar onde consigo ver todas as minhas contas, cartõ A forma mais rápida de instalar. O script verifica dependências, configura o `.env` interativamente e sobe o banco automaticamente. +### 🖥️ Preparar o servidor (Ubuntu 24.04) + +Se você está num **servidor Ubuntu limpo** (VPS, Oracle Cloud, DigitalOcean...) sem Node.js, Docker ou pnpm instalados, use o script de preparação antes de continuar. + +> ⚠️ **Testado apenas em Ubuntu Server 24.04 LTS.** Em outras distribuições ou versões é necessário testar ou ajustar o script. + +```bash +curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/scripts/install-deps.sh -o install-deps.sh +sudo sh install-deps.sh +``` + +O script instala (e pula o que já estiver presente): + +| Ferramenta | Como instala | +|---|---| +| `git`, `curl`, `ca-certificates` | apt | +| Docker Engine + Docker Compose | Repositório oficial do Docker | +| Homebrew | Script oficial (como usuário não-root) | +| Node.js 22 | Via Homebrew | +| pnpm | Via corepack | + +Após a conclusão, adiciona o usuário atual ao grupo `docker` — faça logout/login para ativar. Em seguida, prossiga com o `setup.mjs` abaixo. + +--- + **Pré-requisito:** Node.js 22+ ```bash @@ -168,7 +203,7 @@ O script irá: 5. Acesse `http://localhost:3000` -> **Docker completo** (app + banco em containers): use `pnpm docker:up` ao invés dos passos 3-4. +> **Docker completo** (app + banco em containers): use `pnpm docker:up:local` ao invés dos passos 3-4. --- @@ -190,26 +225,30 @@ pnpm lint:fix # Biome auto-fix pnpm db:generate # Gerar migrations pnpm db:migrate # Executar migrations pnpm db:push # Push schema direto (dev) +pnpm db:extensions # Habilitar extensões PostgreSQL (rodar uma vez) pnpm db:studio # Drizzle Studio (UI visual) ``` ### Utilitários ```bash -pnpm backup # Backup do banco (requer scripts/backup.sh configurado) +pnpm backup # Backup completo do banco (ver seção Backup) ``` ### Docker ```bash -pnpm docker:up # Subir app + banco -pnpm docker:up:d # Subir em background -pnpm docker:up:db # Subir apenas o banco -pnpm docker:down # Parar containers -pnpm docker:down:volumes # Parar e remover volumes (⚠️ apaga dados!) -pnpm docker:logs # Logs em tempo real -pnpm docker:restart # Reiniciar -pnpm docker:rebuild # Rebuild completo +pnpm docker:up:local # Sobe app + banco PostgreSQL juntos (imagem do Hub) +pnpm docker:up # Sobe apenas o app com build local +pnpm docker:up:d # Sobe apenas o app com build local em background +pnpm docker:up:db # Sobe apenas o banco em background +pnpm docker:down # Para e remove os containers +pnpm docker:down:volumes # Para containers e remove volumes (⚠️ apaga dados!) +pnpm docker:logs # Logs em tempo real (todos os containers) +pnpm docker:logs:app # Logs do container da aplicação +pnpm docker:logs:db # Logs do container do banco +pnpm docker:restart # Reinicia todos os containers +pnpm docker:rebuild # Rebuild completo forçando recriação ``` --- @@ -220,6 +259,46 @@ O `Dockerfile` usa multi-stage build (deps → builder → runner) com imagem fi Health checks configurados para ambos os serviços (PostgreSQL via `pg_isready`, app via `GET /api/health`). +### Modos de uso + +**Modo 1 — App + banco local (recomendado para self-hosting)** + +Puxa a imagem pronta do Docker Hub e sobe app + banco juntos. Não precisa de Node.js instalado. + +```bash +# 1. Baixar o docker-compose.yml +curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/docker-compose.yml -o docker-compose.yml + +# 2. Criar o .env (copie o .env.example como referência) +# DATABASE_URL=postgresql://openmonetis:SUA_SENHA@db:5432/openmonetis_db +# POSTGRES_PASSWORD=SUA_SENHA +# BETTER_AUTH_SECRET=string-longa-aleatoria +# BETTER_AUTH_URL=http://localhost:3000 + +# 3. Subir +docker compose --profile local up +# ou, se tiver o projeto clonado: +pnpm docker:up:local +``` + +**Modo 2 — App com banco remoto** + +Use quando o banco está em um provider externo (Supabase, Neon, Railway...). + +```bash +# DATABASE_URL deve apontar para o banco remoto no .env +docker compose up +``` + +**Modo 3 — Build local (desenvolvimento)** + +Builda a imagem localmente a partir do código-fonte. + +```bash +pnpm docker:up # app apenas (banco separado) +pnpm docker:up:db # sobe o banco em background +``` + ### Comandos úteis ```bash @@ -239,6 +318,68 @@ DB_PORT=5433 # Padrão: 5432 --- +## 💾 Backup + +O backup é uma rotina de infraestrutura — não é uma tela no app. Ele opera diretamente sobre o banco PostgreSQL e é executado via linha de comando. + +```bash +pnpm backup +``` + +### O que é salvo + +Cada execução gera **3 arquivos** em `backup/`: + +| Arquivo | Conteúdo | Uso | +|---|---|---| +| `openmonetis_YYYY-MM-DD_HH-MM.dump` | Dump custom do PostgreSQL (binário) | Restore completo via `pg_restore` | +| `openmonetis_YYYY-MM-DD_HH-MM.sql.gz` | Dump SQL completo compactado | Inspeção manual, portabilidade | +| `openmonetis_YYYY-MM-DD_HH-MM.data.sql.gz` | Apenas os dados da schema `public` | Migração parcial, seed de outro ambiente | + +### Modos de conexão + +Configure `DB_MODE` no topo de `scripts/backup.sh`: + +| Modo | Quando usar | Fonte de dados | +|---|---|---| +| `remote` (padrão) | Banco em Supabase, Neon, Railway, etc. | `DATABASE_URL` do `.env` | +| `docker` | Banco no container local | Container `openmonetis_postgres` | + +### Upload para Google Drive (opcional) + +Se o [rclone](https://rclone.org/) estiver instalado e configurado com um remote chamado `gdrive`, os arquivos são enviados automaticamente para `gdrive:BACKUP OPENMONETIS`. Sem o rclone, o backup funciona normalmente e fica apenas local. + +**Retenção:** +- Local: 7 dias +- Google Drive: 30 dias + +### Automatizar com cron + +Para rodar o backup automaticamente todo dia às 3h: + +```bash +crontab -e +``` + +```cron +0 3 * * * cd /caminho/para/openmonetis && pnpm backup >> /var/log/openmonetis-backup.log 2>&1 +``` + +### Restore + +```bash +# A partir do .dump (recomendado — mais rápido) +pg_restore --clean --no-owner --no-privileges \ + -d "postgresql://user:senha@host:5432/openmonetis_db" \ + backup/openmonetis_YYYY-MM-DD_HH-MM.dump + +# A partir do .sql.gz (banco local via Docker) +gunzip -c backup/openmonetis_YYYY-MM-DD_HH-MM.sql.gz | \ + docker compose exec -T db psql -U openmonetis -d openmonetis_db +``` + +--- + ## ☁️ Storage S3 Compatível O suporte a anexos de lançamentos usa upload direto com URL pré-assinada. Essa configuração é opcional, mas passa a ser necessária se você quiser habilitar anexos no app. @@ -256,7 +397,7 @@ S3_BUCKET= ### Compatibilidade - O código atual espera um provider com API compatível com S3 e suporte a `PutObject`, `GetObject`, `HeadObject`, `DeleteObject` e URLs pré-assinadas. -- A implementação usa `endpoint` customizado e `forcePathStyle: true` em [`src/shared/lib/storage/s3-client.ts`](/home/ubuntu/github/openmonetis/src/shared/lib/storage/s3-client.ts). +- A implementação usa `endpoint` customizado e `forcePathStyle: true` em [`src/shared/lib/storage/s3-client.ts`](./src/shared/lib/storage/s3-client.ts). - Em geral isso cobre MinIO, Cloudflare R2, Backblaze B2 S3-Compatible, DigitalOcean Spaces e AWS S3. Mas foi testado apenas no Supabase Storage. - Se o seu provider exigir `virtual-hosted-style` em vez de `path-style`, você vai precisar ajustar essa configuração antes de usar anexos. - Se as variáveis de S3 não forem configuradas, mantenha os anexos desabilitados no seu fluxo de uso. diff --git a/scripts/install-deps.sh b/scripts/install-deps.sh new file mode 100755 index 0000000..101c72e --- /dev/null +++ b/scripts/install-deps.sh @@ -0,0 +1,241 @@ +#!/bin/sh +# install-deps.sh — Instala pré-requisitos do OpenMonetis +# Testado apenas em Ubuntu Server 24.04 LTS +# Uso: curl -fsSL https://raw.githubusercontent.com/felipegcoutinho/openmonetis/main/scripts/install-deps.sh -o install-deps.sh +# sudo sh install-deps.sh + +set -e + +LOG_FILE="/tmp/openmonetis-install.log" +> "$LOG_FILE" + +# ── Cores ────────────────────────────────────────────────────────────────────── +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +CYAN='\033[0;36m' +BOLD='\033[1m' +RESET='\033[0m' + +ok() { printf "${GREEN}✔${RESET} %s\n" "$1"; } +warn() { printf "${YELLOW}!${RESET} %s\n" "$1"; } +info() { printf "${CYAN}→${RESET} %s\n" "$1"; } +fail() { printf "${RED}✗${RESET} %s\n" "$1"; exit 1; } + +# ── Contador de etapas ───────────────────────────────────────────────────────── +_STEP=0 +_TOTAL=5 + +section() { + _STEP=$((_STEP + 1)) + printf "\n${BOLD}[%d/%d] %s${RESET}\n" "$_STEP" "$_TOTAL" "$1" +} + +# ── Spinner ──────────────────────────────────────────────────────────────────── +_spin_pid="" + +spinner_start() { + _spin_label="$1" + ( i=0 + while true; do + case $((i % 4)) in + 0) d=" " ;; 1) d=". " ;; 2) d=".. " ;; *) d="..." ;; + esac + printf "\r${CYAN}→${RESET} %s%s" "$_spin_label" "$d" + i=$((i + 1)) + sleep 0.4 + done + ) & + _spin_pid=$! +} + +spinner_stop() { + if [ -n "$_spin_pid" ]; then + kill "$_spin_pid" 2>/dev/null + wait "$_spin_pid" 2>/dev/null + _spin_pid="" + printf "\r\033[2K" + fi +} + +# ── Executores silenciosos com spinner ───────────────────────────────────────── + +# run_quiet "label" cmd [args...] — roda comando com spinner, falha mostra log +run_quiet() { + _rq_label="$1"; shift + spinner_start "$_rq_label" + if ! "$@" >> "$LOG_FILE" 2>&1; then + spinner_stop + printf "${RED}✗ Falha em: %s${RESET}\n" "$_rq_label" + printf " Log completo: %s\n\n" "$LOG_FILE" + tail -20 "$LOG_FILE" + exit 1 + fi + spinner_stop +} + +# run_as_user "label" "comando_shell" — roda comando como $CURRENT_USER com spinner +run_as_user() { + _ru_label="$1"; shift + spinner_start "$_ru_label" + if ! su - "$CURRENT_USER" -c "$*" >> "$LOG_FILE" 2>&1; then + spinner_stop + printf "${RED}✗ Falha em: %s${RESET}\n" "$_ru_label" + printf " Log completo: %s\n\n" "$LOG_FILE" + tail -20 "$LOG_FILE" + exit 1 + fi + spinner_stop +} + +# ── Cleanup no Ctrl+C ────────────────────────────────────────────────────────── +cleanup() { + spinner_stop + printf "\n${YELLOW}Instalação interrompida.${RESET} Log em: %s\n" "$LOG_FILE" + exit 1 +} +trap cleanup INT TERM + +# ── Tempo total ──────────────────────────────────────────────────────────────── +_START=$(date +%s) +elapsed() { + _secs=$(( $(date +%s) - _START )) + printf "%dm%ds" $((_secs / 60)) $((_secs % 60)) +} + +# ── Root check ───────────────────────────────────────────────────────────────── +if [ "$(id -u)" -ne 0 ]; then + fail "Execute como root ou com sudo: sudo sh install-deps.sh" +fi + +CURRENT_USER="${SUDO_USER:-$(whoami)}" + +printf "\n${BOLD}OpenMonetis — Instalação de Dependências${RESET}\n" +printf "Usuário: ${CYAN}%s${RESET} | Log: %s\n" "$CURRENT_USER" "$LOG_FILE" + +# ── [1/5] Dependências base ──────────────────────────────────────────────────── +section "Dependências base" +run_quiet "Atualizando lista de pacotes" apt-get update -qq +run_quiet "Instalando git, curl, ca-certificates" apt-get install -y -qq ca-certificates curl git +ok "git $(git --version | cut -d' ' -f3) · curl · ca-certificates" + +# ── [2/5] Docker ─────────────────────────────────────────────────────────────── +section "Docker" + +if command -v docker > /dev/null 2>&1; then + ok "Docker já instalado: $(docker --version | cut -d',' -f1)" +else + info "Adicionando repositório oficial do Docker..." + install -m 0755 -d /etc/apt/keyrings + run_quiet "Baixando chave GPG do Docker" \ + curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + + . /etc/os-release + mkdir -p /etc/apt/sources.list.d + printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu %s stable\n' \ + "$(dpkg --print-architecture)" "$VERSION_CODENAME" \ + > /etc/apt/sources.list.d/docker.list + + run_quiet "Atualizando lista de pacotes" apt-get update -qq + run_quiet "Instalando Docker Engine (pode levar alguns minutos)" \ + apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + + systemctl enable docker > /dev/null 2>&1 || true + systemctl start docker > /dev/null 2>&1 || true + ok "Docker $(docker --version | cut -d',' -f1 | cut -d' ' -f3) instalado" +fi + +if docker compose version > /dev/null 2>&1; then + ok "Docker Compose $(docker compose version | cut -d' ' -f4)" +else + run_quiet "Instalando Docker Compose plugin" \ + sh -c 'mkdir -p /usr/local/lib/docker/cli-plugins && curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" -o /usr/local/lib/docker/cli-plugins/docker-compose && chmod +x /usr/local/lib/docker/cli-plugins/docker-compose' + ok "Docker Compose $(docker compose version | cut -d' ' -f4) instalado" +fi + +if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then + if ! groups "$CURRENT_USER" | grep -q docker; then + usermod -aG docker "$CURRENT_USER" + warn "Usuário '$CURRENT_USER' adicionado ao grupo docker — faça logout/login para aplicar" + else + ok "Usuário '$CURRENT_USER' já está no grupo docker" + fi +fi + +# ── [3/5] Homebrew ───────────────────────────────────────────────────────────── +section "Homebrew" + +if command -v brew > /dev/null 2>&1; then + ok "Homebrew já instalado: $(brew --version | head -1)" +else + warn "Esta etapa pode levar de 5 a 10 minutos." + run_quiet "Instalando dependências de compilação" \ + apt-get install -y -qq build-essential procps file + + if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then + run_as_user "Instalando Homebrew" \ + 'NONINTERACTIVE=1 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' + + BREW_PROFILE="/home/$CURRENT_USER/.bashrc" + BREW_EVAL='eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' + grep -qxF "$BREW_EVAL" "$BREW_PROFILE" 2>/dev/null || echo "$BREW_EVAL" >> "$BREW_PROFILE" + export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true + else + fail "Homebrew não pode ser instalado como root. Use sudo com um usuário normal." + fi + + ok "Homebrew instalado" +fi + +# ── [4/5] Node.js 22 ─────────────────────────────────────────────────────────── +section "Node.js 22" + +NODE_MAJOR=0 +if command -v node > /dev/null 2>&1; then + NODE_MAJOR=$(node -e "process.stdout.write(String(parseInt(process.versions.node)))") +fi + +if [ "$NODE_MAJOR" -ge 22 ] 2>/dev/null; then + ok "Node.js já instalado: $(node --version)" +else + warn "Node.js via Homebrew pode levar alguns minutos." + if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then + run_as_user "Instalando Node.js 22" \ + 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && brew install node@22 && brew link node@22 --force --overwrite' + export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 2>/dev/null || true + else + fail "Node.js via Homebrew não pode ser instalado como root." + fi + ok "Node.js $(node --version) instalado" +fi + +# ── [5/5] pnpm ───────────────────────────────────────────────────────────────── +section "pnpm" + +if command -v pnpm > /dev/null 2>&1; then + ok "pnpm já instalado: $(pnpm --version)" +else + if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then + run_as_user "Instalando pnpm via corepack" \ + 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" && corepack enable && corepack prepare pnpm@latest --activate' + else + run_quiet "Instalando pnpm via corepack" \ + sh -c 'corepack enable && corepack prepare pnpm@latest --activate' + fi + ok "pnpm instalado" +fi + +# ── Resumo ───────────────────────────────────────────────────────────────────── +printf "\n${BOLD}Concluído em $(elapsed)${RESET}\n" + +ok "git: $(git --version | cut -d' ' -f3)" +ok "docker: $(docker --version | cut -d',' -f1 | cut -d' ' -f3)" +ok "docker compose: $(docker compose version | cut -d' ' -f4)" +ok "node: $(node --version)" +ok "pnpm: $(pnpm --version)" + +printf "\n${CYAN}Próximo passo:${RESET}\n" +printf " node setup.mjs\n\n"