1. Fuente única de verdad
Principio¶
La infraestructura y la configuración de SmallCountry se definen como código:
se escriben en code-server, se versionan en Forgejo, se planifican con OpenTofu, se despliegan mediante Forgejo Actions y se aplican declarativamente con Ansible usando roles idempotentes.
Cualquier cambio que no recorra este flujo completo es una divergencia que detiene el sistema y exige reconciliación humana.
Ciclo de vida de un cambio¶
-
**Escritura en code-server
Desde el entorno de desarrollo accesible por navegador se editan simultáneamente los módulos de OpenTofu, los roles de Ansible, la documentación y los prompts de IA. Es el único punto de entrada para cualquier modificación del sistema. -
**Versionado en Forgejo
Todo el código se aloja en el repositorio central, que actúa como fuente de verdad, registro OCI de imágenes y motor de CI/CD (integración y despliegue continuo).
El flujo de trabajo es:feature-branch → Pull Request → revisión humana → merge a main. -
**Planificación con OpenTofu
Cada Pull Request ejecutatofu plan. El resultado se compara con el estado real de Proxmox VE a través de su API (interfaz de programación).
Si existe deriva —cambios ejecutados fuera de Git— el flujo se detiene, se genera un commit forense en una ramaquarantiney se notifica al administrador. Este mecanismo garantiza que la deriva nunca quede sin auditar. -
**Aplicación declarativa con Ansible
Tras la creación o actualización de los LXC (contenedor ligero de Proxmox) por OpenTofu, Ansible entra en juego aplicando roles declarativos. Cada rol describe el estado final deseado (paquetes instalados, ficheros de configuración, contenedores Docker activos, certificados TLS (cifrado de comunicaciones) presentes) sin preocuparse del estado actual.
La idempotencia está verificada: ejecutar el mismo rol cien veces produce exactamente el mismo resultado seguro que ejecutarlo una sola vez. -
Consolidación
Al finalizar el despliegue, el estado real del sistema coincide exactamente con el declarado en Forgejo. Cualquier intervención manual futura que se salte este flujo será detectada como deriva y forzará una reconciliación documentada.
flowchart TD
subgraph Escritura [Escritura]
direction LR
CodeServer[code-server] --> Modulos[Módulos OpenTofu / Roles Ansible / Documentación]
end
subgraph Versionado [Versionado]
direction LR
ForgejoNode[Forgejo] --> PR[Pull Request]
PR --> Revision[Revisión humana]
Revision --> Merge[Merge a main]
end
subgraph Planificacion [Planificación]
direction LR
Actions[Forgejo Actions] --> Plan[tofu plan]
Plan --> Drift[Detección de drift]
Drift --> Decision{¿Divergencia?}
Decision -->|Sí| Forense[Commit forense + notificación]
Decision -->|No| Apply[tofu apply]
end
subgraph Aplicacion [Aplicación declarativa]
direction LR
OpenTofu[OpenTofu] --> LXCs[Creación / actualización de LXCs]
LXCs --> Semaphore[Ansible Semaphore]
Semaphore --> Roles[Roles declarativos idempotentes]
end
subgraph Consolidacion [Consolidación]
direction LR
Estado[Estado real = Estado declarado] --> Snapshot[ZFS snapshot]
end
Escritura --> Versionado
Versionado --> Planificacion
Planificacion --> Aplicacion
Aplicacion --> Consolidacion
style Escritura fill:#f9a8d4,stroke:#c2185b,color:#000
style Versionado fill:#f48fb1,stroke:#c2185b,color:#000
style Planificacion fill:#90caf9,stroke:#1565c0,color:#000
style Aplicacion fill:#a5d6a7,stroke:#2e7d32,color:#000
style Consolidacion fill:#ffe082,stroke:#f57f17,color:#000
```
## Ansible como motor declarativo
En <img src="/assets/logo-accent.svg" style="height:1.2em;vertical-align:text-bottom;" alt=""> **SmallCountry**, [**Ansible**](../../fichas/programas/ansible.md) no se usa como un simple ejecutor de comandos, sino como **garante del estado deseado**. Cada servicio tiene su propio rol, estructurado según las mejores prácticas:
| Archivo / Carpeta | Propósito |
|---|---|
| `roles/authentik/tasks/main.yml` | Tareas declarativas: instalar paquetes, crear volúmenes, desplegar docker-compose |
| `roles/authentik/handlers/main.yml` | Acciones reactivas: reiniciar el servicio si cambia la configuración |
| `roles/authentik/templates/` | Plantillas de configuración (authentik.conf.j2, docker-compose.yml.j2) |
| `roles/authentik/vars/main.yml` | Variables específicas del rol (versión de la imagen, puertos, secretos referenciados desde Vault) |
| `roles/authentik/meta/main.yml` | Dependencias: requiere que el rol `docker` esté aplicado primero |
### Propiedades clave
| Propiedad | Descripción |
| :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Declaratividad estricta** | Las tareas describen el estado final deseado, no los pasos para alcanzarlo. |
| **Idempotencia real** | Se emplean condiciones como `creates`, `when` o `changed_when` para que [**Ansible**](../../fichas/programas/ansible.md) decida si debe actuar o si el estado ya es correcto, evitando reinicios innecesarios. |
| **Roles atómicos y desacoplados** | El rol `authentik` no sabe en qué red ni en qué LXC se ejecuta; esa información se la proporciona [**OpenTofu**](../../fichas/programas/opentofu.md). Así se puede reutilizar el mismo rol en distintos entornos sin modificarlo. |
| **Secretos como código** | Las variables sensibles (contraseñas, tokens OIDC (protocolo de autenticación OpenID Connect), claves API) se generan, se cifran con [**Ansible Vault**](../../fichas/programas/ansible.md) y se versionan en [**Forgejo**](../../fichas/stacks/forgejo.md). En el momento del despliegue se inyectan sin que nunca existan en texto plano en producción. |
| **Separación de responsabilidades** | [**OpenTofu**](../../fichas/programas/opentofu.md) crea y dimensiona el LXC (recursos, red, almacenamiento); [**Ansible**](../../fichas/programas/ansible.md) lo provisiona (Docker, configuración, certificados). No se mezclan responsabilidades. |
## Stack necesario
- [**code-server**](../../fichas/programas/codeserver.md) – Entorno de desarrollo desde navegador.
- [**Forgejo**](../../fichas/stacks/forgejo.md) – Repositorio Git, CI/CD y registro OCI.
- [**OpenTofu**](../../fichas/programas/opentofu.md) – Infraestructura como código (módulos declarativos).
- [**Forgejo Actions**](../../fichas/stacks/forgejo.md) – Pipelines de planificación y despliegue.
- [**Ansible**](../../fichas/programas/ansible.md) + [**Semaphore**](../../fichas/programas/semaphore.md) – Configuración declarativa con roles.
- [**Ansible Vault**](../../fichas/programas/ansible.md) – Cifrado de secretos.
- **Script de detección de drift** – Compara plan de [**OpenTofu**](../../fichas/programas/opentofu.md) con estado real de [**Proxmox VE**](../../fichas/programas/proxmox.md).
- [**ZFS**](../../fichas/programas/zfs.md) – Snapshots pre‑cambio para rollback instantáneo.
## Relaciones con otros principios
- [2. Reconstruibilidad total desde cero](02-reconstruibilidad.md): el repositorio central contiene todos los módulos y roles necesarios para regenerar el sistema desde un Proxmox limpio.
- [8. Observabilidad integral desde el origen](08-observabilidad.md): la configuración de los agentes de observabilidad se despliega con roles de [**Ansible**](../../fichas/programas/ansible.md) versionados, asegurando que cada servicio nazca instrumentado.
- [11. Contención de recursos y degradación planificada](11-contencion.md): los límites de RAM/CPU de cada LXC están definidos en código [**OpenTofu**](../../fichas/programas/opentofu.md) y versionados aquí.
- [12. Conocimiento compartido y autonomía de las personas usuarias](12-conocimiento.md): la documentación (`docs.sc`) se escribe en [**code-server**](../../fichas/programas/codeserver.md), se versiona en [**Forgejo**](../../fichas/stacks/forgejo.md) junto al código y se publica con [**MkDocs**](../../fichas/programas/mkdocs.md).
### Flujo de remediación automática
Cuando el sistema detecta una degradación —un servicio caído, un recurso al límite, una métrica fuera de SLO (objetivo de nivel de servicio)— se activa una cadena de corrección que atraviesa todas las capas del ecosistema:
```mermaid
flowchart TD
SkyEye[SkyEye detecta anomalía] --> n8nRecibe[n8n recibe el evento]
n8nRecibe --> n8nConsulta[n8n consulta el runbook]
n8nConsulta --> Decision{¿Acción automática?}
Decision -->|Sí| Semaphore[Semaphore ejecuta playbook]
Decision -->|Requiere humano| Notifica[Notifica al administrador]
Semaphore --> Converge[Ansible converge estado deseado]
Converge --> Restaurado([Sistema restaurado])
Notifica --> Espera([Espera intervención humana])
- SkyEye detecta — una alerta de Prometheus, un healthcheck fallido de Uptime Kuma, o una anomalía detectada por los agentes nocturnos de IA
- n8n interpreta — recibe el evento, consulta el runbook correspondiente y decide la acción (reiniciar servicio, escalar recurso, notificar)
- Semaphore ejecuta — lanza el playbook de Ansible apropiado de forma controlada y registrada
- Ansible converge — aplica el estado deseado de forma idempotente, devolviendo el sistema a su condición normal
Todo este flujo deja trazabilidad completa en Forgejo. Si en algún punto se requiere intervención humana, el sistema notifica y espera. La automatización acelera, pero nunca sustituye el criterio cuando la situación lo exige.
← Mecanismo 10: Perfiles de Energía | Principio 2: Reconstruibilidad total →