From 36196457eadbc56a0f0410c1f6bd31ecca0b84a9 Mon Sep 17 00:00:00 2001 From: Thomas Reitz Date: Sun, 15 Mar 2026 15:23:29 +0100 Subject: [PATCH] =?UTF-8?q?feat(infra):=20vollst=C3=A4ndige=20Ansible-Stru?= =?UTF-8?q?ktur=20Phase=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Alle Ansible-Rollen erstellt: common, disk_setup, docker, postgresql, pgbouncer, redis, nginx, zabbix_agent - ansible.cfg mit Pipeline-Optimierung - hosts.yml mit echten IPs (DBS01=.20, APS01=.21, WEB01=.22) - group_vars für alle Server (dbs, aps, web) - Zabbix-Server auf sentinel.xinion.de gesetzt - vault.yml.example als Vorlage für Secrets - site.yml nutzt import_playbook (DBS01→APS01→WEB01) - BRIEFING.md für alle 4 Repos angelegt (Platform, Apps, Infra, Shared) Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 3 + repos/INSIGHT-Apps/BRIEFING.md | 79 ++++++++++++ repos/INSIGHT-Infra/BRIEFING.md | 82 ++++++++++++ repos/INSIGHT-Infra/ansible/ansible.cfg | 11 ++ .../ansible/inventory/group_vars/all.yml | 28 ++++ .../inventory/group_vars/insight_aps.yml | 8 ++ .../inventory/group_vars/insight_dbs.yml | 37 ++++++ .../inventory/group_vars/insight_web.yml | 9 ++ .../INSIGHT-Infra/ansible/inventory/hosts.yml | 35 +++++ .../INSIGHT-Infra/ansible/playbooks/aps01.yml | 12 ++ .../INSIGHT-Infra/ansible/playbooks/dbs01.yml | 14 ++ .../INSIGHT-Infra/ansible/playbooks/site.yml | 9 ++ .../INSIGHT-Infra/ansible/playbooks/web01.yml | 13 ++ .../ansible/roles/common/tasks/main.yml | 87 +++++++++++++ .../roles/common/templates/chrony.conf.j2 | 9 ++ .../roles/common/templates/sshd_config.j2 | 26 ++++ .../ansible/roles/disk_setup/tasks/main.yml | 63 +++++++++ .../ansible/roles/docker/tasks/main.yml | 65 ++++++++++ .../ansible/roles/nginx/handlers/main.yml | 10 ++ .../ansible/roles/nginx/tasks/main.yml | 64 +++++++++ .../roles/nginx/templates/insight.conf.j2 | 62 +++++++++ .../roles/nginx/templates/nginx.conf.j2 | 46 +++++++ .../ansible/roles/pgbouncer/handlers/main.yml | 5 + .../ansible/roles/pgbouncer/tasks/main.yml | 43 +++++++ .../pgbouncer/templates/pgbouncer.ini.j2 | 33 +++++ .../roles/pgbouncer/templates/userlist.txt.j2 | 6 + .../roles/postgresql/handlers/main.yml | 10 ++ .../ansible/roles/postgresql/tasks/main.yml | 121 ++++++++++++++++++ .../roles/postgresql/templates/pg_hba.conf.j2 | 15 +++ .../postgresql/templates/postgresql.conf.j2 | 42 ++++++ .../ansible/roles/redis/handlers/main.yml | 5 + .../ansible/roles/redis/tasks/main.yml | 55 ++++++++ .../roles/redis/templates/redis.conf.j2 | 38 ++++++ .../roles/zabbix_agent/handlers/main.yml | 5 + .../ansible/roles/zabbix_agent/tasks/main.yml | 59 +++++++++ .../templates/zabbix_agent2.conf.j2 | 27 ++++ repos/INSIGHT-Infra/ansible/vault.yml.example | 36 ++++++ repos/INSIGHT-Platform/BRIEFING.md | 74 +++++++++++ repos/INSIGHT-Shared/BRIEFING.md | 59 +++++++++ .../contracts/module-interface.md | 84 ++++++++++++ repos/INSIGHT-Shared/dev-status/apps.md | 24 ++++ repos/INSIGHT-Shared/dev-status/infra.md | 32 +++++ repos/INSIGHT-Shared/dev-status/platform.md | 25 ++++ 43 files changed, 1570 insertions(+) create mode 100644 repos/INSIGHT-Apps/BRIEFING.md create mode 100644 repos/INSIGHT-Infra/BRIEFING.md create mode 100644 repos/INSIGHT-Infra/ansible/ansible.cfg create mode 100644 repos/INSIGHT-Infra/ansible/inventory/group_vars/all.yml create mode 100644 repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_aps.yml create mode 100644 repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_dbs.yml create mode 100644 repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_web.yml create mode 100644 repos/INSIGHT-Infra/ansible/inventory/hosts.yml create mode 100644 repos/INSIGHT-Infra/ansible/playbooks/aps01.yml create mode 100644 repos/INSIGHT-Infra/ansible/playbooks/dbs01.yml create mode 100644 repos/INSIGHT-Infra/ansible/playbooks/site.yml create mode 100644 repos/INSIGHT-Infra/ansible/playbooks/web01.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/common/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/common/templates/chrony.conf.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/common/templates/sshd_config.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/disk_setup/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/docker/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/nginx/handlers/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/nginx/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/nginx/templates/insight.conf.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/nginx/templates/nginx.conf.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/pgbouncer/handlers/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/pgbouncer/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/pgbouncer.ini.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/userlist.txt.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/postgresql/handlers/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/postgresql/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/postgresql/templates/pg_hba.conf.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/postgresql/templates/postgresql.conf.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/redis/handlers/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/redis/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/redis/templates/redis.conf.j2 create mode 100644 repos/INSIGHT-Infra/ansible/roles/zabbix_agent/handlers/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/zabbix_agent/tasks/main.yml create mode 100644 repos/INSIGHT-Infra/ansible/roles/zabbix_agent/templates/zabbix_agent2.conf.j2 create mode 100644 repos/INSIGHT-Infra/ansible/vault.yml.example create mode 100644 repos/INSIGHT-Platform/BRIEFING.md create mode 100644 repos/INSIGHT-Shared/BRIEFING.md create mode 100644 repos/INSIGHT-Shared/contracts/module-interface.md create mode 100644 repos/INSIGHT-Shared/dev-status/apps.md create mode 100644 repos/INSIGHT-Shared/dev-status/infra.md create mode 100644 repos/INSIGHT-Shared/dev-status/platform.md diff --git a/.gitignore b/.gitignore index 1017fdd..5d893d0 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,9 @@ tmp/ temp/ *.tmp +# SSH Keys & Deploy Keys (NIEMALS committen!) +.keys/ + # Certificates (generierte Zertifikate, nicht die CA-Config) config/step-ca/secrets/ config/step-ca/db/ diff --git a/repos/INSIGHT-Apps/BRIEFING.md b/repos/INSIGHT-Apps/BRIEFING.md new file mode 100644 index 0000000..2171c1a --- /dev/null +++ b/repos/INSIGHT-Apps/BRIEFING.md @@ -0,0 +1,79 @@ +# INSIGHT-Apps — Developer Briefing + +> Dieses Dokument ist der Einstiegspunkt für jede neue Claude Code Session in diesem Repo. +> Lies es vollständig bevor du Code schreibst. + +## Was ist dieses Repo? + +**INSIGHT-Apps** enthält alle fachlichen Module der INSIGHT-Plattform. +Jedes Modul ist ein eigenständiges NestJS-Service + React-Frontend-Paket, +das sich über den `InsightModule`-Contract in die Platform-Shell einbindet. + +## Struktur + +``` +packages/ +├── crm-service/ # NestJS: Company, Contact, Deal, Activity, Pipeline +└── crm-frontend/ # React: CRM-Seiten → wird in INSIGHT-Platform Shell eingebunden +# zukünftig: +# ├── hr-service/ +# └── hr-frontend/ +``` + +## Module-Contract (PFLICHT lesen!) + +Jedes Frontend-Paket **muss** einen `InsightModule`-Manifest exportieren. +Siehe: `INSIGHT-Shared/contracts/module-interface.md` + +```typescript +// Beispiel crm-frontend/src/index.ts +export const CrmModule: InsightModule = { + id: 'crm', + name: 'CRM', + requiredRole: 'crm', + routes: [...], + navigation: [...], +} +``` + +## Abhängigkeiten zu anderen Repos + +| Repo | Beziehung | +|------|-----------| +| `INSIGHT-Shared` | Lesen: module-interface.md, api-platform.md — Schreiben: api-crm.md nach Änderungen | +| `INSIGHT-Platform` | Nutzt Core-Service APIs (Auth, User) — eigene Module registrieren sich in der Shell | +| `INSIGHT-Infra` | Stellt DBS01 (PostgreSQL) und APS01 (Docker) bereit | + +## Confluence Dokumentation + +- **Phase 3 — CRM Modul:** https://xinion.atlassian.net/wiki/x/AoCUEw +- **CRM-Datenmodell:** https://xinion.atlassian.net/wiki/x/AYCVEw +- **Projekt-Übersicht:** https://xinion.atlassian.net/wiki/spaces/ProjektINS/overview + +## Tech Stack + +- **Backend:** NestJS 10, TypeScript strict, Prisma ORM +- **Frontend:** React 18, Vite, TanStack Query +- **Datenbank:** PostgreSQL 16 (eigenes Schema, kein Multi-Tenancy) +- **Auth:** Tokens kommen von INSIGHT-Platform Core-Service + +## Sicherheitsregeln + +- Jeder Endpoint mit `@Roles('crm')` oder spezifischerer Rolle absichern +- Kein `any` in TypeScript +- Daten-Ownership prüfen: User darf nur eigene Deals bearbeiten (außer CRM_ADMIN/MANAGER) +- Soft-Delete (`deletedAt`) statt hartem Löschen + +## Deploy Key (dieses Repo) + +``` +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAw2W+ChU6bjQdlTjHgK2FjMPXUD3/1vwNlU+aosmDqt deploy@INSIGHT-Apps +``` + +Privater Key: `.keys/deploy_apps_ed25519` + +## Aktueller Status + +- **Phase:** 3 — CRM Modul +- **Stand:** Konzeption (CRM-Datenmodell in Klärung) +- **Nächster Schritt:** CRM-Datenmodell finalisieren → Prisma Schema → Company/Contact API diff --git a/repos/INSIGHT-Infra/BRIEFING.md b/repos/INSIGHT-Infra/BRIEFING.md new file mode 100644 index 0000000..6fb4eef --- /dev/null +++ b/repos/INSIGHT-Infra/BRIEFING.md @@ -0,0 +1,82 @@ +# INSIGHT-Infra — Developer Briefing + +> Dieses Dokument ist der Einstiegspunkt für jede neue Claude Code Session in diesem Repo. +> Lies es vollständig bevor du Code schreibst. + +## Was ist dieses Repo? + +**INSIGHT-Infra** enthält alle Infrastruktur-Definitionen für die INSIGHT-Plattform: +Ansible Playbooks zur Server-Provisionierung und Docker Compose Konfigurationen. + +## Struktur + +``` +ansible/ +├── inventory/ +│ ├── hosts.yml # DBS01, APS01, WEB01 +│ └── group_vars/ +│ ├── all.yml +│ ├── insight_dbs.yml +│ ├── insight_aps.yml +│ └── insight_web.yml +├── playbooks/ +│ ├── site.yml # Master (alle Server) +│ ├── dbs01.yml +│ ├── aps01.yml +│ └── web01.yml +└── roles/ + ├── common/ # Hardening, NTP, SSH, unattended-upgrades + ├── docker/ # Docker CE + Compose Plugin + ├── postgresql/ # PostgreSQL 16 + PgBouncer + ├── redis/ # Redis 7 + ├── nginx/ # Nginx + TLS + └── zabbix_agent/ # Zabbix Agent 2 +``` + +## Server + +| Server | Hostname | Rolle | +|--------|----------|-------| +| INSIGHT-DBS01 | insight-dbs01.xinion.lan | PostgreSQL 16 + PgBouncer + Redis 7 | +| INSIGHT-APS01 | insight-aps01.xinion.lan | Docker: core-service, crm-service | +| INSIGHT-WEB01 | insight-web01.xinion.lan | Nginx + React Build | + +IPs und Details: https://xinion.atlassian.net/wiki/x/FYCVEw + +## SSH Keys (Ansible → Server) + +| Server | Key | +|--------|-----| +| DBS01 | `.keys/ansible_dbs01_ed25519` | +| APS01 | `.keys/ansible_aps01_ed25519` | +| WEB01 | `.keys/ansible_web01_ed25519` | + +Öffentliche Keys sind via Cloud-Init bereits auf den Servern hinterlegt. + +## Confluence Dokumentation + +- **Phase 1 — Infrastruktur:** https://xinion.atlassian.net/wiki/x/PACVEw +- **Hardware-Anforderungen:** https://xinion.atlassian.net/wiki/x/tYKSEw +- **Cloud-Init Konfiguration:** https://xinion.atlassian.net/wiki/x/BgCREw +- **SSH Keys & Zugangsverwaltung:** https://xinion.atlassian.net/wiki/x/UACVEw + +## Wichtige Regeln + +- Secrets ausschließlich in **Ansible Vault** (kein Klartext in Git) +- Alle Tasks mit `become: true` wenn Root-Rechte nötig +- Idempotenz: Playbooks müssen mehrfach ausführbar sein ohne Fehler +- Nach jeder Infrastruktur-Änderung `dev-status/infra.md` in INSIGHT-Shared aktualisieren + +## Deploy Key (dieses Repo) + +``` +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILKnKLskXQH6p9Wb+UuzPekLremPJCDxFzE1dchqkXkt deploy@insight-infra +``` + +Privater Key: `.keys/deploy_infra_ed25519` + +## Aktueller Status + +- **Phase:** 1 — Infrastruktur +- **Stand:** VMs angelegt (DBS01 VM 1020), SSH-Zugang funktioniert +- **Nächster Schritt:** IPs eintragen → Ansible Inventory befüllen → Playbooks schreiben diff --git a/repos/INSIGHT-Infra/ansible/ansible.cfg b/repos/INSIGHT-Infra/ansible/ansible.cfg new file mode 100644 index 0000000..818457a --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/ansible.cfg @@ -0,0 +1,11 @@ +[defaults] +inventory = inventory/hosts.yml +roles_path = roles +host_key_checking = False +retry_files_enabled = False +stdout_callback = yaml +callbacks_enabled = profile_tasks + +[ssh_connection] +pipelining = True +ssh_args = -o ControlMaster=auto -o ControlPersist=60s diff --git a/repos/INSIGHT-Infra/ansible/inventory/group_vars/all.yml b/repos/INSIGHT-Infra/ansible/inventory/group_vars/all.yml new file mode 100644 index 0000000..0b9ca61 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/inventory/group_vars/all.yml @@ -0,0 +1,28 @@ +--- +# Globale Variablen für alle INSIGHT-Server +# Secrets gehören in ansible/vault.yml (Ansible Vault verschlüsselt) + +# Zeitzone +timezone: "Europe/Berlin" + +# NTP Server (intern) +ntp_servers: + - 0.de.pool.ntp.org + - 1.de.pool.ntp.org + +# SSH Hardening +ssh_port: 22 +ssh_permit_root_login: "no" +ssh_password_authentication: "no" +ssh_pubkey_authentication: "yes" + +# Zabbix +zabbix_server: "sentinel.xinion.de" +zabbix_agent_version: "2" +zabbix_hostname_prefix: "INSIGHT-" + +# Docker +docker_compose_version: "latest" + +# Domain +internal_domain: "xinion.lan" diff --git a/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_aps.yml b/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_aps.yml new file mode 100644 index 0000000..3c1ed47 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_aps.yml @@ -0,0 +1,8 @@ +--- +# Variablen für INSIGHT-APS01 + +# Docker (core-service + crm-service) +# App-Konfiguration kommt per .env via Vault / CI/CD + +# Kein Data-Disk für APS01 geplant +# Falls später Volume nötig, hier ergänzen diff --git a/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_dbs.yml b/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_dbs.yml new file mode 100644 index 0000000..4208226 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_dbs.yml @@ -0,0 +1,37 @@ +--- +# Variablen für INSIGHT-DBS01 + +# PostgreSQL +postgresql_version: "16" +postgresql_data_dir: "/data/postgresql" +postgresql_port: 5432 +postgresql_max_connections: 100 # PgBouncer übernimmt Pooling +postgresql_shared_buffers: "512MB" +postgresql_effective_cache_size: "2GB" +postgresql_work_mem: "8MB" +postgresql_maintenance_work_mem: "128MB" + +# PostgreSQL Datenbanken (Secrets in vault.yml) +postgresql_databases: + - insight_core + - insight_crm + +# PgBouncer +pgbouncer_port: 6432 +pgbouncer_pool_mode: "transaction" +pgbouncer_default_pool_size: 20 +pgbouncer_min_pool_size: 5 +pgbouncer_reserve_pool_size: 5 +pgbouncer_max_client_conn: 200 + +# Redis +redis_port: 6379 +redis_data_dir: "/data/redis" +redis_maxmemory: "1gb" +redis_maxmemory_policy: "allkeys-lru" + +# Data Disk (separate Volume für Daten) +# Wird von role/disk_setup gemountet +data_disk_device: "/dev/sdb" +data_disk_mountpoint: "/data" +data_disk_filesystem: "ext4" diff --git a/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_web.yml b/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_web.yml new file mode 100644 index 0000000..3ae7593 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/inventory/group_vars/insight_web.yml @@ -0,0 +1,9 @@ +--- +# Variablen für INSIGHT-WEB01 + +# Nginx +nginx_webroot: "/var/www/insight" +nginx_server_name: "insight.xinion.lan" +aps01_ip: "172.20.10.21" + +# Kein Data-Disk (WEB01 braucht keinen separaten Mount) diff --git a/repos/INSIGHT-Infra/ansible/inventory/hosts.yml b/repos/INSIGHT-Infra/ansible/inventory/hosts.yml new file mode 100644 index 0000000..2b6e609 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/inventory/hosts.yml @@ -0,0 +1,35 @@ +--- +# INSIGHT Ansible Inventory +# IPs eintragen sobald Server provisioniert sind +# Confluence: https://xinion.atlassian.net/wiki/x/FYCVEw + +all: + vars: + ansible_user: ansible + ansible_ssh_common_args: '-o StrictHostKeyChecking=no' + + children: + insight_dbs: + hosts: + insight-dbs01: + ansible_host: 172.20.10.20 + ansible_ssh_private_key_file: ~/.ssh/insight/ansible_dbs01_ed25519 + # Proxmox VM: 1020 + # Rolle: PostgreSQL 16 + PgBouncer + Redis 7 + # Data-Disk: /dev/sdb → /data (250 GB) + + insight_aps: + hosts: + insight-aps01: + ansible_host: 172.20.10.21 + ansible_ssh_private_key_file: ~/.ssh/insight/ansible_aps01_ed25519 + # Proxmox VM: 1021 + # Rolle: Docker (core-service, crm-service) + + insight_web: + hosts: + insight-web01: + ansible_host: 172.20.10.22 + ansible_ssh_private_key_file: ~/.ssh/insight/ansible_web01_ed25519 + # Proxmox VM: 1022 + # Rolle: Nginx + React Build diff --git a/repos/INSIGHT-Infra/ansible/playbooks/aps01.yml b/repos/INSIGHT-Infra/ansible/playbooks/aps01.yml new file mode 100644 index 0000000..85fcf85 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/playbooks/aps01.yml @@ -0,0 +1,12 @@ +--- +# Playbook nur für APS01 +# Ausführung: ansible-playbook -i inventory/hosts.yml playbooks/aps01.yml + +- name: "INSIGHT-APS01 Setup" + hosts: insight_aps + become: true + roles: + - role: common + - role: disk_setup + - role: docker + - role: zabbix_agent diff --git a/repos/INSIGHT-Infra/ansible/playbooks/dbs01.yml b/repos/INSIGHT-Infra/ansible/playbooks/dbs01.yml new file mode 100644 index 0000000..d7da4de --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/playbooks/dbs01.yml @@ -0,0 +1,14 @@ +--- +# Playbook nur für DBS01 — schnelles Update ohne alle Server +# Ausführung: ansible-playbook -i inventory/hosts.yml playbooks/dbs01.yml + +- name: "INSIGHT-DBS01 Setup" + hosts: insight_dbs + become: true + roles: + - role: common + - role: disk_setup + - role: postgresql + - role: pgbouncer + - role: redis + - role: zabbix_agent diff --git a/repos/INSIGHT-Infra/ansible/playbooks/site.yml b/repos/INSIGHT-Infra/ansible/playbooks/site.yml new file mode 100644 index 0000000..abc0e1b --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/playbooks/site.yml @@ -0,0 +1,9 @@ +--- +# INSIGHT Master Playbook — importiert alle Server-Playbooks +# Reihenfolge: DBS01 (Datenbank) → APS01 (App) → WEB01 (Web) +# Ausführung: ansible-playbook -i inventory/hosts.yml playbooks/site.yml --ask-vault-pass +# Einzeln: ansible-playbook -i inventory/hosts.yml playbooks/dbs01.yml --ask-vault-pass + +- import_playbook: dbs01.yml +- import_playbook: aps01.yml +- import_playbook: web01.yml diff --git a/repos/INSIGHT-Infra/ansible/playbooks/web01.yml b/repos/INSIGHT-Infra/ansible/playbooks/web01.yml new file mode 100644 index 0000000..82a8f9e --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/playbooks/web01.yml @@ -0,0 +1,13 @@ +--- +# Playbook nur für WEB01 +# Ausführung: ansible-playbook -i inventory/hosts.yml playbooks/web01.yml + +- name: "INSIGHT-WEB01 Setup" + hosts: insight_web + become: true + roles: + - role: common + - role: disk_setup + - role: docker + - role: nginx + - role: zabbix_agent diff --git a/repos/INSIGHT-Infra/ansible/roles/common/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..14cd7be --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/common/tasks/main.yml @@ -0,0 +1,87 @@ +--- +# Role: common +# Basis-Hardening für alle INSIGHT-Server + +- name: "System-Pakete aktualisieren" + apt: + update_cache: true + upgrade: dist + cache_valid_time: 3600 + +- name: "Basis-Pakete installieren" + apt: + name: + - curl + - wget + - git + - htop + - vim + - unzip + - ca-certificates + - gnupg + - lsb-release + - ufw + - fail2ban + - chrony + - python3-pip + state: present + +- name: "Unattended-Upgrades installieren" + apt: + name: unattended-upgrades + state: present + +- name: "Unattended-Upgrades aktivieren" + copy: + dest: /etc/apt/apt.conf.d/20auto-upgrades + content: | + APT::Periodic::Update-Package-Lists "1"; + APT::Periodic::Unattended-Upgrade "1"; + APT::Periodic::Download-Upgradeable-Packages "1"; + APT::Periodic::AutocleanInterval "7"; + +- name: "Zeitzone setzen" + timezone: + name: "{{ timezone }}" + +- name: "Chrony (NTP) konfigurieren" + template: + src: chrony.conf.j2 + dest: /etc/chrony/chrony.conf + notify: restart chrony + +- name: "SSH Hardening — sshd_config" + template: + src: sshd_config.j2 + dest: /etc/ssh/sshd_config + validate: 'sshd -t -f %s' + notify: restart sshd + +- name: "UFW — Standard: alles ablehnen" + ufw: + state: enabled + policy: deny + direction: incoming + +- name: "UFW — SSH erlauben" + ufw: + rule: allow + port: "{{ ssh_port }}" + proto: tcp + +- name: "Fail2ban aktivieren" + service: + name: fail2ban + state: started + enabled: true + +handlers: + - name: restart chrony + service: + name: chrony + state: restarted + + - name: restart sshd + service: + name: sshd + state: restarted diff --git a/repos/INSIGHT-Infra/ansible/roles/common/templates/chrony.conf.j2 b/repos/INSIGHT-Infra/ansible/roles/common/templates/chrony.conf.j2 new file mode 100644 index 0000000..2048bc5 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/common/templates/chrony.conf.j2 @@ -0,0 +1,9 @@ +# chrony.conf — Managed by Ansible (INSIGHT-Infra) +{% for server in ntp_servers %} +pool {{ server }} iburst +{% endfor %} + +driftfile /var/lib/chrony/drift +makestep 1.0 3 +rtcsync +logdir /var/log/chrony diff --git a/repos/INSIGHT-Infra/ansible/roles/common/templates/sshd_config.j2 b/repos/INSIGHT-Infra/ansible/roles/common/templates/sshd_config.j2 new file mode 100644 index 0000000..68ee9f2 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/common/templates/sshd_config.j2 @@ -0,0 +1,26 @@ +# sshd_config — Managed by Ansible (INSIGHT-Infra) +Port {{ ssh_port | default(22) }} +AddressFamily inet +ListenAddress 0.0.0.0 + +# Authentication +PermitRootLogin {{ ssh_permit_root_login | default('no') }} +PasswordAuthentication {{ ssh_password_authentication | default('no') }} +PubkeyAuthentication {{ ssh_pubkey_authentication | default('yes') }} +AuthorizedKeysFile .ssh/authorized_keys +ChallengeResponseAuthentication no +UsePAM yes + +# Security +X11Forwarding no +AllowTcpForwarding no +PermitEmptyPasswords no +MaxAuthTries 3 +LoginGraceTime 30 + +# Session +ClientAliveInterval 300 +ClientAliveCountMax 2 + +# Subsystem +Subsystem sftp /usr/lib/openssh/sftp-server diff --git a/repos/INSIGHT-Infra/ansible/roles/disk_setup/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/disk_setup/tasks/main.yml new file mode 100644 index 0000000..188d895 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/disk_setup/tasks/main.yml @@ -0,0 +1,63 @@ +--- +# Role: disk_setup +# Erkennt und mountet zusätzliche Datendisks (z.B. /dev/sdb auf DBS01) +# Wird nur ausgeführt wenn data_disk_device definiert und Disk vorhanden ist + +- name: "Prüfen ob Data-Disk vorhanden ist" + stat: + path: "{{ data_disk_device }}" + register: data_disk_stat + when: data_disk_device is defined + +- name: "Prüfen ob Data-Disk bereits formatiert ist" + command: "blkid {{ data_disk_device }}" + register: data_disk_blkid + ignore_errors: true + changed_when: false + when: + - data_disk_device is defined + - data_disk_stat.stat.exists + +- name: "Data-Disk formatieren (ext4) — nur wenn noch nicht formatiert" + filesystem: + fstype: "{{ data_disk_filesystem | default('ext4') }}" + dev: "{{ data_disk_device }}" + when: + - data_disk_device is defined + - data_disk_stat.stat.exists + - data_disk_blkid.rc != 0 + +- name: "Mountpoint erstellen" + file: + path: "{{ data_disk_mountpoint }}" + state: directory + mode: '0755' + when: + - data_disk_device is defined + - data_disk_stat.stat.exists + +- name: "Data-Disk in /etc/fstab eintragen und mounten" + mount: + path: "{{ data_disk_mountpoint }}" + src: "{{ data_disk_device }}" + fstype: "{{ data_disk_filesystem | default('ext4') }}" + opts: defaults + state: mounted + when: + - data_disk_device is defined + - data_disk_stat.stat.exists + +- name: "LVM prüfen und erweitern (falls LVM genutzt wird)" + block: + - name: "pvs prüfen" + command: pvs + register: pvs_output + changed_when: false + ignore_errors: true + + - name: "LVM-Infos ausgeben" + debug: + msg: "{{ pvs_output.stdout_lines }}" + when: pvs_output.rc == 0 + when: data_disk_device is not defined + ignore_errors: true diff --git a/repos/INSIGHT-Infra/ansible/roles/docker/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/docker/tasks/main.yml new file mode 100644 index 0000000..e9c1d65 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,65 @@ +--- +# Role: docker +# Installiert Docker CE + Compose Plugin + +- name: "Docker GPG Key hinzufügen" + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + +- name: "Docker Repository hinzufügen" + apt_repository: + repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" + state: present + +- name: "Docker CE installieren" + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + update_cache: true + +- name: "Docker Service aktivieren und starten" + service: + name: docker + state: started + enabled: true + +- name: "ansible User zur docker-Gruppe hinzufügen" + user: + name: ansible + groups: docker + append: true + +- name: "Docker Daemon konfigurieren (log rotation, data-root)" + copy: + dest: /etc/docker/daemon.json + content: | + { + "log-driver": "json-file", + "log-opts": { + "max-size": "100m", + "max-file": "3" + }, + "live-restore": true + } + notify: restart docker + +- name: "Docker Version prüfen" + command: docker --version + register: docker_version + changed_when: false + +- name: "Docker Version ausgeben" + debug: + msg: "{{ docker_version.stdout }}" + +handlers: + - name: restart docker + service: + name: docker + state: restarted diff --git a/repos/INSIGHT-Infra/ansible/roles/nginx/handlers/main.yml b/repos/INSIGHT-Infra/ansible/roles/nginx/handlers/main.yml new file mode 100644 index 0000000..15bc297 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/nginx/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: reload nginx + service: + name: nginx + state: reloaded + +- name: restart nginx + service: + name: nginx + state: restarted diff --git a/repos/INSIGHT-Infra/ansible/roles/nginx/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/nginx/tasks/main.yml new file mode 100644 index 0000000..7182e77 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/nginx/tasks/main.yml @@ -0,0 +1,64 @@ +--- +# Role: nginx +# Installiert und konfiguriert Nginx als Reverse Proxy auf WEB01 +# Stellt das React-Frontend bereit und proxied auf APS01 + +- name: "Nginx installieren" + apt: + name: + - nginx + - certbot + - python3-certbot-nginx + state: present + update_cache: true + +- name: "Nginx Default-Site deaktivieren" + file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: reload nginx + +- name: "Nginx Konfiguration (Haupt)" + template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + mode: '0644' + validate: 'nginx -t -c %s' + notify: reload nginx + +- name: "INSIGHT Site Konfiguration" + template: + src: insight.conf.j2 + dest: /etc/nginx/sites-available/insight + mode: '0644' + notify: reload nginx + +- name: "INSIGHT Site aktivieren" + file: + src: /etc/nginx/sites-available/insight + dest: /etc/nginx/sites-enabled/insight + state: link + notify: reload nginx + +- name: "Web-Root Verzeichnis anlegen" + file: + path: "{{ nginx_webroot | default('/var/www/insight') }}" + state: directory + owner: www-data + group: www-data + mode: '0755' + +- name: "Nginx Service aktivieren und starten" + service: + name: nginx + state: started + enabled: true + +- name: "Nginx Version prüfen" + command: nginx -v + register: nginx_version + changed_when: false + +- name: "Nginx Version ausgeben" + debug: + msg: "{{ nginx_version.stderr }}" diff --git a/repos/INSIGHT-Infra/ansible/roles/nginx/templates/insight.conf.j2 b/repos/INSIGHT-Infra/ansible/roles/nginx/templates/insight.conf.j2 new file mode 100644 index 0000000..13fa1f1 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/nginx/templates/insight.conf.j2 @@ -0,0 +1,62 @@ +# insight.conf — Managed by Ansible (INSIGHT-Infra) +# Reverse Proxy: WEB01 → APS01 + +upstream insight_api { + server {{ aps01_ip | default('172.20.10.21') }}:3000; + keepalive 32; +} + +server { + listen 80; + server_name {{ nginx_server_name | default('_') }}; + + # React SPA (Static Files) + root {{ nginx_webroot | default('/var/www/insight') }}; + index index.html; + + # API Proxy → APS01 + location /api/ { + proxy_pass http://insight_api; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_connect_timeout 30s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + proxy_buffer_size 4k; + proxy_buffers 8 4k; + } + + # WebSocket (für NestJS Events / Pub-Sub) + location /socket.io/ { + proxy_pass http://insight_api; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # React SPA Fallback (client-side routing) + location / { + try_files $uri $uri/ /index.html; + + # Cache Static Assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } + + # Health Check + location /health { + access_log off; + return 200 "OK\n"; + add_header Content-Type text/plain; + } +} diff --git a/repos/INSIGHT-Infra/ansible/roles/nginx/templates/nginx.conf.j2 b/repos/INSIGHT-Infra/ansible/roles/nginx/templates/nginx.conf.j2 new file mode 100644 index 0000000..b8f1ceb --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/nginx/templates/nginx.conf.j2 @@ -0,0 +1,46 @@ +# nginx.conf — Managed by Ansible (INSIGHT-Infra) +user www-data; +worker_processes auto; +pid /run/nginx.pid; + +events { + worker_connections 1024; + multi_accept on; +} + +http { + # Basics + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent"'; + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # Gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json + application/javascript application/xml+rss + application/atom+xml image/svg+xml; + + # Security Headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + include /etc/nginx/sites-enabled/*; +} diff --git a/repos/INSIGHT-Infra/ansible/roles/pgbouncer/handlers/main.yml b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/handlers/main.yml new file mode 100644 index 0000000..956f37b --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart pgbouncer + service: + name: pgbouncer + state: restarted diff --git a/repos/INSIGHT-Infra/ansible/roles/pgbouncer/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/tasks/main.yml new file mode 100644 index 0000000..9f216ed --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/tasks/main.yml @@ -0,0 +1,43 @@ +--- +# Role: pgbouncer +# Installiert und konfiguriert PgBouncer als Connection Pooler vor PostgreSQL + +- name: "PgBouncer installieren" + apt: + name: pgbouncer + state: present + update_cache: true + +- name: "PgBouncer konfigurieren (pgbouncer.ini)" + template: + src: pgbouncer.ini.j2 + dest: /etc/pgbouncer/pgbouncer.ini + owner: postgres + group: postgres + mode: '0640' + notify: restart pgbouncer + +- name: "PgBouncer userlist.txt konfigurieren" + template: + src: userlist.txt.j2 + dest: /etc/pgbouncer/userlist.txt + owner: postgres + group: postgres + mode: '0640' + notify: restart pgbouncer + no_log: true + +- name: "PgBouncer Service aktivieren und starten" + service: + name: pgbouncer + state: started + enabled: true + +- name: "PgBouncer Status prüfen" + command: pgbouncer --version + register: pgbouncer_version + changed_when: false + +- name: "PgBouncer Version ausgeben" + debug: + msg: "{{ pgbouncer_version.stdout }}" diff --git a/repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/pgbouncer.ini.j2 b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/pgbouncer.ini.j2 new file mode 100644 index 0000000..890969d --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/pgbouncer.ini.j2 @@ -0,0 +1,33 @@ +# pgbouncer.ini — Managed by Ansible (INSIGHT-Infra) +[databases] +{% for db in postgresql_databases | default([]) %} +{{ db }} = host=127.0.0.1 port=5432 dbname={{ db }} +{% endfor %} + +[pgbouncer] +listen_addr = 127.0.0.1 +listen_port = {{ pgbouncer_port | default(5433) }} + +auth_type = md5 +auth_file = /etc/pgbouncer/userlist.txt + +pool_mode = {{ pgbouncer_pool_mode | default('transaction') }} +max_client_conn = {{ pgbouncer_max_client_conn | default(200) }} +default_pool_size = {{ pgbouncer_default_pool_size | default(20) }} +min_pool_size = {{ pgbouncer_min_pool_size | default(5) }} +reserve_pool_size = {{ pgbouncer_reserve_pool_size | default(5) }} +reserve_pool_timeout = 5 + +# Logging +log_connections = 0 +log_disconnections = 0 +log_pooler_errors = 1 +stats_period = 60 + +# Admin +admin_users = pgbouncer_admin +stats_users = pgbouncer_stats + +# PID +pidfile = /var/run/postgresql/pgbouncer.pid +logfile = /var/log/postgresql/pgbouncer.log diff --git a/repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/userlist.txt.j2 b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/userlist.txt.j2 new file mode 100644 index 0000000..d34294e --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/pgbouncer/templates/userlist.txt.j2 @@ -0,0 +1,6 @@ +# userlist.txt — Managed by Ansible (INSIGHT-Infra) +# Format: "username" "md5hash_or_plaintext_password" +# Aus Vault: pgbouncer_users +{% for user in pgbouncer_users | default([]) %} +"{{ user.name }}" "{{ user.password }}" +{% endfor %} diff --git a/repos/INSIGHT-Infra/ansible/roles/postgresql/handlers/main.yml b/repos/INSIGHT-Infra/ansible/roles/postgresql/handlers/main.yml new file mode 100644 index 0000000..dbaeb1f --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/postgresql/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: restart postgresql + service: + name: "postgresql@{{ postgresql_version }}-main" + state: restarted + +- name: reload postgresql + service: + name: "postgresql@{{ postgresql_version }}-main" + state: reloaded diff --git a/repos/INSIGHT-Infra/ansible/roles/postgresql/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..2778f35 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/postgresql/tasks/main.yml @@ -0,0 +1,121 @@ +--- +# Role: postgresql +# Installiert und konfiguriert PostgreSQL 16 auf DBS01 + +- name: "PostgreSQL GPG Key hinzufügen" + apt_key: + url: https://www.postgresql.org/media/keys/ACCC4CF8.asc + state: present + +- name: "PostgreSQL Repository hinzufügen" + apt_repository: + repo: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main" + state: present + filename: pgdg + +- name: "PostgreSQL {{ postgresql_version }} installieren" + apt: + name: + - "postgresql-{{ postgresql_version }}" + - "postgresql-client-{{ postgresql_version }}" + - "postgresql-contrib-{{ postgresql_version }}" + - python3-psycopg2 + state: present + update_cache: true + +- name: "PostgreSQL Data-Verzeichnis anlegen" + file: + path: "{{ postgresql_data_dir }}" + state: directory + owner: postgres + group: postgres + mode: '0700' + when: postgresql_data_dir != '/var/lib/postgresql' + +- name: "PostgreSQL Service stoppen (vor Konfiguration)" + service: + name: "postgresql@{{ postgresql_version }}-main" + state: stopped + when: postgresql_data_dir != '/var/lib/postgresql' + +- name: "Prüfen ob PostgreSQL Cluster bereits initialisiert" + stat: + path: "{{ postgresql_data_dir }}/PG_VERSION" + register: pg_cluster_initialized + +- name: "PostgreSQL Cluster in Data-Dir initialisieren" + become_user: postgres + command: > + /usr/lib/postgresql/{{ postgresql_version }}/bin/initdb + -D {{ postgresql_data_dir }} + when: + - postgresql_data_dir != '/var/lib/postgresql' + - not pg_cluster_initialized.stat.exists + +- name: "postgresql.conf konfigurieren" + template: + src: postgresql.conf.j2 + dest: "/etc/postgresql/{{ postgresql_version }}/main/postgresql.conf" + owner: postgres + group: postgres + mode: '0644' + notify: restart postgresql + +- name: "pg_hba.conf konfigurieren" + template: + src: pg_hba.conf.j2 + dest: "/etc/postgresql/{{ postgresql_version }}/main/pg_hba.conf" + owner: postgres + group: postgres + mode: '0640' + notify: reload postgresql + +- name: "PostgreSQL Service aktivieren und starten" + service: + name: "postgresql@{{ postgresql_version }}-main" + state: started + enabled: true + +- name: "PostgreSQL INSIGHT Datenbanken anlegen" + become_user: postgres + postgresql_db: + name: "{{ item }}" + encoding: UTF8 + lc_collate: de_DE.UTF-8 + lc_ctype: de_DE.UTF-8 + template: template0 + state: present + loop: "{{ postgresql_databases }}" + when: postgresql_databases is defined + +- name: "PostgreSQL INSIGHT User anlegen" + become_user: postgres + postgresql_user: + name: "{{ item.name }}" + password: "{{ item.password }}" + role_attr_flags: "{{ item.role_attr_flags | default('LOGIN') }}" + state: present + loop: "{{ postgresql_users }}" + when: postgresql_users is defined + no_log: true + +- name: "PostgreSQL Berechtigungen setzen" + become_user: postgres + postgresql_privs: + db: "{{ item.db }}" + role: "{{ item.role }}" + privs: "{{ item.privs }}" + type: database + state: present + loop: "{{ postgresql_grants }}" + when: postgresql_grants is defined + +- name: "PostgreSQL Version prüfen" + become_user: postgres + command: psql --version + register: pg_version + changed_when: false + +- name: "PostgreSQL Version ausgeben" + debug: + msg: "{{ pg_version.stdout }}" diff --git a/repos/INSIGHT-Infra/ansible/roles/postgresql/templates/pg_hba.conf.j2 b/repos/INSIGHT-Infra/ansible/roles/postgresql/templates/pg_hba.conf.j2 new file mode 100644 index 0000000..0beff9d --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/postgresql/templates/pg_hba.conf.j2 @@ -0,0 +1,15 @@ +# pg_hba.conf — Managed by Ansible (INSIGHT-Infra) +# TYPE DATABASE USER ADDRESS METHOD + +# Local connections (Unix socket) +local all postgres peer +local all all md5 + +# IPv4 local +host all all 127.0.0.1/32 md5 + +# PgBouncer (gleicher Host) +host all pgbouncer 127.0.0.1/32 md5 + +# Replication (falls später benötigt) +# host replication replicator 127.0.0.1/32 md5 diff --git a/repos/INSIGHT-Infra/ansible/roles/postgresql/templates/postgresql.conf.j2 b/repos/INSIGHT-Infra/ansible/roles/postgresql/templates/postgresql.conf.j2 new file mode 100644 index 0000000..2cefb6b --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/postgresql/templates/postgresql.conf.j2 @@ -0,0 +1,42 @@ +# postgresql.conf — Managed by Ansible (INSIGHT-Infra) +# Für {{ inventory_hostname }} + +# Connection +listen_addresses = 'localhost' +port = 5432 +max_connections = {{ postgresql_max_connections | default(100) }} + +# Data Directory +data_directory = '{{ postgresql_data_dir }}' + +# Memory (anpassen je nach RAM — Standard: 25% shared_buffers) +shared_buffers = {{ postgresql_shared_buffers | default('256MB') }} +effective_cache_size = {{ postgresql_effective_cache_size | default('1GB') }} +work_mem = {{ postgresql_work_mem | default('4MB') }} +maintenance_work_mem = {{ postgresql_maintenance_work_mem | default('64MB') }} + +# WAL +wal_level = replica +max_wal_size = {{ postgresql_max_wal_size | default('1GB') }} +min_wal_size = {{ postgresql_min_wal_size | default('80MB') }} + +# Logging +log_destination = 'stderr' +logging_collector = on +log_directory = 'log' +log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' +log_rotation_age = 1d +log_rotation_size = 100MB +log_min_duration_statement = 1000 +log_connections = on +log_disconnections = on +log_line_prefix = '%m [%p] %q%u@%d ' + +# Locale +lc_messages = 'de_DE.UTF-8' +lc_monetary = 'de_DE.UTF-8' +lc_numeric = 'de_DE.UTF-8' +lc_time = 'de_DE.UTF-8' + +# Timezone +timezone = '{{ timezone | default("Europe/Berlin") }}' diff --git a/repos/INSIGHT-Infra/ansible/roles/redis/handlers/main.yml b/repos/INSIGHT-Infra/ansible/roles/redis/handlers/main.yml new file mode 100644 index 0000000..7d70e1b --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/redis/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart redis + service: + name: redis-server + state: restarted diff --git a/repos/INSIGHT-Infra/ansible/roles/redis/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/redis/tasks/main.yml new file mode 100644 index 0000000..a0a4539 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/redis/tasks/main.yml @@ -0,0 +1,55 @@ +--- +# Role: redis +# Installiert und konfiguriert Redis 7 auf DBS01 + +- name: "Redis GPG Key hinzufügen" + apt_key: + url: https://packages.redis.io/gpg + state: present + +- name: "Redis Repository hinzufügen" + apt_repository: + repo: "deb https://packages.redis.io/deb {{ ansible_distribution_release }} main" + state: present + filename: redis + +- name: "Redis installieren" + apt: + name: redis + state: present + update_cache: true + +- name: "Redis konfigurieren" + template: + src: redis.conf.j2 + dest: /etc/redis/redis.conf + owner: redis + group: redis + mode: '0640' + notify: restart redis + +- name: "Redis Daten-Verzeichnis anlegen" + file: + path: "{{ redis_data_dir | default('/data/redis') }}" + state: directory + owner: redis + group: redis + mode: '0750' + +- name: "Redis Service aktivieren und starten" + service: + name: redis-server + state: started + enabled: true + +- name: "Redis Konnektivität prüfen" + command: redis-cli ping + register: redis_ping + changed_when: false + retries: 3 + delay: 2 + until: redis_ping.stdout == "PONG" + +- name: "Redis Status ausgeben" + debug: + msg: "Redis antwortet: {{ redis_ping.stdout }}" diff --git a/repos/INSIGHT-Infra/ansible/roles/redis/templates/redis.conf.j2 b/repos/INSIGHT-Infra/ansible/roles/redis/templates/redis.conf.j2 new file mode 100644 index 0000000..27dc771 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/redis/templates/redis.conf.j2 @@ -0,0 +1,38 @@ +# redis.conf — Managed by Ansible (INSIGHT-Infra) +# Für {{ inventory_hostname }} + +# Binding — nur localhost (kein externer Zugriff) +bind 127.0.0.1 +port {{ redis_port | default(6379) }} +protected-mode yes + +# Passwort (aus Vault) +requirepass {{ redis_password }} + +# Persistenz +dir {{ redis_data_dir | default('/data/redis') }} +save 900 1 +save 300 10 +save 60 10000 +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec + +# Memory +maxmemory {{ redis_maxmemory | default('512mb') }} +maxmemory-policy {{ redis_maxmemory_policy | default('allkeys-lru') }} + +# Logging +loglevel notice +logfile /var/log/redis/redis-server.log + +# Datenbanken +databases 16 + +# Timeouts +timeout 300 +tcp-keepalive 300 + +# Slow Log +slowlog-log-slower-than 10000 +slowlog-max-len 128 diff --git a/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/handlers/main.yml b/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/handlers/main.yml new file mode 100644 index 0000000..719cfe1 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart zabbix-agent2 + service: + name: zabbix-agent2 + state: restarted diff --git a/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/tasks/main.yml b/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/tasks/main.yml new file mode 100644 index 0000000..f8f777d --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/tasks/main.yml @@ -0,0 +1,59 @@ +--- +# Role: zabbix_agent +# Installiert Zabbix Agent 2 und verbindet ihn mit sentinel.xinion.de + +- name: "Zabbix Repository hinzufügen" + apt: + deb: "https://repo.zabbix.com/zabbix/6.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_6.4-1+ubuntu{{ ansible_distribution_version }}_all.deb" + state: present + register: zabbix_repo_result + ignore_errors: true + +- name: "Zabbix Repository (Fallback — via URL)" + get_url: + url: "https://repo.zabbix.com/zabbix/6.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_6.4-1+ubuntu22.04_all.deb" + dest: /tmp/zabbix-release.deb + mode: '0644' + when: zabbix_repo_result is failed + +- name: "Zabbix Repository installieren (Fallback)" + apt: + deb: /tmp/zabbix-release.deb + when: zabbix_repo_result is failed + +- name: "Zabbix Agent 2 installieren" + apt: + name: zabbix-agent2 + state: present + update_cache: true + +- name: "Zabbix Agent 2 konfigurieren" + template: + src: zabbix_agent2.conf.j2 + dest: /etc/zabbix/zabbix_agent2.conf + owner: root + group: zabbix + mode: '0640' + notify: restart zabbix-agent2 + +- name: "Zabbix Agent 2 Service aktivieren und starten" + service: + name: zabbix-agent2 + state: started + enabled: true + +- name: "UFW — Zabbix Agent Port erlauben (vom Zabbix Server)" + ufw: + rule: allow + src: "{{ zabbix_server }}" + port: "10050" + proto: tcp + +- name: "Zabbix Agent Status prüfen" + command: zabbix_agent2 -V + register: zabbix_version + changed_when: false + +- name: "Zabbix Agent Version ausgeben" + debug: + msg: "{{ zabbix_version.stdout_lines[0] }}" diff --git a/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/templates/zabbix_agent2.conf.j2 b/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/templates/zabbix_agent2.conf.j2 new file mode 100644 index 0000000..a9f3c75 --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/roles/zabbix_agent/templates/zabbix_agent2.conf.j2 @@ -0,0 +1,27 @@ +# zabbix_agent2.conf — Managed by Ansible (INSIGHT-Infra) +# Für {{ inventory_hostname }} + +# Server (passive checks) +Server={{ zabbix_server }} +ServerActive={{ zabbix_server }} + +# Hostname (muss im Zabbix Frontend übereinstimmen) +Hostname={{ zabbix_hostname_prefix | default('INSIGHT-') }}{{ inventory_hostname | upper }} + +# Verbindung +ListenPort=10050 +ListenIP=0.0.0.0 + +# Timeouts +Timeout=30 + +# Logging +LogFile=/var/log/zabbix/zabbix_agent2.log +LogFileSize=10 +DebugLevel=3 + +# Plugins +Plugins.SystemRun.LogRemoteCommands=1 + +# Include (für zusätzliche Checks) +Include=/etc/zabbix/zabbix_agent2.d/*.conf diff --git a/repos/INSIGHT-Infra/ansible/vault.yml.example b/repos/INSIGHT-Infra/ansible/vault.yml.example new file mode 100644 index 0000000..ccea95c --- /dev/null +++ b/repos/INSIGHT-Infra/ansible/vault.yml.example @@ -0,0 +1,36 @@ +--- +# vault.yml.example — VORLAGE für ansible/vault.yml +# NIEMALS echte Passwörter committen! +# vault.yml mit Ansible Vault verschlüsseln: +# ansible-vault create ansible/vault.yml +# ansible-vault edit ansible/vault.yml + +# PostgreSQL Passwörter +postgresql_users: + - name: insight_app + password: "CHANGE_ME_STRONG_PASSWORD" + role_attr_flags: "LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE" + - name: pgbouncer + password: "CHANGE_ME_PGBOUNCER_PASSWORD" + role_attr_flags: "LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE" + +postgresql_grants: + - db: insight_core + role: insight_app + privs: "ALL" + - db: insight_crm + role: insight_app + privs: "ALL" + +# PgBouncer Auth +pgbouncer_users: + - name: insight_app + password: "CHANGE_ME_STRONG_PASSWORD" + - name: pgbouncer + password: "CHANGE_ME_PGBOUNCER_PASSWORD" + +# Redis +redis_password: "CHANGE_ME_REDIS_PASSWORD" + +# Zabbix (falls PSK verwendet) +# zabbix_psk_key: "CHANGE_ME_PSK" diff --git a/repos/INSIGHT-Platform/BRIEFING.md b/repos/INSIGHT-Platform/BRIEFING.md new file mode 100644 index 0000000..d009452 --- /dev/null +++ b/repos/INSIGHT-Platform/BRIEFING.md @@ -0,0 +1,74 @@ +# INSIGHT-Platform — Developer Briefing + +> Dieses Dokument ist der Einstiegspunkt für jede neue Claude Code Session in diesem Repo. +> Lies es vollständig bevor du Code schreibst. + +## Was ist dieses Repo? + +Die **INSIGHT-Plattform** ist das Fundament aller INSIGHT-Dienste. +Sie stellt Authentifizierung, User-Verwaltung, Berechtigungssystem und die React-Shell bereit, +in die sich fachliche Apps (aus `INSIGHT-Apps`) einbinden. + +## Struktur + +``` +packages/ +├── core-service/ # NestJS: Auth, JWT, User, Settings, Expertenprofil, RBAC +└── frontend/ # React 18 + Vite: Shell, Login, Navigation, Admin-Bereich +``` + +## Abhängigkeiten zu anderen Repos + +| Repo | Beziehung | +|------|-----------| +| `INSIGHT-Shared` | Lesen: module-interface.md, api-platform.md — Schreiben: api-platform.md nach Änderungen | +| `INSIGHT-Apps` | Apps registrieren sich via InsightModule-Contract in der Shell | +| `INSIGHT-Infra` | Stellt DBS01 (PostgreSQL + Redis) und APS01 (Docker) bereit | + +## Confluence Dokumentation + +- **Phase 2 — Plattform:** https://xinion.atlassian.net/wiki/x/oYKSEw +- **Berechtigungskonzept:** https://xinion.atlassian.net/wiki/x/FQCVEw +- **Projekt-Übersicht:** https://xinion.atlassian.net/wiki/spaces/ProjektINS/overview + +## Tech Stack + +- **Backend:** NestJS 10, TypeScript strict, Prisma ORM +- **Frontend:** React 18, Vite, TanStack Query, React Router +- **Datenbank:** PostgreSQL 16 (auf DBS01, via PgBouncer) +- **Cache:** Redis 7 (auf DBS01) +- **Auth:** JWT RS256, Refresh Token (HttpOnly Cookie), TOTP 2FA + +## Sicherheitsregeln (nicht verhandelbar) + +- Kein `any` in TypeScript — strict mode immer +- Kein `localStorage` für Tokens (Access: Memory, Refresh: HttpOnly Cookie) +- Globaler `ValidationPipe` (whitelist + forbidNonWhitelisted) +- `@Public()` explizit für öffentliche Routen +- Rollen immer via `@Roles()` + `RolesGuard` auf jedem Endpoint +- Kein raw SQL — ausschließlich Prisma + +## RBAC-Modell + +``` +User +├── platformRole: PLATFORM_ADMIN | USER +└── moduleRoles: { crm: ADMIN | MANAGER | VIEWER, ... } +``` + +Platform Admin: User-Verwaltung, System-Einstellungen, Modul-Zuweisung +Module Admin: Berechtigungen innerhalb seines Moduls + +## Deploy Key (dieses Repo) + +``` +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2deXkhf9Ut9728mvxwl+MyFtbjoPRXNbTR2XjmDJVs deploy@INSIGHT-Platform +``` + +Privater Key: `.keys/deploy_platform_ed25519` + +## Aktueller Status + +- **Phase:** 2 — Plattform +- **Stand:** Konzeption (Berechtigungskonzept in Klärung) +- **Nächster Schritt:** Berechtigungskonzept finalisieren → Prisma Schema → Auth-Service diff --git a/repos/INSIGHT-Shared/BRIEFING.md b/repos/INSIGHT-Shared/BRIEFING.md new file mode 100644 index 0000000..f79097b --- /dev/null +++ b/repos/INSIGHT-Shared/BRIEFING.md @@ -0,0 +1,59 @@ +# INSIGHT-Shared — Developer Briefing + +> Dieses Dokument ist der Einstiegspunkt für jede neue Claude Code Session in diesem Repo. +> Lies es vollständig bevor du Code schreibst. + +## Was ist dieses Repo? + +**INSIGHT-Shared** ist das zentrale Wissens- und Vertrags-Repository der INSIGHT-Plattform. +**Kein ausführbarer Code** — ausschließlich Dokumentation, API-Verträge und Architektur-Entscheidungen. + +Ziel: Jede Claude Code Session in jedem Repo kann dieses Repo lesen und weiß sofort: +- Was andere Repos liefern +- Welche Interfaces einzuhalten sind +- Was der aktuelle Entwicklungsstand ist + +## Struktur & Zuständigkeiten + +``` +contracts/ +├── module-interface.md # InsightModule-Contract (Platform ↔ Apps) — PFLICHTLEKTÜRE für Apps +├── api-platform.md # REST-API von INSIGHT-Platform Core-Service +└── api-crm.md # REST-API von INSIGHT-Apps CRM-Service + +architecture/ +├── decisions/ # ADRs — Architektur-Entscheidungen mit Begründung +│ ├── 001-plugin-system.md +│ ├── 002-rbac-model.md +│ └── 003-repo-struktur.md +└── overview.md # Gesamtarchitektur + +types/ +└── shared-types.md # TypeScript Interfaces die alle Repos kennen müssen + +dev-status/ +├── platform.md # Was ist fertig / läuft / blockiert in INSIGHT-Platform +├── apps.md # Status INSIGHT-Apps +└── infra.md # Status INSIGHT-Infra +``` + +## Wer liest/schreibt was + +| Repo | Liest | Schreibt | +|------|-------|----------| +| INSIGHT-Platform | module-interface.md, dev-status/* | api-platform.md, dev-status/platform.md | +| INSIGHT-Apps | api-platform.md, module-interface.md | api-crm.md, dev-status/apps.md | +| INSIGHT-Infra | dev-status/* | dev-status/infra.md | + +## Confluence Dokumentation + +- **Projekt-Übersicht:** https://xinion.atlassian.net/wiki/spaces/ProjektINS/overview +- **Konzeptdokument:** https://xinion.atlassian.net/wiki/x/AQCVEw + +## Deploy Key (dieses Repo) + +``` +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAZp7rLiyNmEm3439yjChUC6TQbP8O7xdfGQyr37MNjh deploy@INSIGHT-Shared +``` + +Privater Key: `.keys/deploy_shared_ed25519` diff --git a/repos/INSIGHT-Shared/contracts/module-interface.md b/repos/INSIGHT-Shared/contracts/module-interface.md new file mode 100644 index 0000000..c5d40f3 --- /dev/null +++ b/repos/INSIGHT-Shared/contracts/module-interface.md @@ -0,0 +1,84 @@ +# InsightModule — Contract + +> Version: 1.0 (Entwurf) +> Dieser Contract definiert wie sich ein App-Modul in die INSIGHT-Platform-Shell einbindet. + +## TypeScript Interface + +```typescript +export interface InsightRoute { + path: string + component: React.ComponentType + exact?: boolean +} + +export interface InsightNavItem { + label: string + path: string + icon?: string + children?: InsightNavItem[] +} + +export interface InsightModule { + /** Eindeutige ID des Moduls (z.B. 'crm', 'hr') */ + id: string + /** Anzeigename in der Navigation */ + name: string + /** Icon-Name (aus Icon-Bibliothek der Platform) */ + icon?: string + /** Welche moduleRole wird benötigt? (aus JWT moduleRoles) */ + requiredRole: string + /** React-Routen die registriert werden */ + routes: InsightRoute[] + /** Navigationspunkte in der Sidebar */ + navigation: InsightNavItem[] + /** Optionaler Dashboard-Widget für die Startseite */ + dashboardWidget?: React.ComponentType +} +``` + +## Implementierung (Beispiel CRM) + +```typescript +// INSIGHT-Apps/packages/crm-frontend/src/index.ts +import { CrmDashboard } from './pages/CrmDashboard' +import { DealsPage } from './pages/DealsPage' +import { ContactsPage } from './pages/ContactsPage' + +export const CrmModule: InsightModule = { + id: 'crm', + name: 'CRM', + icon: 'briefcase', + requiredRole: 'crm', + routes: [ + { path: '/crm', component: CrmDashboard, exact: true }, + { path: '/crm/deals', component: DealsPage }, + { path: '/crm/contacts', component: ContactsPage }, + ], + navigation: [ + { label: 'Dashboard', path: '/crm', icon: 'home' }, + { label: 'Deals', path: '/crm/deals', icon: 'dollar' }, + { label: 'Kontakte', path: '/crm/contacts', icon: 'users' }, + ], +} +``` + +## Registrierung in der Platform-Shell + +```typescript +// INSIGHT-Platform/packages/frontend/src/shell/module-registry.ts +import { CrmModule } from '@insight/crm-frontend' + +export const registeredModules: InsightModule[] = [ + CrmModule, + // weitere Module hier eintragen +] +``` + +## Regeln + +- `id` muss eindeutig und lowercase sein +- `requiredRole` muss einer gültigen moduleRole entsprechen (definiert in INSIGHT-Platform) +- Jede Route muss innerhalb des Modul-Pfads liegen (z.B. `/crm/*`) +- Komponenten dürfen den Platform-Auth-Context nutzen (`useAuth()` Hook) +- Kein direkter Datenbankzugriff aus Frontend-Komponenten diff --git a/repos/INSIGHT-Shared/dev-status/apps.md b/repos/INSIGHT-Shared/dev-status/apps.md new file mode 100644 index 0000000..2b3cb5c --- /dev/null +++ b/repos/INSIGHT-Shared/dev-status/apps.md @@ -0,0 +1,24 @@ +# Dev-Status: INSIGHT-Apps + +_Zuletzt aktualisiert: 15.03.2026_ + +## Status: 🔴 Konzeption + +## Fertig +- (noch nichts — Neubeginn) + +## In Arbeit +- CRM-Datenmodell (Entitäten, Felder, Beziehungen) +- CRM-Berechtigungskonzept + +## Ausstehend +- Prisma Schema CRM +- Company / Contact / Deal / Activity API +- CRM Frontend (Kanban, Listen, Detail) + +## Blockiert durch +- Phase 2 (Platform) muss abgeschlossen sein (Auth + RBAC) +- CRM-Datenmodell muss finalisiert werden + +## Offene Fragen an andere Teams +- INSIGHT-Platform: Welchen Auth-Context stellt die Shell zur Verfügung? (`useAuth()` Hook API?) diff --git a/repos/INSIGHT-Shared/dev-status/infra.md b/repos/INSIGHT-Shared/dev-status/infra.md new file mode 100644 index 0000000..b38bad2 --- /dev/null +++ b/repos/INSIGHT-Shared/dev-status/infra.md @@ -0,0 +1,32 @@ +# Dev-Status: INSIGHT-Infra + +_Zuletzt aktualisiert: 15.03.2026_ + +## Status: 🟡 In Arbeit + +## Fertig +- VM INSIGHT-DBS01 (Proxmox, VM 1020) angelegt +- SSH-Zugang via ansible-Key funktioniert +- Alle SSH + Deploy Keys generiert +- Confluence-Dokumentation vollständig + +## In Arbeit +- APS01 + WEB01 VMs anlegen +- IP-Adressen vergeben + +## Ausstehend +- Ansible Inventory befüllen (IPs) +- Ansible Role: common +- Ansible Role: docker +- Ansible Role: postgresql + pgbouncer +- Ansible Role: redis +- Ansible Role: nginx +- Ansible Role: zabbix_agent +- Forgejo Repos anlegen + Deploy Keys eintragen + +## Blockiert durch +- IP-Adressen ausstehend (APS01, WEB01) +- Ansible-Server Zugang konfigurieren + +## Offene Fragen +- Ansible-Server: IP/Hostname, Version, SSH-Zugang? diff --git a/repos/INSIGHT-Shared/dev-status/platform.md b/repos/INSIGHT-Shared/dev-status/platform.md new file mode 100644 index 0000000..fc82237 --- /dev/null +++ b/repos/INSIGHT-Shared/dev-status/platform.md @@ -0,0 +1,25 @@ +# Dev-Status: INSIGHT-Platform + +_Zuletzt aktualisiert: 15.03.2026_ + +## Status: 🔴 Konzeption + +## Fertig +- (noch nichts — Neubeginn) + +## In Arbeit +- Berechtigungskonzept (Platform- vs. Modul-Rollen) +- Repo-Struktur definiert + +## Ausstehend +- Prisma Schema (User, Roles, Settings) +- NestJS Core-Service (Auth, JWT, User) +- React Shell (Login, Navigation, Module-Registry) +- Admin-Bereich + +## Blockiert durch +- Berechtigungskonzept muss finalisiert werden +- Phase 1 (Infra) muss abgeschlossen sein + +## Offene Fragen an andere Teams +- INSIGHT-Apps: Welche Felder braucht das User-Profil für CRM-Kontext?