# PostgreSQL

Bases PostgreSQL en el VPS con usuario propio de mínimo privilegio, TLS y acceso externo limitado por lista de IP.

Fuente: https://ardumaker.com/docs/postgresql/

- Categoría: Servidores e infraestructura
- Estado: Disponible
- Requisitos: Un VPS activo con la función `postgresql` disponible; Ser miembro con acceso al VPS para instalar; admin para revelar la credencial, abrir el acceso o borrar datos
- Herramientas MCP (Solo leen): `ver_catalogo_apps`, `ver_preflight_app`, `ver_app`, `ver_acceso_app`, `ver_operacion_app`, `listar_motores_db_vps`, `listar_bases_db_vps`
- Herramientas MCP (Escriben o modifican): `instalar_app`, `revelar_credencial_app`, `cambiar_lista_ip_app`, `reintentar_operacion_app`
- Herramientas MCP (Destructivas): `revocar_acceso_app`, `desinstalar_app`

## ¿Qué permite hacer?

Cada base de PostgreSQL es una app: tiene su nombre, su usuario propio con contraseña aleatoria y, si se pide, su acceso desde internet con TLS y lista de IP. El motor se instala una sola vez por VPS y lo comparten todas sus bases.

- Crear varias bases en el mismo motor, cada una aislada de las demás.
- Ver la credencial (usuario, contraseña y URI de conexión) cuando hace falta.
- Abrir, cambiar o cerrar el acceso desde internet por IP.
- Explorar tablas y datos desde el [gestor de bases de datos](https://ardumaker.com/docs/gestor-de-bases-de-datos).
- Desde la pestaña PostgreSQL del VPS: listar y crear bases, usuarios y permisos.

## ¿Cuándo conviene usarlo?

- Una aplicación desplegada en el mismo VPS que necesita una base relacional (se conecta por `127.0.0.1:5432`).
- Un servicio externo (BI, otra nube, tu PC) que tiene que leer la base con TLS desde IP conocidas.

## ¿Cómo funciona en ArduMaker?

1. Validar: VPS activo y función `postgresql` del sistema.
2. Motor: si no está instalado se instala una sola vez (paquetes `postgresql` y `postgresql-contrib` de la distribución, contraseña administrativa aleatoria guardada cifrada). Si ya estaba, no se reinstala ni se cambia su versión ni su contraseña.
3. Credencial: se genera un usuario `app<id>_<hex>` con contraseña aleatoria, cifrada en la plataforma antes de tocar el VPS.
4. Rol y base: el rol se crea `LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION`; la base se crea con ese rol como dueño, se le quita todo a `PUBLIC` y se agrega una línea `hostssl <base> <rol> … scram-sha-256` en `pg_hba.conf`, con un rechazo debajo para cualquier otra base.
5. Verificación: el rol no puede ser superusuario ni crear bases o roles, es dueño de su base y `PUBLIC` no se conecta. Si algo no coincide, falla `permisos_no_verificados`.
6. Acceso externo: solo si se pidió con IP. Se activa el TLS del motor y se pide la apertura como operación aparte; si no se puede abrir todavía, la base queda instalada igual y el motivo queda anotado.

El SQL viaja codificado en el entorno y entra por stdin; la contraseña nunca aparece en una línea de comandos ni en un log.

`config.external_access` decide si la base sale a internet: `{"ips": [...]}` abre solo a esas IP; `{"local": true}` (o `false`) la deja solo adentro del VPS. Si no se indica, desde el panel se toma la IP de quien instala; por MCP la IP del agente no cuenta, así que sin `ips` la base queda local en vez de fallar.

## ¿Qué cambia realmente?

| Qué | Detalle |
| --- | --- |
| Motor | Servicio `postgresql` habilitado, escuchando en `127.0.0.1:5432` |
| Base | Nombre `[A-Za-z_][A-Za-z0-9_]{0,62}`, con mayúsculas tal cual (se cita) |
| Rol de la app | `app<id>_<hex>` |
| `pg_hba.conf` | Su bloque `hostssl` y un `reject` para el resto de las bases |
| TLS (si hay acceso externo) | Certificado propio en `/etc/ardumaker/tls/`, `ssl=on`, TLS 1.2 como mínimo, recarga sin reinicio |

> **INFO:** No se aceptan como nombre `postgres`, `template0`, `template1`, `all`, `sameuser`, `samerole`, `replication` ni nada que empiece con `pg_`.

## Acceso desde internet y lista de IP

Por defecto el motor escucha solo dentro del VPS (5432 en `127.0.0.1`). Para usar la base desde afuera, ArduMaker publica un endpoint propio del motor: `db-<id del VPS>` dentro de la zona de la plataforma (registro DNS directo, sin proxy), en un puerto reservado entre 61000 y 64999. En el host, un proxy lleva ese puerto al 5432 del VPS y el firewall deja entrar solo las IP de la lista.

El certificado es propio de ese nombre (no un comodín) y se instala en el motor: PostgreSQL activa `ssl` con TLS 1.2 como mínimo y recarga la configuración sin reiniciar. El usuario de la app solo entra por TCP con TLS (`hostssl`) y solo a su base.

| Regla de la lista | Detalle |
| --- | --- |
| Formato | IPv4 o IPv6, con o sin prefijo; una IP suelta es /32 o /128. Nada de nombres, loopback, link-local ni multicast |
| Amplitud | Prefijo mínimo /24 en IPv4 y /48 en IPv6 por defecto; `0.0.0.0/0` y `::/0` se rechazan en la lista |
| Tamaño | Hasta 20 entradas; vacía = cerrado para todos |
| Nombres de host | Hasta 5 (`allowed_hosts`), con hasta 8 IP públicas cada uno; se vuelven a resolver cada 5 minutos y una falla de DNS conserva lo último conocido |
| Abierto a todos | Solo con `allow_any` y `confirm_open` juntos; la respuesta lo dice en `open_to_any` |

La lista es del motor, no de cada base: cambiarla desde cualquier base de PostgreSQL cambia el acceso de todas las bases de ese motor en ese VPS (la respuesta lo indica con `shared_with_engine` y `affected_apps`).

1. Se agregan las reglas de firewall que faltan, una por CIDR (`ufw allow from <cidr> to any port <puerto> proto tcp`).
2. Después se quitan las que sobran y se cortan sus conexiones abiertas.
3. Recién entonces se crea el listener y se verifica: reglas iguales a la lista, ninguna regla abierta a cualquiera, listener presente y handshake TLS con el nombre correcto.

> **INFO:** Agregar antes de quitar hace que el estado intermedio sea siempre la unión de dos listas válidas: nunca hay un instante abierto a todos. Si la verificación falla, se borra el listener.

> **PRECAUCION:** Otro VPS del mismo host no llega por la IP pública aunque esté en la lista: la conexión se corta por tiempo. La respuesta avisa con `warnings` (`mismo_host`); entre dos VPS así se usa un túnel SSH (`configurar_tunel_entre_vps`).

## Desinstalar, persistencia y copias

- Sin `delete_data` (lo normal): se cierra el acceso externo propio y la app deja de figurar como instalada, pero la base, su usuario y sus datos quedan en el VPS. La siguiente reconciliación la muestra como detectada. No se pierde nada.
- Con `delete_data: true` (confirmado dos veces, solo admin): `DROP DATABASE` y, si el usuario lo creó Apps (`app<id>_<hex>`), también el rol y su contraseña guardada. Nunca un usuario del cliente ni el administrador del motor.
- El motor nunca se desinstala desde Apps: queda instalado aunque no tenga bases.
- Si el VPS o el motor no responden, `desinstalar_app` con `forzar=true` intenta cada paso una vez, da de baja la app y lista en `not_cleaned` lo que quedó en el servidor.
- Los datos viven en el disco del VPS y viajan en su copia semanal, que se toma con el VPS andando. Para una copia consistente de una base concreta conviene además un volcado propio desde el [gestor de bases de datos](https://ardumaker.com/docs/gestor-de-bases-de-datos) (exportar) o con las herramientas del motor.

> **PELIGRO:** Con `delete_data` la baja es irreversible.

## Esquema

**Conexiones a una base PostgreSQL**

```text
App en el mismo VPS (`127.0.0.1:5432`)
  ↓
Cliente externo (`db-<id>:<puerto>` con `sslmode=verify-full`)
  ↓
Firewall del host (solo las IP de la lista)
  ↓
Proxy del host (puerto 61000–64999 → 5432 del VPS)
  ↓
PostgreSQL (`hostssl` + `scram-sha-256`, solo su base)
```

## Resultado esperado

La base aparece en Apps colgando de su motor. `ver_acceso_app` muestra estado, host, puerto, TLS y la lista aplicada; `revelar_credencial_app` da usuario, contraseña y la URI. La URI solo existe cuando el acceso externo está listo:

```text
postgresql://app<id>_<hex>@db-<vps>.<zona>:<puerto>/<base>?sslmode=verify-full
```

## Errores y resolución

| Error | Causa | ¿Reintentar? |
| --- | --- | --- |
| `base_ya_existe` | Ya hay una base con ese nombre de otro dueño; no se pisa ni se adopta | No |
| `motor_detenido` / `motor_instalandose` / `motor_no_instalado` | El motor está parado, instalándose o no se pudo instalar | Sí |
| `base_no_creada` / `permisos_no_verificados` | Falló el SQL o los permisos del rol no verificaron | Sí |
| `lista_ip_invalida` / `lista_ip_demasiado_amplia` | Entrada mal formada o rango más ancho que el mínimo; `data.invalid` dice cuáles | Corrigiendo la lista |
| `credencial_legacy` / `cuentas_del_motor_sin_verificar` / `tls_no_disponible` | El motor todavía no cumple las condiciones para exponerse (claves antiguas, cuentas propias del cliente sin verificar o TLS pendiente) | Tras resolverlo |
| `acceso_externo_no_habilitado` | El acceso externo no está habilitado para ese tipo en la empresa | No |
| `cierre_no_verificado` | Al cerrar quedó una regla o el listener; se informa lo que realmente hay | Sí |

Estado y pasos de cada operación: `ver_operacion_app` (incluye si es reintentable). Una operación fallida reintentable se retoma desde el paso que falló con `reintentar_operacion_app`, sin reinstalar el motor. Pedir la lista que ya está aplicada no encola nada (`idempotent: true`).

## Relación con MCP

Todo el ciclo de la base se opera por MCP con los permisos de la persona que autorizó al agente. Referencia en [/mcp/docs](https://ardumaker.com/mcp/docs) y [/mcp/bases-de-datos](https://ardumaker.com/mcp/bases-de-datos).

- Lectura: `ver_catalogo_apps`, `ver_preflight_app`, `listar_apps_empresa`, `ver_app`, `ver_acceso_app` (host, puerto, TLS y lista de IP, sin secretos), `ver_operacion_app`.
- Modifican: `instalar_app` con `app_type: postgres` (exige `idempotency_key`), `cambiar_lista_ip_app` (solo si su dueño es admin; las IP se nombran explícitas: la IP del agente no es la de la persona), `reintentar_operacion_app`, `refrescar_apps`.
- Sensible: `revelar_credencial_app` (solo admin, no acepta `idempotency_key`; la respuesta viene marcada `sensible: true` y la auditoría registra que se reveló, nunca el valor).
- Destructivas: `revocar_acceso_app` (cierra el acceso externo de todo el motor) y `desinstalar_app` (con `delete_data` borra los datos; requiere `confirm_delete_data`).

Para leer y escribir datos dentro de la base, el agente usa las herramientas del [gestor de bases de datos](https://ardumaker.com/docs/gestor-de-bases-de-datos). Un agente autorizado solo lectura no ve las herramientas que modifican ni puede revelar credenciales.

## Funciones relacionadas

- [Gestor de bases de datos](https://ardumaker.com/docs/gestor-de-bases-de-datos/)
- [MySQL](https://ardumaker.com/docs/mysql/)
- [Redis](https://ardumaker.com/docs/redis/)
- [Firewall y puertos](https://ardumaker.com/docs/firewall-y-puertos/)
- [Túneles SSH](https://ardumaker.com/docs/tuneles-ssh/)
- [Copias de seguridad](https://ardumaker.com/docs/backups/)
