feat: Enhance security and validation in backend
- Added helmet for security headers and configured content security policy - Implemented CORS with a whitelist for allowed origins - Introduced express-validator for input validation in API endpoints - Set request size limits to prevent DoS attacks - Added global error handling and 404 response - Updated TypeScript configuration to use node16 module resolution - Improved Docker Compose configuration for security and resource limits - Created a comprehensive .env.example for environment configuration - Implemented automated security scans in CI/CD with Trivy - Added cleanup script for debugging ports - Established a detailed security policy document
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
FROM node:18
|
||||
|
||||
# Install git and other useful tools
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
curl \
|
||||
vim \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /workspace
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g typescript ts-node nodemon
|
||||
|
||||
# Create user with same UID as host (for file permissions)
|
||||
ARG USERNAME=node
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
RUN groupmod --gid $USER_GID $USERNAME \
|
||||
&& usermod --uid $USER_UID --gid $USER_GID $USERNAME \
|
||||
&& chown -R $USER_UID:$USER_GID /home/$USERNAME
|
||||
|
||||
USER $USERNAME
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "Fotodrucker Full Stack",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"workspaceFolder": "/workspace",
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-vscode.vscode-typescript-next",
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-vscode.vscode-json",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"ms-vscode.vscode-eslint",
|
||||
"ms-vscode-remote.remote-containers"
|
||||
],
|
||||
"settings": {
|
||||
"typescript.preferences.includePackageJsonAutoImports": "auto",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "cd backend && npm install && cd ../frontend && npm install",
|
||||
"forwardPorts": [3000, 3001, 9229, 9230],
|
||||
"portsAttributes": {
|
||||
"3000": {
|
||||
"label": "Frontend (React)",
|
||||
"onAutoForward": "openBrowser"
|
||||
},
|
||||
"3001": {
|
||||
"label": "Backend (Express)"
|
||||
},
|
||||
"9229": {
|
||||
"label": "Backend Debug Port"
|
||||
},
|
||||
"9230": {
|
||||
"label": "Frontend Debug Port"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3001:3001"
|
||||
- "9229:9229"
|
||||
- "9230:9230"
|
||||
command: sleep infinity
|
||||
working_dir: /workspace
|
||||
118
.env.example
Normal file
118
.env.example
Normal file
@@ -0,0 +1,118 @@
|
||||
# =============================================================================
|
||||
# Full Stack TypeScript Template - Environment Configuration
|
||||
# =============================================================================
|
||||
#
|
||||
# SICHERHEITSHINWEISE:
|
||||
# 1. Kopiere diese Datei zu '.env' und fülle die Werte aus
|
||||
# 2. NIEMALS .env in Git committen! (bereits in .gitignore)
|
||||
# 3. Verwende STARKE, ZUFÄLLIGE Passwörter und Secrets!
|
||||
# 4. Unterschiedliche Werte für Development, Staging und Production!
|
||||
#
|
||||
# Generiere sichere Secrets mit:
|
||||
# openssl rand -base64 32
|
||||
# oder: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Application Settings
|
||||
# -----------------------------------------------------------------------------
|
||||
NODE_ENV=production
|
||||
APP_PORT=8080
|
||||
|
||||
# Docker Container Namen (optional)
|
||||
APP_CONTAINER_NAME=fullstack-app
|
||||
POSTGRES_CONTAINER_NAME=postgres-db
|
||||
|
||||
# Docker Image (für CI/CD)
|
||||
APP_IMAGE=fullstack-app:latest
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Database Configuration (OPTIONAL)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Dieses Template kann mit verschiedenen Datenbanken verwendet werden:
|
||||
# - SQLite (Standard für einfache Projekte, keine Konfiguration nötig)
|
||||
# - PostgreSQL (für Production, siehe unten)
|
||||
# - MySQL, MongoDB, etc. (eigene Konfiguration)
|
||||
#
|
||||
# Für SQLite: Keine weiteren Einstellungen nötig!
|
||||
# Die Datenbank wird automatisch im /data Volume erstellt.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# === PostgreSQL Configuration (Optional) ===
|
||||
# Nur notwendig wenn PostgreSQL statt SQLite verwendet wird.
|
||||
# Aktivierung:
|
||||
# 1. Entkommentiere den postgres-Service in docker-compose.yml
|
||||
# 2. Setze die Werte unten
|
||||
# 3. Aktiviere DATABASE_URL
|
||||
|
||||
# POSTGRES_DB=appdb
|
||||
# POSTGRES_USER=postgres
|
||||
|
||||
# ⚠️ KRITISCH: MUSS geändert werden! Niemals Default-Passwort verwenden!
|
||||
# Beispiel für sicheres Passwort generieren:
|
||||
# openssl rand -base64 32
|
||||
# POSTGRES_PASSWORD=
|
||||
|
||||
# Optional: Für lokales Debugging (default: Port nicht exponiert)
|
||||
# POSTGRES_PORT=5432
|
||||
|
||||
# Vollständige Database URL (nur für PostgreSQL)
|
||||
# Format: postgresql://USER:PASSWORD@HOST:PORT/DATABASE
|
||||
# DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@postgres:5432/appdb
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Security & Authentication
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# ⚠️ KRITISCH: JWT Secret für Token-Signierung
|
||||
# MUSS ein starker, zufälliger String sein (min. 32 Zeichen)
|
||||
# Beispiel generieren:
|
||||
# openssl rand -base64 32
|
||||
JWT_SECRET=
|
||||
|
||||
# Optional: JWT Token Gültigkeit
|
||||
JWT_EXPIRY=7d
|
||||
|
||||
# Optional: Session Secret (falls Sessions verwendet werden)
|
||||
# SESSION_SECRET=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Application Secrets & API Keys
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Füge hier weitere Secrets hinzu, z.B.:
|
||||
# STRIPE_API_KEY=
|
||||
# SENDGRID_API_KEY=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# REDIS_URL=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Development Settings (nur für lokale Entwicklung)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Für Development-Modus setze NODE_ENV=development
|
||||
# NODE_ENV=development
|
||||
# APP_PORT=3000
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Beispiel für verschiedene Umgebungen:
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# === DEVELOPMENT (mit SQLite) ===
|
||||
# NODE_ENV=development
|
||||
# APP_PORT=3000
|
||||
# JWT_SECRET=dev_jwt_secret_12345 # NUR für lokales Development!
|
||||
# # Keine DB-Config nötig für SQLite!
|
||||
|
||||
# === STAGING (mit PostgreSQL) ===
|
||||
# NODE_ENV=staging
|
||||
# APP_PORT=8080
|
||||
# JWT_SECRET=[SICHERES_RANDOM_SECRET]
|
||||
# DATABASE_URL=postgresql://postgres:[PASSWORD]@postgres:5432/appdb
|
||||
|
||||
# === PRODUCTION (mit PostgreSQL) ===
|
||||
# NODE_ENV=production
|
||||
# APP_PORT=8080
|
||||
# JWT_SECRET=[SEHR_SICHERES_RANDOM_SECRET]
|
||||
# DATABASE_URL=postgresql://postgres:[PASSWORD]@postgres:5432/appdb
|
||||
301
.gittea/workflows/build.yaml
Normal file
301
.gittea/workflows/build.yaml
Normal file
@@ -0,0 +1,301 @@
|
||||
name: 🚀 Docker Image Build & Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- alpha
|
||||
- Nachweise
|
||||
workflow_dispatch: # Manueller Trigger
|
||||
schedule:
|
||||
# Täglicher Security-Scan um 6:00 UTC
|
||||
- cron: '0 6 * * *'
|
||||
|
||||
env:
|
||||
# ⚠️ ANPASSEN: Nur die Registry-URL anpassen
|
||||
REGISTRY: git.csnetworkx.dev
|
||||
# Image-Name wird automatisch aus Repository generiert: REGISTRY/owner/repo
|
||||
# Beispiel: git.csnetworkx.dev/user/project
|
||||
TRIVY_VERSION: 0.58.1
|
||||
|
||||
jobs:
|
||||
# 🔍 Security-Scan des Quellcodes und der Abhängigkeiten
|
||||
security-scan:
|
||||
name: 🔍 Sicherheitsüberprüfung
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 📥 Repository auschecken
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🔧 Trivy installieren
|
||||
run: |
|
||||
echo "📦 Installiere Trivy Security Scanner..."
|
||||
# Moderne Installation ohne deprecated apt-key
|
||||
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy-archive-keyring.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/trivy-archive-keyring.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y trivy
|
||||
trivy --version
|
||||
|
||||
- name: 🔍 Quellcode & Abhängigkeiten scannen
|
||||
run: |
|
||||
echo "🔍 Scanne Quellcode und npm-Abhängigkeiten..."
|
||||
trivy fs --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed .
|
||||
|
||||
- name: 🔍 Dockerfile & Konfiguration scannen
|
||||
run: |
|
||||
echo "🔍 Scanne Dockerfile und IaC-Konfigurationen..."
|
||||
# Erzwinge auch kleingeschriebene Dockerfiles (z.B. ./dockerfile)
|
||||
trivy config --severity CRITICAL,HIGH --exit-code 1 \
|
||||
--file-patterns "dockerfile:.*[Dd]ockerfile.*" .
|
||||
|
||||
# 🔄 Täglicher Scan und automatische Fixes (nur bei scheduled runs)
|
||||
daily-security-scan:
|
||||
name: 🔄 Täglicher Security-Scan & Auto-Fix
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'schedule'
|
||||
steps:
|
||||
- name: 📥 Repository auschecken (main Branch)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: 🏷️ Image-Namen generieren
|
||||
id: image_name
|
||||
run: |
|
||||
# Generiere Image-Name: REGISTRY/repository-lowercase
|
||||
# Gitea: nutze GITHUB_REPOSITORY oder GITEA_REPOSITORY
|
||||
REPO="${GITHUB_REPOSITORY:-$GITEA_REPOSITORY}"
|
||||
IMAGE="${{ env.REGISTRY }}/${REPO}"
|
||||
IMAGE_LOWER=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')
|
||||
echo "image=${IMAGE_LOWER}" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Image-Name: ${IMAGE_LOWER}"
|
||||
echo "📦 Repository: ${REPO}"
|
||||
|
||||
- name: 🟢 Node.js einrichten
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: 🔧 Trivy installieren
|
||||
run: |
|
||||
echo "📦 Installiere Trivy Security Scanner..."
|
||||
# Moderne Installation ohne deprecated apt-key
|
||||
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy-archive-keyring.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/trivy-archive-keyring.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y trivy
|
||||
trivy --version
|
||||
|
||||
- name: 🔐 In Docker Registry einloggen
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: 🔍 Docker Image scannen (latest)
|
||||
id: image_scan
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "🐳 Scanne Docker Image: ${{ steps.image_name.outputs.image }}:latest"
|
||||
trivy image --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed ${{ steps.image_name.outputs.image }}:latest || echo "scan_failed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 🔍 Dateisystem scannen
|
||||
id: fs_scan
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "📁 Scanne Dateisystem und Abhängigkeiten..."
|
||||
trivy fs --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed . || echo "scan_failed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 🔧 npm audit fix ausführen (Frontend)
|
||||
if: steps.fs_scan.outcome == 'failure'
|
||||
working-directory: ./frontend
|
||||
run: |
|
||||
echo "🔧 Führe npm audit fix aus (Frontend)..."
|
||||
npm audit fix --force || true
|
||||
npm update || true
|
||||
|
||||
- name: 🔧 npm audit fix ausführen (Backend)
|
||||
if: steps.fs_scan.outcome == 'failure'
|
||||
working-directory: ./backend
|
||||
run: |
|
||||
echo "🔧 Führe npm audit fix aus (Backend)..."
|
||||
npm audit fix --force || true
|
||||
npm update || true
|
||||
|
||||
- name: 🔎 Änderungen prüfen
|
||||
id: changes
|
||||
run: |
|
||||
if git diff --quiet; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Keine Änderungen durch npm audit fix"
|
||||
else
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
echo "📝 Änderungen wurden vorgenommen"
|
||||
fi
|
||||
|
||||
- name: 💾 Änderungen committen & pushen
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "💾 Committe automatische Security-Fixes..."
|
||||
git config user.name "Security Bot"
|
||||
git config user.email "security-bot@csnetworkx.dev"
|
||||
git add -A
|
||||
git commit -m "🔒 Security: Automatische Dependency-Updates [skip ci]
|
||||
|
||||
- npm audit fix ausgeführt
|
||||
- Schwachstellen in Abhängigkeiten behoben
|
||||
|
||||
🤖 Automatisch generiert durch den täglichen Security-Scan
|
||||
|
||||
Der Build wird beim nächsten manuellen Push ausgeführt."
|
||||
git push
|
||||
|
||||
- name: 🔨 Docker Buildx einrichten
|
||||
if: steps.image_scan.outcome == 'failure' || steps.changes.outputs.has_changes == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 🏗️ Docker Image neu bauen & pushen
|
||||
if: steps.image_scan.outcome == 'failure' || steps.changes.outputs.has_changes == 'true'
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
pull: true # Zieht immer das neueste Base-Image
|
||||
build-args: |
|
||||
VERSION_TAG=latest
|
||||
VERSION_SHA=${{ github.sha }}
|
||||
tags: ${{ steps.image_name.outputs.image }}:latest
|
||||
|
||||
- name: 📊 Security-Report erstellen
|
||||
if: always()
|
||||
run: |
|
||||
echo "╔════════════════════════════════════════╗"
|
||||
echo "║ 🔒 SECURITY SCAN REPORT 🔒 ║"
|
||||
echo "╚════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "📋 Scan-Ergebnisse:"
|
||||
echo " • Docker Image: ${{ steps.image_scan.outcome }}"
|
||||
echo " • Dateisystem: ${{ steps.fs_scan.outcome }}"
|
||||
echo " • Änderungen: ${{ steps.changes.outputs.has_changes }}"
|
||||
echo ""
|
||||
if [[ "${{ steps.image_scan.outcome }}" == "failure" ]] || [[ "${{ steps.fs_scan.outcome }}" == "failure" ]]; then
|
||||
echo "⚠️ SCHWACHSTELLEN GEFUNDEN!"
|
||||
if [[ "${{ steps.changes.outputs.has_changes }}" == "true" ]]; then
|
||||
echo "✅ Automatische Fixes wurden angewendet und committed."
|
||||
else
|
||||
echo "❌ Keine automatischen Fixes möglich - manuelle Überprüfung erforderlich!"
|
||||
fi
|
||||
else
|
||||
echo "✅ Keine kritischen Schwachstellen gefunden."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 🐳 Docker Image bauen und pushen
|
||||
docker-build-and-push:
|
||||
name: 🐳 Docker Image Build & Push
|
||||
runs-on: ubuntu-latest
|
||||
needs: security-scan
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.security-scan.result == 'success'
|
||||
steps:
|
||||
- name: 📥 Repository auschecken
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🏷️ Image-Namen & Tags berechnen
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
# Generiere Image-Name aus Repository (lowercase)
|
||||
# Gitea: nutze GITHUB_REPOSITORY oder GITEA_REPOSITORY
|
||||
REPO="${GITHUB_REPOSITORY:-$GITEA_REPOSITORY}"
|
||||
IMAGE="${{ env.REGISTRY }}/${REPO}"
|
||||
IMAGE_LOWER=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')
|
||||
echo "image_name=${IMAGE_LOWER}" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Image-Name: ${IMAGE_LOWER}"
|
||||
echo "📦 Repository: ${REPO}"
|
||||
|
||||
# Berechne Branch-Tag (Gitea kompatibel)
|
||||
BRANCH="${GITHUB_REF_NAME:-$GITEA_REF_NAME}"
|
||||
echo "🏷️ Berechne Image-Tags für Branch: ${BRANCH}"
|
||||
case "${BRANCH}" in
|
||||
main) echo "branch_tag=latest" >> "$GITHUB_OUTPUT" ;;
|
||||
beta) echo "branch_tag=beta" >> "$GITHUB_OUTPUT" ;;
|
||||
alpha) echo "branch_tag=alpha" >> "$GITHUB_OUTPUT" ;;
|
||||
Nachweise) echo "branch_tag=Nachweise" >> "$GITHUB_OUTPUT" ;;
|
||||
*) echo "branch_tag=${BRANCH}" >> "$GITHUB_OUTPUT" ;;
|
||||
esac
|
||||
|
||||
# SHA (Gitea kompatibel)
|
||||
SHA="${GITHUB_SHA:-$GITEA_SHA}"
|
||||
echo "short_sha=${SHA::8}" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Branch-Tag: $(cat $GITHUB_OUTPUT | grep branch_tag | cut -d'=' -f2)"
|
||||
echo "✅ SHA-Tag: ${SHA::8}"
|
||||
|
||||
- name: 🖥️ QEMU einrichten (Multi-Arch Support)
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: 🔨 Docker Buildx einrichten
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 🔌 Registry-Verbindung testen
|
||||
run: |
|
||||
echo "🔌 Teste Verbindung zur Registry: ${{ env.REGISTRY }}"
|
||||
echo ""
|
||||
echo "1️⃣ HTTP-Verbindungstest..."
|
||||
curl -v --connect-timeout 10 https://${{ env.REGISTRY }}/v2/ || echo "⚠️ Curl fehlgeschlagen"
|
||||
echo ""
|
||||
echo "2️⃣ DNS-Auflösung..."
|
||||
nslookup ${{ env.REGISTRY }} || echo "⚠️ DNS-Lookup fehlgeschlagen"
|
||||
echo ""
|
||||
echo "3️⃣ Netzwerk-Ping..."
|
||||
ping -c 3 ${{ env.REGISTRY }} || echo "⚠️ Ping fehlgeschlagen"
|
||||
|
||||
- name: 🔐 Manueller Login-Test
|
||||
run: |
|
||||
echo "🔐 Teste manuellen Docker-Login..."
|
||||
echo ${{ secrets.DOCKER_PASSWORD }} | docker login --username ${{ secrets.DOCKER_USERNAME }} --password-stdin ${{ env.REGISTRY }}
|
||||
echo "✅ Login erfolgreich!"
|
||||
|
||||
- name: 🔐 In Docker Registry einloggen
|
||||
uses: docker/login-action@v3
|
||||
env:
|
||||
DOCKER_CLI_DEBUG: 1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: 🏗️ Docker Image bauen & pushen
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
VERSION_TAG=${{ steps.vars.outputs.branch_tag }}
|
||||
VERSION_SHA=${{ steps.vars.outputs.short_sha }}
|
||||
tags: |
|
||||
${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.branch_tag }}
|
||||
${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.short_sha }}
|
||||
|
||||
- name: ✅ Build-Zusammenfassung
|
||||
if: success()
|
||||
run: |
|
||||
echo "╔════════════════════════════════════════╗"
|
||||
echo "║ ✅ BUILD ERFOLGREICH ✅ ║"
|
||||
echo "╚════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "📦 Gebaute Images:"
|
||||
echo " • ${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.branch_tag }}"
|
||||
echo " • ${{ steps.vars.outputs.image_name }}:${{ steps.vars.outputs.short_sha }}"
|
||||
echo ""
|
||||
echo "🎯 Plattformen: linux/amd64, linux/arm64"
|
||||
echo "📤 Erfolgreich in Registry gepusht!"
|
||||
echo ""
|
||||
echo "🏷️ Image-Name: ${{ steps.vars.outputs.image_name }}"
|
||||
echo "📍 Registry: ${{ env.REGISTRY }}"
|
||||
echo "🔗 Repository: ${GITHUB_REPOSITORY:-$GITEA_REPOSITORY}"
|
||||
echo ""
|
||||
|
||||
59
.vscode/cleanup-ports.sh
vendored
Normal file
59
.vscode/cleanup-ports.sh
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Debug Port Cleanup Script
|
||||
# Stellt sicher, dass die Debug-Ports frei sind und beendet laufende Prozesse
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PORTS=("3000" "3001" "9229" "5173" "9222")
|
||||
|
||||
echo "🧹 Prüfe und bereinige Debug-Ports (${PORTS[*]})..."
|
||||
|
||||
have_lsof=false
|
||||
have_fuser=false
|
||||
have_ss=false
|
||||
|
||||
command -v lsof >/dev/null 2>&1 && have_lsof=true
|
||||
command -v fuser >/dev/null 2>&1 && have_fuser=true
|
||||
command -v ss >/dev/null 2>&1 && have_ss=true
|
||||
|
||||
if [ "$have_lsof" = false ] && [ "$have_fuser" = false ] && [ "$have_ss" = false ]; then
|
||||
echo "⚠️ Weder lsof noch fuser/ss verfügbar. Ports können nicht geprüft/gekilled werden."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for PORT in "${PORTS[@]}"; do
|
||||
PIDS=""
|
||||
|
||||
if [ "$have_lsof" = true ]; then
|
||||
PIDS=$({ lsof -ti :"$PORT" 2>/dev/null || true; } | tr '\n' ' ')
|
||||
elif [ "$have_fuser" = true ]; then
|
||||
# fuser listet PIDs, -n tcp beschränkt auf TCP
|
||||
PIDS=$({ fuser -n tcp "$PORT" 2>/dev/null || true; } | tr '\n' ' ')
|
||||
elif [ "$have_ss" = true ]; then
|
||||
# ss fallback, extrahiere pid=... aus der letzten Spalte
|
||||
PIDS=$({ ss -ltnp "sport = :$PORT" 2>/dev/null || true; } | awk 'NR>1 {split($NF,pid,"pid="); split(pid[2],p,","); if(p[1]!=""){print p[1]}}' | tr '\n' ' ')
|
||||
fi
|
||||
|
||||
if [ -n "$PIDS" ]; then
|
||||
echo " ✓ Beende Prozesse auf Port $PORT (PID: $PIDS)"
|
||||
if [ "$have_fuser" = true ]; then
|
||||
fuser -k -n tcp "$PORT" 2>/dev/null || true
|
||||
else
|
||||
kill -9 $PIDS 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
echo " ○ Port $PORT ist frei"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✅ Port-Bereinigung abgeschlossen."
|
||||
echo ""
|
||||
echo "Belegte Debug-Ports aktuell:"
|
||||
if [ "$have_lsof" = true ]; then
|
||||
lsof -i :3000 -i :3001 -i :9229 -i :5173 -i :9222 2>/dev/null || echo " Alle Debug-Ports sind frei ✓"
|
||||
elif [ "$have_ss" = true ]; then
|
||||
ss -ltnp "( sport = :3000 or sport = :3001 or sport = :9229 or sport = :5173 or sport = :9222 )" 2>/dev/null || echo " Alle Debug-Ports sind frei ✓"
|
||||
else
|
||||
echo " Port-Status nicht prüfbar (lsof/ss fehlen), Cleanup wurde dennoch ausgeführt."
|
||||
fi
|
||||
86
.vscode/launch.json
vendored
86
.vscode/launch.json
vendored
@@ -2,71 +2,49 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "🚀 Debug Backend",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npx",
|
||||
"runtimeArgs": [
|
||||
"ts-node-dev",
|
||||
"--respawn",
|
||||
"--transpile-only",
|
||||
"--no-notify"
|
||||
],
|
||||
"args": ["src/index.ts"],
|
||||
"cwd": "${workspaceFolder}/backend",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"sourceMaps": true,
|
||||
"restart": true,
|
||||
"outputCapture": "std",
|
||||
"presentation": {
|
||||
"group": "fullstack",
|
||||
"panel": "new"
|
||||
},
|
||||
"postDebugTask": "🛑 Terminate All Development Processes"
|
||||
},
|
||||
{
|
||||
"name": "🌐 Debug Frontend (Chrome)",
|
||||
"name": "Debug Vite",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}/frontend/src",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
"webRoot": "${workspaceFolder}/Client",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"preLaunchTask": "Start Vite (after backend ready)"
|
||||
},
|
||||
{
|
||||
"name": "🔧 Debug Frontend (Edge)",
|
||||
"type": "msedge",
|
||||
"name": "Debug Backend",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}/frontend/src",
|
||||
"runtimeExecutable": "npx",
|
||||
"args": ["tsx", "watch", "--inspect=9229", "./src/index.ts"],
|
||||
"cwd": "${workspaceFolder}/Server",
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"preLaunchTask": "Clean Debug Ports",
|
||||
"envFile": "${workspaceFolder}/Server/.env",
|
||||
"env": {
|
||||
"PORT": "3001"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "Debug Jest Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/Server/node_modules/jest/bin/jest.js",
|
||||
"args": [
|
||||
"--config=Server/tests/jest.config.cjs",
|
||||
"--runInBand"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "🚀🌐 Debug Full Stack (Chrome)",
|
||||
"preLaunchTask": "🌐 Frontend",
|
||||
"configurations": ["🚀 Debug Backend", "🌐 Debug Frontend (Chrome)"],
|
||||
"stopAll": true,
|
||||
"presentation": {
|
||||
"hidden": false,
|
||||
"group": "fullstack",
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "🚀🔧 Debug Full Stack (Edge)",
|
||||
"preLaunchTask": "🌐 Frontend",
|
||||
"configurations": ["🚀 Debug Backend", "🔧 Debug Frontend (Edge)"],
|
||||
"stopAll": true,
|
||||
"presentation": {
|
||||
"hidden": false,
|
||||
"group": "fullstack",
|
||||
"order": 2
|
||||
}
|
||||
"name": "Compound Debug",
|
||||
"configurations": ["Debug Vite", "Debug Backend"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
35
.vscode/settings.json
vendored
35
.vscode/settings.json
vendored
@@ -1,28 +1,9 @@
|
||||
{
|
||||
"typescript.preferences.includePackageJsonAutoImports": "auto",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/dist": true,
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/dist": true
|
||||
},
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"emmet.includeLanguages": {
|
||||
"typescript": "typescriptreact"
|
||||
},
|
||||
"debug.allowBreakpointsEverywhere": true,
|
||||
"debug.node.autoAttach": "on",
|
||||
"terminal.integrated.enablePersistentSessions": false,
|
||||
"terminal.integrated.confirmOnKill": "editor",
|
||||
"task.autoDetect": "off",
|
||||
"task.showDecorations": true
|
||||
}
|
||||
"github.copilot.chat.languageContext.inline.typescript.enabled": true,
|
||||
"github.copilot.chat.languageContext.fix.typescript.enabled": true,
|
||||
"github.copilot.chat.edits.temporalContext.enabled": true,
|
||||
"github.copilot.chat.completionContext.typescript.mode": "on",
|
||||
"github.copilot.chat.agent.thinkingTool": true,
|
||||
"github.copilot.chat.followUps": "always",
|
||||
"debug.onTaskErrors": "abort"
|
||||
}
|
||||
350
.vscode/tasks.json
vendored
350
.vscode/tasks.json
vendored
@@ -2,320 +2,80 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Install Backend Dependencies",
|
||||
"label": "Clean Debug Ports",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["install"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
},
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
}
|
||||
"command": "bash ${workspaceFolder}/.vscode/cleanup-ports.sh",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install Frontend Dependencies",
|
||||
"label": "Wait for Backend Ready",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["install"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/frontend"
|
||||
},
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
}
|
||||
"command": "bash -lc 'for i in {1..120}; do if (echo > /dev/tcp/127.0.0.1/3001) >/dev/null 2>&1; then exit 0; fi; sleep 1; done; echo \"Backend not ready on :3001\"; exit 1'",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "🖥️ Backend",
|
||||
"label": "Start Backend (watch + inspect)",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["run", "dev"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
},
|
||||
"group": "build",
|
||||
"command": "npx tsx watch --inspect=9229 ./src/index.ts",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"group": "dev",
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": {
|
||||
"pattern": {
|
||||
"regexp": "^.*$",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^.*ts-node-dev.*$",
|
||||
"endsPattern": "^🚀 Backend Server läuft auf Port \\d+$"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "🌐 Frontend",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["start"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/frontend",
|
||||
"cwd": "${workspaceFolder}/Server",
|
||||
"env": {
|
||||
"BROWSER": "none"
|
||||
"PORT": "3001"
|
||||
}
|
||||
},
|
||||
"group": "build",
|
||||
"dependsOn": "Clean Debug Ports",
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "backend-ready",
|
||||
"fileLocation": ["absolute"],
|
||||
"pattern": {
|
||||
"regexp": "^(.*)$",
|
||||
"message": 1
|
||||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "Starte Datenbankinitialisierung|Server läuft unter http://localhost:3001",
|
||||
"endsPattern": "NotificationScheduler erfolgreich initialisiert"
|
||||
}
|
||||
}
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"clear": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Start Vite (after backend ready)",
|
||||
"type": "npm",
|
||||
"script": "frontend",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"group": "dev",
|
||||
"showReuseMessage": false
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/Client"
|
||||
},
|
||||
"problemMatcher": {
|
||||
"owner": "custom",
|
||||
"pattern": {
|
||||
"regexp": "^.*$",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^.*Starting the development server.*$",
|
||||
"endsPattern": "^.*webpack compiled.*$"
|
||||
"dependsOn": "Wait for Backend Ready",
|
||||
"dependsOrder": "sequence",
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "vite",
|
||||
"fileLocation": ["relative", "${workspaceFolder}"],
|
||||
"pattern": {
|
||||
"regexp": "^(.*)$",
|
||||
"file": 1,
|
||||
"message": 1
|
||||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "VITE|ready in",
|
||||
"endsPattern": "localhost:3000"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "🛑 Kill Frontend Process",
|
||||
"type": "shell",
|
||||
"command": "pkill",
|
||||
"args": ["-f", "npm start"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/frontend"
|
||||
},
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "🛑 Kill Backend Process",
|
||||
"type": "shell",
|
||||
"command": "pkill",
|
||||
"args": ["-f", "ts-node-dev"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
},
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "📊 Show Development Processes Status",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-c",
|
||||
"echo '📊 Aktuelle Development-Prozesse:'; echo ''; ps aux | grep -E 'npm.*start|react-scripts|ts-node-dev' | grep -v grep | awk '{print \"PID: \" $2 \" - \" $11 \" \" $12 \" \" $13}' || echo 'Keine Development-Prozesse gefunden'"
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "🛑 Cleanup Development Processes",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-c",
|
||||
"pkill -f 'ts-node-dev' > /dev/null 2>&1 || true; pkill -f 'npm.*start' > /dev/null 2>&1 || true; pkill -f 'react-scripts' > /dev/null 2>&1 || true"
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "never",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"clear": false,
|
||||
"showReuseMessage": false,
|
||||
"close": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"runOptions": {
|
||||
"reevaluateOnRerun": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "🧹 Post Debug Cleanup",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": ["-c", "/workspace/scripts/post-debug-cleanup.sh"],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "never",
|
||||
"focus": false,
|
||||
"panel": "new",
|
||||
"clear": false,
|
||||
"showReuseMessage": false,
|
||||
"close": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"runOptions": {
|
||||
"reevaluateOnRerun": true,
|
||||
"runOn": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "🔄 Force Kill All Dev Processes",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-c",
|
||||
"RANDOM_ID=$RANDOM; echo \"Cleanup ID: $RANDOM_ID\" > /dev/null; pkill -9 -f 'ts-node-dev' > /dev/null 2>&1 || true; pkill -9 -f 'npm.*start' > /dev/null 2>&1 || true; pkill -9 -f 'react-scripts' > /dev/null 2>&1 || true"
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "never",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"clear": false,
|
||||
"showReuseMessage": false,
|
||||
"close": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "🛑 Terminate All Development Processes",
|
||||
"type": "shell",
|
||||
"command": "/workspace/scripts/kill-dev-processes.sh",
|
||||
"args": [],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": false,
|
||||
"reveal": "never",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"clear": false,
|
||||
"showReuseMessage": false,
|
||||
"close": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"runOptions": {
|
||||
"runOn": "default"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "🛑 Terminate All Development Processes (Verbose)",
|
||||
"type": "shell",
|
||||
"command": "/workspace/scripts/kill-dev-processes.sh",
|
||||
"args": [],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"clear": true,
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "🚀 Start Full Stack (Split Terminal)",
|
||||
"dependsOrder": "parallel",
|
||||
"dependsOn": ["🖥️ Backend", "🌐 Frontend"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "🔄 Restart Full Stack",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": [
|
||||
"-c",
|
||||
"/workspace/scripts/kill-dev-processes.sh && sleep 2 && echo '🚀 Starte Full Stack...' && code --command 'workbench.action.tasks.runTask' 'shell: 🚀 Start Full Stack (Split Terminal)'"
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Build Backend",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["run", "build"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
},
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": ["$tsc"]
|
||||
},
|
||||
{
|
||||
"label": "Build Frontend",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["run", "build"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/frontend"
|
||||
},
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
"clear": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
164
.woodpecker.yml
164
.woodpecker.yml
@@ -1,164 +0,0 @@
|
||||
# Woodpecker CI Pipeline für Full Stack TypeScript Template
|
||||
# Automatisiert Docker Builds für main, beta und alpha branches
|
||||
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
|
||||
steps:
|
||||
# =============================================================================
|
||||
# Dependencies installieren und Tests ausführen
|
||||
# =============================================================================
|
||||
|
||||
dependencies:
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- echo "📦 Installing dependencies..."
|
||||
- npm ci
|
||||
- cd backend && npm ci && cd ..
|
||||
- cd frontend && npm ci && cd ..
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
|
||||
lint_and_test:
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- echo "🔍 Running linting and tests..."
|
||||
- cd backend && npm run test && cd ..
|
||||
- cd frontend && npm run test -- --coverage --watchAll=false && cd ..
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
depends_on: [dependencies]
|
||||
|
||||
build_check:
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- echo "🏗️ Testing production builds..."
|
||||
- cd backend && npm run build && cd ..
|
||||
- cd frontend && npm run build && cd ..
|
||||
when:
|
||||
event: [push, pull_request]
|
||||
depends_on: [dependencies]
|
||||
|
||||
# =============================================================================
|
||||
# Docker Build & Push für MAIN Branch (Production)
|
||||
# =============================================================================
|
||||
|
||||
docker_build_push_main:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
|
||||
registry: YOUR_REGISTRY
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tag: latest
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
build_args:
|
||||
- NODE_ENV=production
|
||||
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
|
||||
when:
|
||||
branch: main
|
||||
event: push
|
||||
depends_on: [lint_and_test, build_check]
|
||||
|
||||
# =============================================================================
|
||||
# Docker Build & Push für BETA Branch (Staging)
|
||||
# =============================================================================
|
||||
|
||||
docker_build_push_beta:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
|
||||
registry: YOUR_REGISTRY
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tag: beta
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
build_args:
|
||||
- NODE_ENV=staging
|
||||
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
|
||||
when:
|
||||
branch: beta
|
||||
event: push
|
||||
depends_on: [lint_and_test, build_check]
|
||||
|
||||
# =============================================================================
|
||||
# Docker Build & Push für ALPHA Branch (Development)
|
||||
# =============================================================================
|
||||
|
||||
docker_build_push_alpha:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
repo: YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template
|
||||
registry: YOUR_REGISTRY
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tag: alpha
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
build_args:
|
||||
- NODE_ENV=development
|
||||
- BUILD_VERSION=${CI_COMMIT_SHA:0:8}
|
||||
when:
|
||||
branch: alpha
|
||||
event: push
|
||||
depends_on: [lint_and_test, build_check]
|
||||
|
||||
# =============================================================================
|
||||
# Notification bei erfolgreichem Build
|
||||
# =============================================================================
|
||||
|
||||
notify_success:
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- echo "✅ Pipeline erfolgreich abgeschlossen!"
|
||||
- echo "🐳 Docker Image gebaut für Branch: ${CI_COMMIT_BRANCH}"
|
||||
- echo "📦 Tag: ${CI_COMMIT_BRANCH == 'main' ? 'latest' : CI_COMMIT_BRANCH}"
|
||||
- echo "🔗 Commit: ${CI_COMMIT_SHA:0:8}"
|
||||
when:
|
||||
status: success
|
||||
event: push
|
||||
branch: [main, beta, alpha]
|
||||
depends_on: [docker_build_push_main, docker_build_push_beta, docker_build_push_alpha]
|
||||
|
||||
# =============================================================================
|
||||
# Security Scan (Optional - kann aktiviert werden)
|
||||
# =============================================================================
|
||||
|
||||
# security_scan:
|
||||
# image: aquasec/trivy:latest
|
||||
# commands:
|
||||
# - trivy image --exit-code 0 --severity HIGH,CRITICAL YOUR_REGISTRY/YOUR_ORG/fullstack-typescript-template:${CI_COMMIT_BRANCH == 'main' ? 'latest' : CI_COMMIT_BRANCH}
|
||||
# when:
|
||||
# branch: [main, beta, alpha]
|
||||
# event: push
|
||||
# depends_on: [docker_build_push_main, docker_build_push_beta, docker_build_push_alpha]
|
||||
|
||||
# =============================================================================
|
||||
# Pipeline Services (falls externe Services für Tests benötigt werden)
|
||||
# =============================================================================
|
||||
|
||||
# services:
|
||||
# postgres:
|
||||
# image: postgres:15-alpine
|
||||
# environment:
|
||||
# POSTGRES_DB: testdb
|
||||
# POSTGRES_USER: test
|
||||
# POSTGRES_PASSWORD: test
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
#
|
||||
# redis:
|
||||
# image: redis:7-alpine
|
||||
# ports:
|
||||
# - 6379:6379
|
||||
79
Dockerfile
79
Dockerfile
@@ -52,13 +52,21 @@ RUN apk add --no-cache \
|
||||
curl \
|
||||
tzdata \
|
||||
tini \
|
||||
su-exec \
|
||||
shadow \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Arbeitsverzeichnisse erstellen
|
||||
RUN mkdir -p /var/www/html \
|
||||
# Non-root User erstellen (UID/GID 1000 für Kompatibilität)
|
||||
RUN addgroup -g 1000 appuser \
|
||||
&& adduser -D -u 1000 -G appuser appuser \
|
||||
&& mkdir -p /var/www/html \
|
||||
&& mkdir -p /app/backend/dist \
|
||||
&& mkdir -p /etc/supervisor.d \
|
||||
&& mkdir -p /data
|
||||
&& mkdir -p /data \
|
||||
&& mkdir -p /var/log/nginx \
|
||||
&& mkdir -p /var/lib/nginx \
|
||||
&& mkdir -p /var/tmp/nginx \
|
||||
&& mkdir -p /run/nginx
|
||||
|
||||
# Frontend Build von Builder Stage kopieren
|
||||
COPY --from=frontend-builder /app/frontend/build /var/www/html
|
||||
@@ -67,10 +75,10 @@ COPY --from=frontend-builder /app/frontend/build /var/www/html
|
||||
COPY --from=backend-builder /app/backend/dist /app/backend/dist
|
||||
COPY --from=backend-builder /app/backend/node_modules /app/backend/node_modules
|
||||
|
||||
# Nginx Konfiguration erstellen
|
||||
# Nginx Konfiguration erstellen (non-root optimiert)
|
||||
RUN printf 'worker_processes auto;\n\
|
||||
error_log /var/log/nginx/error.log warn;\n\
|
||||
pid /var/run/nginx.pid;\n\
|
||||
pid /run/nginx/nginx.pid;\n\
|
||||
\n\
|
||||
events {\n\
|
||||
worker_connections 1024;\n\
|
||||
@@ -81,6 +89,13 @@ events {\n\
|
||||
http {\n\
|
||||
include /etc/nginx/mime.types;\n\
|
||||
default_type application/octet-stream;\n\
|
||||
\n\
|
||||
# Temp-Pfade für non-root\n\
|
||||
client_body_temp_path /var/tmp/nginx/client_body;\n\
|
||||
proxy_temp_path /var/tmp/nginx/proxy;\n\
|
||||
fastcgi_temp_path /var/tmp/nginx/fastcgi;\n\
|
||||
uwsgi_temp_path /var/tmp/nginx/uwsgi;\n\
|
||||
scgi_temp_path /var/tmp/nginx/scgi;\n\
|
||||
\n\
|
||||
# Logging\n\
|
||||
log_format main '\''$remote_addr - $remote_user [$time_local] "$request" '\''\n\
|
||||
@@ -96,19 +111,24 @@ http {\n\
|
||||
gzip on;\n\
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;\n\
|
||||
\n\
|
||||
# Security Headers\n\
|
||||
# Moderne Security Headers\n\
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;\n\
|
||||
add_header X-Content-Type-Options "nosniff" always;\n\
|
||||
add_header X-XSS-Protection "1; mode=block" always;\n\
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;\n\
|
||||
add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\''; style-src '\''self'\'' '\''unsafe-inline'\''; img-src '\''self'\'' data: https:; font-src '\''self'\'' data:; connect-src '\''self'\'' https:; frame-ancestors '\''self'\''; base-uri '\''self'\''; form-action '\''self'\'';" always;\n\
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;\n\
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;\n\
|
||||
\n\
|
||||
# Request-Limits gegen DoS\n\
|
||||
client_max_body_size 50M;\n\
|
||||
client_body_timeout 60s;\n\
|
||||
client_header_timeout 60s;\n\
|
||||
client_body_buffer_size 128k;\n\
|
||||
large_client_header_buffers 4 16k;\n\
|
||||
\n\
|
||||
server {\n\
|
||||
listen 80;\n\
|
||||
listen 8080;\n\
|
||||
server_name _;\n\
|
||||
\n\
|
||||
client_max_body_size 50M;\n\
|
||||
client_body_timeout 60s;\n\
|
||||
client_header_timeout 60s;\n\
|
||||
\n\
|
||||
# Health Check Endpoint\n\
|
||||
location /health {\n\
|
||||
@@ -147,16 +167,22 @@ http {\n\
|
||||
proxy_connect_timeout 60s;\n\
|
||||
proxy_send_timeout 60s;\n\
|
||||
proxy_read_timeout 60s;\n\
|
||||
\n\
|
||||
# Buffer-Limits\n\
|
||||
proxy_buffer_size 4k;\n\
|
||||
proxy_buffers 8 4k;\n\
|
||||
proxy_busy_buffers_size 8k;\n\
|
||||
}\n\
|
||||
}\n\
|
||||
}' > /etc/nginx/nginx.conf
|
||||
|
||||
# Supervisor Konfiguration erstellen
|
||||
# Supervisor Konfiguration erstellen (non-root)
|
||||
RUN printf '[supervisord]\n\
|
||||
nodaemon=true\n\
|
||||
loglevel=info\n\
|
||||
logfile=/dev/stdout\n\
|
||||
logfile_maxbytes=0\n\
|
||||
user=appuser\n\
|
||||
\n\
|
||||
[program:nginx]\n\
|
||||
command=/usr/sbin/nginx -g "daemon off;"\n\
|
||||
@@ -167,6 +193,7 @@ stdout_logfile_maxbytes=0\n\
|
||||
stderr_logfile=/dev/stderr\n\
|
||||
stderr_logfile_maxbytes=0\n\
|
||||
priority=10\n\
|
||||
user=appuser\n\
|
||||
\n\
|
||||
[program:backend]\n\
|
||||
command=node /app/backend/dist/index.js\n\
|
||||
@@ -179,12 +206,12 @@ stdout_logfile_maxbytes=0\n\
|
||||
stderr_logfile=/dev/stderr\n\
|
||||
stderr_logfile_maxbytes=0\n\
|
||||
priority=20\n\
|
||||
user=node\n' > /etc/supervisor.d/supervisord.ini
|
||||
user=appuser\n' > /etc/supervisor.d/supervisord.ini
|
||||
|
||||
# Health Check Script erstellen
|
||||
RUN printf '#!/bin/sh\n\
|
||||
# Health check for both nginx and backend\n\
|
||||
nginx_status=$(curl -f -s http://localhost/health > /dev/null && echo "ok" || echo "fail")\n\
|
||||
nginx_status=$(curl -f -s http://localhost:8080/health > /dev/null && echo "ok" || echo "fail")\n\
|
||||
backend_status=$(curl -f -s http://localhost:3001/api/health > /dev/null && echo "ok" || echo "fail")\n\
|
||||
\n\
|
||||
if [ "$nginx_status" = "ok" ] && [ "$backend_status" = "ok" ]; then\n\
|
||||
@@ -194,9 +221,17 @@ else\n\
|
||||
exit 1\n\
|
||||
fi' > /usr/local/bin/healthcheck.sh && chmod +x /usr/local/bin/healthcheck.sh
|
||||
|
||||
# Berechtigungen setzen
|
||||
RUN chown -R node:node /data /app/backend
|
||||
RUN chown nginx:nginx /var/www/html
|
||||
# Berechtigungen setzen für non-root User
|
||||
RUN chown -R appuser:appuser \
|
||||
/data \
|
||||
/app/backend \
|
||||
/var/www/html \
|
||||
/var/log/nginx \
|
||||
/var/lib/nginx \
|
||||
/var/tmp/nginx \
|
||||
/run/nginx \
|
||||
/etc/nginx \
|
||||
/usr/local/bin/healthcheck.sh
|
||||
|
||||
# Health Check konfigurieren
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
@@ -205,6 +240,12 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
# Arbeitsverzeichnis setzen
|
||||
WORKDIR /app
|
||||
|
||||
# Exponiere Port 8080 (non-root) statt 80
|
||||
EXPOSE 8080
|
||||
|
||||
# Wechsle zu non-root User
|
||||
USER appuser
|
||||
|
||||
# Startup mit Tini für proper signal handling
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor.d/supervisord.ini"]
|
||||
@@ -216,7 +257,7 @@ LABEL traefik.enable=true
|
||||
LABEL traefik.http.routers.app.rule="Host(\`your-domain.com\`)"
|
||||
LABEL traefik.http.routers.app.entrypoints=websecure
|
||||
LABEL traefik.http.routers.app.tls.certresolver=letsencrypt
|
||||
LABEL traefik.http.services.app.loadbalancer.server.port=80
|
||||
LABEL traefik.http.services.app.loadbalancer.server.port=8080
|
||||
|
||||
# =============================================================================
|
||||
# Metadata Labels
|
||||
|
||||
195
README.md
195
README.md
@@ -1,15 +1,29 @@
|
||||
# 🚀 Full Stack TypeScript Template
|
||||
|
||||
Ein modernes Full Stack Template mit React Frontend und Express Backend, komplett containerisiert mit Docker.
|
||||
Ein modernes, **sicherheitsoptimiertes** Full Stack Template mit React Frontend und Express Backend, komplett containerisiert mit Docker.
|
||||
|
||||
## 📋 Übersicht
|
||||
|
||||
- **Frontend**: React 18 + TypeScript + Create React App
|
||||
- **Backend**: Express.js + TypeScript + Node.js
|
||||
- **Database**: PostgreSQL 15
|
||||
- **Backend**: Express.js + TypeScript + Node.js + Helmet + CORS
|
||||
- **Database**: SQLite (Standard) oder PostgreSQL 15 (optional, mit scram-sha-256 Auth)
|
||||
- **Container**: Docker + Docker Compose
|
||||
- **Development**: Hot Reload für Frontend & Backend
|
||||
- **CI/CD**: Woodpecker CI Ready
|
||||
- **CI/CD**: Gitea Actions mit automatischen Security-Scans
|
||||
- **Security**: Non-root Container, Input-Validierung, Security Headers
|
||||
- **Flexibel**: PostgreSQL optional - auch für SQLite-only Projekte geeignet
|
||||
|
||||
## 🔐 Sicherheitsfeatures
|
||||
|
||||
✅ **Non-root Docker Container** - Alle Container laufen als nicht-privilegierte User
|
||||
✅ **Security Headers** - Helmet.js & moderne CSP-Policies
|
||||
✅ **Input-Validierung** - express-validator für alle API-Endpoints
|
||||
✅ **CORS-Whitelist** - Konfigurierbare Origin-Beschränkung
|
||||
✅ **Resource Limits** - CPU/Memory-Limits gegen DoS
|
||||
✅ **Isolierte Datenbank** - PostgreSQL (falls verwendet) nur intern erreichbar
|
||||
✅ **Automatische Scans** - Trivy Security Scans in CI/CD
|
||||
✅ **Secrets Management** - Keine Hardcoded Credentials
|
||||
✅ **Flexible Architektur** - SQLite für einfache Projekte, PostgreSQL für Production
|
||||
|
||||
## 🛠️ Voraussetzungen
|
||||
|
||||
@@ -123,10 +137,14 @@ npm run build:backend # Nur Backend
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
# Mit Docker (empfohlen)
|
||||
npm run docker:prod
|
||||
# Mit Docker (empfohlen - baut automatisch)
|
||||
docker-compose up --build -d
|
||||
|
||||
# Manuell
|
||||
# Oder mit npm
|
||||
npm run docker:build # Baut das Image
|
||||
npm run docker:up # Startet die Services
|
||||
|
||||
# Manuell (ohne Docker)
|
||||
npm run build
|
||||
NODE_ENV=production npm start
|
||||
```
|
||||
@@ -149,29 +167,93 @@ npm run docker:clean # Docker Cleanup
|
||||
|
||||
## 🌍 Environment Variables
|
||||
|
||||
Erstelle eine `.env` Datei im Root-Verzeichnis:
|
||||
### ⚠️ WICHTIG: Sicherheitskonfiguration
|
||||
|
||||
```bash
|
||||
# Database
|
||||
POSTGRES_DB=appdb
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=secure-password-change-this
|
||||
DATABASE_URL=postgresql://postgres:secure-password-change-this@postgres:5432/appdb
|
||||
1. **Kopiere die Beispiel-Datei**:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
# App
|
||||
NODE_ENV=development
|
||||
JWT_SECRET=your-jwt-secret-change-this
|
||||
2. **Generiere sichere Secrets**:
|
||||
```bash
|
||||
# JWT Secret generieren
|
||||
openssl rand -base64 32
|
||||
|
||||
# Ports
|
||||
APP_PORT=3000
|
||||
POSTGRES_PORT=5432
|
||||
```
|
||||
# PostgreSQL Passwort generieren
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
3. **Fülle die `.env` Datei mit deinen Werten**:
|
||||
```bash
|
||||
# Database (⚠️ MUSS geändert werden!)
|
||||
POSTGRES_DB=appdb
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=[DEIN_SICHERES_PASSWORT_HIER]
|
||||
|
||||
# Security (⚠️ MUSS geändert werden!)
|
||||
JWT_SECRET=[DEIN_SICHERES_SECRET_HIER]
|
||||
|
||||
# App
|
||||
NODE_ENV=production
|
||||
APP_PORT=8080
|
||||
```
|
||||
|
||||
### 📋 Vollständige Dokumentation
|
||||
|
||||
Siehe [.env.example](.env.example) für alle verfügbaren Optionen und detaillierte Erklärungen.
|
||||
|
||||
### 🚨 Sicherheitshinweise
|
||||
|
||||
- ❌ **NIEMALS** `.env` in Git committen (bereits in `.gitignore`)
|
||||
- ✅ Verwende **unterschiedliche Secrets** für Development, Staging und Production
|
||||
- ✅ Nutze **starke, zufällige Passwörter** (min. 32 Zeichen)
|
||||
- ✅ Rotiere Secrets regelmäßig in Production-Umgebungen
|
||||
|
||||
## 🗄️ Datenbank-Konfiguration
|
||||
|
||||
Dieses Template ist **flexibel** und unterstützt verschiedene Datenbanken:
|
||||
|
||||
### Option 1: SQLite (Standard - keine Konfiguration nötig)
|
||||
Perfekt für:
|
||||
- ✅ Einfache Projekte und Prototypen
|
||||
- ✅ Low-Traffic Anwendungen
|
||||
- ✅ Development und Testing
|
||||
- ✅ Keine separate DB-Server benötigt
|
||||
|
||||
**Keine weiteren Schritte nötig!** Die Datenbank wird automatisch im `/data` Volume erstellt.
|
||||
|
||||
### Option 2: PostgreSQL (Optional - für Production)
|
||||
Empfohlen für:
|
||||
- ✅ Production-Umgebungen
|
||||
- ✅ High-Traffic Anwendungen
|
||||
- ✅ Komplexe Datenmodelle
|
||||
- ✅ Mehrere parallele Connections
|
||||
|
||||
**Aktivierung:**
|
||||
1. Öffne [docker-compose.yml](docker-compose.yml) und entkommentiere den `postgres`-Service (Zeile 82-135)
|
||||
2. Aktiviere `depends_on` im `app`-Service (Zeile 52-54)
|
||||
3. Entkommentiere `postgres_data` Volume (Zeile 149-150)
|
||||
4. Setze in `.env`: `DATABASE_URL=postgresql://postgres:YOUR_PASSWORD@postgres:5432/appdb`
|
||||
5. Setze ein sicheres `POSTGRES_PASSWORD`
|
||||
|
||||
### Option 3: Andere Datenbanken
|
||||
Du kannst auch MySQL, MongoDB, etc. verwenden:
|
||||
- Füge den entsprechenden Service in `docker-compose.yml` hinzu
|
||||
- Passe `DATABASE_URL` in `.env` an
|
||||
- Installiere den entsprechenden Client (`mysql2`, `mongoose`, etc.)
|
||||
|
||||
## 📡 Ports
|
||||
|
||||
### Development (lokal ohne Docker)
|
||||
- **Frontend**: http://localhost:3000
|
||||
- **Backend**: http://localhost:3001
|
||||
- **Database**: localhost:5432
|
||||
|
||||
### Production (Docker Container)
|
||||
- **Application**: http://localhost:8080 (Nginx + Backend)
|
||||
- **Database (PostgreSQL)**: Nur intern erreichbar (kein Port-Expose für Sicherheit)
|
||||
- **SQLite**: Wird im `/data` Volume gespeichert (kein Port nötig)
|
||||
|
||||
> 💡 **Tipp**: Für lokales PostgreSQL-Debugging den Port in `docker-compose.yml` freigeben (Zeile 127-128)
|
||||
|
||||
## 🏗️ Projekt-Struktur
|
||||
|
||||
@@ -249,6 +331,75 @@ docker system prune -a
|
||||
./scripts/kill-dev-processes.sh
|
||||
```
|
||||
|
||||
## 🔒 Sicherheits-Best-Practices
|
||||
|
||||
### Vor dem Deployment
|
||||
|
||||
1. **Environment-Variablen überprüfen**
|
||||
- [ ] Alle Secrets in `.env` sind gesetzt und stark
|
||||
- [ ] `NODE_ENV=production` ist gesetzt
|
||||
- [ ] JWT_SECRET ist ein starker, zufälliger String
|
||||
- [ ] POSTGRES_PASSWORD ist sicher
|
||||
|
||||
2. **CORS-Konfiguration anpassen**
|
||||
```typescript
|
||||
// backend/src/index.ts - Zeile 36
|
||||
const allowedOrigins = [
|
||||
'https://your-domain.com', // ⚠️ Anpassen!
|
||||
'https://www.your-domain.com'
|
||||
];
|
||||
```
|
||||
|
||||
3. **Gitea Actions konfigurieren**
|
||||
```yaml
|
||||
# .gittea/workflows/build.yaml - Zeile 17
|
||||
REGISTRY: git.csnetworkx.dev # Deine Gitea-Instanz URL
|
||||
# Image-Name wird automatisch generiert: REGISTRY/owner/repo
|
||||
```
|
||||
|
||||
**Secrets in Gitea setzen** (Repository Settings → Secrets):
|
||||
- `DOCKER_USERNAME` → Dein Gitea Username
|
||||
- `DOCKER_PASSWORD` → Gitea Access Token mit Package-Rechten
|
||||
|
||||
### Container Security
|
||||
|
||||
- ✅ Alle Container laufen als **non-root** (UID 1000 / 70)
|
||||
- ✅ `no-new-privileges` Security Option aktiv
|
||||
- ✅ Minimale Linux Capabilities (CAP_DROP: ALL)
|
||||
- ✅ Resource Limits gegen DoS-Angriffe
|
||||
- ✅ PostgreSQL verwendet scram-sha-256 Authentifizierung
|
||||
|
||||
### API Security
|
||||
|
||||
- ✅ **Helmet.js** für Security Headers
|
||||
- ✅ **CORS** mit Origin-Whitelist
|
||||
- ✅ **Input-Validierung** mit express-validator
|
||||
- ✅ **Request Size Limits** (10MB max)
|
||||
- ✅ **Error Handling** ohne Stacktrace-Leaks in Production
|
||||
|
||||
### CI/CD Security
|
||||
|
||||
Die Gitea Actions Pipeline führt automatisch aus:
|
||||
- 🔍 **Trivy Security Scans** (Quellcode, Dependencies, Docker Images)
|
||||
- 🔍 **Dockerfile Lint** (IaC-Konfiguration)
|
||||
- 🔄 **Automatische npm audit fix** bei Schwachstellen
|
||||
- 📊 **Security Reports** nach jedem Scan
|
||||
|
||||
### Empfohlene Zusatz-Maßnahmen
|
||||
|
||||
1. **Secrets Management**: Verwende Vault oder Docker Secrets in Production
|
||||
2. **TLS/HTTPS**: Nutze Traefik mit Let's Encrypt (Labels bereits im Dockerfile)
|
||||
3. **Monitoring**: Implementiere Health-Checks und Alerting
|
||||
4. **Backups**: Automatisiere PostgreSQL-Backups
|
||||
5. **Updates**: Halte Dependencies aktuell (`npm audit`, `npm outdated`)
|
||||
6. **Penetration Testing**: Führe regelmäßige Security-Audits durch
|
||||
|
||||
### Weitere Ressourcen
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [Docker Security Best Practices](https://docs.docker.com/develop/security-best-practices/)
|
||||
- [Node.js Security Checklist](https://blog.risingstack.com/node-js-security-checklist/)
|
||||
|
||||
## 📄 License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
208
SECURITY.md
Normal file
208
SECURITY.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 🔒 Security Policy
|
||||
|
||||
## Sicherheitsarchitektur
|
||||
|
||||
Dieses Template wurde mit einem **Security-First-Ansatz** entwickelt und ist optimiert für den Einsatz mit Gitea Actions und anderen CI/CD-Systemen.
|
||||
|
||||
## 🛡️ Implementierte Sicherheitsmaßnahmen
|
||||
|
||||
### Container Security
|
||||
|
||||
#### Non-Root Container (✅ Kritisch)
|
||||
Alle Container laufen als nicht-privilegierte Benutzer:
|
||||
|
||||
- **Application Container**: UID 1000 (`appuser`)
|
||||
- **PostgreSQL Container**: UID 70 (Standard postgres-User)
|
||||
- **Frontend Dev Container**: UID 1000 (`node`)
|
||||
|
||||
**Warum wichtig?**: Container die als root laufen, können bei einem Breakout den Host kompromittieren.
|
||||
|
||||
#### Security Options
|
||||
```yaml
|
||||
security_opt:
|
||||
- no-new-privileges:true # Verhindert Privilege Escalation
|
||||
cap_drop:
|
||||
- ALL # Entfernt alle Linux Capabilities
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE # Nur benötigte Capabilities
|
||||
```
|
||||
|
||||
#### Resource Limits
|
||||
```yaml
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 2G
|
||||
```
|
||||
Verhindert DoS-Angriffe durch Resource-Exhaustion.
|
||||
|
||||
### Network Security
|
||||
|
||||
#### Isolierte Datenbank
|
||||
- PostgreSQL ist **nicht** nach außen exponiert
|
||||
- Nur über internes Docker-Netzwerk erreichbar
|
||||
- Für lokales Debugging kann der Port bei Bedarf freigegeben werden
|
||||
|
||||
#### CORS-Whitelist
|
||||
```typescript
|
||||
const allowedOrigins = [
|
||||
'https://your-domain.com',
|
||||
'https://www.your-domain.com'
|
||||
];
|
||||
```
|
||||
Nur explizit erlaubte Origins dürfen API-Requests durchführen.
|
||||
|
||||
### Application Security
|
||||
|
||||
#### Security Headers (Helmet.js)
|
||||
- `Content-Security-Policy`: Verhindert XSS-Angriffe
|
||||
- `Strict-Transport-Security`: Erzwingt HTTPS
|
||||
- `X-Content-Type-Options`: Verhindert MIME-Sniffing
|
||||
- `X-Frame-Options`: Verhindert Clickjacking
|
||||
- `Permissions-Policy`: Kontrolliert Browser-Features
|
||||
|
||||
#### Input-Validierung
|
||||
Alle API-Endpoints validieren ihre Inputs:
|
||||
|
||||
```typescript
|
||||
app.post('/api/print', [
|
||||
body('photoId').isInt({ min: 1 }),
|
||||
body('copies').isInt({ min: 1, max: 100 }),
|
||||
validateRequest
|
||||
], handler);
|
||||
```
|
||||
|
||||
#### Request Size Limits
|
||||
```typescript
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
```
|
||||
Verhindert DoS durch große Payloads.
|
||||
|
||||
### Database Security
|
||||
|
||||
#### Authentifizierung
|
||||
- Verwendet `scram-sha-256` statt MD5
|
||||
- Keine Default-Passwörter (müssen gesetzt werden)
|
||||
- Sichere Passwortgenerierung empfohlen
|
||||
|
||||
#### Secrets Management
|
||||
- Keine Secrets im Code
|
||||
- `.env` in `.gitignore`
|
||||
- `.env.example` als Vorlage
|
||||
|
||||
## 🔍 CI/CD Security Scans
|
||||
|
||||
### Automatische Scans in Gitea Actions
|
||||
|
||||
#### 1. Quellcode-Scan
|
||||
```bash
|
||||
trivy fs --severity CRITICAL,HIGH --exit-code 1 .
|
||||
```
|
||||
Scannt auf Schwachstellen in Dependencies.
|
||||
|
||||
#### 2. Docker Image-Scan
|
||||
```bash
|
||||
trivy image --severity CRITICAL,HIGH $IMAGE_NAME
|
||||
```
|
||||
Scannt fertige Docker Images.
|
||||
|
||||
#### 3. IaC-Scan
|
||||
```bash
|
||||
trivy config --severity CRITICAL,HIGH .
|
||||
```
|
||||
Prüft Dockerfiles und docker-compose.yml.
|
||||
|
||||
#### 4. Automatische Fixes
|
||||
Bei gefundenen Schwachstellen:
|
||||
- Führt `npm audit fix` aus
|
||||
- Erstellt automatischen Commit
|
||||
- Baut neue Docker Images
|
||||
|
||||
### Tägliche Security-Scans
|
||||
- Laufen täglich um 6:00 UTC (Cron-Job)
|
||||
- Prüfen auf neue Schwachstellen
|
||||
- Erstellen automatische Updates
|
||||
|
||||
## ⚠️ Bekannte Einschränkungen
|
||||
|
||||
### Kein Rate Limiting
|
||||
Rate Limiting ist **absichtlich nicht** implementiert, da es in Development-Umgebungen Probleme verursachen kann.
|
||||
|
||||
**Für Production**: Implementiere Rate Limiting über:
|
||||
- Traefik/Nginx Rate Limiting
|
||||
- express-rate-limit Middleware
|
||||
- CloudFlare oder ähnliche Services
|
||||
|
||||
### Keine End-to-End Verschlüsselung
|
||||
Das Template setzt voraus, dass TLS/HTTPS auf Reverse-Proxy-Ebene (z.B. Traefik) implementiert wird.
|
||||
|
||||
## 🚨 Vulnerability Reporting
|
||||
|
||||
### Sicherheitslücken melden
|
||||
|
||||
Wenn du eine Sicherheitslücke in diesem Template findest:
|
||||
|
||||
1. **NICHT** als öffentliches GitHub Issue melden
|
||||
2. Sende eine E-Mail an: [security@your-domain.com]
|
||||
3. Beschreibe das Problem detailliert
|
||||
4. Füge PoC hinzu (falls vorhanden)
|
||||
|
||||
### Response Timeline
|
||||
- **Initial Response**: Innerhalb von 48 Stunden
|
||||
- **Fix & Patch**: Abhängig von Severity (Critical: 7 Tage, High: 30 Tage)
|
||||
- **Public Disclosure**: Nach Patch-Veröffentlichung
|
||||
|
||||
## ✅ Security Checklist vor Deployment
|
||||
|
||||
### Pflicht (vor jedem Deployment)
|
||||
- [ ] `.env` Datei erstellt und ausgefüllt
|
||||
- [ ] Starke Secrets generiert (min. 32 Zeichen)
|
||||
- [ ] `NODE_ENV=production` gesetzt
|
||||
- [ ] CORS-Origins auf Production-Domains angepasst
|
||||
- [ ] PostgreSQL-Passwort geändert
|
||||
- [ ] JWT-Secret geändert
|
||||
|
||||
### Empfohlen
|
||||
- [ ] TLS/HTTPS konfiguriert (Traefik mit Let's Encrypt)
|
||||
- [ ] Monitoring & Alerting eingerichtet
|
||||
- [ ] Backup-Strategie implementiert
|
||||
- [ ] Secrets in Vault oder Secret Manager
|
||||
- [ ] Logging konfiguriert (keine Secrets in Logs!)
|
||||
- [ ] Firewall-Regeln geprüft
|
||||
|
||||
### Optional (Production)
|
||||
- [ ] Rate Limiting aktiviert
|
||||
- [ ] WAF (Web Application Firewall) vorgeschaltet
|
||||
- [ ] DDoS-Protection aktiv
|
||||
- [ ] Penetration Testing durchgeführt
|
||||
- [ ] Security Headers im Reverse Proxy verdoppelt
|
||||
|
||||
## 📚 Weitere Ressourcen
|
||||
|
||||
### Standards & Best Practices
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)
|
||||
- [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker)
|
||||
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
|
||||
|
||||
### Tools
|
||||
- [Trivy Scanner](https://github.com/aquasecurity/trivy)
|
||||
- [npm audit](https://docs.npmjs.com/cli/v8/commands/npm-audit)
|
||||
- [Snyk](https://snyk.io/)
|
||||
- [OWASP ZAP](https://www.zaproxy.org/)
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### Version 1.0.0 (2024)
|
||||
- ✅ Non-root Container für alle Services
|
||||
- ✅ Security Headers mit Helmet.js
|
||||
- ✅ Input-Validierung mit express-validator
|
||||
- ✅ CORS-Whitelist
|
||||
- ✅ Resource Limits
|
||||
- ✅ Isolierte PostgreSQL-Datenbank
|
||||
- ✅ Automatische Security-Scans in CI/CD
|
||||
- ✅ scram-sha-256 Authentifizierung für PostgreSQL
|
||||
|
||||
## 📄 License
|
||||
|
||||
Dieses Security-Dokument ist Teil des Full Stack TypeScript Templates und unterliegt der MIT-Lizenz.
|
||||
@@ -24,6 +24,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.3"
|
||||
"express": "^4.18.3",
|
||||
"helmet": "^7.1.0",
|
||||
"express-validator": "^7.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,91 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import helmet from "helmet";
|
||||
import { body, validationResult } from "express-validator";
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
// =============================================================================
|
||||
// Security Middleware
|
||||
// =============================================================================
|
||||
|
||||
// Helmet: Setzt wichtige Security Headers
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"], // Für React Development
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'", "data:"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"],
|
||||
},
|
||||
},
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
}
|
||||
}));
|
||||
|
||||
// CORS: Konfiguriert mit Whitelist (anpassen für Production!)
|
||||
const allowedOrigins = process.env.NODE_ENV === 'production'
|
||||
? [
|
||||
'https://your-domain.com',
|
||||
'https://www.your-domain.com'
|
||||
]
|
||||
: [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:8080',
|
||||
'http://127.0.0.1:3000',
|
||||
'http://127.0.0.1:8080'
|
||||
];
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
// Erlaube Requests ohne Origin (z.B. Mobile Apps, Postman)
|
||||
if (!origin) return callback(null, true);
|
||||
|
||||
if (allowedOrigins.indexOf(origin) !== -1) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
console.warn(`🚫 CORS blocked request from origin: ${origin}`);
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
maxAge: 86400 // 24 Stunden
|
||||
}));
|
||||
|
||||
// Body Parser mit Size Limits (gegen DoS)
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
|
||||
// =============================================================================
|
||||
// Helper: Validierungsergebnis prüfen
|
||||
// =============================================================================
|
||||
const validateRequest = (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Routes
|
||||
// =============================================================================
|
||||
|
||||
// Health Check Endpoint (ohne Validierung)
|
||||
app.get("/api/health", (req, res) => {
|
||||
// 🐛 Breakpoint-Test: Setze hier einen Breakpoint zum Testen
|
||||
console.log("Health check aufgerufen!");
|
||||
@@ -17,9 +94,11 @@ app.get("/api/health", (req, res) => {
|
||||
status: "OK",
|
||||
message: "Backend is running",
|
||||
timestamp: new Date().toISOString(),
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
});
|
||||
});
|
||||
|
||||
// Beispiel: GET Endpoint ohne Body
|
||||
app.get("/api/photos", (req, res) => {
|
||||
// Beispiel-Endpunkt für Fotos
|
||||
res.json({
|
||||
@@ -30,16 +109,64 @@ app.get("/api/photos", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/api/print", (req, res) => {
|
||||
// Beispiel-Endpunkt für Druckaufträge
|
||||
const { photoId, copies } = req.body;
|
||||
// Beispiel: POST Endpoint mit Input-Validierung
|
||||
app.post(
|
||||
"/api/print",
|
||||
[
|
||||
// Validierungsregeln
|
||||
body('photoId')
|
||||
.isInt({ min: 1 })
|
||||
.withMessage('photoId muss eine positive Ganzzahl sein'),
|
||||
body('copies')
|
||||
.isInt({ min: 1, max: 100 })
|
||||
.withMessage('copies muss zwischen 1 und 100 liegen'),
|
||||
validateRequest
|
||||
],
|
||||
(req, res) => {
|
||||
// Input ist validiert - sicher zu verwenden
|
||||
const { photoId, copies } = req.body;
|
||||
|
||||
console.log(`Druckauftrag erhalten: Photo ID ${photoId}, Kopien: ${copies}`);
|
||||
console.log(`✅ Druckauftrag erhalten: Photo ID ${photoId}, Kopien: ${copies}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Druckauftrag für Photo ${photoId} mit ${copies} Kopien wurde erstellt`,
|
||||
jobId: Math.random().toString(36).substr(2, 9),
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Druckauftrag für Photo ${photoId} mit ${copies} Kopien wurde erstellt`,
|
||||
jobId: Math.random().toString(36).substr(2, 9),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// =============================================================================
|
||||
// Error Handler
|
||||
// =============================================================================
|
||||
|
||||
// 404 Handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: 'Endpoint nicht gefunden',
|
||||
path: req.path
|
||||
});
|
||||
});
|
||||
|
||||
// Global Error Handler
|
||||
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
console.error('❌ Error:', err);
|
||||
|
||||
// CORS Error
|
||||
if (err.message === 'Not allowed by CORS') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: 'CORS policy: Origin not allowed'
|
||||
});
|
||||
}
|
||||
|
||||
// Generic Error
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: process.env.NODE_ENV === 'production'
|
||||
? 'Interner Serverfehler'
|
||||
: err.message
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"module": "node16",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
@@ -20,7 +20,7 @@
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "node16",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
|
||||
@@ -1,47 +1,150 @@
|
||||
# Docker Compose für Full Stack Application
|
||||
# SICHERHEITS-OPTIMIERT für Gitea Actions & Production
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: ${APP_IMAGE:-fullstack-app:latest}
|
||||
# Automatischer Build aus dem aktuellen Verzeichnis
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
NODE_ENV: ${NODE_ENV:-production}
|
||||
BUILD_VERSION: ${BUILD_VERSION:-latest}
|
||||
|
||||
# Optional: Image-Name für Registry-Push (für CI/CD)
|
||||
# image: ${REGISTRY:-localhost}/${IMAGE_NAME:-fullstack-app}:${TAG:-latest}
|
||||
|
||||
container_name: ${APP_CONTAINER_NAME:-fullstack-app}
|
||||
restart: unless-stopped
|
||||
|
||||
# Non-root User (UID/GID 1000)
|
||||
user: "1000:1000"
|
||||
|
||||
environment:
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/appdb}
|
||||
- JWT_SECRET=${JWT_SECRET:-your-jwt-secret-change-this}
|
||||
networks:
|
||||
- app-network
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "${APP_PORT:-3000}:3000"
|
||||
# Optional: Nur wenn PostgreSQL verwendet wird
|
||||
- DATABASE_URL=${DATABASE_URL:-}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
|
||||
# Security Options
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE # Erlaubt Binding an Ports < 1024 (falls nötig)
|
||||
|
||||
# Resource Limits (verhindern DoS)
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 512M
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: ${POSTGRES_CONTAINER_NAME:-postgres-db}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-appdb}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
# Optional: Nur aktivieren wenn PostgreSQL verwendet wird
|
||||
# depends_on:
|
||||
# postgres:
|
||||
# condition: service_healthy
|
||||
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
- "${APP_PORT:-8080}:8080" # Port 8080 für non-root
|
||||
|
||||
# Volumes für persistente Daten
|
||||
volumes:
|
||||
- app-data:/data
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# =============================================================================
|
||||
# PostgreSQL Service (OPTIONAL)
|
||||
# =============================================================================
|
||||
# Nur aktivieren wenn PostgreSQL benötigt wird.
|
||||
# Für SQLite oder andere Datenbanken kann dieser Service entfernt werden.
|
||||
#
|
||||
# Um PostgreSQL zu aktivieren:
|
||||
# 1. Entferne die Kommentare (#) vor dem gesamten postgres-Service
|
||||
# 2. Aktiviere "depends_on" im app-Service (siehe oben)
|
||||
# 3. Setze DATABASE_URL in .env
|
||||
# =============================================================================
|
||||
|
||||
# postgres:
|
||||
# image: postgres:15-alpine
|
||||
# container_name: ${POSTGRES_CONTAINER_NAME:-postgres-db}
|
||||
# restart: unless-stopped
|
||||
#
|
||||
# # Non-root User (postgres verwendet UID 70 standardmäßig)
|
||||
# user: "70:70"
|
||||
#
|
||||
# environment:
|
||||
# POSTGRES_DB: ${POSTGRES_DB:-appdb}
|
||||
# POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
# POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # MUSS in .env gesetzt werden!
|
||||
# POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256" # Sichere Auth
|
||||
# POSTGRES_HOST_AUTH_METHOD: scram-sha-256
|
||||
#
|
||||
# # Security Options
|
||||
# security_opt:
|
||||
# - no-new-privileges:true
|
||||
# cap_drop:
|
||||
# - ALL
|
||||
# cap_add:
|
||||
# - CHOWN
|
||||
# - DAC_OVERRIDE
|
||||
# - FOWNER
|
||||
# - SETGID
|
||||
# - SETUID
|
||||
#
|
||||
# # Resource Limits
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: '1.0'
|
||||
# memory: 1G
|
||||
# reservations:
|
||||
# cpus: '0.25'
|
||||
# memory: 256M
|
||||
#
|
||||
# volumes:
|
||||
# - postgres_data:/var/lib/postgresql/data
|
||||
#
|
||||
# networks:
|
||||
# - app-network
|
||||
#
|
||||
# # KEIN Port-Expose nach außen - nur intern erreichbar!
|
||||
# # Für lokales Debugging kann der Port freigegeben werden:
|
||||
# # ports:
|
||||
# # - "${POSTGRES_PORT:-5432}:5432"
|
||||
#
|
||||
# healthcheck:
|
||||
# test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
|
||||
# interval: 10s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
# start_period: 10s
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
# Optional: Netzwerk isolieren
|
||||
internal: false # Auf 'true' setzen wenn kein Internet-Zugriff nötig
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
# Persistente Daten für die App (z.B. SQLite-Datenbank, Uploads, etc.)
|
||||
app-data:
|
||||
driver: local
|
||||
|
||||
# Optional: Nur wenn PostgreSQL verwendet wird
|
||||
# postgres_data:
|
||||
# driver: local
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
# Frontend Development Dockerfile
|
||||
# SICHERHEITS-OPTIMIERT: Non-root User
|
||||
FROM node:18-alpine
|
||||
|
||||
# Non-root User erstellen (node-User existiert bereits)
|
||||
RUN mkdir -p /app && chown -R node:node /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Wechsle zu non-root User VOR Package-Installation
|
||||
USER node
|
||||
|
||||
# Package files kopieren
|
||||
COPY package*.json ./
|
||||
COPY --chown=node:node package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Source code kopieren
|
||||
COPY . .
|
||||
COPY --chown=node:node . .
|
||||
|
||||
# Port exposieren
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Einfacher Lock-Mechanismus ohne störende Ausgaben
|
||||
LOCK_FILE="/tmp/kill-dev-processes.lock"
|
||||
|
||||
# Prüfe auf bereits laufende Instanz
|
||||
if [ -f "$LOCK_FILE" ]; then
|
||||
# Prüfe ob Prozess tatsächlich noch läuft
|
||||
if [ -f "$LOCK_FILE" ] && kill -0 $(cat "$LOCK_FILE") 2>/dev/null; then
|
||||
# Stilles Beenden wenn bereits aktiv
|
||||
exit 0
|
||||
else
|
||||
# Aufräumen alter Lock-Dateien
|
||||
rm -f "$LOCK_FILE" 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
# Erstelle Lock-File mit aktueller PID
|
||||
echo $$ > "$LOCK_FILE"
|
||||
trap "rm -f $LOCK_FILE 2>/dev/null" EXIT
|
||||
|
||||
echo "🛑 Beende alle Development-Prozesse..."
|
||||
echo ""
|
||||
|
||||
# Finde alle Development-Prozesse
|
||||
FRONTEND_PIDS=$(ps aux | grep -E 'npm start|react-scripts' | grep -v grep | awk '{print $2}')
|
||||
BACKEND_PIDS=$(ps aux | grep 'ts-node-dev' | grep -v grep | awk '{print $2}')
|
||||
|
||||
echo "Frontend-Prozesse (PIDs): $FRONTEND_PIDS"
|
||||
echo "Backend-Prozesse (PIDs): $BACKEND_PIDS"
|
||||
echo ""
|
||||
|
||||
# Beende Frontend-Prozesse
|
||||
if [ ! -z "$FRONTEND_PIDS" ]; then
|
||||
echo "Beende Frontend-Prozesse..."
|
||||
echo $FRONTEND_PIDS | xargs kill -TERM 2>/dev/null
|
||||
sleep 2
|
||||
echo $FRONTEND_PIDS | xargs kill -KILL 2>/dev/null
|
||||
echo "✅ Frontend-Prozesse beendet"
|
||||
else
|
||||
echo "❌ Keine Frontend-Prozesse gefunden"
|
||||
fi
|
||||
|
||||
# Beende Backend-Prozesse
|
||||
if [ ! -z "$BACKEND_PIDS" ]; then
|
||||
echo "Beende Backend-Prozesse..."
|
||||
echo $BACKEND_PIDS | xargs kill -TERM 2>/dev/null
|
||||
sleep 1
|
||||
echo $BACKEND_PIDS | xargs kill -KILL 2>/dev/null
|
||||
echo "✅ Backend-Prozesse beendet"
|
||||
else
|
||||
echo "❌ Keine Backend-Prozesse gefunden"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Verbleibende Development-Prozesse:"
|
||||
ps aux | grep -E 'npm start|react-scripts|ts-node-dev' | grep -v grep || echo "✅ Keine Development-Prozesse mehr vorhanden"
|
||||
Reference in New Issue
Block a user