Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions configs/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@
"fastfetch",
"git",
"curl",
"wget"
"wget",
"okular"
],
"services": [
"NetworkManager",
"sshd"
],
"timezone": "Europe/Madrid",
"locale": "es_ES.UTF-8",
"keyboard": "es"
"keyboard": "es",
"encryption": {
"enabled": false,
"method": "keyfile",
"passphrase": "",
"cipher": "aes-xts-plain64",
"key_size": 512
}
}
16 changes: 14 additions & 2 deletions configs/developer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@
"go",
"rust",
"code",
"jetbrains-toolbox",
"postman-bin",
"insomnia",
"github-cli",
"gitlab-runner",
"kubectl",
"helm",
"minikube",
"terraform",
"ansible",
"git"
"git",
"okular"
],
"services": [
"NetworkManager",
Expand All @@ -32,5 +37,12 @@
],
"timezone": "Europe/Madrid",
"locale": "es_ES.UTF-8",
"keyboard": "es"
"keyboard": "es",
"encryption": {
"enabled": false,
"method": "keyfile",
"passphrase": "",
"cipher": "aes-xts-plain64",
"key_size": 512
}
}
11 changes: 10 additions & 1 deletion configs/production.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"firefox",
"libreoffice-fresh",
"vlc",
"gimp"
"gimp",
"okular"
],
"services": [
"NetworkManager",
Expand All @@ -46,5 +47,13 @@
"schedule": "daily",
"target": "/home/neubat/backups"
}
},
"encryption": {
"enabled": true,
"method": "keyfile",
"passphrase": "neubat",
"cipher": "aes-xts-plain64",
"key_size": 512,
"_warning": "El método 'keyfile' almacena la llave en /boot para arranque desatendido. Para mayor seguridad física cambia a 'passphrase' tras la instalación o usa TPM2."
}
}
57 changes: 56 additions & 1 deletion docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Como servicio systemd, usar como plantilla la unidad que genera `scripts/40-port

| Método | Ruta | Descripción |
|--------|------|-------------|
| POST | `/api/install` | Crea instalación; body: `profile`, `hostname?`, `username?`, `desktop?`, `packages?[]` |
| 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`, `error?` |
| GET | `/api/installations` | Últimas 50 instalaciones |
Expand Down Expand Up @@ -164,6 +164,61 @@ bash scripts/neubat-install.sh <token> [perfil]

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/efi`) permanece descifrada porque el firmware UEFI debe poder leer el cargador de arranque.

### 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://<portal>: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 /boot/luks-keyfile
sudo rm /boot/luks-keyfile
```

Para TPM2 o FIDO2, consulta `systemd-cryptenroll` (fuera del alcance del MVP).

## 7. Perfiles de configuración

Los perfiles viven en `configs/` (`base`, `production`, `developer`). Claves:
Expand Down
133 changes: 133 additions & 0 deletions docs/PACKAGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# NEUBAT — Panel de paquetes por perfil

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
18 changes: 17 additions & 1 deletion portal/routes/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ const BOOT_BASE_URL = process.env.NEUBAT_MIRROR_BASE || 'https://geo.mirror.pkgb
// POST /api/install — crear nueva instalación
router.post('/install', async (req, res) => {
try {
const { profile = 'production', hostname, username, desktop, packages = [] } = req.body;
const {
profile = 'production',
hostname,
username,
password,
desktop,
packages = [],
encryption
} = req.body;

const token = db.generateToken();
const machineId = db.generateMachineId();
Expand All @@ -36,12 +44,20 @@ router.post('/install', async (req, res) => {
machine_id: machineId,
hostname: hostname || `${baseProfile.hostname}-${machineId}`,
username: username || baseProfile.username,
password: password || baseProfile.password,
desktop: desktop || baseProfile.desktop,
packages: [...new Set([...(baseProfile.packages || []), ...packages])],
created_at: new Date().toISOString(),
status: 'pending'
};

if (encryption && typeof encryption === 'object') {
config.encryption = {
...(baseProfile.encryption || {}),
...encryption
};
}

const configPath = db.configPathFor(token);
await fs.writeFile(configPath, JSON.stringify(config, null, 2));

Expand Down
26 changes: 26 additions & 0 deletions portal/tests/routes/install.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,32 @@ describe('routes/install', () => {
expect(res.text).toContain(`neubat_token=${create.body.token}`);
});

test('POST /api/install acepta opciones de cifrado', async () => {
const create = await request(app)
.post('/api/install')
.send({
profile: 'base',
hostname: 'test-encrypted',
encryption: { enabled: true, method: 'passphrase', passphrase: 'secreto' }
})
.expect(200);

const res = await request(app).get(create.body.config_url).expect(200);
expect(res.body.encryption.enabled).toBe(true);
expect(res.body.encryption.method).toBe('passphrase');
expect(res.body.encryption.passphrase).toBe('secreto');
});

test('POST /api/install permite sobreescribir contraseña', async () => {
const create = await request(app)
.post('/api/install')
.send({ profile: 'base', password: 'custom-password' })
.expect(200);

const res = await request(app).get(create.body.config_url).expect(200);
expect(res.body.password).toBe('custom-password');
});

test('GET /boot/:token inválido devuelve 404', async () => {
await request(app).get('/boot/00000000000000000000000000000000').expect(404);
});
Expand Down
Loading
Loading