feat(infra): vollständige Ansible-Struktur Phase 1

- 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 <noreply@anthropic.com>
This commit is contained in:
Thomas Reitz 2026-03-15 15:23:29 +01:00
parent 0c8a23ddc4
commit 36196457ea
43 changed files with 1570 additions and 0 deletions

3
.gitignore vendored
View file

@ -50,6 +50,9 @@ tmp/
temp/ temp/
*.tmp *.tmp
# SSH Keys & Deploy Keys (NIEMALS committen!)
.keys/
# Certificates (generierte Zertifikate, nicht die CA-Config) # Certificates (generierte Zertifikate, nicht die CA-Config)
config/step-ca/secrets/ config/step-ca/secrets/
config/step-ca/db/ config/step-ca/db/

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,10 @@
---
- name: reload nginx
service:
name: nginx
state: reloaded
- name: restart nginx
service:
name: nginx
state: restarted

View file

@ -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 }}"

View file

@ -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;
}
}

View file

@ -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/*;
}

View file

@ -0,0 +1,5 @@
---
- name: restart pgbouncer
service:
name: pgbouncer
state: restarted

View file

@ -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 }}"

View file

@ -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

View file

@ -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 %}

View file

@ -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

View file

@ -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 }}"

View file

@ -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

View file

@ -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") }}'

View file

@ -0,0 +1,5 @@
---
- name: restart redis
service:
name: redis-server
state: restarted

View file

@ -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 }}"

View file

@ -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

View file

@ -0,0 +1,5 @@
---
- name: restart zabbix-agent2
service:
name: zabbix-agent2
state: restarted

View file

@ -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] }}"

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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`

View file

@ -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

View file

@ -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?)

View file

@ -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?

View file

@ -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?