From 1f32d9719f55f3f358643c34a036fc006bb9fc68 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 24 Sep 2026 15:26:42 +0000 Subject: [PATCH] chore: alinea el repo al contrato P0/P1/P2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conserva stack y producto; añade docs canónicos, CI quality/test/build/smoke, Renovate, Make fachada y meta-secciones. LICENSE GPL-3.0 intacta. Co-authored-by: Alexendros · Alejandro Domingo Agustí --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug.md | 28 ++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature.md | 22 ++ .github/PULL_REQUEST_TEMPLATE.md | 19 ++ .github/renovate.json | 39 +++ .github/workflows/ci.yml | 135 +++++---- .gitignore | 4 + AGENTS.md | 94 +++++++ ARCHITECTURE.md | 94 +++++++ CHANGELOG.md | 22 ++ CODE_OF_CONDUCT.md | 39 +++ CONTRIBUTING.md | 46 +++ Makefile | 36 ++- README.md | 22 +- SECURITY.md | 36 +++ deploy/pacman-cache/README.md | 6 + docs/ANSIBLE.md | 61 +--- docs/ARCHITECTURE.md | 65 +---- docs/INSTALL.md | 387 +------------------------ docs/PACKAGES.md | 136 +-------- docs/README.md | 23 ++ docs/RELEASE-v1.0.0.md | 6 + docs/ROADMAP.md | 55 +--- docs/architecture/decisions/README.md | 29 ++ docs/architecture/overview.md | 68 +++++ docs/guides/ansible.md | 64 +++++ docs/guides/desarrollo.md | 53 ++++ docs/guides/install.md | 390 ++++++++++++++++++++++++++ docs/guides/packages.md | 139 +++++++++ docs/guides/roadmap.md | 58 ++++ docs/runbooks/ci.md | 28 ++ docs/runbooks/iso.md | 36 +++ docs/runbooks/portal.md | 41 +++ tests/vm/README.md | 6 + 35 files changed, 1548 insertions(+), 748 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/renovate.json create mode 100644 AGENTS.md create mode 100644 ARCHITECTURE.md create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 docs/README.md create mode 100644 docs/architecture/decisions/README.md create mode 100644 docs/architecture/overview.md create mode 100644 docs/guides/ansible.md create mode 100644 docs/guides/desarrollo.md create mode 100644 docs/guides/install.md create mode 100644 docs/guides/packages.md create mode 100644 docs/guides/roadmap.md create mode 100644 docs/runbooks/ci.md create mode 100644 docs/runbooks/iso.md create mode 100644 docs/runbooks/portal.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e7e7210 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @Alexendros diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..3104b25 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,28 @@ +--- +name: Bug +description: Comportamiento incorrecto de NEUBAT +--- + +### Propósito de este documento + +- **Objetivos:** Recoger un fallo reproducible (portal, instalador o ISO) sin filtrar secretos. +- **Estructura:** Comando y salida → esperado vs obtenido → reproducción → entorno. +- **Contenido a integrar según contexto:** Adapta el formulario a NEUBAT. Adjunta logs mínimos sintéticos; no pegues `ADMIN_TOKEN`, HMAC, `.env` ni keyfiles LUKS. + +## Comando y salida + +```bash +# comando ejecutado + salida relevante (sin secretos) +``` + +## Esperado vs obtenido + + + +## Reproducción + + + +## Entorno + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..44f3d2c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Vulnerabilidad de seguridad + url: https://github.com/Alexendros/neubat/security/advisories/new + about: No abras un issue público. Sigue SECURITY.md. + - name: Documentación + url: https://github.com/Alexendros/neubat/blob/main/README.md + about: README, AGENTS.md, ARCHITECTURE.md y docs/ antes de abrir un issue. diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..bb3cd5c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,22 @@ +--- +name: Feature +description: Propuesta de mejora para NEUBAT +--- + +### Propósito de este documento + +- **Objetivos:** Recoger una propuesta de mejora del portal, instalador o perfiles con impacto de contrato. +- **Estructura:** Problema → propuesta → impacto (API, JSON, particionado, HMAC). +- **Contenido a integrar según contexto:** Adapta el formulario a NEUBAT. Si toca contrato, enlaza un ADR en `docs/architecture/decisions/`. + +## Problema + + + +## Propuesta + + + +## Impacto en contrato + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7161549 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ + + +### Propósito de este documento + +- **Objetivos:** Plantilla de PR para describir el cambio y exigir las comprobaciones `lint` / `test` / `smoke` / `validate` y los jobs `quality` / `test` / `smoke`. +- **Estructura:** Qué cambia → checklist (Make, docs, artefactos, CI). +- **Contenido a integrar según contexto:** Adapta el checklist a NEUBAT. No copies plantillas de otro producto. `test-vm` y `build-iso` son opt-in. + +## Qué cambia + + + +## Checklist + +- [ ] `make lint && make test && make smoke && make validate` +- [ ] Si toca frontend: `make test-frontend && make build-frontend` +- [ ] Docs actualizadas (`README.md`, `ARCHITECTURE.md` o ADR si cambia contrato) +- [ ] Sin artefactos (`out/`, `portal/public/assets/`, `coverage/`) ni secretos +- [ ] CI `quality` / `test` / `smoke` en verde (`build` si aplica) diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..2480d37 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "timezone": "Europe/Madrid", + "labels": ["dependencies"], + "schedule": ["before 10am on monday"], + "rangeStrategy": "bump", + "dependencyDashboard": true, + "semanticCommits": "enabled", + "rebaseWhen": "behind-base-branch", + "enabledManagers": ["npm", "github-actions"], + "lockFileMaintenance": { + "enabled": true, + "schedule": ["before 10am on monday"] + }, + "vulnerabilityAlerts": { + "enabled": true, + "labels": ["security"] + }, + "packageRules": [ + { + "matchManagers": ["npm"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "npm (non-major)", + "automerge": true + }, + { + "matchManagers": ["github-actions"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "GitHub Actions (non-major)", + "automerge": true + }, + { + "matchUpdateTypes": ["major"], + "automerge": false, + "labels": ["dependencies", "breaking-change"] + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b51ea3f..3f95259 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,14 +1,23 @@ name: CI +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: push: branches: [main] pull_request: branches: [main] +permissions: + contents: read + jobs: - validate: + quality: + name: quality runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -17,17 +26,35 @@ jobs: with: node-version: 22 cache: npm - cache-dependency-path: portal/package-lock.json + cache-dependency-path: | + portal/package-lock.json + portal/frontend/package-lock.json - name: Install portal dependencies run: cd portal && npm ci + - name: Install frontend dependencies + run: cd portal/frontend && npm ci + + - name: Install shellcheck and Ansible + run: | + sudo apt-get update + sudo apt-get install -y shellcheck ansible + pip3 install ansible-lint --break-system-packages || pip3 install ansible-lint + - name: Validate scripts and configs run: make validate + - name: Lint shell and frontend + run: make lint + + - name: Validate Ansible + run: make test-ansible + test: + name: test runs-on: ubuntu-latest - needs: validate + timeout-minutes: 20 steps: - uses: actions/checkout@v4 @@ -36,55 +63,63 @@ jobs: with: node-version: 22 cache: npm - cache-dependency-path: portal/package-lock.json + cache-dependency-path: | + portal/package-lock.json + portal/frontend/package-lock.json - name: Install portal dependencies run: cd portal && npm ci - - name: Run portal tests - run: make test - - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install shellcheck - run: sudo apt-get update && sudo apt-get install -y shellcheck - - - name: Run shellcheck - run: make lint - - test-bash: - runs-on: ubuntu-latest - needs: validate - steps: - - uses: actions/checkout@v4 + - name: Install frontend dependencies + run: cd portal/frontend && npm ci - name: Install bats run: sudo apt-get update && sudo apt-get install -y bats + - name: Run portal tests + run: make test + + - name: Run frontend tests + run: cd portal/frontend && npm test + - name: Run bats tests run: make test-bash - ansible: + build: + name: build runs-on: ubuntu-latest - needs: validate + timeout-minutes: 15 steps: - uses: actions/checkout@v4 - - name: Install Ansible - run: | - sudo apt-get update - sudo apt-get install -y ansible - pip3 install ansible-lint --break-system-packages || pip3 install ansible-lint + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: portal/frontend/package-lock.json - - name: Validate Ansible - run: make test-ansible + - name: Install frontend dependencies + run: cd portal/frontend && npm ci - test-frontend: + - name: Build frontend for production + run: make build-frontend + + - name: Upload frontend artifact + uses: actions/upload-artifact@v4 + with: + name: neubat-frontend + path: | + portal/public/index.html + portal/public/assets/ + portal/public/favicon.svg + if-no-files-found: error + retention-days: 7 + + smoke: + name: smoke runs-on: ubuntu-latest - needs: validate + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -93,14 +128,19 @@ jobs: with: node-version: 22 cache: npm - cache-dependency-path: portal/frontend/package-lock.json + cache-dependency-path: | + portal/package-lock.json + portal/frontend/package-lock.json + + - name: Install portal dependencies + run: cd portal && npm ci + + - name: API smoke (health + install) + run: make smoke - name: Install frontend dependencies run: cd portal/frontend && npm ci - - name: Run frontend tests - run: cd portal/frontend && npm test - - name: Install axe-core for a11y smoke run: cd portal/frontend && npm install --no-save axe-core jsdom @@ -134,22 +174,3 @@ jobs: }) .catch((err) => { console.error(err); process.exit(1); }); NODE - - build-frontend: - runs-on: ubuntu-latest - needs: validate - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: portal/frontend/package-lock.json - - - name: Install frontend dependencies - run: cd portal/frontend && npm ci - - - name: Build frontend for production - run: make build-frontend diff --git a/.gitignore b/.gitignore index a95b244..43f997c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ node_modules/ npm-debug.log* +# Secretos locales (usa .env.example) +.env +.env.local + # Datos en runtime del portal portal/data/ portal/configs/generated/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..85fd0cc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# AGENTS.md + +### Propósito de este documento + +- **Objetivos:** Fijar el contrato operativo para agentes de código y el rol Mantenedor: fuentes de verdad, autonomía, comandos y Definition of Done. +- **Estructura:** Destinatarios → fuentes de verdad → unidad de trabajo → autonomía → stack y comandos → convenciones → layout → Definition of Done. +- **Contenido a integrar según contexto:** Adapta layout, Make y scripts npm de este repo. No copies un `AGENTS.md` de landing/SaaS. No conviertas `test-vm` ni el build de ISO en required. No commitees `.env` ni keyfiles. + +**Destinatarios:** agentes de código y el rol Mantenedor que trabajen en este repositorio. +**Propósito:** contrato operativo. Homogeneizamos **nombres y contratos**, no el lenguaje ni la API del producto. + +## Fuentes de verdad (orden) + +1. [README.md](./README.md) — producto, quickstart y avisos de destrucción de disco +2. Este archivo +3. [ARCHITECTURE.md](./ARCHITECTURE.md) +4. [docs/architecture/](./docs/architecture/) — overview y ADRs +5. [CONTRIBUTING.md](./CONTRIBUTING.md) +6. [SECURITY.md](./SECURITY.md) + +No reinventes requisitos. Si falta ancla, paras y preguntas. + +## Unidad de trabajo + +``` +Objetivo: +Traza: +Alcance: +Exclusiones: +Pruebas: make lint && make test && make smoke && make validate +Criterio de cierre: CI quality + test + smoke (+ build si toca frontend) verdes +``` + +Una sesión = una unidad cohesiva. PR pequeño. Mensajes al humano y commits en español (Conventional Commits). + +## Autonomía + +**Puedes sin preguntar** + +- Tests que fijan comportamiento ya aceptado +- Corregir lint/format/typecheck causados por tu cambio +- Docs de guía/runbook en español +- Refactors locales que no cambien la API del portal ni el particionado + +**Requiere confirmación** + +- Cambiar el contrato de `configs/*.json`, tokens hex o HMAC +- Relajar `00-preinstall.sh`, `10-partition.sh` o `part_name()` +- Dependencia runtime nueva en `portal/` +- Convertir `test-vm` o `build-iso` en job required +- Tocar secretos, branch protection u org settings + +## Stack y comandos + +- Portal: Node ≥ 18 (CI 22), Express, Jest + Supertest +- Frontend: React + Vite + TypeScript + shadcn/ui + Vitest + oxlint +- Instalador: Bash + python3 (sin jq/bc) + Ansible first-boot +- Fachada: GNU Make + +```bash +make install-deps && make install-deps-frontend +make lint && make test && make smoke && make validate +make test-frontend && make build-frontend # si tocas portal/frontend +``` + +CI principal (`.github/workflows/ci.yml`): jobs `quality`, `test`, `build`, `smoke`. +ISO y QEMU quedan en workflow/target opt-in. + +## Convenciones + +- Ramas `feat/` `fix/` `docs/` `chore/` (los agentes Cloud usan `cursor/…`) +- Idioma: README/CONTRIBUTING/docs de guía en español; jobs de CI en inglés +- No commitees `out/`, `portal/data/`, `portal/public/assets/`, `.env` ni secretos +- Conserva GPL-3.0; no sustituyas `LICENSE` + +## Layout + +``` +portal/ Express + SPA React (frontend/) +scripts/ Instalador por fases (00–50) + build-iso +configs/ Perfiles JSON +netboot/ iPXE + GRUB loopback +iso/ Overlay archiso (autoinstall) +ansible/ First-boot +tests/ bats + vm opt-in +docs/ architecture/, guides/, runbooks/ +``` + +## Definition of Done + +- Criterios de la traza cumplidos +- Jobs `quality`, `test` y `smoke` verdes (`build` si hay artefacto frontend) +- Docs canónicos actualizados si cambia el contrato +- Sin secretos en el diff diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..9e0ae60 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,94 @@ +# Arquitectura de NEUBAT + +### Propósito de este documento + +- **Objetivos:** Describir capas, fronteras y no-objetivos para que un cambio no rompa el arranque iPXE, los tokens ni el particionado desatendido. +- **Estructura:** Propósito del producto → capas → módulos → contratos → calidad → no-objetivos → stack. +- **Contenido a integrar según contexto:** Adapta módulos de este repo. No copies la arquitectura de una CLI de bundles ni de un SaaS. El detalle de flujo está en [docs/architecture/overview.md](docs/architecture/overview.md). + +NEUBAT instala Arch Linux de forma desatendida: el usuario define la máquina en un portal web, obtiene un token/URL, y el destino arranca por red (iPXE) o ISO híbrida. + +El [README.md](./README.md) cubre el uso. Las decisiones vivas están abajo; los ADRs numerados, si los hay, en [`docs/architecture/decisions/`](docs/architecture/decisions/). + +## 1. Propósito + +Entrada: perfil JSON (`configs/`) o config firmada servida por el portal (`GET /api/config/`). +Salida: sistema Arch particionado (GPT/UEFI/btrfs, LUKS opcional), portal local y URL única de setup. + +Flujo: portal `POST /api/install` → boot iPXE/ISO → `neubat-install.sh` → fases 00–50 → `POST /api/complete` → first-boot Ansible. + +## 2. Capas + +``` +Usuario (móvil/escritorio) + │ SPA React + Express + ▼ +portal/ token, boot_url, HMAC, admin + │ HTTP / iPXE + ▼ +netboot/ + iso/ kernel cmdline (neubat_token, neubat_portal_url) + │ + ▼ +scripts/neubat-install.sh + 00-preinstall validaciones + 10-partition GPT/UEFI/btrfs/NVMe, LUKS2 opcional + 20-archinstall config remota + pacstrap + 30-postinstall chroot + aplicaciones + 35-snapper snapshots btrfs + 40-portal-deploy portal local + URL + 50-firstboot Ansible + │ + ▼ +configs/*.json perfiles declarativos +ansible/ first-boot (ConditionFirstBoot) +``` + +## 3. Módulos + +| Módulo | Responsabilidad | +| ------ | --------------- | +| `portal/server.js` | HTTP, rate-limit, estáticos, SPA fallback | +| `portal/routes/install.js` | Alta, config, boot iPXE, complete | +| `portal/routes/admin.js` | Panel `/admin` con `ADMIN_TOKEN` | +| `portal/lib/db.js` | Persistencia JSON (`portal/data/`) | +| `portal/frontend/` | SPA Vite + shadcn/ui | +| `scripts/lib/utils.sh` | Utilidades Bash (sin jq/bc) | +| `scripts/10-partition.sh` | `part_name()` NVMe-safe | +| `netboot/ipxe/neubat.ipxe` | Menú de arranque por red | + +## 4. Contratos + +| Qué | Dónde | Quién la mueve | +| --- | ----- | -------------- | +| Producto / ISO | `1.0.x`, tags `v*` | Release manual + `build-iso.yml` | +| Perfil JSON | `configs/*.json` | PR + docs/guides/packages.md | +| Token de instalación | hex 32 chars | `configPathFor()` — no relajar | +| Firma de config | `NEUBAT_HMAC_SECRET` | Entorno portal + live (nunca en git) | + +## 5. Calidad + +- Jest + Supertest sobre el portal; Vitest + oxlint en el frontend +- `bash -n` + shellcheck + bats en `scripts/` +- Ansible syntax-check / ansible-lint +- CI: `quality` → `test` → `build` (SPA) → `smoke` (`/api/health` + `POST /api/install`) +- QEMU/NVMe (`make test-vm`) y build de ISO: **opt-in**, no required + +## 6. Decisiones de diseño (vivas) + +- **Sin jq/bc**: el live de Arch garantiza `python3`; JSON con `python3`, aritmética con `awk`/`$(( ))`. +- **NVMe-safe**: `part_name()` resuelve `/dev/sda1` vs `/dev/nvme0n1p1`. +- **btrfs + zstd**: compresión transparente y `noatime` para SSD. +- **Perfiles declarativos**: los JSON definen paquetes y servicios; el portal los extiende sin tocar el instalador. +- **HMAC-SHA256**: integridad de la config en tránsito. +- **LUKS2 opcional**: `keyfile` (desatendido) o passphrase interactiva. + +## 7. No-objetivos + +- No reescribir la API/UX del portal en PRs de plataforma +- No exigir e2e QEMU ni ISO en cada PR +- No cambiar branch protection, org settings ni secretos reales +- No sustituir la licencia GPL-3.0 + +## 8. Stack + +Node 22 · Express · React 19 + Vite + TypeScript · Jest · Vitest · Bash · Ansible · archiso/Docker · iPXE. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2476fbc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +Registro de cambios relevantes de NEUBAT. Formato inspirado en [Keep a Changelog](https://keepachangelog.com/es/1.1.0/). +Las versiones de producto (`1.0.x`) no se generan con semantic-release. + +## [Unreleased] + +### Added + +- Alineación P0/P1/P2 al contrato de repositorio (docs canónicos, CI `quality`/`test`/`build`/`smoke`, Renovate, Make fachada, meta-secciones). + +## [1.0.0] - 2026-09-20 + +### Added + +- Portal web (Express + SPA React) con cuentas, configurador e integridad HMAC. +- Arranque iPXE e ISO híbrida autoinstalable. +- Particionado GPT/UEFI/btrfs, LUKS2 opcional y snapper. +- Post-instalación Ansible (first-boot) y panel `/admin`. +- Suite de tests del portal, frontend, bats y workflow opt-in de ISO. + +Ver [docs/RELEASE-v1.0.0.md](docs/RELEASE-v1.0.0.md). diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d0ed568 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,39 @@ +# Código de conducta + +### Propósito de este documento + +- **Objetivos:** Definir el estándar de conducta del proyecto y el canal privado para denunciar acoso o abuso. +- **Estructura:** Compromiso → estándares (aceptable / inaceptable) → alcance → aplicación y contacto. +- **Contenido a integrar según contexto:** Adapta el correo de aplicación (`operaciones@alexendros.dev`). No copies el CoC de un SaaS con desk de comunidad. + +## Compromiso + +Como personas contribuyentes y mantenedoras de este proyecto, nos comprometemos a hacer de la participación una experiencia libre de acoso para todas las personas, con independencia de edad, cuerpo, discapacidad, etnia, identidad o expresión de género, nivel de experiencia, nacionalidad, apariencia, raza, religión u orientación sexual. + +## Estándares + +Comportamiento que contribuye a un ambiente positivo: + +- Lenguaje acogedor e inclusivo +- Respeto a puntos de vista y experiencias distintas +- Crítica constructiva +- Enfoque en lo que es mejor para el proyecto +- Empatía hacia otras personas + +Comportamiento inaceptable: + +- Lenguaje o imágenes sexualizadas +- Trolling, insultos y ataques personales o políticos +- Acoso público o privado +- Publicar información privada de terceros sin permiso explícito +- Cualquier otra conducta que pueda considerarse inapropiada en un entorno profesional + +## Alcance + +Este código aplica en issues, pull requests, discusiones, canales asociados y cualquier espacio donde se represente el proyecto. + +## Aplicación + +Los casos de comportamiento abusivo o inaceptable se reportan en privado a [operaciones@alexendros.dev](mailto:operaciones@alexendros.dev) o mediante un aviso a los mantenedores. No uses un issue público para denunciar acoso. Todas las quejas se revisarán e investigarán. + +Este documento se inspira en el [Contributor Covenant](https://www.contributor-covenant.org/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7b54f62 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# Contribuir a NEUBAT + +### Propósito de este documento + +- **Objetivos:** Explicar setup, flujo de rama/PR y reglas para contribuir sin romper el instalador, los perfiles JSON ni el portal. +- **Estructura:** Idioma → setup → flujo de trabajo → comprobaciones antes del PR → reglas. +- **Contenido a integrar según contexto:** Adapta Make y scripts npm de este repo. No copies un flujo pnpm/monorepo. La e2e QEMU y el build de ISO no son required. + +Idioma: este fichero, `README.md` y `docs/guides|runbooks` en español. Identificadores de CI y nombres de jobs en inglés (`quality`, `test`, `build`, `smoke`). + +Lee también [AGENTS.md](AGENTS.md), [ARCHITECTURE.md](ARCHITECTURE.md) y [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). + +## Setup + +```bash +cp -n .env.example .env +make install-deps +make install-deps-frontend +``` + +## Flujo de trabajo + +Rama `feat/*` / `fix/*` / `docs/*` / `chore/*` → PR contra `main`. Los agentes Cloud usan `cursor/…`. + +## Antes de un PR + +```bash +make lint +make test +make smoke +make validate +``` + +Si tocas el frontend: `make test-frontend` y `make build-frontend`. +Si tocas `ansible/`: `make test-ansible`. +Si tocas `scripts/*.sh`: `make test-bash` (requiere `bats`). + +`make test-vm` y `make build-iso` son opt-in (largos / Docker). + +## Reglas + +- El instalador **destruye el disco objetivo**. No relajes validaciones de `00-preinstall.sh` ni de `part_name()` sin prueba explícita. +- Cambios de contrato (API `/api/*`, formato JSON de `configs/`, HMAC, tokens) → actualiza `ARCHITECTURE.md` o un ADR en `docs/architecture/decisions/`. +- Conventional Commits. Cuerpo y docs en español. +- Vulnerabilidades: [SECURITY.md](SECURITY.md), no un issue público. +- Sin secretos, `.env`, `out/*.iso` ni datos de `portal/data/` en el diff. diff --git a/Makefile b/Makefile index 77a2734..2e84c27 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # NEUBAT - Makefile TAG ?= 1.0.0 -.PHONY: portal install-deps install-deps-frontend build-frontend validate lint test test-smoke test-vm test-bash test-ansible validate-ansible lint-ansible test-frontend build-iso release +.PHONY: portal install-deps install-deps-frontend build-frontend validate lint test smoke test-smoke test-vm test-bash test-ansible validate-ansible lint-ansible test-frontend build-iso release install-deps: cd portal && npm install @@ -26,19 +26,33 @@ validate: lint: @command -v shellcheck >/dev/null 2>&1 && shellcheck -x scripts/*.sh || echo "shellcheck no instalado; omitido" + @if [ -d portal/frontend/node_modules ]; then cd portal/frontend && npm run lint; else echo "oxlint omitido (sin node_modules del frontend)"; fi test: cd portal && npm test -test-smoke: validate - @cd portal && PORT=3100 timeout 8 node server.js & \ - sleep 2; \ - curl -sf http://localhost:3100/api/health && echo "OK /api/health"; \ - curl -sf -X POST http://localhost:3100/api/install -H 'Content-Type: application/json' \ - -d '{"profile":"base","hostname":"neubat-test"}' && echo "OK /api/install"; \ - wait || true - -# Prueba end-to-end en VM QEMU/NVMe (larga: ~40 min). Ver tests/vm/README.md +# Fachada canónica: health + POST /api/install (falla si el portal no responde) +smoke: + @cd portal && \ + PORT=3100 node server.js >/tmp/neubat-smoke.log 2>&1 & pid=$$!; \ + ok=0; \ + for i in 1 2 3 4 5 6 7 8 9 10 11 12; do \ + if curl -sf http://127.0.0.1:3100/api/health >/dev/null; then ok=1; break; fi; \ + sleep 0.5; \ + done; \ + if [ $$ok -ne 1 ]; then echo "smoke: /api/health no respondió"; cat /tmp/neubat-smoke.log; kill $$pid 2>/dev/null || true; exit 1; fi; \ + curl -sf http://127.0.0.1:3100/api/health && echo " OK /api/health"; \ + curl -sf -X POST http://127.0.0.1:3100/api/install -H 'Content-Type: application/json' \ + -d '{"profile":"base","hostname":"neubat-test"}' && echo " OK /api/install"; \ + status=$$?; \ + kill $$pid 2>/dev/null || true; \ + wait $$pid 2>/dev/null || true; \ + exit $$status + +# Alias conservado +test-smoke: smoke + +# Prueba end-to-end en VM QEMU/NVMe (larga: ~40 min, opt-in). Ver tests/vm/README.md test-vm: python3 tests/vm/neubat_vm_test.py @@ -57,7 +71,7 @@ test-ansible: validate-ansible lint-ansible test-frontend: install-deps-frontend cd portal/frontend && npm test -# Construir ISO híbrida con autoinstalación (requiere Docker) +# Construir ISO híbrida con autoinstalación (requiere Docker; opt-in) build-iso: bash scripts/build-iso.sh "$(TAG)" diff --git a/README.md b/README.md index 45b2592..6f4143f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # NEUBAT +### Propósito de este documento + +- **Objetivos:** Presentar el producto, el quickstart y los avisos de seguridad (destrucción de disco, HMAC, LUKS) para un operador o contribuidor nuevo. +- **Estructura:** Concepto → objetivos → características → árbol → quickstart → documentación → ISO → admin → seguridad → licencia. +- **Contenido a integrar según contexto:** Conserva el stack (Express + React + Bash + Ansible + iPXE). No copies un README de SaaS. Contratos operativos en [AGENTS.md](AGENTS.md) y [ARCHITECTURE.md](ARCHITECTURE.md). + **Instalación desatendida de Arch Linux por red (iPXE), con portal web responsive que genera URLs únicas de configuración.** Versión: 1.0.0 · Arquitectura: x86_64 · Sistema base: Arch Linux (rolling release) · Licencia: GPL-3.0 @@ -77,13 +83,15 @@ neubat/ ├── tests/ │ └── vm/ # Prueba end-to-end QEMU/NVMe (neubat_vm_test.py) └── docs/ - ├── INSTALL.md # Documento maestro de instalación y despliegue - ├── ARCHITECTURE.md # Arquitectura técnica y diagrama de flujo - ├── ROADMAP.md # Próximos pasos + ├── architecture/ # Overview + ADRs + ├── guides/ # Instalación, desarrollo, Ansible, paquetes + ├── runbooks/ # Portal, ISO, CI ├── RELEASE-v1.0.0.md # Notas de la release v1.0.0 └── assets/ # Capturas de pantalla ``` +Fachada local: `make lint`, `make test`, `make smoke`, `make validate`. + ## Quickstart ### 1. Portal web (Docker) @@ -150,9 +158,11 @@ bash scripts/validate-install.sh ## Documentación -- [docs/INSTALL.md](docs/INSTALL.md) — documento maestro completo -- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — diagrama de flujo y componentes -- [docs/ROADMAP.md](docs/ROADMAP.md) — próximos pasos +- [docs/README.md](docs/README.md) — índice +- [docs/guides/install.md](docs/guides/install.md) — documento maestro de instalación +- [ARCHITECTURE.md](ARCHITECTURE.md) — capas y contratos +- [docs/guides/roadmap.md](docs/guides/roadmap.md) — próximos pasos +- [CONTRIBUTING.md](CONTRIBUTING.md) · [SECURITY.md](SECURITY.md) · [AGENTS.md](AGENTS.md) ## Descarga de la ISO diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ae4b272 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,36 @@ +# Política de seguridad + +### Propósito de este documento + +- **Objetivos:** Declarar versiones soportadas, el canal privado de avisos y la superficie (portal, HMAC, LUKS, instalador). +- **Estructura:** Versiones soportadas → cómo reportar → superficie relevante → alcance. +- **Contenido a integrar según contexto:** Adapta versiones del portal/ISO y secretos de `.env.example`. No copies la política de un SaaS. No reutilices tokens de ejemplo con secretos reales; no commitees `.env` ni keyfiles LUKS. + +## Versiones soportadas + +| Versión | Soportada | +| ------- | --------- | +| 1.0.x (`main`, ISO/portal) | Sí | +| Ramas de trabajo / snapshots previos a v1.0.0 | No | + +## Cómo reportar una vulnerabilidad + +**No abras un issue público** si el hallazgo puede filtrar `ADMIN_TOKEN`, `NEUBAT_HMAC_SECRET`, keyfiles LUKS o facilitar una instalación manipulada. + +1. Preferible: [GitHub Security Advisory](https://github.com/Alexendros/neubat/security/advisories/new) en este repositorio. +2. Alternativa: correo a [operaciones@alexendros.dev](mailto:operaciones@alexendros.dev). + +Incluye: versión o commit, componente (portal / scripts / ISO), sistema operativo, y un caso **mínimo sintético** (nunca secretos reales). Responderemos en un plazo máximo de 7 días naturales. + +## Superficie relevante + +- El portal aplica rate-limiting básico en `/api/*`. En exposición pública, sitúalo detrás de TLS. +- `NEUBAT_HMAC_SECRET` firma configuraciones en tránsito; debe coincidir entre portal e instalador. +- LUKS2 con `keyfile` permite arranque desatendido: rota el keyfile tras instalar en entornos sensibles. +- Tokens de instalación son hex de 32 caracteres (`configPathFor`); no aceptes otros formatos. +- Contraseñas por defecto de los perfiles JSON (`neubat`) deben cambiarse en el primer acceso. +- No commitees `.env`, `portal/data/`, `ansible/generated/` ni keyfiles. + +## Alcance + +Este repositorio entrega un portal local/self-hosted y un instalador desatendido. No opera un SaaS multi-tenant. Las vulnerabilidades de un host ya instalado (servicios del perfil, AUR, escritorio) pertenecen a esos componentes, salvo que el defecto esté en los scripts o en la API del portal. diff --git a/deploy/pacman-cache/README.md b/deploy/pacman-cache/README.md index 10113da..efbb114 100644 --- a/deploy/pacman-cache/README.md +++ b/deploy/pacman-cache/README.md @@ -1,5 +1,11 @@ # Proxy caché de paquetes pacman (opcional) +### Propósito de este documento + +- **Objetivos:** Explicar el perfil Docker opcional de caché pacman. +- **Estructura:** Motivo → arranque → uso con el portal. +- **Contenido a integrar según contexto:** No es required de CI. No copies un proxy de otro mirror. + Acelera instalaciones NEUBAT repetidas en VMs o redes locales: nginx cachea los paquetes de un mirror upstream (los `.pkg.tar.*` son inmutables por versión) y los sirve a velocidad de red local. diff --git a/docs/ANSIBLE.md b/docs/ANSIBLE.md index 4e4403b..02d2a65 100644 --- a/docs/ANSIBLE.md +++ b/docs/ANSIBLE.md @@ -1,58 +1,9 @@ -# NEUBAT - Post-instalación con Ansible +# Trasladado -El directorio `ansible/` contiene el playbook de post-instalación que se ejecuta una sola vez en el primer arranque del sistema instalado. +### Propósito de este documento -## Estructura +- **Objetivos:** Conservar el enlace histórico `docs/ANSIBLE.md`. +- **Estructura:** Puntero a la guía canónica. +- **Contenido a integrar según contexto:** No edites Ansible aquí. -``` -ansible/ -├── ansible.cfg # Configuración de Ansible -├── inventory/local.yml # Inventario de localhost -├── site.yml # Playbook principal -├── roles/neubat/ # Rol de post-instalación -│ ├── defaults/main.yml # Variables por defecto -│ ├── handlers/main.yml # Handlers (reload systemd) -│ └── tasks/main.yml # Tareas del rol -│ └── templates/ -│ ├── neubat-portal.service.j2 # Servicio del portal local -│ └── neubat-firstboot.service.j2 # Servicio one-shot de first-boot -└── run-firstboot.sh # Script invocado por systemd -``` - -## Flujo - -1. Durante la instalación, `scripts/50-firstboot-ansible.sh` copia esta carpeta a `/opt/neubat-ansible` del sistema destino. -2. Genera `ansible/generated/neubat-ansible-vars.yml` a partir del JSON de configuración del portal/perfil. -3. Habilita `neubat-firstboot.service` con `ConditionFirstBoot=yes`. -4. Al arrancar por primera vez, systemd ejecuta `ansible-playbook site.yml`. -5. Ansible se encarga de: - - Asegurar el usuario NEUBAT y los grupos necesarios. - - Instalar los paquetes declarados en el perfil. - - Habilitar/iniciar los servicios declarados. - - Desplegar el portal local en `/opt/neubat-portal`. - - Generar la URL única de setup en `~/NEUBAT-URL.txt`. - -## Uso manual - -```bash -cd ansible -ansible-playbook -i inventory/local.yml site.yml -``` - -Para probar con variables generadas: - -```bash -python3 scripts/generate-ansible-vars.py \ - --config configs/base.json \ - --output ansible/generated/neubat-ansible-vars.yml -cd ansible -ansible-playbook -i inventory/local.yml site.yml -``` - -## Validación - -```bash -make validate-ansible # syntax-check -make lint-ansible # ansible-lint (si está instalado) -make test-ansible # ambos -``` +Guía: [guides/ansible.md](guides/ansible.md). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cb6e7b6..dca5865 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,62 +1,9 @@ -# NEUBAT — Arquitectura técnica +# Trasladado -## Diagrama de flujo completo +### Propósito de este documento -``` -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ USUARIO │────▶│ PORTAL WEB │────▶│ GENERACIÓN │ -│ (cualquier │ │ NEUBAT │ │ URL ÚNICA │ -│ dispositivo) │ │ (responsive) │ │ + TOKEN │ -└─────────────────┘ └─────────────────┘ └────────┬────────┘ - │ -┌─────────────────┐ ┌─────────────────┐ │ -│ SISTEMA │◀────│ POST-SCRIPT │◀─────────────┘ -│ INSTALADO │ │ (40-portal- │ -│ + PORTAL │ │ deploy.sh) │ -│ FUNCIONANDO │ └─────────────────┘ -└─────────────────┘ - ▲ - │ -┌─────────────────┐ ┌─────────────────┐ -│ NEUBAT- │────▶│ CONFIG JSON │ -│ INSTALL.SH │ │ (por token / │ -│ (desatendido) │ │ perfil local) │ -└─────────────────┘ └─────────────────┘ - ▲ - │ -┌─────────────────┐ -│ ARRANQUE │ -│ iPXE/HTTP │ -│ (sin USB) │ -└─────────────────┘ -``` +- **Objetivos:** Conservar el enlace histórico `docs/ARCHITECTURE.md` sin duplicar el contrato. +- **Estructura:** Punteros al overview y al contrato de raíz. +- **Contenido a integrar según contexto:** No edites arquitectura aquí. -## Componentes del sistema - -| Capa | Componente | Tecnología | Función | -|------|------------|------------|---------| -| **Presentación** | Portal web | Node.js + Express, SPA React + Vite + shadcn/ui | Interfaz usuario, generación de configs | -| **Persistencia** | DB JSON | `portal/data/installations.json` | Registro y seguimiento de instalaciones | -| **Distribución** | Arranque por red | iPXE + HTTP (mirror Arch / live NEUBAT) | Arranque sin medios físicos | -| **Fallback** | GRUB loopback | GRUB2 + ISO en disco | Arranque de ISO sin reescribir USB | -| **Instalación** | Script maestro | Bash + archinstall (cuando disponible) + pacstrap | Sistema base desatendido | -| **Configuración** | Módulos de fases | Bash (00–50) + JSON | Personalización por token | -| **Post-instalación** | Portal local + Ansible | systemd + Node.js + Ansible | Portal en el sistema instalado, URL única | - -## Secuencia de una instalación - -1. El usuario crea la instalación en el portal → `POST /api/install` → token + `boot_url`. -2. La máquina destino arranca por red y encadena `boot_url` (`/boot/`), que sirve un script iPXE personalizado con `neubat_token` en la línea de kernel. -3. El live ISO arranca; el operador (o un hook del ISO) ejecuta `neubat-install.sh `. -4. `20-archinstall.sh` descarga la config del portal (`GET /api/config/`); si falla, usa el perfil local. -5. Fases 1–5: particionado → pacstrap → chroot → aplicaciones → portal local. -6. El instalador notifica el resultado (`POST /api/complete`) y reinicia. -7. En el sistema instalado, `neubat-portal.service` sirve el portal local y `~/NEUBAT-URL.txt` contiene la URL única de setup. - -## Decisiones de diseño - -- **Sin jq/bc**: el ISO live de Arch garantiza `python3` pero no `jq` ni `bc`; el parseo JSON usa `python3` y la aritmética `awk`/`$(( ))`. -- **NVMe-safe**: `part_name()` resuelve `/dev/sda1` vs `/dev/nvme0n1p1`. -- **btrfs con zstd**: compresión transparente y `noatime` para SSD. -- **Perfiles declarativos**: los JSON de `configs/` definen paquetes y servicios; el portal los extiende sin tocar código. -- **Validación token**: `configPathFor()` exige hex de 32 caracteres (defensa contra path traversal). +El overview vive en [architecture/overview.md](architecture/overview.md). El contrato de raíz es [ARCHITECTURE.md](../ARCHITECTURE.md). diff --git a/docs/INSTALL.md b/docs/INSTALL.md index a08dad2..8aac002 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,384 +1,9 @@ -# NEUBAT — Documento Maestro de Instalación y Despliegue +# Trasladado -**Versión:** 1.0.0 -**Fecha:** 20 de septiembre de 2026 -**Arquitectura:** x86_64 -**Sistema base:** Arch Linux (rolling release) -**Entorno:** Producción — SSD/HDD bare metal +### Propósito de este documento ---- +- **Objetivos:** Conservar el enlace histórico `docs/INSTALL.md`. +- **Estructura:** Puntero a la guía canónica. +- **Contenido a integrar según contexto:** No edites instalación aquí. -## 1. Concepto y filosofía - -NEUBAT es un sistema de instalación desatendida de Arch Linux que despliega un entorno completo, preconfigurado y funcional desde Internet, sin medios físicos (USB/CD), mediante un portal web responsive que genera URLs únicas de configuración. - -- **Zero-touch deployment:** instalación sin intervención tras el arranque inicial. -- **Infrastructure as Code:** toda la configuración versionada y reproducible. -- **Rolling release:** sistema siempre actualizado sin migraciones traumáticas. -- **Monolito recortado:** sistema mínimo, sin bloatware. - -### Objetivos medibles - -| # | Objetivo | Métrica de éxito | -|---|----------|------------------| -| 1 | Portal usuarios responsive | Accesible desde móvil/desktop, < 2 s de carga | -| 2 | URL generada post-instalación | URL única funcional en < 5 min desde el arranque | -| 3 | Instalación desatendida | 0 intervenciones tras la selección inicial | - -## 2. Requisitos - -**Servidor del portal:** Node.js ≥ 18, puerto 3000 libre, conectividad con las máquinas destino. - -**Máquina destino:** arranque UEFI, soporte de arranque por red (PXE/iPXE) o ISO en disco (fallback GRUB loopback), disco ≥ 32 GiB, conexión a Internet. - -**Entorno live:** ISO oficial de Arch Linux reciente (incluye `python3`, `parted`, `pacstrap`, `reflector`). - -## 3. Despliegue del portal - -### Docker Compose (recomendado) - -```bash -cp .env.example .env # opcional: ajusta ADMIN_TOKEN y NEUBAT_MIRROR_BASE -docker compose up -d -``` - -Variables de entorno útiles: - -| Variable | Descripción | Defecto | -|----------|-------------|---------| -| `ADMIN_TOKEN` | Token para el panel `/admin` | — (panel deshabilitado si falta) | -| `NEUBAT_MIRROR_BASE` | Mirror base para el netboot iPXE | `https://geo.mirror.pkgbuild.com/iso/latest` | -| `NEUBAT_PORT` | Puerto expuesto del portal | `3000` | -| `NEUBAT_HMAC_SECRET` | Secreto compartido para firma HMAC de configuraciones | — | - -### Node.js nativo - -```bash -cd portal -npm install -npm start # producción en :3000 -npm run dev # desarrollo -``` - -Como servicio systemd, usar como plantilla la unidad que genera `scripts/40-portal-deploy.sh` (`neubat-portal.service`). - -### API - -| Método | Ruta | Descripción | -|--------|------|-------------| -| POST | `/api/install` | Crea instalación; body: `profile`, `hostname?`, `username?`, `password?`, `desktop?`, `packages?[]`, `encryption?` | -| GET | `/api/config/:token` | Devuelve el JSON de configuración (consumido por el instalador) | -| POST | `/api/complete` | El instalador notifica `status`, `hostname`, `duration?`, `error?` | -| GET | `/api/metrics` | Métricas agregadas de instalaciones | -| GET | `/api/installations` | Últimas 50 instalaciones | -| GET | `/api/installations/:token` | Estado de una instalación | -| GET | `/api/health` | Health check | -| GET | `/boot/:token` | Script iPXE personalizado para el token | - -### Panel de administración - -Disponible en `/admin`. Requiere `ADMIN_TOKEN`. Endpoints bajo `/api/admin`: - -| Método | Ruta | Descripción | -|--------|------|-------------| -| GET | `/api/admin/installations` | Listado completo de instalaciones | -| POST | `/api/admin/installations/:token/status` | Actualizar estado/hostname/error | -| POST | `/api/admin/installations/:token/reset` | Volver a estado `pending` | -| DELETE | `/api/admin/installations/:token` | Eliminar registro y config | - -El portal aplica rate-limiting (100 req / 15 min por IP) en `/api/*`. Para exposición pública, desplegar detrás de un reverse proxy con TLS. - -Variable de entorno opcional: `NEUBAT_MIRROR_BASE` — mirror base para el netboot iPXE (defecto: `https://geo.mirror.pkgbuild.com/iso/latest`). Apúntala a una caché local (`deploy/pacman-cache/`) cuando el firmware iPXE no tenga HTTPS compilado o para acelerar los arranques por red. - -## 4. Construcción de la ISO híbrida - -Para generar una ISO personalizada a partir del código actual (requiere Docker): - -```bash -make build-iso -# Salida: out/neubat-1.0.0-x86_64.iso -``` - -El proceso usa un contenedor Arch Linux con `archiso`, remasteriza el perfil `releng`, inyecta `/opt/neubat` y habilita `neubat-autoinstall.service`. Para publicar la ISO en GitHub: - -```bash -make release -``` - -## 5. Flujo de instalación - -### 5.1 Crear la instalación - -Desde la web (`http:///configurar`) o por API: - -```bash -curl -X POST http://:3000/api/install \ - -H 'Content-Type: application/json' \ - -d '{"profile":"production","hostname":"mi-equipo","desktop":"hyprland"}' -``` - -Respuesta: `token`, `config_url`, `boot_url`. - -Cuenta de usuario: registro en `/cuenta`. Absorción del sistema actual: genera un código en la cuenta y ejecuta `scripts/neubat-absorb.sh --code … --portal …`. - -### 5.2 Arrancar la máquina destino - -- **Por red (recomendado):** encadenar iPXE a `http://:3000/boot/`. El script incluye `neubat_token`, `neubat_profile` y `neubat_portal_url`. Para cero toques, publica el live NEUBAT en `NEUBAT_LIVE_DIR` (servido en `/live`) y define `NEUBAT_USE_LIVE=1` o `NEUBAT_LIVE_BASE`. Sin live, el mirror Arch arranca pero requiere ejecutar el instalador a mano o usar la ISO NEUBAT. -- **ISO híbrida autoinstalable:** en `/descargar` el portal verifica SHA-256 antes de guardar. También desde la [release](https://github.com/Alexendros/neubat/releases) con el `.sha256` generado por CI. Arranque: - - ``` - neubat_token= neubat_profile=production neubat_portal_url=http://:3000 - ``` - - El servicio `neubat-autoinstall.service` del live ISO lee esos parámetros y ejecuta el instalador de forma desatendida. - -- **Fallback USB/disco:** `netboot/grub/loopback.cfg` arranca el ISO almacenado en disco sin reescribir el medio. - -### 5.3 Ejecutar el instalador (desde el live ISO) - -```bash -export NEUBAT_PORTAL_URL="http://:3000" -export NEUBAT_ASSUME_YES=true # omite la confirmación de borrado -bash scripts/neubat-install.sh [perfil] -``` - -> **AVISO:** el instalador destruye todos los datos del disco objetivo. - -### Fases - -| Fase | Módulo | Acción | -|------|--------|--------| -| 0 | `00-preinstall.sh` | root, Internet, UEFI, herramientas live | -| 0b | `20-archinstall.sh` | Descarga config por token o usa perfil local | -| 1 | `10-partition.sh` | GPT: EFI 1 GiB + raíz btrfs + home btrfs + swap 4G | -| 2 | `20-archinstall.sh` | Mirrors (reflector) + pacstrap + fstab | -| 3 | `30-postinstall.sh` | chroot: locale, usuarios, systemd-boot, yay, `/etc/neubat-release` | -| 4 | `30-postinstall.sh` | Desktop y paquetes/servicios de la configuración | -| 5 | `35-snapper.sh` | Snapper + snap-pac si `snapshots.enabled` | -| 6 | `40-portal-deploy.sh` | Portal local + `~/NEUBAT-URL.txt` | -| 7 | `50-firstboot-ansible.sh` | Ansible first-boot | -| 8 | maestro | Notificación al portal, resumen y reinicio | - -## 6. Esquema de particionado - -| Partición | Tamaño | FS | Montaje | -|-----------|--------|-----|---------| -| p1 (ESP) | 1 GiB | FAT32 | `/boot` | -| p2 (raíz) | 19–29 GiB (según tamaño del disco) | btrfs (zstd, noatime) | `/` | -| p3 (home) | resto − 4 GiB | btrfs (zstd, noatime) | `/home` | -| p4 (swap) | 4 GiB | swap | — | - -Los nombres de partición se resuelven con `part_name()` (soporta `/dev/sda1` y `/dev/nvme0n1p1`). - -## 6.1 Cifrado de disco LUKS (Fase 6) - -NEUBAT puede cifrar las particiones de **raíz** y **home** con LUKS2. La partición EFI (`/boot`) permanece descifrada porque el firmware UEFI debe poder leer el cargador de arranque (systemd-boot). - -### Modos de arranque - -| Método | Campo `encryption.method` | Comportamiento | Seguridad | -|--------|---------------------------|----------------|-----------| -| **Keyfile en `/boot`** | `keyfile` | Arranque completamente desatendido | Protege datos en reposo si el disco está apagado; no protege si roban el disco con la partición EFI | -| **Passphrase manual** | `passphrase` | El initramfs pide la contraseña en cada arranque | Mayor seguridad física; rompe el despliegue zero-touch | - -### Configuración en el perfil - -```json -{ - "encryption": { - "enabled": true, - "method": "keyfile", - "passphrase": "cambiar-post-instalacion", - "cipher": "aes-xts-plain64", - "key_size": 512 - } -} -``` - -- `enabled`: activa/desactiva LUKS. -- `method`: `keyfile` (desatendido) o `passphrase` (interactivo). -- `passphrase`: se usa para formatear el contenedor cuando no hay keyfile; también puede usarse para añadir frases adicionales tras la instalación. -- `cipher` / `key_size`: parámetros de `cryptsetup luksFormat` (defecto `aes-xts-plain64` / 512). - -### Desde la API - -```bash -curl -X POST http://:3000/api/install \ - -H 'Content-Type: application/json' \ - -d '{ - "profile": "production", - "hostname": "mi-equipo", - "encryption": { "enabled": true, "method": "passphrase", "passphrase": "MiFraseSegura" } - }' -``` - -### Post-instalación recomendada - -Cuando uses `method: "keyfile"`, rota la llave tras el primer arranque: - -```bash -# Añade una passphrase y elimina el keyfile del slot 0 -sudo cryptsetup luksAddKey /dev/nvme0n1p2 -sudo cryptsetup luksRemoveKey /dev/nvme0n1p2 /etc/cryptsetup-keys.d/neubat_root.key -sudo rm /etc/cryptsetup-keys.d/neubat_root.key /etc/cryptsetup-keys.d/neubat_home.key -``` - -Para TPM2 o FIDO2, consulta `systemd-cryptenroll` (fuera del alcance del MVP). - -## 6.2 Snapshots btrfs automáticos (Fase 7) - -Cuando el perfil activa `snapshots.enabled`, NEUBAT instala `snapper` y `snap-pac` y configura snapshots automáticos de `/` y `/home`: - -- **Timeline:** snapshot cada hora (gestionado por `snapper-timeline.timer`). -- **Pacman:** `snap-pac` crea snapshots `pre`/`post` en cada operación de paquetes, permitiendo rollback si una actualización rompe el sistema. -- **Limpieza:** `snapper-cleanup.timer` aplica los límites configurados. - -### Configuración en el perfil - -```json -{ - "snapshots": { - "enabled": true, - "cleanup": { - "hourly": 5, - "daily": 7, - "weekly": 2, - "monthly": 2 - } - } -} -``` - -### Gestión básica - -```bash -# Listar snapshots de raíz -sudo snapper -c root list - -# Ver diferencias entre dos snapshots -sudo snapper -c root status .. - -# Restaurar un snapshot (boot desde snapshot + rollback) -sudo snapper -c root rollback -``` - -## 6.3 Firma HMAC y métricas de instalación (Fase 8) - -El portal puede firmar cada configuración con **HMAC-SHA256** para que el instalador verifique que no ha sido alterada en tránsito. - -### Configuración - -Establece el mismo secreto en el portal y en el entorno live del instalador: - -```bash -# .env del portal (o docker compose) -NEUBAT_HMAC_SECRET=una-cadena-larga-y-aleatoria - -# Entorno live del instalador -export NEUBAT_HMAC_SECRET="una-cadena-larga-y-aleatoria" -``` - -Si el secreto está configurado, el portal añade un campo `signature` al JSON de configuración. El instalador lo verifica automáticamente en `fetch_configuration()` y aborta si la firma no coincide. - -### Métricas - -El instalador mide su duración en segundos y la envía al portal en `/api/complete`: - -```bash -curl http://:3000/api/metrics -``` - -Respuesta: - -```json -{ - "total": 10, - "completed": 8, - "failed": 1, - "pending": 1, - "avg_duration_seconds": 420, - "duration_count": 8 -} -``` - -## 7. Perfiles de configuración - -Los perfiles viven en `configs/` (`base`, `production`, `developer`). Claves: - -| Clave | Defecto | Descripción | -|-------|---------|-------------| -| `hostname` | `neubat-*` | Nombre del equipo | -| `username` | `neubat` | Usuario principal (grupo wheel) | -| `password` | `neubat` | Contraseña inicial de usuario y root — **cambiar en el primer acceso** | -| `disk` | `/dev/sda` | Disco objetivo (**se borra entero**) | -| `desktop` | `none` | `kde` · `gnome` · `xfce` · `none` | -| `packages` | — | Paquetes pacman adicionales (los paquetes AUR deben instalarse post-instalación con yay) | -| `services` | — | Servicios systemd a habilitar | -| `timezone` / `locale` / `keyboard` | Madrid / es_ES / es | Regionalización | - -## 8. Portal local post-instalación - -El sistema instalado incluye `neubat-portal.service` (Node.js en :3000, usuario no-root, código en `/opt/neubat-portal`). La URL única de setup queda en `~/NEUBAT-URL.txt`: - -``` -http://.local:3000/setup/ -``` - -## 9. Validación post-instalación - -```bash -bash scripts/validate-install.sh -``` - -Comprueba: `/etc/neubat-release`, hostname, usuario no-root, Internet, NetworkManager, sshd, Docker, portal local, espacio en disco y fstab. Devuelve código de salida no nulo si algo falla. - -## 10. Notas de seguridad - -- La construcción desatendida de paquetes AUR (yay) requiere `NOPASSWD` temporal en `%wheel`; **el instalador lo retira automáticamente** al terminar (`/etc/sudoers.d/neubat` queda `%wheel ALL=(ALL:ALL) ALL`). -- Cambiar las contraseñas iniciales de usuario y root en el primer acceso. -- Los tokens son hex aleatorios de 128 bits; el portal valida su formato antes de tocar el sistema de archivos. -- `boot_url` y `config_url` no llevan autenticación: quien posea el token puede descargar la configuración. Tratar los tokens como secretos y, en producción, servir bajo TLS. - -## 11. Solución de problemas - -| Síntoma | Causa probable | Acción | -|---------|----------------|--------| -| `Sin config personalizada; usando perfil local` | Portal inalcanzable o token inexistente | Verificar `NEUBAT_PORTAL_URL` y el token | -| `parted` falla en NVMe | Nombre de partición | Resuelto por `part_name()`; si persiste, revisar `lsblk` | -| `yay` no se instaló | Fallo de red/AUR durante el chroot | No crítico: `git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si` | -| Portal local no responde | `npm install` falló en destino | `cd /opt/neubat-portal && npm install --omit=dev && systemctl restart neubat-portal` | -| Log completo | — | `/var/log/neubat-install.log` (en el entorno live) | - -## 12. Pruebas en VM (QEMU/KVM) - -Lecciones aprendidas al validar NEUBAT en QEMU con disco NVMe virtual: - -| Problema | Causa | Solución | -|----------|-------|----------| -| `IP-Config: no response` en initramfs | `ip=dhcp` usa `ipconfig` (klibc), que busca `eth0`; con nombres predecibles no existe. Incluso con `eth0`, `ipconfig` puede no obtener respuesta del servidor DHCP interno de QEMU slirp | Añadir `net.ifnames=0` a la cmdline. En red con DHCP real (slirp) / router doméstico) el netboot por iPXE funciona; en slirp pura la fase de `archiso_http_srv` puede quedarse sin red. Usar kernel directo para pruebas locales o una red con DHCP real | -| iPXE con build estándar sin HTTPS | `ipxe.lkrn` de boot.ipxe.org no incluye HTTPS en su build por defecto | Usar `NEUBAT_MIRROR_BASE` apuntando a la caché HTTP local (`deploy/pacman-cache/`) o un build de iPXE con HTTPS | -| `/dev/disk/by-label/ARCH_*` no aparece | cdrom IDE sin módulo en initramfs (máquina `pc`) | Usar `-machine q35` (cdrom SATA/AHCI) | -| Descarga de pacman congelada | virtio-net + red slirp se cuelga en transferencias grandes | Usar NIC `-device e1000,netdev=...` | -| reflector agota timeouts | su rating usa 5 s por defecto | `--download-timeout 30` en redes lentas | -| Consola serie sin prompt | el prompt zsh del ISO lleva códigos ANSI | En automatización (pexpect), usar patrones tolerantes a escapes | -| SSH tras instalar | `PermitRootLogin prohibit-password` por defecto | Entrar con el usuario del perfil, no root | - -Ejemplo de lanzamiento con kernel directo (consola serie completa): - -```bash -qemu-system-x86_64 -machine q35 -enable-kvm -cpu host -m 4096 -smp 4 \ - -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ - -drive if=pflash,format=raw,file=vars.fd \ - -drive file=disk.qcow2,if=none,id=nvm0,format=qcow2 -device nvme,drive=nvm0 \ - -cdrom archlinux-x86_64.iso \ - -kernel vmlinuz-linux -initrd initramfs-linux.img \ - -append "archisobasedir=arch archisolabel=ARCH_YYYYMM console=ttyS0" \ - -netdev user,id=n0,hostfwd=tcp::2222-:22 -device e1000,netdev=n0 \ - -nographic -``` - -Para iterar rápido, usar la caché de paquetes de `deploy/pacman-cache/`. - ---- - -**Hash de verificación del documento:** `neubat-doc-v1.0-20260920` +Documento maestro: [guides/install.md](guides/install.md). diff --git a/docs/PACKAGES.md b/docs/PACKAGES.md index 2293a05..67a7c08 100644 --- a/docs/PACKAGES.md +++ b/docs/PACKAGES.md @@ -1,133 +1,9 @@ -# NEUBAT — Panel de paquetes por perfil +# Trasladado -Este documento lista los paquetes que vienen predefinidos en cada perfil de instalación. Los paquetes marcados con **(AUR)** no están en los repositorios oficiales de Arch Linux y se instalan con `yay` tras el primer arranque si fallan durante la instalación desatendida. +### Propósito de este documento -## Leyenda +- **Objetivos:** Conservar el enlace histórico `docs/PACKAGES.md`. +- **Estructura:** Puntero a la guía canónica. +- **Contenido a integrar según contexto:** No edites paquetes aquí. -| Símbolo | Significado | -|---------|-------------| -| 🖥️ | Entorno de escritorio / gestor de ventanas | -| 🔧 | Herramientas de desarrollo/sistema | -| 🌐 | Red / Internet | -| 🛡️ | Seguridad / cifrado | -| 🎨 | Multimedia / productividad | -| 📦 | Servicios / infraestructura | - ---- - -## Perfil `base` (mínimo) - -Escritorio: `none` · Disco: `/dev/sda` · Cifrado: desactivado - -| Paquete | Categoría | Descripción | -|---------|-----------|-------------| -| `htop` | 🔧 | Monitor de procesos interactivo | -| `btop` | 🔧 | Monitor de recursos con gráficos ANSI | -| `fastfetch` | 🔧 | Información del sistema (sustituto moderno de neofetch) | -| `git` | 🔧 | Control de versiones | -| `curl` | 🌐 | Cliente HTTP/HTTPS | -| `wget` | 🌐 | Descarga de archivos por HTTP/FTP | -| `okular` | 🎨 | Visor de documentos universal (PDF, ePub, DjVu, etc.) | - -Servicios habilitados: `NetworkManager`, `sshd` - ---- - -## Perfil `production` (escritorio KDE + apps ofimáticas) - -Escritorio: `kde` · Disco: `/dev/nvme0n1` · Cifrado: **activado con keyfile** - -| Paquete | Categoría | Descripción | -|---------|-----------|-------------| -| `plasma-meta` | 🖥️ | Escritorio KDE Plasma | -| `kde-applications-meta` | 🖥️ | Aplicaciones básicas de KDE | -| `sddm` | 🖥️ | Gestor de pantalla | -| `docker` | 📦 | Contenedores | -| `docker-compose` | 📦 | Orquestación de contenedores | -| `nodejs` / `npm` | 🔧 | Runtime y gestor de paquetes JavaScript | -| `python` / `python-pip` | 🔧 | Python 3 y pip | -| `nginx` | 📦 | Servidor web/proxy inverso | -| `postgresql` | 📦 | Base de datos relacional | -| `redis` | 📦 | Almacén clave-valor en memoria | -| `htop` / `btop` / `fastfetch` | 🔧 | Monitores del sistema | -| `git` / `curl` / `wget` | 🔧🌐 | Desarrollo y red | -| `firefox` | 🌐 | Navegador web | -| `libreoffice-fresh` | 🎨 | Suite ofimática | -| `vlc` | 🎨 | Reproductor multimedia | -| `gimp` | 🎨 | Edición de imágenes | -| `okular` | 🎨 | Visor de documentos | - -Servicios habilitados: `NetworkManager`, `sshd`, `docker`, `nginx`, `postgresql`, `redis` - -### Cambios recientes - -- **Añadido:** `okular` (visor de documentos). -- **Eliminados:** `audacity`, `shotwell`. Si necesitas gestión fotográfica avanzada, instala `digiKam` desde KDE. - ---- - -## Perfil `developer` (escritorio GNOME + toolchain) - -Escritorio: `gnome` · Disco: `/dev/sda` · Cifrado: desactivado - -| Paquete | Categoría | Descripción | -|---------|-----------|-------------| -| `gnome` / `gnome-extra` | 🖥️ | Escritorio GNOME y aplicaciones extra | -| `gdm` | 🖥️ | Gestor de pantalla | -| `docker` / `docker-compose` | 📦 | Contenedores | -| `nodejs` / `npm` / `yarn` | 🔧 | JavaScript/TypeScript | -| `python` / `python-pip` / `python-poetry` | 🔧 | Python y gestores de dependencias | -| `go` | 🔧 | Lenguaje Go | -| `rust` | 🔧 | Lenguaje Rust (toolchain) | -| `code` | 🔧 | Visual Studio Code (editor) | -| `jetbrains-toolbox` | 🔧 | Gestor de IDEs JetBrains **(AUR)** | -| `postman-bin` | 🔧 | Cliente API REST **(AUR)** | -| `insomnia` | 🔧 | Cliente API REST alternativo **(AUR)** | -| `github-cli` | 🔧 | CLI de GitHub (`gh`) | -| `gitlab-runner` | 🔧 | Runner de CI/CD de GitLab | -| `kubectl` / `helm` | 🔧 | Orquestación Kubernetes | -| `minikube` | 🔧 | Kubernetes local | -| `terraform` | 🔧 | Infraestructura como código | -| `ansible` | 🔧 | Automatización de configuración | -| `git` | 🔧 | Control de versiones | -| `okular` | 🎨 | Visor de documentos | - -Servicios habilitados: `NetworkManager`, `sshd`, `docker` - ---- - -## Paquetes base del sistema (todos los perfiles) - -Instalados siempre por `pacstrap` en `scripts/20-archinstall.sh`: - -| Paquete | Propósito | -|---------|-----------| -| `base` | Sistema base de Arch Linux | -| `linux` / `linux-firmware` | Kernel y firmware | -| `btrfs-progs` | Utilidades para btrfs | -| `cryptsetup` | Cifrado LUKS (necesario aunque el perfil no lo active) | -| `grub` / `efibootmgr` | Cargador de arranque UEFI | -| `networkmanager` | Conectividad de red | -| `sudo` / `git` / `base-devel` | Privilegios, fuentes y compilación | -| `curl` / `wget` / `inetutils` | Herramientas de red | -| `reflector` | Optimización de mirrors pacman | -| `neovim` / `nano` | Editores de texto | -| `terminus-font` | Fuente para consola | -| `openssh` | Servidor SSH | -| `ansible` | Automatización post-instalación (first-boot) | - ---- - -## Aplicaciones sugeridas (no incluidas por defecto) - -| Aplicación | Paquete | Notas | -|------------|---------|-------| -| Proton Mail (escritorio) | `proton-mail` | **AUR**; cliente oficial de Proton Mail. Instalar tras el primer arranque con `yay -S proton-mail` | -| Proton VPN | `proton-vpn-gtk-app` | **AUR** | -| Brave | `brave-bin` | **AUR**; navegador centrado en privacidad | -| KeePassXC | `keepassxc` | Gestor de contraseñas | -| Nextcloud Desktop | `nextcloud-client` | Sincronización de nube | - ---- - -**Fecha del documento:** 22 de septiembre de 2026 +Guía: [guides/packages.md](guides/packages.md). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..7108b89 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,23 @@ +# Documentación de NEUBAT + +### Propósito de este documento + +- **Objetivos:** Indexar la documentación de producto (arquitectura, guías y runbooks) y apuntar a los contratos de la raíz. +- **Estructura:** Tabla de rutas `docs/` → enlaces a README, AGENTS, ARCHITECTURE, CONTRIBUTING y SECURITY. +- **Contenido a integrar según contexto:** Adapta el índice al árbol de este repo. No copies guías de un SaaS ni de una CLI de bundles. El instalador y el portal son el producto; la ISO QEMU e2e es opt-in. + +| Ruta | Para qué | +| ---- | -------- | +| [architecture/overview.md](./architecture/overview.md) | Flujo iPXE → portal → instalador | +| [architecture/decisions/](./architecture/decisions/) | ADRs (las decisiones vivas están en ARCHITECTURE.md) | +| [guides/install.md](./guides/install.md) | Documento maestro de instalación | +| [guides/desarrollo.md](./guides/desarrollo.md) | Arranque local y PR | +| [guides/ansible.md](./guides/ansible.md) | First-boot Ansible | +| [guides/packages.md](./guides/packages.md) | Paquetes por perfil | +| [guides/roadmap.md](./guides/roadmap.md) | Estado de fases y siguientes pasos | +| [runbooks/portal.md](./runbooks/portal.md) | Diagnóstico del portal y `/api/health` | +| [runbooks/iso.md](./runbooks/iso.md) | Construcción de la ISO híbrida | +| [runbooks/ci.md](./runbooks/ci.md) | Jobs `quality` / `test` / `build` / `smoke` | +| [RELEASE-v1.0.0.md](./RELEASE-v1.0.0.md) | Notas de la release v1.0.0 | + +En la raíz: [README.md](../README.md), [AGENTS.md](../AGENTS.md), [ARCHITECTURE.md](../ARCHITECTURE.md), [CONTRIBUTING.md](../CONTRIBUTING.md), [SECURITY.md](../SECURITY.md). diff --git a/docs/RELEASE-v1.0.0.md b/docs/RELEASE-v1.0.0.md index 1a1c2df..0a13d4c 100644 --- a/docs/RELEASE-v1.0.0.md +++ b/docs/RELEASE-v1.0.0.md @@ -1,5 +1,11 @@ # NEUBAT v1.0.0 — Release Notes +### Propósito de este documento + +- **Objetivos:** Conservar las notas de la release pública v1.0.0 (ISO, portal Docker, panel admin). +- **Estructura:** Novedades → artefactos → uso de la ISO → seguridad → checksum. +- **Contenido a integrar según contexto:** No reescribas este histórico; las entradas nuevas van a [CHANGELOG.md](../CHANGELOG.md). + **Fecha:** 19 de septiembre de 2026 ## Novedades diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 5d5b871..4c276f3 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,52 +1,9 @@ -# NEUBAT — Roadmap y próximos pasos +# Trasladado -## Estado actual (24 de septiembre de 2026) +### Propósito de este documento -| Prioridad | Tarea | Responsable | Estimación | Estado | -|-----------|-------|-------------|------------|--------| -| P0 | Desplegar servidor portal en entorno de pruebas | DevOps | 2h | ✅ Hecho (19-sep-2026) | -| P0 | Validar script iPXE en VM (VirtualBox/QEMU) | QA | 1h | ✅ Hecho parcialmente (19-sep-2026): cadena iPXE probada en QEMU — `dhcp` + `chain` al portal + descarga kernel/initrd + boot. La fase `archiso_http_srv` queda validada en red con DHCP real (QEMU slirp no responde a `ipconfig` de klibc) | -| P0 | Probar instalación completa en VM con disco NVMe virtual | QA | 2h | ✅ Hecho (19-sep-2026, perfil base en QEMU/NVMe: particionado, chroot, portal local y URL única verificados por SSH) | -| P1 | Crear imagen Docker del portal para distribución | Dev | 3h | ✅ Hecho (19-sep-2026) | -| P1 | Generación de ISO híbrida con hook de autoinstalación | Dev | 4h | ✅ Hecho (19-sep-2026) | -| P1 | Endurecer sudoers post-instalación (retirar NOPASSWD) | Dev | 1h | ✅ Hecho (19-sep-2026, automático al final de `30-postinstall.sh`) | -| P2 | Panel de administración web para seguimiento | Frontend | 8h | ✅ Hecho (19-sep-2026) | -| P1 | Publicar release v1.0.0 con ISO híbrida en GitHub | DevOps | 1h | ✅ Hecho (20-sep-2026) | -| P2 | Integración con Ansible para configuración post-instalación | DevOps | 6h | ✅ Hecho (20-sep-2026) | -| P1 | Rediseño GUI-UX con React + shadcn/ui | Frontend | 10h | ✅ Hecho (20-sep-2026) | -| P2 | Soporte de cifrado LUKS en particionado | Dev | 4h | ✅ Hecho (22-sep-2026, PR #17) | -| P3 | Snapshots btrfs automáticos pre/post actualización | Dev | 3h | ✅ Hecho (22-sep-2026, PR #18) | -| P2 | Firma y verificación de configuraciones (HMAC) | Dev | 3h | ✅ Hecho (22-sep-2026, PR #18); extendedido a encryption/snapshots (24-sep-2026) | -| P3 | Métricas de instalación reportadas al portal | Dev | 2h | ✅ Hecho (22-sep-2026, PR #18) | -| P1 | Listado de instalaciones solo con ADMIN_TOKEN | Dev | 1h | ✅ Hecho (24-sep-2026) | +- **Objetivos:** Conservar el enlace histórico `docs/ROADMAP.md`. +- **Estructura:** Puntero a la guía canónica. +- **Contenido a integrar según contexto:** No edites el roadmap aquí. -## Objetivos de producto (implementados 24-sep-2026) - -| Fase | Objetivo | Estado | -|------|----------|--------| -| 1 | Saneamiento (HMAC encryption/snapshots, listados privados, docs) | ✅ | -| 2 | Sitio de presentación, tokens OKLCH, WCAG 2.2 AA + axe en CI | ✅ | -| 3 | Panel de usuario con login, perfiles y recomendaciones | ✅ | -| 4 | Configurador (paquetes, WM/escritorio, locale) | ✅ | -| 5 | Motor `archinstall` (opcional) + scripts para LUKS/snapper/portal/AUR | ✅ | -| 6 | Absorber configuración del sistema actual (`scripts/neubat-absorb.sh`) | ✅ | -| 7 | ISO con verificación automática de hash al descargar | ✅ | -| 8 | Netinstall por URL con `neubat_portal_url` y live en `/live` | ✅ | - -## Próximos pasos técnicos - -| Prioridad | Tarea | Motivación | Estimación | -|-----------|-------|------------|------------| -| P1 | **Validación end-to-end de LUKS + snapper + HMAC en VM** | Confirmar que las nuevas fases funcionan juntas en un flujo real de instalación | 2h | -| P2 | **Rotación automática del keyfile LUKS** | Tras el primer arranque, reemplazar el keyfile de `/boot` por una passphrase o enrolar TPM2/FIDO2 | 3h | -| P3 | **Servidor iPXE propio con imágenes cacheadas** | Independencia del mirror upstream de Arch y arranques más rápidos/repetibles | 6h | -| P3 | **Perfiles como paquetes versionados (`neubat-profile-*`)** | Distribuir perfiles por separado y permitir comunidad/contribuciones | 8h | -| P3 | **Métricas por fase de instalación** | Reportar duración de cada fase (particionado, pacstrap, chroot, etc.) para diagnóstico | 3h | - -## Ideas a evaluar - -- Soporte para RAID/btrfs en múltiples discos. -- Instalaciones remotas con consola serie y watchdog. -- Dashboard en tiempo real de instalaciones en curso (WebSockets). -- Notificaciones por correo/Telegram al completar una instalación. -- Integrar Proton Mail app en perfil production (AUR). +Roadmap: [guides/roadmap.md](guides/roadmap.md). diff --git a/docs/architecture/decisions/README.md b/docs/architecture/decisions/README.md new file mode 100644 index 0000000..82508ef --- /dev/null +++ b/docs/architecture/decisions/README.md @@ -0,0 +1,29 @@ +# Decisiones de arquitectura + +### Propósito de este documento + +- **Objetivos:** Señalar dónde viven las decisiones de diseño y cuándo abrir un ADR numerado. +- **Estructura:** Estado actual → criterio para un ADR nuevo → plantilla mínima. +- **Contenido a integrar según contexto:** No copies ADRs de otro producto. Las decisiones vivas de NEUBAT (sin jq/bc, NVMe-safe, btrfs+zstd, tokens hex, HMAC) están en [ARCHITECTURE.md](../../../ARCHITECTURE.md). + +Hasta que un cambio de contrato lo exija, **no hay ADRs numerados**. Resume y justifica en `ARCHITECTURE.md` y, si el cambio toca: + +- el formato de `configs/*.json` o la validación de tokens, +- el contrato HMAC / LUKS / snapper, +- la API pública del portal (`/api/*`, `/boot/`), +- o el particionado desatendido, + +abre `NNNN-titulo.md` en este directorio en el mismo PR. + +## Plantilla + +```markdown +# NNNN — Título + +Fecha: YYYY-MM-DD +Estado: propuesta | aceptada | supersedida + +## Contexto +## Decisión +## Consecuencias +``` diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..c2b49b8 --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,68 @@ +# NEUBAT — Arquitectura técnica + +### Propósito de este documento + +- **Objetivos:** Detallar el flujo iPXE → portal → instalador → post-instalación y las fronteras entre capas para no romper el contrato de tokens, JSON de perfiles ni el particionado. +- **Estructura:** Diagrama de flujo → componentes → secuencia de instalación → decisiones de diseño. +- **Contenido a integrar según contexto:** Adapta este overview a NEUBAT (Arch, iPXE, portal Express). No copies la arquitectura de una CLI de bundles ni de un SaaS. El contrato de raíz está en [ARCHITECTURE.md](../../ARCHITECTURE.md). + +## Diagrama de flujo completo + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ USUARIO │────▶│ PORTAL WEB │────▶│ GENERACIÓN │ +│ (cualquier │ │ NEUBAT │ │ URL ÚNICA │ +│ dispositivo) │ │ (responsive) │ │ + TOKEN │ +└─────────────────┘ └─────────────────┘ └────────┬────────┘ + │ +┌─────────────────┐ ┌─────────────────┐ │ +│ SISTEMA │◀────│ POST-SCRIPT │◀─────────────┘ +│ INSTALADO │ │ (40-portal- │ +│ + PORTAL │ │ deploy.sh) │ +│ FUNCIONANDO │ └─────────────────┘ +└─────────────────┘ + ▲ + │ +┌─────────────────┐ ┌─────────────────┐ +│ NEUBAT- │────▶│ CONFIG JSON │ +│ INSTALL.SH │ │ (por token / │ +│ (desatendido) │ │ perfil local) │ +└─────────────────┘ └─────────────────┘ + ▲ + │ +┌─────────────────┐ +│ ARRANQUE │ +│ iPXE/HTTP │ +│ (sin USB) │ +└─────────────────┘ +``` + +## Componentes del sistema + +| Capa | Componente | Tecnología | Función | +|------|------------|------------|---------| +| **Presentación** | Portal web | Node.js + Express, SPA React + Vite + shadcn/ui | Interfaz usuario, generación de configs | +| **Persistencia** | DB JSON | `portal/data/installations.json` | Registro y seguimiento de instalaciones | +| **Distribución** | Arranque por red | iPXE + HTTP (mirror Arch / live NEUBAT) | Arranque sin medios físicos | +| **Fallback** | GRUB loopback | GRUB2 + ISO en disco | Arranque de ISO sin reescribir USB | +| **Instalación** | Script maestro | Bash + archinstall (cuando disponible) + pacstrap | Sistema base desatendido | +| **Configuración** | Módulos de fases | Bash (00–50) + JSON | Personalización por token | +| **Post-instalación** | Portal local + Ansible | systemd + Node.js + Ansible | Portal en el sistema instalado, URL única | + +## Secuencia de una instalación + +1. El usuario crea la instalación en el portal → `POST /api/install` → token + `boot_url`. +2. La máquina destino arranca por red y encadena `boot_url` (`/boot/`), que sirve un script iPXE personalizado con `neubat_token` en la línea de kernel. +3. El live ISO arranca; el operador (o un hook del ISO) ejecuta `neubat-install.sh `. +4. `20-archinstall.sh` descarga la config del portal (`GET /api/config/`); si falla, usa el perfil local. +5. Fases 1–5: particionado → pacstrap → chroot → aplicaciones → portal local. +6. El instalador notifica el resultado (`POST /api/complete`) y reinicia. +7. En el sistema instalado, `neubat-portal.service` sirve el portal local y `~/NEUBAT-URL.txt` contiene la URL única de setup. + +## Decisiones de diseño + +- **Sin jq/bc**: el ISO live de Arch garantiza `python3` pero no `jq` ni `bc`; el parseo JSON usa `python3` y la aritmética `awk`/`$(( ))`. +- **NVMe-safe**: `part_name()` resuelve `/dev/sda1` vs `/dev/nvme0n1p1`. +- **btrfs con zstd**: compresión transparente y `noatime` para SSD. +- **Perfiles declarativos**: los JSON de `configs/` definen paquetes y servicios; el portal los extiende sin tocar código. +- **Validación token**: `configPathFor()` exige hex de 32 caracteres (defensa contra path traversal). diff --git a/docs/guides/ansible.md b/docs/guides/ansible.md new file mode 100644 index 0000000..810b0ec --- /dev/null +++ b/docs/guides/ansible.md @@ -0,0 +1,64 @@ +# NEUBAT - Post-instalación con Ansible + +### Propósito de este documento + +- **Objetivos:** Explicar el playbook de first-boot, qué toca en el sistema instalado y cómo validarlo en local. +- **Estructura:** Árbol `ansible/` → flujo first-boot → uso manual → validación (`make test-ansible`). +- **Contenido a integrar según contexto:** Adapta roles y variables generadas desde `configs/*.json`. No copies un playbook de otro host. No commitees `ansible/generated/`. + +El directorio `ansible/` contiene el playbook de post-instalación que se ejecuta una sola vez en el primer arranque del sistema instalado. + +## Estructura + +``` +ansible/ +├── ansible.cfg # Configuración de Ansible +├── inventory/local.yml # Inventario de localhost +├── site.yml # Playbook principal +├── roles/neubat/ # Rol de post-instalación +│ ├── defaults/main.yml # Variables por defecto +│ ├── handlers/main.yml # Handlers (reload systemd) +│ └── tasks/main.yml # Tareas del rol +│ └── templates/ +│ ├── neubat-portal.service.j2 # Servicio del portal local +│ └── neubat-firstboot.service.j2 # Servicio one-shot de first-boot +└── run-firstboot.sh # Script invocado por systemd +``` + +## Flujo + +1. Durante la instalación, `scripts/50-firstboot-ansible.sh` copia esta carpeta a `/opt/neubat-ansible` del sistema destino. +2. Genera `ansible/generated/neubat-ansible-vars.yml` a partir del JSON de configuración del portal/perfil. +3. Habilita `neubat-firstboot.service` con `ConditionFirstBoot=yes`. +4. Al arrancar por primera vez, systemd ejecuta `ansible-playbook site.yml`. +5. Ansible se encarga de: + - Asegurar el usuario NEUBAT y los grupos necesarios. + - Instalar los paquetes declarados en el perfil. + - Habilitar/iniciar los servicios declarados. + - Desplegar el portal local en `/opt/neubat-portal`. + - Generar la URL única de setup en `~/NEUBAT-URL.txt`. + +## Uso manual + +```bash +cd ansible +ansible-playbook -i inventory/local.yml site.yml +``` + +Para probar con variables generadas: + +```bash +python3 scripts/generate-ansible-vars.py \ + --config configs/base.json \ + --output ansible/generated/neubat-ansible-vars.yml +cd ansible +ansible-playbook -i inventory/local.yml site.yml +``` + +## Validación + +```bash +make validate-ansible # syntax-check +make lint-ansible # ansible-lint (si está instalado) +make test-ansible # ambos +``` diff --git a/docs/guides/desarrollo.md b/docs/guides/desarrollo.md new file mode 100644 index 0000000..3da359c --- /dev/null +++ b/docs/guides/desarrollo.md @@ -0,0 +1,53 @@ +# Guía de desarrollo + +### Propósito de este documento + +- **Objetivos:** Arrancar el portal en local, correr la fachada `make` y abrir un PR sin romper CI. +- **Estructura:** Requisitos → setup → comandos → flujo de rama → qué no commitear. +- **Contenido a integrar según contexto:** Adapta scripts npm de `portal/` y `portal/frontend/`. No copies un flujo pnpm/monorepo. La e2e QEMU (`make test-vm`) es opt-in y no es required en CI. + +## Requisitos + +- Node.js ≥ 18 (CI usa 22) +- npm +- `make`, `curl`, `python3` +- Opcional: `shellcheck`, `bats`, `ansible` + `ansible-lint`, Docker (ISO) + +## Setup + +```bash +cp -n .env.example .env # no commitees .env +make install-deps +make install-deps-frontend +``` + +Portal en local: + +```bash +make portal # build frontend + npm start → http://localhost:3000 +# o +docker compose up -d +``` + +## Fachada Make (contrato P1) + +| Objetivo | Qué hace | +| -------- | -------- | +| `make lint` | shellcheck de `scripts/*.sh` + oxlint del frontend si hay `node_modules` | +| `make test` | Jest del portal | +| `make smoke` | `/api/health` + `POST /api/install` contra un portal temporal | +| `make validate` | `bash -n`, `node --check` y JSON de `configs/` | + +Complementarios (no required de CI): `make test-frontend`, `make test-bash`, `make test-ansible`, `make test-vm`, `make build-iso`. + +## Flujo de rama + +`feat/*` / `fix/*` / `docs/*` / `chore/*` → PR contra `main`. Los agentes Cloud usan `cursor/…`. + +Mensajes: Conventional Commits; cuerpo y docs en español. + +## Qué no commitear + +- `.env`, tokens reales, `ADMIN_TOKEN` / `NEUBAT_HMAC_SECRET` de producción +- `portal/data/`, `portal/configs/generated/`, `ansible/generated/`, `out/` +- `portal/public/assets/` y `portal/public/index.html` (artefacto Vite) diff --git a/docs/guides/install.md b/docs/guides/install.md new file mode 100644 index 0000000..80dce6c --- /dev/null +++ b/docs/guides/install.md @@ -0,0 +1,390 @@ +# NEUBAT — Documento Maestro de Instalación y Despliegue + +### Propósito de este documento + +- **Objetivos:** Guiar el despliegue del portal, el arranque por red/ISO y la instalación desatendida sin destruir datos por error. +- **Estructura:** Concepto → requisitos → portal → arranque iPXE/ISO → fases del instalador → validación y seguridad. +- **Contenido a integrar según contexto:** Adapta URLs, perfiles JSON y variables de `.env.example`. No copies un runbook de otro producto. El instalador **destruye el disco objetivo**. + +**Versión:** 1.0.0 +**Fecha:** 20 de septiembre de 2026 +**Arquitectura:** x86_64 +**Sistema base:** Arch Linux (rolling release) +**Entorno:** Producción — SSD/HDD bare metal + +--- + +## 1. Concepto y filosofía + +NEUBAT es un sistema de instalación desatendida de Arch Linux que despliega un entorno completo, preconfigurado y funcional desde Internet, sin medios físicos (USB/CD), mediante un portal web responsive que genera URLs únicas de configuración. + +- **Zero-touch deployment:** instalación sin intervención tras el arranque inicial. +- **Infrastructure as Code:** toda la configuración versionada y reproducible. +- **Rolling release:** sistema siempre actualizado sin migraciones traumáticas. +- **Monolito recortado:** sistema mínimo, sin bloatware. + +### Objetivos medibles + +| # | Objetivo | Métrica de éxito | +|---|----------|------------------| +| 1 | Portal usuarios responsive | Accesible desde móvil/desktop, < 2 s de carga | +| 2 | URL generada post-instalación | URL única funcional en < 5 min desde el arranque | +| 3 | Instalación desatendida | 0 intervenciones tras la selección inicial | + +## 2. Requisitos + +**Servidor del portal:** Node.js ≥ 18, puerto 3000 libre, conectividad con las máquinas destino. + +**Máquina destino:** arranque UEFI, soporte de arranque por red (PXE/iPXE) o ISO en disco (fallback GRUB loopback), disco ≥ 32 GiB, conexión a Internet. + +**Entorno live:** ISO oficial de Arch Linux reciente (incluye `python3`, `parted`, `pacstrap`, `reflector`). + +## 3. Despliegue del portal + +### Docker Compose (recomendado) + +```bash +cp .env.example .env # opcional: ajusta ADMIN_TOKEN y NEUBAT_MIRROR_BASE +docker compose up -d +``` + +Variables de entorno útiles: + +| Variable | Descripción | Defecto | +|----------|-------------|---------| +| `ADMIN_TOKEN` | Token para el panel `/admin` | — (panel deshabilitado si falta) | +| `NEUBAT_MIRROR_BASE` | Mirror base para el netboot iPXE | `https://geo.mirror.pkgbuild.com/iso/latest` | +| `NEUBAT_PORT` | Puerto expuesto del portal | `3000` | +| `NEUBAT_HMAC_SECRET` | Secreto compartido para firma HMAC de configuraciones | — | + +### Node.js nativo + +```bash +cd portal +npm install +npm start # producción en :3000 +npm run dev # desarrollo +``` + +Como servicio systemd, usar como plantilla la unidad que genera `scripts/40-portal-deploy.sh` (`neubat-portal.service`). + +### API + +| Método | Ruta | Descripción | +|--------|------|-------------| +| POST | `/api/install` | Crea instalación; body: `profile`, `hostname?`, `username?`, `password?`, `desktop?`, `packages?[]`, `encryption?` | +| GET | `/api/config/:token` | Devuelve el JSON de configuración (consumido por el instalador) | +| POST | `/api/complete` | El instalador notifica `status`, `hostname`, `duration?`, `error?` | +| GET | `/api/metrics` | Métricas agregadas de instalaciones | +| GET | `/api/installations` | Últimas 50 instalaciones | +| GET | `/api/installations/:token` | Estado de una instalación | +| GET | `/api/health` | Health check | +| GET | `/boot/:token` | Script iPXE personalizado para el token | + +### Panel de administración + +Disponible en `/admin`. Requiere `ADMIN_TOKEN`. Endpoints bajo `/api/admin`: + +| Método | Ruta | Descripción | +|--------|------|-------------| +| GET | `/api/admin/installations` | Listado completo de instalaciones | +| POST | `/api/admin/installations/:token/status` | Actualizar estado/hostname/error | +| POST | `/api/admin/installations/:token/reset` | Volver a estado `pending` | +| DELETE | `/api/admin/installations/:token` | Eliminar registro y config | + +El portal aplica rate-limiting (100 req / 15 min por IP) en `/api/*`. Para exposición pública, desplegar detrás de un reverse proxy con TLS. + +Variable de entorno opcional: `NEUBAT_MIRROR_BASE` — mirror base para el netboot iPXE (defecto: `https://geo.mirror.pkgbuild.com/iso/latest`). Apúntala a una caché local (`deploy/pacman-cache/`) cuando el firmware iPXE no tenga HTTPS compilado o para acelerar los arranques por red. + +## 4. Construcción de la ISO híbrida + +Para generar una ISO personalizada a partir del código actual (requiere Docker): + +```bash +make build-iso +# Salida: out/neubat-1.0.0-x86_64.iso +``` + +El proceso usa un contenedor Arch Linux con `archiso`, remasteriza el perfil `releng`, inyecta `/opt/neubat` y habilita `neubat-autoinstall.service`. Para publicar la ISO en GitHub: + +```bash +make release +``` + +## 5. Flujo de instalación + +### 5.1 Crear la instalación + +Desde la web (`http:///configurar`) o por API: + +```bash +curl -X POST http://:3000/api/install \ + -H 'Content-Type: application/json' \ + -d '{"profile":"production","hostname":"mi-equipo","desktop":"hyprland"}' +``` + +Respuesta: `token`, `config_url`, `boot_url`. + +Cuenta de usuario: registro en `/cuenta`. Absorción del sistema actual: genera un código en la cuenta y ejecuta `scripts/neubat-absorb.sh --code … --portal …`. + +### 5.2 Arrancar la máquina destino + +- **Por red (recomendado):** encadenar iPXE a `http://:3000/boot/`. El script incluye `neubat_token`, `neubat_profile` y `neubat_portal_url`. Para cero toques, publica el live NEUBAT en `NEUBAT_LIVE_DIR` (servido en `/live`) y define `NEUBAT_USE_LIVE=1` o `NEUBAT_LIVE_BASE`. Sin live, el mirror Arch arranca pero requiere ejecutar el instalador a mano o usar la ISO NEUBAT. +- **ISO híbrida autoinstalable:** en `/descargar` el portal verifica SHA-256 antes de guardar. También desde la [release](https://github.com/Alexendros/neubat/releases) con el `.sha256` generado por CI. Arranque: + + ``` + neubat_token= neubat_profile=production neubat_portal_url=http://:3000 + ``` + + El servicio `neubat-autoinstall.service` del live ISO lee esos parámetros y ejecuta el instalador de forma desatendida. + +- **Fallback USB/disco:** `netboot/grub/loopback.cfg` arranca el ISO almacenado en disco sin reescribir el medio. + +### 5.3 Ejecutar el instalador (desde el live ISO) + +```bash +export NEUBAT_PORTAL_URL="http://:3000" +export NEUBAT_ASSUME_YES=true # omite la confirmación de borrado +bash scripts/neubat-install.sh [perfil] +``` + +> **AVISO:** el instalador destruye todos los datos del disco objetivo. + +### Fases + +| Fase | Módulo | Acción | +|------|--------|--------| +| 0 | `00-preinstall.sh` | root, Internet, UEFI, herramientas live | +| 0b | `20-archinstall.sh` | Descarga config por token o usa perfil local | +| 1 | `10-partition.sh` | GPT: EFI 1 GiB + raíz btrfs + home btrfs + swap 4G | +| 2 | `20-archinstall.sh` | Mirrors (reflector) + pacstrap + fstab | +| 3 | `30-postinstall.sh` | chroot: locale, usuarios, systemd-boot, yay, `/etc/neubat-release` | +| 4 | `30-postinstall.sh` | Desktop y paquetes/servicios de la configuración | +| 5 | `35-snapper.sh` | Snapper + snap-pac si `snapshots.enabled` | +| 6 | `40-portal-deploy.sh` | Portal local + `~/NEUBAT-URL.txt` | +| 7 | `50-firstboot-ansible.sh` | Ansible first-boot | +| 8 | maestro | Notificación al portal, resumen y reinicio | + +## 6. Esquema de particionado + +| Partición | Tamaño | FS | Montaje | +|-----------|--------|-----|---------| +| p1 (ESP) | 1 GiB | FAT32 | `/boot` | +| p2 (raíz) | 19–29 GiB (según tamaño del disco) | btrfs (zstd, noatime) | `/` | +| p3 (home) | resto − 4 GiB | btrfs (zstd, noatime) | `/home` | +| p4 (swap) | 4 GiB | swap | — | + +Los nombres de partición se resuelven con `part_name()` (soporta `/dev/sda1` y `/dev/nvme0n1p1`). + +## 6.1 Cifrado de disco LUKS (Fase 6) + +NEUBAT puede cifrar las particiones de **raíz** y **home** con LUKS2. La partición EFI (`/boot`) permanece descifrada porque el firmware UEFI debe poder leer el cargador de arranque (systemd-boot). + +### Modos de arranque + +| Método | Campo `encryption.method` | Comportamiento | Seguridad | +|--------|---------------------------|----------------|-----------| +| **Keyfile en `/boot`** | `keyfile` | Arranque completamente desatendido | Protege datos en reposo si el disco está apagado; no protege si roban el disco con la partición EFI | +| **Passphrase manual** | `passphrase` | El initramfs pide la contraseña en cada arranque | Mayor seguridad física; rompe el despliegue zero-touch | + +### Configuración en el perfil + +```json +{ + "encryption": { + "enabled": true, + "method": "keyfile", + "passphrase": "cambiar-post-instalacion", + "cipher": "aes-xts-plain64", + "key_size": 512 + } +} +``` + +- `enabled`: activa/desactiva LUKS. +- `method`: `keyfile` (desatendido) o `passphrase` (interactivo). +- `passphrase`: se usa para formatear el contenedor cuando no hay keyfile; también puede usarse para añadir frases adicionales tras la instalación. +- `cipher` / `key_size`: parámetros de `cryptsetup luksFormat` (defecto `aes-xts-plain64` / 512). + +### Desde la API + +```bash +curl -X POST http://:3000/api/install \ + -H 'Content-Type: application/json' \ + -d '{ + "profile": "production", + "hostname": "mi-equipo", + "encryption": { "enabled": true, "method": "passphrase", "passphrase": "MiFraseSegura" } + }' +``` + +### Post-instalación recomendada + +Cuando uses `method: "keyfile"`, rota la llave tras el primer arranque: + +```bash +# Añade una passphrase y elimina el keyfile del slot 0 +sudo cryptsetup luksAddKey /dev/nvme0n1p2 +sudo cryptsetup luksRemoveKey /dev/nvme0n1p2 /etc/cryptsetup-keys.d/neubat_root.key +sudo rm /etc/cryptsetup-keys.d/neubat_root.key /etc/cryptsetup-keys.d/neubat_home.key +``` + +Para TPM2 o FIDO2, consulta `systemd-cryptenroll` (fuera del alcance del MVP). + +## 6.2 Snapshots btrfs automáticos (Fase 7) + +Cuando el perfil activa `snapshots.enabled`, NEUBAT instala `snapper` y `snap-pac` y configura snapshots automáticos de `/` y `/home`: + +- **Timeline:** snapshot cada hora (gestionado por `snapper-timeline.timer`). +- **Pacman:** `snap-pac` crea snapshots `pre`/`post` en cada operación de paquetes, permitiendo rollback si una actualización rompe el sistema. +- **Limpieza:** `snapper-cleanup.timer` aplica los límites configurados. + +### Configuración en el perfil + +```json +{ + "snapshots": { + "enabled": true, + "cleanup": { + "hourly": 5, + "daily": 7, + "weekly": 2, + "monthly": 2 + } + } +} +``` + +### Gestión básica + +```bash +# Listar snapshots de raíz +sudo snapper -c root list + +# Ver diferencias entre dos snapshots +sudo snapper -c root status .. + +# Restaurar un snapshot (boot desde snapshot + rollback) +sudo snapper -c root rollback +``` + +## 6.3 Firma HMAC y métricas de instalación (Fase 8) + +El portal puede firmar cada configuración con **HMAC-SHA256** para que el instalador verifique que no ha sido alterada en tránsito. + +### Configuración + +Establece el mismo secreto en el portal y en el entorno live del instalador: + +```bash +# .env del portal (o docker compose) +NEUBAT_HMAC_SECRET=una-cadena-larga-y-aleatoria + +# Entorno live del instalador +export NEUBAT_HMAC_SECRET="una-cadena-larga-y-aleatoria" +``` + +Si el secreto está configurado, el portal añade un campo `signature` al JSON de configuración. El instalador lo verifica automáticamente en `fetch_configuration()` y aborta si la firma no coincide. + +### Métricas + +El instalador mide su duración en segundos y la envía al portal en `/api/complete`: + +```bash +curl http://:3000/api/metrics +``` + +Respuesta: + +```json +{ + "total": 10, + "completed": 8, + "failed": 1, + "pending": 1, + "avg_duration_seconds": 420, + "duration_count": 8 +} +``` + +## 7. Perfiles de configuración + +Los perfiles viven en `configs/` (`base`, `production`, `developer`). Claves: + +| Clave | Defecto | Descripción | +|-------|---------|-------------| +| `hostname` | `neubat-*` | Nombre del equipo | +| `username` | `neubat` | Usuario principal (grupo wheel) | +| `password` | `neubat` | Contraseña inicial de usuario y root — **cambiar en el primer acceso** | +| `disk` | `/dev/sda` | Disco objetivo (**se borra entero**) | +| `desktop` | `none` | `kde` · `gnome` · `xfce` · `none` | +| `packages` | — | Paquetes pacman adicionales (los paquetes AUR deben instalarse post-instalación con yay) | +| `services` | — | Servicios systemd a habilitar | +| `timezone` / `locale` / `keyboard` | Madrid / es_ES / es | Regionalización | + +## 8. Portal local post-instalación + +El sistema instalado incluye `neubat-portal.service` (Node.js en :3000, usuario no-root, código en `/opt/neubat-portal`). La URL única de setup queda en `~/NEUBAT-URL.txt`: + +``` +http://.local:3000/setup/ +``` + +## 9. Validación post-instalación + +```bash +bash scripts/validate-install.sh +``` + +Comprueba: `/etc/neubat-release`, hostname, usuario no-root, Internet, NetworkManager, sshd, Docker, portal local, espacio en disco y fstab. Devuelve código de salida no nulo si algo falla. + +## 10. Notas de seguridad + +- La construcción desatendida de paquetes AUR (yay) requiere `NOPASSWD` temporal en `%wheel`; **el instalador lo retira automáticamente** al terminar (`/etc/sudoers.d/neubat` queda `%wheel ALL=(ALL:ALL) ALL`). +- Cambiar las contraseñas iniciales de usuario y root en el primer acceso. +- Los tokens son hex aleatorios de 128 bits; el portal valida su formato antes de tocar el sistema de archivos. +- `boot_url` y `config_url` no llevan autenticación: quien posea el token puede descargar la configuración. Tratar los tokens como secretos y, en producción, servir bajo TLS. + +## 11. Solución de problemas + +| Síntoma | Causa probable | Acción | +|---------|----------------|--------| +| `Sin config personalizada; usando perfil local` | Portal inalcanzable o token inexistente | Verificar `NEUBAT_PORTAL_URL` y el token | +| `parted` falla en NVMe | Nombre de partición | Resuelto por `part_name()`; si persiste, revisar `lsblk` | +| `yay` no se instaló | Fallo de red/AUR durante el chroot | No crítico: `git clone https://aur.archlinux.org/yay.git && cd yay && makepkg -si` | +| Portal local no responde | `npm install` falló en destino | `cd /opt/neubat-portal && npm install --omit=dev && systemctl restart neubat-portal` | +| Log completo | — | `/var/log/neubat-install.log` (en el entorno live) | + +## 12. Pruebas en VM (QEMU/KVM) + +Lecciones aprendidas al validar NEUBAT en QEMU con disco NVMe virtual: + +| Problema | Causa | Solución | +|----------|-------|----------| +| `IP-Config: no response` en initramfs | `ip=dhcp` usa `ipconfig` (klibc), que busca `eth0`; con nombres predecibles no existe. Incluso con `eth0`, `ipconfig` puede no obtener respuesta del servidor DHCP interno de QEMU slirp | Añadir `net.ifnames=0` a la cmdline. En red con DHCP real (slirp) / router doméstico) el netboot por iPXE funciona; en slirp pura la fase de `archiso_http_srv` puede quedarse sin red. Usar kernel directo para pruebas locales o una red con DHCP real | +| iPXE con build estándar sin HTTPS | `ipxe.lkrn` de boot.ipxe.org no incluye HTTPS en su build por defecto | Usar `NEUBAT_MIRROR_BASE` apuntando a la caché HTTP local (`deploy/pacman-cache/`) o un build de iPXE con HTTPS | +| `/dev/disk/by-label/ARCH_*` no aparece | cdrom IDE sin módulo en initramfs (máquina `pc`) | Usar `-machine q35` (cdrom SATA/AHCI) | +| Descarga de pacman congelada | virtio-net + red slirp se cuelga en transferencias grandes | Usar NIC `-device e1000,netdev=...` | +| reflector agota timeouts | su rating usa 5 s por defecto | `--download-timeout 30` en redes lentas | +| Consola serie sin prompt | el prompt zsh del ISO lleva códigos ANSI | En automatización (pexpect), usar patrones tolerantes a escapes | +| SSH tras instalar | `PermitRootLogin prohibit-password` por defecto | Entrar con el usuario del perfil, no root | + +Ejemplo de lanzamiento con kernel directo (consola serie completa): + +```bash +qemu-system-x86_64 -machine q35 -enable-kvm -cpu host -m 4096 -smp 4 \ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ + -drive if=pflash,format=raw,file=vars.fd \ + -drive file=disk.qcow2,if=none,id=nvm0,format=qcow2 -device nvme,drive=nvm0 \ + -cdrom archlinux-x86_64.iso \ + -kernel vmlinuz-linux -initrd initramfs-linux.img \ + -append "archisobasedir=arch archisolabel=ARCH_YYYYMM console=ttyS0" \ + -netdev user,id=n0,hostfwd=tcp::2222-:22 -device e1000,netdev=n0 \ + -nographic +``` + +Para iterar rápido, usar la caché de paquetes de `deploy/pacman-cache/`. + +--- + +**Hash de verificación del documento:** `neubat-doc-v1.0-20260920` diff --git a/docs/guides/packages.md b/docs/guides/packages.md new file mode 100644 index 0000000..8955dee --- /dev/null +++ b/docs/guides/packages.md @@ -0,0 +1,139 @@ +# NEUBAT — Panel de paquetes por perfil + +### Propósito de este documento + +- **Objetivos:** Inventariar paquetes por perfil (`base`, `developer`, `production`, …) para revisar el monolito recortado sin abrir cada JSON. +- **Estructura:** Leyenda → tablas por perfil → notas AUR. +- **Contenido a integrar según contexto:** Sincroniza esta guía cuando cambie `configs/*.json`. No copies un catálogo de otro distro o producto. + +Este documento lista los paquetes que vienen predefinidos en cada perfil de instalación. Los paquetes marcados con **(AUR)** no están en los repositorios oficiales de Arch Linux y se instalan con `yay` tras el primer arranque si fallan durante la instalación desatendida. + +## Leyenda + +| Símbolo | Significado | +|---------|-------------| +| 🖥️ | Entorno de escritorio / gestor de ventanas | +| 🔧 | Herramientas de desarrollo/sistema | +| 🌐 | Red / Internet | +| 🛡️ | Seguridad / cifrado | +| 🎨 | Multimedia / productividad | +| 📦 | Servicios / infraestructura | + +--- + +## Perfil `base` (mínimo) + +Escritorio: `none` · Disco: `/dev/sda` · Cifrado: desactivado + +| Paquete | Categoría | Descripción | +|---------|-----------|-------------| +| `htop` | 🔧 | Monitor de procesos interactivo | +| `btop` | 🔧 | Monitor de recursos con gráficos ANSI | +| `fastfetch` | 🔧 | Información del sistema (sustituto moderno de neofetch) | +| `git` | 🔧 | Control de versiones | +| `curl` | 🌐 | Cliente HTTP/HTTPS | +| `wget` | 🌐 | Descarga de archivos por HTTP/FTP | +| `okular` | 🎨 | Visor de documentos universal (PDF, ePub, DjVu, etc.) | + +Servicios habilitados: `NetworkManager`, `sshd` + +--- + +## Perfil `production` (escritorio KDE + apps ofimáticas) + +Escritorio: `kde` · Disco: `/dev/nvme0n1` · Cifrado: **activado con keyfile** + +| Paquete | Categoría | Descripción | +|---------|-----------|-------------| +| `plasma-meta` | 🖥️ | Escritorio KDE Plasma | +| `kde-applications-meta` | 🖥️ | Aplicaciones básicas de KDE | +| `sddm` | 🖥️ | Gestor de pantalla | +| `docker` | 📦 | Contenedores | +| `docker-compose` | 📦 | Orquestación de contenedores | +| `nodejs` / `npm` | 🔧 | Runtime y gestor de paquetes JavaScript | +| `python` / `python-pip` | 🔧 | Python 3 y pip | +| `nginx` | 📦 | Servidor web/proxy inverso | +| `postgresql` | 📦 | Base de datos relacional | +| `redis` | 📦 | Almacén clave-valor en memoria | +| `htop` / `btop` / `fastfetch` | 🔧 | Monitores del sistema | +| `git` / `curl` / `wget` | 🔧🌐 | Desarrollo y red | +| `firefox` | 🌐 | Navegador web | +| `libreoffice-fresh` | 🎨 | Suite ofimática | +| `vlc` | 🎨 | Reproductor multimedia | +| `gimp` | 🎨 | Edición de imágenes | +| `okular` | 🎨 | Visor de documentos | + +Servicios habilitados: `NetworkManager`, `sshd`, `docker`, `nginx`, `postgresql`, `redis` + +### Cambios recientes + +- **Añadido:** `okular` (visor de documentos). +- **Eliminados:** `audacity`, `shotwell`. Si necesitas gestión fotográfica avanzada, instala `digiKam` desde KDE. + +--- + +## Perfil `developer` (escritorio GNOME + toolchain) + +Escritorio: `gnome` · Disco: `/dev/sda` · Cifrado: desactivado + +| Paquete | Categoría | Descripción | +|---------|-----------|-------------| +| `gnome` / `gnome-extra` | 🖥️ | Escritorio GNOME y aplicaciones extra | +| `gdm` | 🖥️ | Gestor de pantalla | +| `docker` / `docker-compose` | 📦 | Contenedores | +| `nodejs` / `npm` / `yarn` | 🔧 | JavaScript/TypeScript | +| `python` / `python-pip` / `python-poetry` | 🔧 | Python y gestores de dependencias | +| `go` | 🔧 | Lenguaje Go | +| `rust` | 🔧 | Lenguaje Rust (toolchain) | +| `code` | 🔧 | Visual Studio Code (editor) | +| `jetbrains-toolbox` | 🔧 | Gestor de IDEs JetBrains **(AUR)** | +| `postman-bin` | 🔧 | Cliente API REST **(AUR)** | +| `insomnia` | 🔧 | Cliente API REST alternativo **(AUR)** | +| `github-cli` | 🔧 | CLI de GitHub (`gh`) | +| `gitlab-runner` | 🔧 | Runner de CI/CD de GitLab | +| `kubectl` / `helm` | 🔧 | Orquestación Kubernetes | +| `minikube` | 🔧 | Kubernetes local | +| `terraform` | 🔧 | Infraestructura como código | +| `ansible` | 🔧 | Automatización de configuración | +| `git` | 🔧 | Control de versiones | +| `okular` | 🎨 | Visor de documentos | + +Servicios habilitados: `NetworkManager`, `sshd`, `docker` + +--- + +## Paquetes base del sistema (todos los perfiles) + +Instalados siempre por `pacstrap` en `scripts/20-archinstall.sh`: + +| Paquete | Propósito | +|---------|-----------| +| `base` | Sistema base de Arch Linux | +| `linux` / `linux-firmware` | Kernel y firmware | +| `btrfs-progs` | Utilidades para btrfs | +| `cryptsetup` | Cifrado LUKS (necesario aunque el perfil no lo active) | +| `grub` / `efibootmgr` | Cargador de arranque UEFI | +| `networkmanager` | Conectividad de red | +| `sudo` / `git` / `base-devel` | Privilegios, fuentes y compilación | +| `curl` / `wget` / `inetutils` | Herramientas de red | +| `reflector` | Optimización de mirrors pacman | +| `neovim` / `nano` | Editores de texto | +| `terminus-font` | Fuente para consola | +| `openssh` | Servidor SSH | +| `ansible` | Automatización post-instalación (first-boot) | + +--- + +## Aplicaciones sugeridas (no incluidas por defecto) + +| Aplicación | Paquete | Notas | +|------------|---------|-------| +| Proton Mail (escritorio) | `proton-mail` | **AUR**; cliente oficial de Proton Mail. Instalar tras el primer arranque con `yay -S proton-mail` | +| Proton VPN | `proton-vpn-gtk-app` | **AUR** | +| Brave | `brave-bin` | **AUR**; navegador centrado en privacidad | +| KeePassXC | `keepassxc` | Gestor de contraseñas | +| Nextcloud Desktop | `nextcloud-client` | Sincronización de nube | + +--- + +**Fecha del documento:** 22 de septiembre de 2026 diff --git a/docs/guides/roadmap.md b/docs/guides/roadmap.md new file mode 100644 index 0000000..427eeef --- /dev/null +++ b/docs/guides/roadmap.md @@ -0,0 +1,58 @@ +# NEUBAT — Roadmap y próximos pasos + +### Propósito de este documento + +- **Objetivos:** Registrar el estado de fases de producto y los siguientes pasos técnicos comprobables. +- **Estructura:** Estado actual → objetivos de producto → próximos pasos. +- **Contenido a integrar según contexto:** Actualiza filas al cerrar una fase. No copies un roadmap de otro repo. Las estimaciones son esfuerzo técnico, no calendario. + +## Estado actual (24 de septiembre de 2026) + +| Prioridad | Tarea | Responsable | Estimación | Estado | +|-----------|-------|-------------|------------|--------| +| P0 | Desplegar servidor portal en entorno de pruebas | DevOps | 2h | ✅ Hecho (19-sep-2026) | +| P0 | Validar script iPXE en VM (VirtualBox/QEMU) | QA | 1h | ✅ Hecho parcialmente (19-sep-2026): cadena iPXE probada en QEMU — `dhcp` + `chain` al portal + descarga kernel/initrd + boot. La fase `archiso_http_srv` queda validada en red con DHCP real (QEMU slirp no responde a `ipconfig` de klibc) | +| P0 | Probar instalación completa en VM con disco NVMe virtual | QA | 2h | ✅ Hecho (19-sep-2026, perfil base en QEMU/NVMe: particionado, chroot, portal local y URL única verificados por SSH) | +| P1 | Crear imagen Docker del portal para distribución | Dev | 3h | ✅ Hecho (19-sep-2026) | +| P1 | Generación de ISO híbrida con hook de autoinstalación | Dev | 4h | ✅ Hecho (19-sep-2026) | +| P1 | Endurecer sudoers post-instalación (retirar NOPASSWD) | Dev | 1h | ✅ Hecho (19-sep-2026, automático al final de `30-postinstall.sh`) | +| P2 | Panel de administración web para seguimiento | Frontend | 8h | ✅ Hecho (19-sep-2026) | +| P1 | Publicar release v1.0.0 con ISO híbrida en GitHub | DevOps | 1h | ✅ Hecho (20-sep-2026) | +| P2 | Integración con Ansible para configuración post-instalación | DevOps | 6h | ✅ Hecho (20-sep-2026) | +| P1 | Rediseño GUI-UX con React + shadcn/ui | Frontend | 10h | ✅ Hecho (20-sep-2026) | +| P2 | Soporte de cifrado LUKS en particionado | Dev | 4h | ✅ Hecho (22-sep-2026, PR #17) | +| P3 | Snapshots btrfs automáticos pre/post actualización | Dev | 3h | ✅ Hecho (22-sep-2026, PR #18) | +| P2 | Firma y verificación de configuraciones (HMAC) | Dev | 3h | ✅ Hecho (22-sep-2026, PR #18); extendedido a encryption/snapshots (24-sep-2026) | +| P3 | Métricas de instalación reportadas al portal | Dev | 2h | ✅ Hecho (22-sep-2026, PR #18) | +| P1 | Listado de instalaciones solo con ADMIN_TOKEN | Dev | 1h | ✅ Hecho (24-sep-2026) | + +## Objetivos de producto (implementados 24-sep-2026) + +| Fase | Objetivo | Estado | +|------|----------|--------| +| 1 | Saneamiento (HMAC encryption/snapshots, listados privados, docs) | ✅ | +| 2 | Sitio de presentación, tokens OKLCH, WCAG 2.2 AA + axe en CI | ✅ | +| 3 | Panel de usuario con login, perfiles y recomendaciones | ✅ | +| 4 | Configurador (paquetes, WM/escritorio, locale) | ✅ | +| 5 | Motor `archinstall` (opcional) + scripts para LUKS/snapper/portal/AUR | ✅ | +| 6 | Absorber configuración del sistema actual (`scripts/neubat-absorb.sh`) | ✅ | +| 7 | ISO con verificación automática de hash al descargar | ✅ | +| 8 | Netinstall por URL con `neubat_portal_url` y live en `/live` | ✅ | + +## Próximos pasos técnicos + +| Prioridad | Tarea | Motivación | Estimación | +|-----------|-------|------------|------------| +| P1 | **Validación end-to-end de LUKS + snapper + HMAC en VM** | Confirmar que las nuevas fases funcionan juntas en un flujo real de instalación | 2h | +| P2 | **Rotación automática del keyfile LUKS** | Tras el primer arranque, reemplazar el keyfile de `/boot` por una passphrase o enrolar TPM2/FIDO2 | 3h | +| P3 | **Servidor iPXE propio con imágenes cacheadas** | Independencia del mirror upstream de Arch y arranques más rápidos/repetibles | 6h | +| P3 | **Perfiles como paquetes versionados (`neubat-profile-*`)** | Distribuir perfiles por separado y permitir comunidad/contribuciones | 8h | +| P3 | **Métricas por fase de instalación** | Reportar duración de cada fase (particionado, pacstrap, chroot, etc.) para diagnóstico | 3h | + +## Ideas a evaluar + +- Soporte para RAID/btrfs en múltiples discos. +- Instalaciones remotas con consola serie y watchdog. +- Dashboard en tiempo real de instalaciones en curso (WebSockets). +- Notificaciones por correo/Telegram al completar una instalación. +- Integrar Proton Mail app en perfil production (AUR). diff --git a/docs/runbooks/ci.md b/docs/runbooks/ci.md new file mode 100644 index 0000000..c81f16d --- /dev/null +++ b/docs/runbooks/ci.md @@ -0,0 +1,28 @@ +# Runbook: CI + +### Propósito de este documento + +- **Objetivos:** Explicar los jobs canónicos y qué hacer cuando uno falla, sin promover e2e frágil a required. +- **Estructura:** Jobs → mapeo a Make → fallos → opt-in. +- **Contenido a integrar según contexto:** Renombra o documenta jobs aquí si cambia `.github/workflows/ci.yml`. No copies un pipeline de otro stack. `build-iso.yml` y `make test-vm` siguen opt-in. + +## Jobs canónicos (`.github/workflows/ci.yml`) + +| Job | Equivale a | Qué cubre | +| --- | ---------- | --------- | +| `quality` | `make validate` + `make lint` + Ansible syntax/lint + oxlint frontend | Estática | +| `test` | `make test` + frontend Vitest + `make test-bash` | Unidad / integración rápida | +| `build` | `make build-frontend` | Artefacto desplegable (SPA Vite) | +| `smoke` | `make smoke` + axe sintético del landing | Health + `POST /api/install` + a11y mínima | + +## Fallos + +1. Abre el log del job con el nombre canónico (`quality`, `test`, `build`, `smoke`). +2. Reproduce en local el objetivo Make de la tabla. +3. No “arregles” un rojo aflojando el job ni saltándote `validate`. +4. Si el fallo es de dependencia de Actions, Renovate debe proponer el bump (sin automerge de majors). + +## Opt-in (no required) + +- `make test-vm` — QEMU/NVMe, ~40 min. Ver [tests/vm/README.md](../../tests/vm/README.md). +- Workflow **Build ISO** — `workflow_dispatch` o tag `v*`. diff --git a/docs/runbooks/iso.md b/docs/runbooks/iso.md new file mode 100644 index 0000000..f8ae4d8 --- /dev/null +++ b/docs/runbooks/iso.md @@ -0,0 +1,36 @@ +# Runbook: ISO híbrida + +### Propósito de este documento + +- **Objetivos:** Construir y diagnosticar la ISO autoinstalable sin convertir el job en required de cada PR. +- **Estructura:** Cuándo usarlo → comando → fallos habituales → artefactos. +- **Contenido a integrar según contexto:** El workflow `.github/workflows/build-iso.yml` es opt-in (`workflow_dispatch` / tags `v*`). No lo marques required. No subas la ISO al git. + +## Cuándo + +- Release o prueba de arranque UEFI/BIOS +- Cambio en `scripts/build-iso.sh`, `iso/airootfs/` o el hook `neubat-autoinstall` + +No forma parte de `quality` / `test` / `smoke`. QEMU e2e (`make test-vm`) es otro opt-in (~40 min). + +## Comando + +```bash +make build-iso # TAG=1.0.0 por defecto +# out/neubat-1.0.0-x86_64.iso +``` + +Requiere Docker. El workflow de GitHub sube el ISO + SHA256 como artefacto (7 días) y, en tags `v*`, crea el Release. + +## Fallos habituales + +| Señal | Acción | +| ----- | ------ | +| Docker no disponible | Instala el daemon o usa el workflow `workflow_dispatch` | +| Espacio en disco | La ISO ronda 1.6 GB; limpia `out/` | +| Hook no arranca | Revisa `iso/airootfs/.../neubat-autoinstall.service` y el cmdline `neubat_token` | +| Hash no coincide | Compara con `*.iso.sha256` de la release; no reutilices ISOs a medias | + +## Artefactos + +`out/` está en `.gitignore`. Publica solo vía GitHub Release, no en el árbol. diff --git a/docs/runbooks/portal.md b/docs/runbooks/portal.md new file mode 100644 index 0000000..8774809 --- /dev/null +++ b/docs/runbooks/portal.md @@ -0,0 +1,41 @@ +# Runbook: portal NEUBAT + +### Propósito de este documento + +- **Objetivos:** Diagnosticar fallos del portal (arranque, health, creación de instalación, admin) sin tocar secretos reales. +- **Estructura:** Síntomas → comprobaciones → causas frecuentes → recuperación. +- **Contenido a integrar según contexto:** Adapta puertos y variables de `.env.example`. No copies un runbook de SaaS. No pegas tokens ni HMAC de producción en issues. + +## Síntomas + +- `docker compose` no levanta o el healthcheck falla +- `GET /api/health` no responde +- `POST /api/install` 4xx/5xx +- `/admin` rechaza el token + +## Comprobaciones + +```bash +make smoke +curl -sf http://localhost:3000/api/health +docker compose ps +docker compose logs portal --tail=80 +``` + +Confirma que `ADMIN_TOKEN` y `NEUBAT_HMAC_SECRET` en el entorno coinciden con lo esperado (valores de ejemplo en `.env.example`, nunca secretos reales en el repo). + +## Causas frecuentes + +| Señal | Causa probable | Acción | +| ----- | -------------- | ------ | +| Puerto ocupado | `NEUBAT_PORT` / `PORT` en uso | Cambia el puerto o mata el proceso | +| Healthcheck Docker | portal aún no ha llamado a `db.initStorage()` | Revisa logs; el `start_period` es 10 s | +| 401/403 en `/admin` | `ADMIN_TOKEN` vacío o distinto | Exporta el token y reinicia | +| HMAC inválido en el live | secreto distinto portal vs instalador | Alinea `NEUBAT_HMAC_SECRET` | + +## Recuperación + +1. Para el contenedor o el proceso Node. +2. No borres `portal/data/` en un entorno con instalaciones reales. +3. En local de desarrollo, puedes vaciar `portal/data/` y `portal/configs/generated/` (están en `.gitignore`). +4. Vuelve a `make smoke` antes de reintentar iPXE/ISO. diff --git a/tests/vm/README.md b/tests/vm/README.md index 0e9927b..ba38bac 100644 --- a/tests/vm/README.md +++ b/tests/vm/README.md @@ -1,5 +1,11 @@ # Prueba end-to-end en VM (QEMU/KVM + NVMe virtual) +### Propósito de este documento + +- **Objetivos:** Documentar la e2e QEMU/NVMe como **opt-in** (no required de CI). +- **Estructura:** Requisitos → uso → variables. +- **Contenido a integrar según contexto:** No conviertas este flujo en job required. ~40 min, necesita KVM. + `neubat_vm_test.py` reproduce la validación realizada el 19-sep-2026: instalación desatendida completa del perfil elegido sobre un disco NVMe virtual y verificación SSH del sistema instalado.