From fa0cbe421dae0fdd3fd2614d7076d2b25dcd2034 Mon Sep 17 00:00:00 2001 From: Carlos Barbosa Date: Thu, 18 Jun 2026 21:21:30 -0300 Subject: [PATCH 1/2] feat(devops): configure custom domain + HTTPS via Let's Encrypt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Domínio: simples.163.176.220.47.nip.io (nip.io gratuito) - HTTPS com Let's Encrypt (certbot standalone) - Nginx: HTTP→HTTPS redirect + SSL proxy - DOMAIN.md atualizado com status, URLs e passo a passo - Certificado expira 16/Set/2026 (renovação automática via cron) Closes #49 --- DOMAIN.md | 88 ++++++++++++++++++++++++++-------------------- nginx/default.conf | 25 +++++++++++++ 2 files changed, 75 insertions(+), 38 deletions(-) diff --git a/DOMAIN.md b/DOMAIN.md index b214cdf..f8b913b 100644 --- a/DOMAIN.md +++ b/DOMAIN.md @@ -1,63 +1,75 @@ # Configuração de Domínio Próprio -> **Nota:** Este é um guia de referência para ser usado **após o deploy da VM na Oracle Cloud** (ver `terraform/README.md` e PRD §14.7). As instruções abaixo pressupõem que a instância OCI já está provisionada e a aplicação está rodando. +> **Status:** ✅ Configurado e funcionando — 19/Jun/2026 -Para habilitar a presença pública do **Simples Editor** em um domínio próprio (ex: `simples.seu-dominio.edu.br`), siga as instruções abaixo. +## Domínio -## Pré-requisitos +| Item | Valor | +|---|---| +| **Domínio** | `simples.163.176.220.47.nip.io` | +| **IP OCI** | `163.176.220.47` | +| **HTTPS** | Let's Encrypt (certbot) | +| **Expira cert** | 16/Set/2026 (renovação automática) | +| **Email** | carlos.barbosa@alunos.ifsuldeminas.edu.br | -- [ ] VM OCI Ampere A1 provisionada (via Terraform em `terraform/`) -- [ ] Aplicação rodando na VM (`docker compose up -d`) -- [ ] Domínio registrado com acesso ao painel DNS +## URLs de acesso -## 1. Apontamento de DNS +| URL | Descrição | +|---|---| +| `https://simples.163.176.220.47.nip.io` | IDE completa | +| `https://simples.163.176.220.47.nip.io/login` | Login Supabase | +| `https://simples.163.176.220.47.nip.io/api/health` | Health check | -Acesse o painel do provedor de DNS do seu domínio e crie um registro do tipo **A**: -- **Nome/Host**: `simples` (ou o subdomínio desejado) -- **Tipo**: `A` -- **Valor**: `` (substitua pelo IP da sua instância na Oracle Cloud) -- **TTL**: `Auto` ou `3600` +> HTTP (porta 80) redireciona automaticamente para HTTPS. -Aguarde a propagação do DNS (pode levar alguns minutos a algumas horas). Verifique se o domínio já responde ao IP usando ferramentas como `ping` ou `nslookup`. +## Como foi configurado -## 2. Configuração do Nginx e Let's Encrypt (TLS) - -Após a propagação, execute os comandos abaixo na instância OCI. Como alternativa, use o script `setup-tls.sh` provisionado pelo cloud-init (`/home/ubuntu/setup-tls.sh`): +### 1. DNS (nip.io) +`nip.io` é um serviço gratuito de DNS wildcard — qualquer subdomínio no formato `..nip.io` resolve automaticamente para o IP. Zero configuração de DNS necessária. +Verificação: ```bash -# Opção 1: Script automatizado (recomendado) -sudo DOMAIN=simples.seu-dominio.edu.br EMAIL=admin@seu-dominio.edu.br /home/ubuntu/setup-tls.sh +nslookup simples.163.176.220.47.nip.io +# → 163.176.220.47 +``` -# Opção 2: Passo a passo manual +### 2. OCI Security List +No OCI Console, adicionar regras de ingresso na subnet pública: +- TCP/80 (HTTP) de 0.0.0.0/0 +- TCP/443 (HTTPS) de 0.0.0.0/0 +### 3. Let's Encrypt (certbot) ```bash -# Instalar certbot -sudo snap install --classic certbot -sudo ln -s /snap/bin/certbot /usr/bin/certbot +# Instalar +sudo apt-get install -y certbot -# Parar o nginx temporariamente +# Parar nginx temporariamente docker compose stop nginx -# Emitir o certificado TLS para o domínio configurado -sudo certbot certonly --standalone -d simples.seu-dominio.edu.br \ - --non-interactive --agree-tos -m admin@seu-dominio.edu.br +# Obter certificado +sudo certbot certonly --standalone \ + -d simples.163.176.220.47.nip.io \ + --non-interactive --agree-tos \ + -m carlos.barbosa@alunos.ifsuldeminas.edu.br -# Mover os certificados para a pasta do Nginx -sudo cp /etc/letsencrypt/live/simples.seu-dominio.edu.br/fullchain.pem ./nginx/certs/ -sudo cp /etc/letsencrypt/live/simples.seu-dominio.edu.br/privkey.pem ./nginx/certs/ +# Copiar para o diretório do nginx +sudo cp /etc/letsencrypt/live/simples.163.176.220.47.nip.io/fullchain.pem ./nginx/certs/ +sudo cp /etc/letsencrypt/live/simples.163.176.220.47.nip.io/privkey.pem ./nginx/certs/ -# Reiniciar o Nginx -docker compose start nginx +# Reiniciar +docker compose up -d nginx ``` -## 3. Renovação Automática - -Os certificados do Let's Encrypt expiram a cada 90 dias. Adicione no cronjob para renovação automática: - +### 4. Renovação automática ```bash -echo "0 3 * * * certbot renew --quiet --post-hook 'cd /home/ubuntu/simples-online && docker compose restart nginx'" | sudo crontab - +echo "0 3 * * * certbot renew --quiet --post-hook 'docker compose -f /home/ubuntu/simples-online/docker-compose.yml restart nginx'" | sudo crontab - ``` +## Nginx config (resumo) +- Porta 80: redireciona para HTTPS +- Porta 443: SSL com proxy para frontend:80 e backend:5000 +- Certificados montados via volume: `./nginx/certs:/etc/nginx/certs:ro` + ## Validação (Critérios de Aceite) -- [ ] Domínio próprio apontando para o IP da OCI (requer deploy prévio da VM). -- [ ] Domínio resolvendo e acessível publicamente via HTTPS (requer execução do setup-tls.sh). +- [x] Domínio próprio apontando para o IP da OCI. +- [x] Domínio resolvendo e acessível publicamente via HTTPS. diff --git a/nginx/default.conf b/nginx/default.conf index 2632941..97400e3 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -1,11 +1,34 @@ +# HTTP → HTTPS redirect server { listen 80; server_name localhost; + # Let's Encrypt webroot challenges (certbot renew) + location /.well-known/acme-challenge/ { + root /tmp; + } + + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS +server { + listen 443 ssl; + server_name localhost; + + ssl_certificate /etc/nginx/certs/fullchain.pem; + ssl_certificate_key /etc/nginx/certs/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + location / { proxy_pass http://frontend:80; 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; } location /api/ { @@ -13,6 +36,7 @@ server { 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; proxy_read_timeout 30s; } @@ -23,6 +47,7 @@ server { proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 600s; proxy_send_timeout 600s; } From da8fe4b63175d9926cb82aa2282f7708e4de9601 Mon Sep 17 00:00:00 2001 From: Carlos Barbosa Date: Thu, 18 Jun 2026 21:25:49 -0300 Subject: [PATCH 2/2] fix: add auth gate (LoginPage) + logout button to App.tsx --- frontend/src/App.tsx | 63 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ac76380..df2c125 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,6 +12,8 @@ import { SIMPLES_EDITOR_OPTIONS, } from "./lib/simples-language"; import type * as Monaco from "monaco-editor"; +import { supabase } from "./lib/supabase"; +import { LoginPage } from "./components/LoginPage"; // ── Types ──────────────────────────────────────────────────────────────── @@ -135,6 +137,28 @@ function App() { const [wsConnected, setWsConnected] = useState(false); const [examplesOpen, setExamplesOpen] = useState(false); + // ── Auth State ────────────────────────────────────────────────────────── + + const [authChecked, setAuthChecked] = useState(false); + const [isAuthenticated, setIsAuthenticated] = useState( + import.meta.env.VITE_DEMO_MODE === "true" + ); + + useEffect(() => { + if (import.meta.env.VITE_DEMO_MODE === "true") { + setAuthChecked(true); + return; + } + supabase.auth.getSession().then(({ data }) => { + setIsAuthenticated(!!data.session); + setAuthChecked(true); + }); + const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + setIsAuthenticated(!!session); + }); + return () => subscription.unsubscribe(); + }, []); + // ── Monaco Language Registration ─────────────────────────────────────── const handleBeforeMount: BeforeMount = useCallback((monaco) => { @@ -418,6 +442,11 @@ function App() { setExamplesOpen(false); }, []); + const handleLogout = useCallback(async () => { + await supabase.auth.signOut(); + setIsAuthenticated(false); + }, []); + // Close examples dropdown on outside click useEffect(() => { const handler = (e: MouseEvent) => { @@ -442,6 +471,31 @@ function App() { // ── Render ────────────────────────────────────────────────────────────── + // Auth gate: show login if not authenticated (skip in demo mode) + if (!authChecked) { + return ( +
+
Carregando...
+
+ ); + } + + if (!isAuthenticated) { + return ( +
+
+
+

Simples Editor

+

+ IDE web para a linguagem SIMPLES +

+
+ +
+
+ ); + } + return (
{/* Header */} @@ -502,6 +556,15 @@ function App() { > Limpar + + {import.meta.env.VITE_DEMO_MODE !== "true" && ( + + )}