Compare commits
8
Commits
aa8de00969
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fddc78618e | ||
|
|
6cd051d571 | ||
|
|
ed9793937e | ||
|
|
23475f650f | ||
|
|
284e30da71 | ||
|
|
d0bd84f4a2 | ||
|
|
94d02736f7 | ||
|
|
2f7bd923df |
No files matched your search
@@ -27,15 +27,15 @@ LOG_LEVEL=debug
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
# Secondary PostgreSQL Connection (for backend schema)
|
||||
# Secondary PostgreSQL Connection (for backend schema) - Local Docker
|
||||
DB_CONNECTION2=pgsql
|
||||
DB_HOST2=127.0.0.1
|
||||
DB_PORT2=5433
|
||||
DB_DATABASE2=risk_ingest_db
|
||||
DB_USERNAME2=risk_ingest_user
|
||||
DB_PASSWORD2=S0prast3r1a
|
||||
DB_PORT2=5432
|
||||
DB_DATABASE2=ingest_db
|
||||
DB_USERNAME2=ingest_user
|
||||
DB_PASSWORD2=ingest_pwd
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
@@ -43,9 +43,9 @@ SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
QUEUE_CONNECTION=sync
|
||||
|
||||
CACHE_STORE=database
|
||||
CACHE_STORE=file
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Gitea Runner Configuration
|
||||
# Kopiere diese Datei zu .env.gitea-runner und passe die Werte an
|
||||
|
||||
# ============================================
|
||||
# Gitea Instance
|
||||
# ============================================
|
||||
# URL deiner Gitea-Installation
|
||||
# Wenn Gitea auf der gleichen NAS läuft:
|
||||
GITEA_URL=http://your-synology-ip:3000
|
||||
|
||||
# Wenn Gitea in Docker läuft und im gleichen Netzwerk:
|
||||
# GITEA_URL=http://gitea:3000
|
||||
|
||||
# ============================================
|
||||
# Runner Configuration
|
||||
# ============================================
|
||||
# Runner Registration Token
|
||||
# Generiere diesen Token in Gitea:
|
||||
# Repository → Settings → Actions → Runners → Create new Runner
|
||||
RUNNER_TOKEN=your_gitea_runner_registration_token_here
|
||||
|
||||
# Runner Name (wird in Gitea angezeigt)
|
||||
RUNNER_NAME=synology-runner
|
||||
|
||||
# Maximale Anzahl gleichzeitiger Jobs
|
||||
RUNNER_CAPACITY=1
|
||||
|
||||
# Log Level: trace, debug, info, warn, error, fatal
|
||||
LOG_LEVEL=info
|
||||
@@ -0,0 +1,75 @@
|
||||
# Laravel Application Configuration für Synology NAS Deployment
|
||||
# Kopiere diese Datei zu .env.synology und passe die Werte an
|
||||
|
||||
# ============================================
|
||||
# Application Settings
|
||||
# ============================================
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=production
|
||||
APP_KEY=base64:L9RVZ3pNFyAvTbMqicT1rL5GbgE+7lJerkU9wyc95H8=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://your-synology-ip:8080
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
# ============================================
|
||||
# Logging
|
||||
# ============================================
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=info
|
||||
|
||||
# ============================================
|
||||
# Database - PostgreSQL (Haupt-Datenbank)
|
||||
# ============================================
|
||||
DB_CONNECTION2=pgsql
|
||||
DB_HOST2=postgres
|
||||
DB_PORT2=5432
|
||||
DB_DATABASE2=ingest_db
|
||||
DB_USERNAME2=ingest_user
|
||||
DB_PASSWORD2=ingest_pwd
|
||||
|
||||
# ============================================
|
||||
# Session & Cache
|
||||
# ============================================
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
# ============================================
|
||||
# Broadcasting & Queues
|
||||
# ============================================
|
||||
BROADCAST_CONNECTION=log
|
||||
QUEUE_CONNECTION=sync
|
||||
|
||||
# ============================================
|
||||
# Cache
|
||||
# ============================================
|
||||
CACHE_STORE=file
|
||||
FILESYSTEM_DISK=local
|
||||
|
||||
# ============================================
|
||||
# Mail Configuration
|
||||
# ============================================
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# ============================================
|
||||
# Vite
|
||||
# ============================================
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
@@ -0,0 +1,247 @@
|
||||
name: Build and Deploy to Synology NAS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: laravel-app
|
||||
DOCKER_REGISTRY: ${{ secrets.SYNOLOGY_HOST }}:5000 # Synology Docker Registry Port
|
||||
|
||||
jobs:
|
||||
# Job 1: Run Tests
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Validate Conventional Commit (subject line only)
|
||||
id: validate
|
||||
run: |
|
||||
SUBJECT="$(git log -1 --pretty=%s)"
|
||||
echo "Commit subject: $SUBJECT"
|
||||
if echo "$SUBJECT" | grep -Eq '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\(.+\))?: .+'; then
|
||||
echo "valid=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "valid=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup PHP
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
extensions: mbstring, xml, ctype, json, bcmath, pdo, pdo_sqlite
|
||||
coverage: none
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
run: composer install --prefer-dist --no-progress --no-suggest
|
||||
|
||||
- name: Setup Node.js
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
run: npm ci
|
||||
|
||||
- name: Build Frontend Assets
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
run: npm run build
|
||||
|
||||
- name: Run Tests
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
run: php artisan test --exclude-group=backend-db
|
||||
|
||||
# Job 2: Build and Push Docker Image
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Conventional Commit
|
||||
id: validate
|
||||
run: |
|
||||
SUBJECT="$(git log -1 --pretty=%s)"
|
||||
echo "Commit subject: $SUBJECT"
|
||||
if echo "$SUBJECT" | grep -Eq '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\(.+\))?: .+'; then
|
||||
echo "valid=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "valid=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Stop if not a Conventional Commit
|
||||
if: ${{ steps.validate.outputs.valid != 'true' }}
|
||||
run: |
|
||||
echo "Latest commit is not a valid Conventional Commit. Skipping build."
|
||||
exit 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Determine Docker Tag
|
||||
id: docker_tag
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
run: |
|
||||
if [ "${{ github.ref_name }}" == "main" ]; then
|
||||
echo "tag=latest" >> "$GITHUB_OUTPUT"
|
||||
echo "env=production" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
|
||||
echo "env=staging" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build Docker Image
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: ${{ env.DOCKER_IMAGE }}:${{ steps.docker_tag.outputs.tag }}
|
||||
|
||||
- name: Save Docker Image
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
run: |
|
||||
docker save ${{ env.DOCKER_IMAGE }}:${{ steps.docker_tag.outputs.tag }} | gzip > laravel-app.tar.gz
|
||||
|
||||
- name: Upload Docker Image Artifact
|
||||
if: ${{ steps.validate.outputs.valid == 'true' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-image
|
||||
path: laravel-app.tar.gz
|
||||
retention-days: 1
|
||||
|
||||
# Job 3: Deploy to Synology NAS
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'develop')
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Docker Image Artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
- name: Determine Environment
|
||||
id: env
|
||||
run: |
|
||||
if [ "${{ github.ref_name }}" == "main" ]; then
|
||||
echo "env=production" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "env=staging" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup SSH
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SYNOLOGY_SSH_KEY }}" > ~/.ssh/synology_key
|
||||
chmod 600 ~/.ssh/synology_key
|
||||
# Disable strict host key checking for deployment
|
||||
cat >> ~/.ssh/config << 'EOF'
|
||||
Host *
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
EOF
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
- name: Copy Files to Synology
|
||||
run: |
|
||||
# Create directory if it doesn't exist
|
||||
ssh -i ~/.ssh/synology_key \
|
||||
${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \
|
||||
"mkdir -p /volume1/docker/laravel-app"
|
||||
|
||||
# Transfer Docker Image using cat over SSH
|
||||
cat laravel-app.tar.gz | ssh -i ~/.ssh/synology_key \
|
||||
${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \
|
||||
"cat > /volume1/docker/laravel-app/laravel-app.tar.gz"
|
||||
|
||||
# Transfer Docker Compose file
|
||||
cat docker-compose.synology.yml | ssh -i ~/.ssh/synology_key \
|
||||
${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \
|
||||
"cat > /volume1/docker/laravel-app/docker-compose.synology.yml"
|
||||
|
||||
# Transfer deployment script
|
||||
cat scripts/deploy-synology.sh | ssh -i ~/.ssh/synology_key \
|
||||
${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \
|
||||
"cat > /volume1/docker/laravel-app/deploy-synology.sh"
|
||||
|
||||
- name: Deploy on Synology
|
||||
run: |
|
||||
ssh -i ~/.ssh/synology_key \
|
||||
${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \
|
||||
"cd /volume1/docker/laravel-app && chmod +x deploy-synology.sh && ./deploy-synology.sh"
|
||||
|
||||
- name: Health Check
|
||||
run: |
|
||||
sleep 10 # Wait for container to start
|
||||
|
||||
# Check if the app is responding
|
||||
SYNOLOGY_IP="${{ secrets.SYNOLOGY_HOST }}"
|
||||
MAX_RETRIES=30
|
||||
RETRY_COUNT=0
|
||||
|
||||
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
||||
if curl -f -s http://$SYNOLOGY_IP:8080/health > /dev/null 2>&1; then
|
||||
echo "✅ Application is healthy!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "⏳ Waiting for application to be ready... ($RETRY_COUNT/$MAX_RETRIES)"
|
||||
sleep 5
|
||||
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||
done
|
||||
|
||||
echo "❌ Health check failed after $MAX_RETRIES attempts"
|
||||
exit 1
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
# Remove Docker image from Synology
|
||||
ssh -i ~/.ssh/synology_key \
|
||||
${{ secrets.SYNOLOGY_USER }}@${{ secrets.SYNOLOGY_HOST }} \
|
||||
"rm -f /volume1/docker/laravel-app/laravel-app.tar.gz" || true
|
||||
# Remove SSH key
|
||||
rm -f ~/.ssh/synology_key
|
||||
|
||||
# Job 4: Notify on Success/Failure (optional)
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, build, deploy]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Deployment Status
|
||||
run: |
|
||||
if [ "${{ needs.deploy.result }}" == "success" ]; then
|
||||
echo "✅ Deployment to Synology NAS successful!"
|
||||
echo "🌐 Application available at: http://${{ secrets.SYNOLOGY_HOST }}:8080"
|
||||
else
|
||||
echo "❌ Deployment failed. Check the logs for details."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,242 @@
|
||||
name: Deploy to Azure AKS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Environment to deploy to'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
default: staging
|
||||
|
||||
env:
|
||||
REGISTRY: mylaravelregistry.azurecr.io
|
||||
IMAGE_NAME: laravel-app
|
||||
TERRAFORM_VERSION: 1.9.0
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
image_tag: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Azure Container Registry
|
||||
uses: azure/docker-login@v1
|
||||
with:
|
||||
login-server: ${{ env.REGISTRY }}
|
||||
username: ${{ secrets.ACR_USERNAME }}
|
||||
password: ${{ secrets.ACR_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha,prefix={{branch}}-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
|
||||
|
||||
terraform-deploy:
|
||||
name: Terraform Deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-push
|
||||
environment: ${{ github.event.inputs.environment || 'staging' }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: terraform
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Terraform
|
||||
uses: hashicorp/setup-terraform@v3
|
||||
with:
|
||||
terraform_version: ${{ env.TERRAFORM_VERSION }}
|
||||
|
||||
- name: Azure Login
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- name: Create terraform.tfvars
|
||||
run: |
|
||||
cat > terraform.tfvars <<EOF
|
||||
subscription_id = "${{ secrets.AZURE_SUBSCRIPTION_ID }}"
|
||||
resource_group_name = "trusted_ai_demo_rg"
|
||||
location = "germanywestcentral"
|
||||
aks_cluster_name = "trai_k8s_cluster"
|
||||
|
||||
app_name = "laravel-app"
|
||||
app_namespace = "laravel-app"
|
||||
app_env = "${{ github.event.inputs.environment || 'staging' }}"
|
||||
app_debug = ${{ github.event.inputs.environment == 'production' && 'false' || 'true' }}
|
||||
app_replicas = ${{ github.event.inputs.environment == 'production' && '3' || '2' }}
|
||||
|
||||
docker_image = "${{ needs.build-and-push.outputs.image_tag }}"
|
||||
app_key = "${{ secrets.LARAVEL_APP_KEY }}"
|
||||
|
||||
postgresql_admin_username = "${{ secrets.POSTGRESQL_ADMIN_USERNAME }}"
|
||||
postgresql_admin_password = "${{ secrets.POSTGRESQL_ADMIN_PASSWORD }}"
|
||||
postgresql_sku_name = "${{ github.event.inputs.environment == 'production' && 'GP_Standard_D2s_v3' || 'B_Standard_B1ms' }}"
|
||||
postgresql_storage_mb = ${{ github.event.inputs.environment == 'production' && '131072' || '32768' }}
|
||||
|
||||
ingress_enabled = true
|
||||
ingress_host = "${{ secrets.INGRESS_HOST }}"
|
||||
|
||||
ssl_enabled = ${{ github.event.inputs.environment == 'production' && 'true' || 'false' }}
|
||||
ssl_issuer_email = "${{ secrets.SSL_ISSUER_EMAIL }}"
|
||||
|
||||
db_restore_enabled = false
|
||||
|
||||
alert_email_address = "${{ secrets.ALERT_EMAIL }}"
|
||||
EOF
|
||||
|
||||
- name: Terraform Init
|
||||
run: terraform init
|
||||
|
||||
- name: Terraform Validate
|
||||
run: terraform validate
|
||||
|
||||
- name: Terraform Plan
|
||||
run: terraform plan -out=tfplan
|
||||
|
||||
- name: Terraform Apply
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
|
||||
run: terraform apply -auto-approve tfplan
|
||||
|
||||
- name: Get kubectl credentials
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
|
||||
run: |
|
||||
az aks get-credentials \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--overwrite-existing
|
||||
|
||||
- name: Wait for deployment
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
|
||||
run: |
|
||||
kubectl wait --for=condition=ready pod \
|
||||
-l app=laravel-app \
|
||||
-n laravel-app \
|
||||
--timeout=300s || true
|
||||
|
||||
- name: Get Application URL
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
|
||||
id: get-url
|
||||
run: |
|
||||
APP_URL=$(terraform output -raw app_url)
|
||||
echo "url=$APP_URL" >> $GITHUB_OUTPUT
|
||||
echo "### Deployment Complete! 🚀" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Application URL: $APP_URL" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload Terraform Plan
|
||||
if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/production'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: terraform-plan
|
||||
path: terraform/tfplan
|
||||
|
||||
smoke-tests:
|
||||
name: Smoke Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: terraform-deploy
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
|
||||
|
||||
steps:
|
||||
- name: Azure Login
|
||||
uses: azure/login@v1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||
|
||||
- name: Get kubectl credentials
|
||||
run: |
|
||||
az aks get-credentials \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--overwrite-existing
|
||||
|
||||
- name: Check pod health
|
||||
run: |
|
||||
kubectl get pods -n laravel-app
|
||||
READY_PODS=$(kubectl get pods -n laravel-app -l app=laravel-app -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' | grep -o "True" | wc -l)
|
||||
if [ "$READY_PODS" -lt 1 ]; then
|
||||
echo "Error: No ready pods found"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ $READY_PODS pod(s) are ready"
|
||||
|
||||
- name: Check service endpoint
|
||||
run: |
|
||||
LOADBALANCER_IP=$(kubectl get svc ingress-nginx-controller -n ingress-nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
||||
if [ -z "$LOADBALANCER_IP" ]; then
|
||||
echo "Warning: LoadBalancer IP not yet assigned"
|
||||
else
|
||||
echo "✓ LoadBalancer IP: $LOADBALANCER_IP"
|
||||
|
||||
# Try to reach the application
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://$LOADBALANCER_IP --max-time 10)
|
||||
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 302 ]; then
|
||||
echo "✓ Application is responding (HTTP $HTTP_CODE)"
|
||||
else
|
||||
echo "Warning: Application returned HTTP $HTTP_CODE"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Check database connectivity
|
||||
run: |
|
||||
DB_SECRET=$(kubectl get secret laravel-app-db-credentials -n laravel-app -o jsonpath='{.data.DB_HOST}' | base64 -d)
|
||||
echo "✓ Database host configured: $DB_SECRET"
|
||||
|
||||
notify:
|
||||
name: Notify Deployment Status
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-and-push, terraform-deploy, smoke-tests]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Deployment Success
|
||||
if: ${{ needs.terraform-deploy.result == 'success' && needs.smoke-tests.result == 'success' }}
|
||||
run: |
|
||||
echo "### ✅ Deployment Successful!" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Environment: ${{ github.event.inputs.environment || 'staging' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Commit: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Deployment Failed
|
||||
if: ${{ needs.terraform-deploy.result == 'failure' || needs.smoke-tests.result == 'failure' }}
|
||||
run: |
|
||||
echo "### ❌ Deployment Failed!" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Please check the logs for details." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
@@ -39,6 +39,14 @@ yarn-error.log
|
||||
/database/*.sqlite
|
||||
/database/*.sqlite-journal
|
||||
*.sql.gz
|
||||
/backups
|
||||
|
||||
# CI/CD & Deployment
|
||||
.env.synology
|
||||
.env.gitea-runner
|
||||
kubeconfig
|
||||
*.tar.gz
|
||||
laravel-app.tar.gz
|
||||
|
||||
# Backup & Temporary Files
|
||||
*.bak
|
||||
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
# CI/CD Automatisierung für Synology NAS
|
||||
|
||||
Dieses Projekt ist vollständig für automatisches Deployment auf deiner Synology NAS mit Gitea Actions vorbereitet.
|
||||
|
||||
## 🚀 Schnellstart
|
||||
|
||||
### 1. Gitea Runner einrichten (einmalig)
|
||||
|
||||
```bash
|
||||
# Auf der Synology NAS
|
||||
cd /volume1/docker
|
||||
mkdir gitea-runner
|
||||
cd gitea-runner
|
||||
|
||||
# Environment Datei erstellen
|
||||
cat > .env << 'EOF'
|
||||
GITEA_URL=http://your-synology-ip:3000
|
||||
RUNNER_TOKEN=your_token_from_gitea
|
||||
RUNNER_NAME=synology-runner
|
||||
EOF
|
||||
|
||||
# Runner starten
|
||||
ll
|
||||
```
|
||||
|
||||
**Runner Token generieren:**
|
||||
1. Gitea öffnen
|
||||
2. Repository → Settings → Actions → Runners
|
||||
3. "Create new Runner" klicken
|
||||
4. Token kopieren und in `.env` einfügen
|
||||
|
||||
📖 Detaillierte Anleitung: [GITEA_RUNNER_SETUP.md](GITEA_RUNNER_SETUP.md)
|
||||
|
||||
### 2. Repository Secrets konfigurieren (einmalig)
|
||||
|
||||
In Gitea: **Repository → Settings → Secrets**
|
||||
|
||||
| Secret Name | Wert | Beschreibung |
|
||||
|------------|------|--------------|
|
||||
| `SYNOLOGY_HOST` | `192.168.1.100` | IP deiner Synology |
|
||||
| `SYNOLOGY_USER` | `admin` | SSH-Benutzername |
|
||||
| `SYNOLOGY_SSH_KEY` | `-----BEGIN...` | Private SSH-Key |
|
||||
| `APP_KEY` | `base64:...` | Laravel APP_KEY |
|
||||
|
||||
**SSH-Key erstellen:**
|
||||
|
||||
```bash
|
||||
# Auf der Synology NAS
|
||||
ssh-keygen -t ed25519 -C "gitea-deploy" -f ~/.ssh/gitea_deploy
|
||||
cat ~/.ssh/gitea_deploy.pub >> ~/.ssh/authorized_keys
|
||||
cat ~/.ssh/gitea_deploy # Diesen Key als Secret verwenden
|
||||
```
|
||||
|
||||
### 3. Code pushen und automatisch deployen! 🎉
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: meine neue Funktion"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Das wars! Der Rest läuft automatisch:
|
||||
- ✅ Tests werden ausgeführt
|
||||
- ✅ Docker Image wird gebaut
|
||||
- ✅ Deployment auf NAS
|
||||
- ✅ Health Check
|
||||
- ✅ Benachrichtigung bei Erfolg/Fehler
|
||||
|
||||
## 📁 Wichtige Dateien
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| [`.gitea/workflows/deploy-synology.yml`](.gitea/workflows/deploy-synology.yml) | Gitea Actions Workflow |
|
||||
| [`docker-compose.synology.yml`](docker-compose.synology.yml) | Docker Compose für Deployment |
|
||||
| [`scripts/deploy-synology.sh`](scripts/deploy-synology.sh) | Deployment-Script |
|
||||
| [`.env.synology.example`](.env.synology.example) | Environment-Beispiel |
|
||||
| [`SYNOLOGY_DEPLOYMENT.md`](SYNOLOGY_DEPLOYMENT.md) | Vollständige Deployment-Doku |
|
||||
| [`GITEA_RUNNER_SETUP.md`](GITEA_RUNNER_SETUP.md) | Gitea Runner Setup-Anleitung |
|
||||
|
||||
## 🔄 Workflow-Prozess
|
||||
|
||||
```
|
||||
1. Developer pushed Code zu Gitea
|
||||
↓
|
||||
2. Gitea Actions Workflow startet
|
||||
↓
|
||||
3. Tests laufen (Pest)
|
||||
↓
|
||||
4. Docker Image wird gebaut
|
||||
↓
|
||||
5. Image wird zur Synology NAS übertragen
|
||||
↓
|
||||
6. Deployment-Script läuft auf NAS
|
||||
↓
|
||||
7. Container werden aktualisiert
|
||||
↓
|
||||
8. Migrationen & Cache-Optimierung
|
||||
↓
|
||||
9. Health Check
|
||||
↓
|
||||
10. ✅ Deployment erfolgreich!
|
||||
```
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
### Automatisierte Tests
|
||||
- PHP-Tests mit Pest
|
||||
- Frontend-Build-Validierung
|
||||
- Nur erfolgreiche Builds werden deployed
|
||||
|
||||
### Intelligentes Deployment
|
||||
- Automatisches Backup vor Deployment
|
||||
- Rollback bei Fehlern
|
||||
- Health Checks nach Deployment
|
||||
- Zero-Downtime durch Docker
|
||||
|
||||
### Branch-spezifisches Deployment
|
||||
- `main` → Production (Port 8080)
|
||||
- `develop` → Staging (Port 8081)
|
||||
- Feature-Branches → Nur Tests
|
||||
|
||||
### Conventional Commits
|
||||
Der Workflow akzeptiert nur Conventional Commits:
|
||||
- `feat:` - Neue Features
|
||||
- `fix:` - Bugfixes
|
||||
- `docs:` - Dokumentation
|
||||
- `chore:` - Wartung
|
||||
- `refactor:` - Code-Refactoring
|
||||
- `test:` - Tests
|
||||
- `ci:` - CI/CD Änderungen
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
git commit -m "feat: add user authentication"
|
||||
git commit -m "fix: resolve database connection issue"
|
||||
```
|
||||
|
||||
## 🔍 Monitoring
|
||||
|
||||
### Workflow Status prüfen
|
||||
|
||||
**In Gitea:**
|
||||
1. Repository öffnen
|
||||
2. Tab "Actions" anklicken
|
||||
3. Alle Runs mit Status sehen
|
||||
|
||||
**Logs ansehen:**
|
||||
- Klicke auf einen Run
|
||||
- Klicke auf einen Job (test/build/deploy)
|
||||
- Sieh detaillierte Logs
|
||||
|
||||
### Container Logs
|
||||
|
||||
```bash
|
||||
# Auf der Synology NAS via SSH
|
||||
docker logs -f laravel_app
|
||||
docker logs -f laravel_postgres
|
||||
```
|
||||
|
||||
### Health Endpoint
|
||||
|
||||
```bash
|
||||
curl http://your-synology-ip:8080/health
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-12-17T10:30:00Z",
|
||||
"app": "Laravel",
|
||||
"env": "production"
|
||||
}
|
||||
```
|
||||
|
||||
## 🛠 Manuelle Befehle
|
||||
|
||||
### Manuelles Deployment
|
||||
|
||||
```bash
|
||||
# Auf der Synology NAS
|
||||
cd /volume1/docker/laravel-app
|
||||
./scripts/deploy-synology.sh
|
||||
```
|
||||
|
||||
### Container verwalten
|
||||
|
||||
```bash
|
||||
# Status prüfen
|
||||
docker-compose -f docker-compose.synology.yml ps
|
||||
|
||||
# Logs ansehen
|
||||
docker-compose -f docker-compose.synology.yml logs -f
|
||||
|
||||
# Neustart
|
||||
docker-compose -f docker-compose.synology.yml restart
|
||||
|
||||
# Stoppen
|
||||
docker-compose -f docker-compose.synology.yml down
|
||||
```
|
||||
|
||||
### Laravel Artisan
|
||||
|
||||
```bash
|
||||
# Migrationen
|
||||
docker exec laravel_app php artisan migrate
|
||||
|
||||
# Cache leeren
|
||||
docker exec laravel_app php artisan cache:clear
|
||||
|
||||
# Shell öffnen
|
||||
docker exec -it laravel_app sh
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Workflow startet nicht
|
||||
|
||||
**Prüfen:**
|
||||
1. Runner Status in Gitea → Settings → Actions → Runners
|
||||
2. Commit-Message ist Conventional Commit Format
|
||||
3. Branch ist `main` oder `develop`
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Runner neu starten
|
||||
docker restart gitea-runner
|
||||
|
||||
# Runner Logs prüfen
|
||||
docker logs gitea-runner
|
||||
```
|
||||
|
||||
### Deployment schlägt fehl
|
||||
|
||||
**Prüfen:**
|
||||
1. SSH-Verbindung zur NAS funktioniert
|
||||
2. Alle Secrets sind korrekt gesetzt
|
||||
3. Deployment-Script ist executable
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# SSH-Key testen
|
||||
ssh -i ~/.ssh/gitea_deploy admin@your-synology-ip
|
||||
|
||||
# Permissions prüfen
|
||||
chmod +x scripts/deploy-synology.sh
|
||||
```
|
||||
|
||||
### Health Check fehlschlägt
|
||||
|
||||
**Prüfen:**
|
||||
1. Container läuft: `docker ps | grep laravel_app`
|
||||
2. Logs: `docker logs laravel_app`
|
||||
3. Netzwerk: `curl http://localhost:8080/health`
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Container neu starten
|
||||
docker-compose -f docker-compose.synology.yml restart
|
||||
|
||||
# .env prüfen
|
||||
docker exec laravel_app cat .env.synology
|
||||
```
|
||||
|
||||
## 📚 Weitere Dokumentation
|
||||
|
||||
- **Deployment Details:** [SYNOLOGY_DEPLOYMENT.md](SYNOLOGY_DEPLOYMENT.md)
|
||||
- **Gitea Runner Setup:** [GITEA_RUNNER_SETUP.md](GITEA_RUNNER_SETUP.md)
|
||||
- **Workflow Konfiguration:** [.gitea/workflows/deploy-synology.yml](.gitea/workflows/deploy-synology.yml)
|
||||
- **Deployment Script:** [scripts/deploy-synology.sh](scripts/deploy-synology.sh)
|
||||
|
||||
## 🎉 Das wars!
|
||||
|
||||
Nach der einmaligen Einrichtung läuft alles automatisch:
|
||||
|
||||
1. Code schreiben
|
||||
2. Commit & Push
|
||||
3. Warten bis Deployment fertig ist
|
||||
4. Anwendung ist live! 🚀
|
||||
|
||||
**Happy Deploying!** 🎊
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
# 🚀 Laravel Application - Azure Kubernetes Deployment
|
||||
|
||||
Diese Anwendung ist bereit für das Deployment auf Azure Kubernetes Service (AKS) mit vollständiger Terraform-Automatisierung.
|
||||
|
||||
## 📁 Struktur
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── Dockerfile # Production-ready multi-stage Docker build
|
||||
├── docker-compose.yml # Lokale Entwicklung
|
||||
├── deploy/ # Docker-spezifische Konfiguration
|
||||
│ ├── docker-entrypoint.sh
|
||||
│ ├── nginx/
|
||||
│ └── supervisord.conf
|
||||
├── terraform/ # Terraform Infrastructure-as-Code
|
||||
│ ├── main.tf # Haupt-Konfiguration
|
||||
│ ├── variables.tf # Variablen
|
||||
│ ├── outputs.tf # Outputs
|
||||
│ ├── postgresql.tf # Azure PostgreSQL
|
||||
│ ├── kubernetes.tf # K8s Resources
|
||||
│ ├── ingress.tf # Nginx Ingress Controller
|
||||
│ ├── db-restore.tf # Database Restore Job
|
||||
│ ├── terraform.tfvars.example # Beispiel-Konfiguration
|
||||
│ ├── README.md # Ausführliche Dokumentation
|
||||
│ ├── QUICKSTART.md # 30-Minuten Schnellstart
|
||||
│ ├── GITHUB_ACTIONS_SETUP.md # CI/CD Setup
|
||||
│ └── scripts/
|
||||
│ ├── deploy.sh # Automatisches Deployment
|
||||
│ └── restore-db.sh # Database Restore
|
||||
├── .github/workflows/
|
||||
│ └── deploy-azure.yml # GitHub Actions CI/CD Pipeline
|
||||
└── backups/ # PostgreSQL Backup-Dateien
|
||||
└── *.dump
|
||||
```
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
### Infrastruktur
|
||||
- ✅ **Azure PostgreSQL Flexible Server** (16) mit automatischen Backups
|
||||
- ✅ **Azure Kubernetes Service (AKS)** mit Auto-Scaling
|
||||
- ✅ **Nginx Ingress Controller** mit LoadBalancer
|
||||
- ✅ **Let's Encrypt SSL/TLS** (optional)
|
||||
- ✅ **Horizontal Pod Autoscaler** (CPU & Memory basiert)
|
||||
- ✅ **Azure Monitor Integration** mit Log Analytics
|
||||
|
||||
### Container
|
||||
- ✅ **Multi-Stage Docker Build** (optimiert für Production)
|
||||
- ✅ **Alpine Linux** (minimales Image)
|
||||
- ✅ **PHP 8.2 + Nginx + Supervisor**
|
||||
- ✅ **PostgreSQL & SQLite Support**
|
||||
- ✅ **Vite Assets** werden beim Build kompiliert
|
||||
|
||||
### Deployment
|
||||
- ✅ **Terraform Infrastructure-as-Code**
|
||||
- ✅ **Kubernetes Manifests** als Terraform Resources
|
||||
- ✅ **Automatische DB-Migration** bei Deployment
|
||||
- ✅ **Database Backup Restore** als Kubernetes Job
|
||||
- ✅ **Zero-Downtime Rolling Updates**
|
||||
- ✅ **GitHub Actions CI/CD Pipeline**
|
||||
|
||||
## 🚀 Schnellstart
|
||||
|
||||
### Option 1: Automatisches Deployment (Empfohlen)
|
||||
|
||||
```bash
|
||||
# 1. Azure Login
|
||||
az login
|
||||
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
|
||||
|
||||
# 2. Container Registry Setup
|
||||
az acr create --resource-group trusted_ai_demo_rg --name mylaravelregistry --sku Basic
|
||||
az aks update --resource-group trusted_ai_demo_rg --name trai_k8s_cluster --attach-acr mylaravelregistry
|
||||
az acr login --name mylaravelregistry
|
||||
|
||||
# 3. Docker Image bauen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
|
||||
|
||||
# 4. Terraform konfigurieren
|
||||
cd terraform
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
nano terraform.tfvars # Wichtige Werte anpassen
|
||||
|
||||
# 5. Deployment ausführen
|
||||
./scripts/deploy.sh
|
||||
|
||||
# 6. Datenbank wiederherstellen
|
||||
./scripts/restore-db.sh
|
||||
```
|
||||
|
||||
**Deployment Zeit**: ~30 Minuten
|
||||
|
||||
### Option 2: Manuelle Schritte
|
||||
|
||||
Siehe [terraform/QUICKSTART.md](terraform/QUICKSTART.md) für detaillierte Anleitung.
|
||||
|
||||
## 📖 Dokumentation
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| [terraform/QUICKSTART.md](terraform/QUICKSTART.md) | 30-Minuten Schnellstart-Guide |
|
||||
| [terraform/README.md](terraform/README.md) | Ausführliche Dokumentation (Troubleshooting, Wartung, etc.) |
|
||||
| [terraform/GITHUB_ACTIONS_SETUP.md](terraform/GITHUB_ACTIONS_SETUP.md) | CI/CD Pipeline Setup |
|
||||
|
||||
## 🔧 Wichtige Konfiguration
|
||||
|
||||
### Terraform Variables ([terraform/terraform.tfvars](terraform/terraform.tfvars.example))
|
||||
|
||||
```hcl
|
||||
# Docker Image (von ACR)
|
||||
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
|
||||
|
||||
# Laravel APP_KEY (generiere mit: php artisan key:generate --show)
|
||||
app_key = "base64:..."
|
||||
|
||||
# PostgreSQL
|
||||
postgresql_admin_username = "pgadmin"
|
||||
postgresql_admin_password = "YourSecurePassword123!"
|
||||
|
||||
# Ingress & SSL
|
||||
ingress_enabled = true
|
||||
ssl_enabled = false # Auf true für Production mit Domain
|
||||
|
||||
# Database Restore
|
||||
db_restore_enabled = true
|
||||
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
|
||||
```
|
||||
|
||||
## 🏗️ Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Azure Cloud (germanywestcentral) │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────┐ │
|
||||
│ │ AKS Cluster: trai_k8s_cluster │ │
|
||||
│ │ │ │
|
||||
│ │ • Laravel App (2-6 Pods, Auto-Scaling) │ │
|
||||
│ │ • Nginx Ingress (LoadBalancer) │ │
|
||||
│ │ • ConfigMaps & Secrets │ │
|
||||
│ └────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────┐ │
|
||||
│ │ PostgreSQL Flexible Server │ │
|
||||
│ │ • PostgreSQL 16 │ │
|
||||
│ │ • 32 GB Storage │ │
|
||||
│ │ • Auto Backups (7 days) │ │
|
||||
│ └────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 🎮 Häufige Befehle
|
||||
|
||||
### Deployment
|
||||
|
||||
```bash
|
||||
# Neues Image deployen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 .
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1
|
||||
kubectl set image deployment/laravel-app laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 -n laravel-app
|
||||
|
||||
# Terraform apply
|
||||
cd terraform && terraform apply
|
||||
|
||||
# Database Restore
|
||||
cd terraform && ./scripts/restore-db.sh
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
|
||||
```bash
|
||||
# kubectl credentials abrufen
|
||||
az aks get-credentials --resource-group trusted_ai_demo_rg --name trai_k8s_cluster
|
||||
|
||||
# Pods prüfen
|
||||
kubectl get pods -n laravel-app
|
||||
|
||||
# Logs anzeigen
|
||||
kubectl logs -n laravel-app -l app=laravel-app -f
|
||||
|
||||
# Service Status
|
||||
kubectl get svc -n laravel-app
|
||||
kubectl get ingress -n laravel-app
|
||||
|
||||
# LoadBalancer IP
|
||||
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
```bash
|
||||
# Pod Status detailliert
|
||||
kubectl describe pod <pod-name> -n laravel-app
|
||||
|
||||
# Events prüfen
|
||||
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
|
||||
|
||||
# Shell in Pod
|
||||
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
|
||||
|
||||
# Port-forward für lokalen Zugriff
|
||||
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
|
||||
```
|
||||
|
||||
## 🔄 CI/CD Pipeline
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
Die Pipeline wird automatisch ausgeführt bei:
|
||||
- Push auf `main` Branch (Staging)
|
||||
- Push auf `production` Branch (Production)
|
||||
- Manuell über GitHub UI
|
||||
|
||||
**Setup**: Siehe [terraform/GITHUB_ACTIONS_SETUP.md](terraform/GITHUB_ACTIONS_SETUP.md)
|
||||
|
||||
### Workflow Steps
|
||||
|
||||
1. **Build & Push** - Docker Image bauen und zu ACR pushen
|
||||
2. **Terraform Deploy** - Infrastruktur mit Terraform deployen
|
||||
3. **Smoke Tests** - Basis-Tests nach Deployment
|
||||
4. **Notifications** - Status-Benachrichtigungen
|
||||
|
||||
## 📊 Kosten-Übersicht
|
||||
|
||||
**Staging/Development:**
|
||||
- AKS: ~30-50€/Monat (2 Nodes, B2s)
|
||||
- PostgreSQL: ~15-20€/Monat (Basic tier)
|
||||
- Load Balancer: ~5€/Monat
|
||||
- **Total: ~50-75€/Monat**
|
||||
|
||||
**Production:**
|
||||
- AKS: ~100-150€/Monat (3 Nodes, D2s_v3)
|
||||
- PostgreSQL: ~50-80€/Monat (General Purpose)
|
||||
- Load Balancer: ~5€/Monat
|
||||
- **Total: ~155-235€/Monat**
|
||||
|
||||
## 🔒 Sicherheit
|
||||
|
||||
### Implementiert
|
||||
- ✅ Secrets Management via Kubernetes Secrets
|
||||
- ✅ PostgreSQL Firewall Rules
|
||||
- ✅ Nginx Rate Limiting
|
||||
- ✅ Resource Limits für Pods
|
||||
- ✅ Rolling Updates (Zero Downtime)
|
||||
|
||||
### Empfohlen für Production
|
||||
- [ ] Azure Key Vault Integration
|
||||
- [ ] VNet Integration für PostgreSQL
|
||||
- [ ] Private Endpoints
|
||||
- [ ] Network Policies
|
||||
- [ ] Pod Security Policies
|
||||
- [ ] RBAC für Kubernetes
|
||||
- [ ] Azure AD Integration
|
||||
|
||||
## 🚦 Status Checks
|
||||
|
||||
### Deployment erfolgreich?
|
||||
|
||||
```bash
|
||||
# Pods running?
|
||||
kubectl get pods -n laravel-app
|
||||
# Sollte: 2/2 Running
|
||||
|
||||
# Service erreichbar?
|
||||
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||
# Sollte: EXTERNAL-IP anzeigen
|
||||
|
||||
# Database connected?
|
||||
kubectl logs -n laravel-app -l app=laravel-app | grep -i "database"
|
||||
|
||||
# Anwendung im Browser öffnen
|
||||
terraform output app_url
|
||||
```
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
### Bei Problemen
|
||||
|
||||
1. **Terraform Issues**: Siehe [terraform/README.md#troubleshooting](terraform/README.md#troubleshooting)
|
||||
2. **Kubernetes Issues**: `kubectl describe pod <pod-name> -n laravel-app`
|
||||
3. **Database Issues**: Prüfe Firewall Rules und Secrets
|
||||
4. **Ingress Issues**: `kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller`
|
||||
|
||||
### Nützliche Logs
|
||||
|
||||
```bash
|
||||
# Application Logs
|
||||
kubectl logs -n laravel-app -l app=laravel-app -f
|
||||
|
||||
# Database Restore Logs
|
||||
kubectl logs -n laravel-app -l job-type=database-restore
|
||||
|
||||
# Ingress Controller Logs
|
||||
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f
|
||||
|
||||
# All Events
|
||||
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
|
||||
```
|
||||
|
||||
## 📚 Nächste Schritte
|
||||
|
||||
Nach erfolgreichem Deployment:
|
||||
|
||||
1. **Domain konfigurieren**
|
||||
- DNS A-Record auf LoadBalancer IP
|
||||
- SSL aktivieren in terraform.tfvars
|
||||
- `terraform apply`
|
||||
|
||||
2. **Monitoring einrichten**
|
||||
- Azure Monitor im Portal prüfen
|
||||
- Alert Rules konfigurieren
|
||||
- Application Insights (optional)
|
||||
|
||||
3. **CI/CD Pipeline**
|
||||
- GitHub Actions Secrets konfigurieren
|
||||
- Branch Protection Rules
|
||||
- Staging → Production Workflow
|
||||
|
||||
4. **Security Hardening**
|
||||
- Azure Key Vault
|
||||
- VNet Integration
|
||||
- Private Endpoints
|
||||
- RBAC
|
||||
|
||||
5. **Performance Optimization**
|
||||
- Redis für Cache/Sessions
|
||||
- CDN für Static Assets
|
||||
- Database Query Optimization
|
||||
|
||||
## 🎉 Fertig!
|
||||
|
||||
Deine Laravel-Anwendung läuft jetzt produktionsbereit auf Azure Kubernetes Service!
|
||||
|
||||
**Deployment Command**: `cd terraform && ./scripts/deploy.sh`
|
||||
|
||||
---
|
||||
|
||||
**Erstellt**: Dezember 2024
|
||||
**Version**: 1.0.0
|
||||
**Status**: Production Ready ✅
|
||||
@@ -0,0 +1,210 @@
|
||||
# Deployment Files Übersicht
|
||||
|
||||
Diese Datei gibt einen Überblick über alle Dateien, die für das automatisierte Deployment auf deiner Synology NAS erstellt wurden.
|
||||
|
||||
## 📋 Erstelle Dateien
|
||||
|
||||
### 1. CI/CD Workflow
|
||||
- **`.gitea/workflows/deploy-synology.yml`**
|
||||
- Gitea Actions Workflow für automatisches Build & Deployment
|
||||
- Führt Tests aus, baut Docker Image, deployt auf NAS
|
||||
- Triggert bei Push auf `main` oder `develop`
|
||||
|
||||
### 2. Docker Konfiguration
|
||||
- **`docker-compose.synology.yml`**
|
||||
- Docker Compose für Synology NAS Deployment
|
||||
- Definiert Laravel App + PostgreSQL Datenbank
|
||||
- Mit persistenten Volumes und Health Checks
|
||||
|
||||
- **`docker-compose.gitea-runner.yml`**
|
||||
- Docker Compose für Gitea Runner
|
||||
- Ermöglicht automatische Workflows
|
||||
|
||||
- **`Dockerfile`**
|
||||
- Bereits vorhanden, wird verwendet für Image-Build
|
||||
- Multi-stage Build für optimale Image-Größe
|
||||
|
||||
### 3. Environment Konfiguration
|
||||
- **`.env.synology.example`**
|
||||
- Beispiel-Environment für Synology Deployment
|
||||
- Muss kopiert werden zu `.env.synology`
|
||||
- Enthält alle notwendigen Variablen
|
||||
|
||||
- **`.env.gitea-runner.example`**
|
||||
- Beispiel-Environment für Gitea Runner
|
||||
- Muss kopiert werden zu `.env.gitea-runner`
|
||||
|
||||
### 4. Deployment Scripts
|
||||
- **`scripts/deploy-synology.sh`**
|
||||
- Automatisches Deployment-Script
|
||||
- Läuft auf der Synology NAS
|
||||
- Handhabt Backup, Deployment, Rollback, Health Checks
|
||||
|
||||
### 5. Dokumentation
|
||||
- **`SYNOLOGY_DEPLOYMENT.md`**
|
||||
- Vollständige Anleitung für manuelles Deployment
|
||||
- Inklusive CI/CD Setup Abschnitt
|
||||
- Troubleshooting Tipps
|
||||
|
||||
- **`GITEA_RUNNER_SETUP.md`**
|
||||
- Detaillierte Anleitung für Gitea Runner Setup
|
||||
- Secrets-Konfiguration
|
||||
- Monitoring & Troubleshooting
|
||||
|
||||
- **`CI_CD_README.md`**
|
||||
- Schnellstart-Anleitung für CI/CD
|
||||
- Übersicht über alle Features
|
||||
- Wichtige Befehle
|
||||
|
||||
- **`DEPLOYMENT_FILES_OVERVIEW.md`** (diese Datei)
|
||||
- Übersicht über alle erstellten Dateien
|
||||
|
||||
### 6. Application Code
|
||||
- **`routes/web.php`**
|
||||
- Health Check Endpoint hinzugefügt (`/health`)
|
||||
- Wird für automatische Health Checks verwendet
|
||||
|
||||
### 7. Git Konfiguration
|
||||
- **`.gitignore`**
|
||||
- Erweitert um CI/CD spezifische Dateien
|
||||
- `.env.synology` und `.env.gitea-runner` ignoriert
|
||||
- Build-Artefakte ignoriert
|
||||
|
||||
## 🔧 Verwendung der Dateien
|
||||
|
||||
### Einmalige Einrichtung
|
||||
|
||||
1. **Gitea Runner deployen:**
|
||||
```bash
|
||||
# Auf Synology NAS
|
||||
cp .env.gitea-runner.example .env.gitea-runner
|
||||
# Bearbeite .env.gitea-runner mit deinen Werten
|
||||
docker-compose -f docker-compose.gitea-runner.yml up -d
|
||||
```
|
||||
|
||||
2. **Repository Secrets in Gitea setzen:**
|
||||
- `SYNOLOGY_HOST`
|
||||
- `SYNOLOGY_USER`
|
||||
- `SYNOLOGY_SSH_KEY`
|
||||
- `APP_KEY`
|
||||
|
||||
3. **Workflow-Dateien committen:**
|
||||
```bash
|
||||
git add .gitea/
|
||||
git commit -m "ci: add automated deployment"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### Manuelles Deployment
|
||||
|
||||
Falls du manuell deployen möchtest (ohne CI/CD):
|
||||
|
||||
```bash
|
||||
# Auf Synology NAS
|
||||
cd /volume1/docker/laravel-app
|
||||
cp .env.synology.example .env.synology
|
||||
# Bearbeite .env.synology
|
||||
docker-compose -f docker-compose.synology.yml up -d --build
|
||||
```
|
||||
|
||||
## 📊 Datei-Abhängigkeiten
|
||||
|
||||
```
|
||||
.gitea/workflows/deploy-synology.yml
|
||||
├── docker-compose.synology.yml
|
||||
├── scripts/deploy-synology.sh
|
||||
├── .env.synology (zu erstellen)
|
||||
└── Dockerfile
|
||||
|
||||
docker-compose.gitea-runner.yml
|
||||
└── .env.gitea-runner (zu erstellen)
|
||||
```
|
||||
|
||||
## ✅ Checkliste für Deployment
|
||||
|
||||
### Vor dem ersten Deployment:
|
||||
|
||||
- [ ] Gitea Runner deployed (`docker-compose.gitea-runner.yml`)
|
||||
- [ ] Runner in Gitea registriert
|
||||
- [ ] Repository Secrets gesetzt
|
||||
- [ ] `.env.synology` auf NAS erstellt und konfiguriert
|
||||
- [ ] SSH-Keys für Deployment eingerichtet
|
||||
- [ ] Projekt-Verzeichnis auf NAS: `/volume1/docker/laravel-app`
|
||||
|
||||
### Vor jedem Push:
|
||||
|
||||
- [ ] Tests lokal ausgeführt (`php artisan test`)
|
||||
- [ ] Conventional Commit Message verwendet
|
||||
- [ ] Code reviewed
|
||||
|
||||
### Nach dem Deployment:
|
||||
|
||||
- [ ] Workflow Status in Gitea Actions überprüft
|
||||
- [ ] Health Check erfolgreich: `curl http://nas-ip:8080/health`
|
||||
- [ ] Anwendung erreichbar: `http://nas-ip:8080`
|
||||
- [ ] Logs prüfen: `docker logs laravel_app`
|
||||
|
||||
## 🔐 Sicherheit
|
||||
|
||||
### Dateien die NICHT committet werden sollten:
|
||||
|
||||
- `.env.synology` - Enthält Produktions-Secrets
|
||||
- `.env.gitea-runner` - Enthält Runner-Token
|
||||
- `kubeconfig` - Falls verwendet
|
||||
- `*.tar.gz` - Build-Artefakte
|
||||
- SSH Private Keys
|
||||
|
||||
Diese Dateien sind bereits in `.gitignore` eingetragen!
|
||||
|
||||
### Dateien die committet werden sollten:
|
||||
|
||||
- `.env.synology.example` - Template für andere Entwickler
|
||||
- `.env.gitea-runner.example` - Template für Runner
|
||||
- Alle `docker-compose*.yml` Dateien
|
||||
- Alle Dokumentations-Dateien
|
||||
- Workflow-Dateien in `.gitea/workflows/`
|
||||
- Deployment-Scripts in `scripts/`
|
||||
|
||||
## 🚀 Nächste Schritte
|
||||
|
||||
1. **Lies die Dokumentation:**
|
||||
- Start mit [CI_CD_README.md](CI_CD_README.md) für Schnellstart
|
||||
- Dann [GITEA_RUNNER_SETUP.md](GITEA_RUNNER_SETUP.md) für Runner-Setup
|
||||
- Bei Problemen: [SYNOLOGY_DEPLOYMENT.md](SYNOLOGY_DEPLOYMENT.md)
|
||||
|
||||
2. **Setup durchführen:**
|
||||
- Folge der Anleitung in [GITEA_RUNNER_SETUP.md](GITEA_RUNNER_SETUP.md)
|
||||
- Konfiguriere Secrets in Gitea
|
||||
- Teste mit einem Push
|
||||
|
||||
3. **Deployment testen:**
|
||||
- Mache eine kleine Änderung
|
||||
- Committe mit Conventional Commit
|
||||
- Push und beobachte Workflow in Gitea Actions
|
||||
|
||||
4. **Optional: Erweitere Workflow:**
|
||||
- Füge Notifications hinzu (Email, Telegram, Discord)
|
||||
- Konfiguriere mehrere Environments (Staging, Production)
|
||||
- Füge weitere Tests hinzu
|
||||
|
||||
## 📝 Hinweise
|
||||
|
||||
- Alle Scripts sind für **Synology DSM 7.x** optimiert
|
||||
- Docker und Docker Compose müssen auf der NAS installiert sein
|
||||
- Gitea Actions muss in Gitea aktiviert sein
|
||||
- PHP 8.4 und Laravel 12 werden verwendet
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
Bei Fragen oder Problemen:
|
||||
|
||||
1. Prüfe die Troubleshooting-Sektionen in den Dokumentations-Dateien
|
||||
2. Prüfe Logs: `docker logs gitea-runner` und `docker logs laravel_app`
|
||||
3. Verifiziere alle Secrets und Environment-Variablen
|
||||
4. Stelle sicher, dass alle Ports verfügbar sind
|
||||
|
||||
---
|
||||
|
||||
**Erstellt am:** 2025-12-17
|
||||
**Version:** 1.0
|
||||
**Für:** Synology NAS Deployment mit Gitea Actions
|
||||
+6
-4
@@ -30,13 +30,14 @@
|
||||
# Create user, dirs, and sockets
|
||||
RUN mkdir -p /run/nginx /run/php /var/www/html /var/log/supervisor
|
||||
|
||||
# Configure PHP-FPM socket
|
||||
# Configure PHP-FPM socket and environment variables
|
||||
RUN sed -i 's|^listen = 127\\.0\\.0\\.1:9000|listen = /run/php/php-fpm.sock|' /etc/php82/php-fpm.d/www.conf \
|
||||
&& sed -i 's|^;listen.owner = nobody|listen.owner = nginx|' /etc/php82/php-fpm.d/www.conf \
|
||||
&& sed -i 's|^;listen.group = nobody|listen.group = nginx|' /etc/php82/php-fpm.d/www.conf \
|
||||
&& sed -i 's|^;listen.mode = 0660|listen.mode = 0660|' /etc/php82/php-fpm.d/www.conf \
|
||||
&& sed -i 's|^user = nobody|user = nginx|' /etc/php82/php-fpm.d/www.conf \
|
||||
&& sed -i 's|^group = nobody|group = nginx|' /etc/php82/php-fpm.d/www.conf
|
||||
&& sed -i 's|^group = nobody|group = nginx|' /etc/php82/php-fpm.d/www.conf \
|
||||
&& sed -i 's|^;clear_env = no|clear_env = no|' /etc/php82/php-fpm.d/www.conf
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
@@ -46,6 +47,7 @@
|
||||
COPY deploy/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY deploy/supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY deploy/nginx/nginx.default.conf.template /etc/nginx/templates/default.conf.template
|
||||
COPY deploy/nginx/.htpasswd /etc/nginx/.htpasswd
|
||||
COPY deploy/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
@@ -64,8 +66,8 @@ RUN mkdir -p /var/log/nginx \
|
||||
# Build frontend assets
|
||||
RUN npm ci && npm run build
|
||||
|
||||
# Optimize Laravel configuration
|
||||
RUN php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan event:cache
|
||||
# Optimize Laravel configuration (config:cache removed - will be done at runtime with correct env vars)
|
||||
RUN php artisan route:cache && php artisan view:cache && php artisan event:cache
|
||||
|
||||
# Entferne .env Datei, um sensible Daten nicht im finalen Image zu behalten
|
||||
RUN rm -f /var/www/html/.env
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
# Gitea Runner Setup auf Synology NAS
|
||||
|
||||
Diese Anleitung erklärt, wie du einen Gitea Actions Runner auf deiner Synology NAS einrichtest, um automatische Deployments durchzuführen.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Gitea läuft auf deiner Synology NAS
|
||||
- Docker ist auf der Synology NAS installiert
|
||||
- SSH-Zugriff auf die Synology NAS
|
||||
- Portainer läuft (optional, aber empfohlen für Container-Management)
|
||||
|
||||
## Option 1: Gitea Runner mit Docker Compose (empfohlen)
|
||||
|
||||
### 1. Runner Docker Compose Datei erstellen
|
||||
|
||||
Erstelle eine Datei `/volume1/docker/gitea-runner/docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
runner:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=http://your-gitea-url:3000
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}
|
||||
- GITEA_RUNNER_NAME=synology-runner
|
||||
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye
|
||||
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
networks:
|
||||
- gitea-network
|
||||
|
||||
networks:
|
||||
gitea-network:
|
||||
external: true
|
||||
```
|
||||
|
||||
### 2. Runner Token generieren
|
||||
|
||||
1. Öffne deine Gitea-Instanz im Browser
|
||||
2. Navigiere zu deinem Repository
|
||||
3. Gehe zu **Settings** → **Actions** → **Runners**
|
||||
4. Klicke auf **Create new Runner**
|
||||
5. Kopiere den generierten Token
|
||||
|
||||
### 3. Environment Variables setzen
|
||||
|
||||
Erstelle eine `.env` Datei im selben Verzeichnis:
|
||||
|
||||
```env
|
||||
RUNNER_TOKEN=dein_gitea_runner_token_hier
|
||||
```
|
||||
|
||||
### 4. Runner starten
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/gitea-runner
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 5. Runner verifizieren
|
||||
|
||||
```bash
|
||||
# Logs anschauen
|
||||
docker logs -f gitea-runner
|
||||
|
||||
# Status prüfen
|
||||
docker ps | grep gitea-runner
|
||||
```
|
||||
|
||||
In Gitea solltest du nun unter **Settings** → **Actions** → **Runners** deinen registrierten Runner sehen.
|
||||
|
||||
## Option 2: Runner direkt in Portainer deployen
|
||||
|
||||
### 1. In Portainer einloggen
|
||||
|
||||
Öffne Portainer: `http://your-synology-ip:9000`
|
||||
|
||||
### 2. Stack erstellen
|
||||
|
||||
1. Gehe zu **Stacks** → **+ Add stack**
|
||||
2. Name: `gitea-runner`
|
||||
3. Wähle **Web editor**
|
||||
4. Füge die obige docker-compose.yml ein
|
||||
|
||||
### 3. Environment Variables
|
||||
|
||||
Füge folgende Environment Variable hinzu:
|
||||
- **RUNNER_TOKEN**: Dein Gitea Runner Token
|
||||
|
||||
### 4. Deploy
|
||||
|
||||
Klicke auf **Deploy the stack**
|
||||
|
||||
## Runner Konfiguration
|
||||
|
||||
### Custom Runner Labels
|
||||
|
||||
Um spezifische Workflows auszuführen, kannst du Custom Labels hinzufügen:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye,ubuntu-22.04:docker://ubuntu:22.04,php-8.4:docker://php:8.4-cli
|
||||
```
|
||||
|
||||
### Mehrere Runner
|
||||
|
||||
Für parallele Builds kannst du mehrere Runner starten:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
runner-1:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner-1
|
||||
# ... config ...
|
||||
|
||||
runner-2:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner-2
|
||||
# ... config ...
|
||||
```
|
||||
|
||||
## Repository Secrets konfigurieren
|
||||
|
||||
Um das automatische Deployment zu ermöglichen, musst du Secrets in deinem Gitea-Repository hinterlegen:
|
||||
|
||||
### 1. In Gitea Repository Settings
|
||||
|
||||
1. Gehe zu deinem Repository
|
||||
2. Klicke auf **Settings** → **Secrets**
|
||||
3. Füge folgende Secrets hinzu:
|
||||
|
||||
| Secret Name | Beschreibung | Beispiel |
|
||||
|------------|--------------|----------|
|
||||
| `SYNOLOGY_HOST` | IP oder Hostname deiner Synology | `192.168.1.100` |
|
||||
| `SYNOLOGY_USER` | SSH-Benutzername | `admin` |
|
||||
| `SYNOLOGY_SSH_KEY` | Privater SSH-Key für Zugriff | `-----BEGIN OPENSSH PRIVATE KEY-----...` |
|
||||
| `APP_KEY` | Laravel Application Key | `base64:xyz...` |
|
||||
|
||||
### 2. SSH-Key generieren (falls nicht vorhanden)
|
||||
|
||||
Auf deiner Synology NAS:
|
||||
|
||||
```bash
|
||||
# SSH-Key-Pair generieren
|
||||
ssh-keygen -t ed25519 -C "gitea-runner@synology" -f ~/.ssh/gitea_deploy
|
||||
|
||||
# Public Key zum authorized_keys hinzufügen
|
||||
cat ~/.ssh/gitea_deploy.pub >> ~/.ssh/authorized_keys
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
|
||||
# Private Key anzeigen (für Secret)
|
||||
cat ~/.ssh/gitea_deploy
|
||||
```
|
||||
|
||||
Kopiere den **gesamten** Private Key (inklusive `-----BEGIN` und `-----END` Zeilen) und füge ihn als `SYNOLOGY_SSH_KEY` Secret hinzu.
|
||||
|
||||
### 3. Secrets verifizieren
|
||||
|
||||
Stelle sicher, dass alle benötigten Secrets gesetzt sind:
|
||||
|
||||
```bash
|
||||
# In Gitea Repository Settings → Secrets
|
||||
✅ SYNOLOGY_HOST
|
||||
✅ SYNOLOGY_USER
|
||||
✅ SYNOLOGY_SSH_KEY
|
||||
✅ APP_KEY
|
||||
```
|
||||
|
||||
## Workflow aktivieren
|
||||
|
||||
### 1. Workflow-Datei pushen
|
||||
|
||||
```bash
|
||||
# Lokal im Projekt
|
||||
git add .gitea/workflows/deploy-synology.yml
|
||||
git commit -m "ci: add automated deployment workflow"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 2. Workflow ausführen
|
||||
|
||||
Gitea Actions wird automatisch bei jedem Push auf `main` oder `develop` ausgelöst.
|
||||
|
||||
### 3. Workflow Status prüfen
|
||||
|
||||
1. Gehe zu deinem Repository in Gitea
|
||||
2. Klicke auf **Actions**
|
||||
3. Du siehst alle Workflow-Runs mit Status (Success/Failure)
|
||||
4. Klicke auf einen Run, um Details und Logs zu sehen
|
||||
|
||||
## Workflow manuell auslösen
|
||||
|
||||
Falls du den Workflow manuell starten möchtest:
|
||||
|
||||
### 1. Workflow-Datei erweitern
|
||||
|
||||
Füge in `.gitea/workflows/deploy-synology.yml` hinzu:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch: # Ermöglicht manuelles Auslösen
|
||||
```
|
||||
|
||||
### 2. Manuell auslösen
|
||||
|
||||
1. Gehe zu **Actions** in deinem Repository
|
||||
2. Wähle den Workflow aus
|
||||
3. Klicke auf **Run workflow**
|
||||
|
||||
## Monitoring & Logs
|
||||
|
||||
### Runner Logs
|
||||
|
||||
```bash
|
||||
# Live Logs
|
||||
docker logs -f gitea-runner
|
||||
|
||||
# Letzte 100 Zeilen
|
||||
docker logs --tail 100 gitea-runner
|
||||
```
|
||||
|
||||
### Workflow Logs
|
||||
|
||||
In Gitea UI:
|
||||
1. **Actions** Tab
|
||||
2. Wähle einen Workflow Run
|
||||
3. Klicke auf die einzelnen Jobs, um Logs zu sehen
|
||||
|
||||
### Deployment Logs auf Synology
|
||||
|
||||
```bash
|
||||
# Laravel App Logs
|
||||
docker logs -f laravel_app
|
||||
|
||||
# Deployment Script Logs
|
||||
# Diese werden in der Workflow-Ausgabe angezeigt
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: Runner verbindet sich nicht
|
||||
|
||||
```bash
|
||||
# Logs prüfen
|
||||
docker logs gitea-runner
|
||||
|
||||
# Runner neu registrieren
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Problem: Workflow startet nicht
|
||||
|
||||
1. **Runner Status prüfen**: Gehe zu Repository → Settings → Actions → Runners
|
||||
2. **Runner Labels prüfen**: Stelle sicher, dass `runs-on: ubuntu-latest` mit den Runner Labels übereinstimmt
|
||||
3. **Secrets prüfen**: Alle benötigten Secrets müssen gesetzt sein
|
||||
|
||||
### Problem: SSH-Verbindung fehlschlägt
|
||||
|
||||
```bash
|
||||
# SSH-Key Permissions prüfen
|
||||
chmod 600 ~/.ssh/gitea_deploy
|
||||
chmod 644 ~/.ssh/gitea_deploy.pub
|
||||
chmod 700 ~/.ssh
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
|
||||
# SSH-Verbindung testen (lokal auf der NAS)
|
||||
ssh -i ~/.ssh/gitea_deploy admin@localhost
|
||||
```
|
||||
|
||||
### Problem: Deployment schlägt fehl
|
||||
|
||||
```bash
|
||||
# Prüfe deployment script permissions
|
||||
cd /volume1/docker/laravel-app
|
||||
ls -la deploy-synology.sh
|
||||
|
||||
# Mache es executable
|
||||
chmod +x deploy-synology.sh
|
||||
|
||||
# Teste deployment script manuell
|
||||
./deploy-synology.sh
|
||||
```
|
||||
|
||||
### Problem: Health Check fehlschlägt
|
||||
|
||||
```bash
|
||||
# Prüfe ob Container läuft
|
||||
docker ps | grep laravel_app
|
||||
|
||||
# Prüfe Logs
|
||||
docker logs laravel_app
|
||||
|
||||
# Teste Health Endpoint manuell
|
||||
curl http://localhost:8080/health
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Separate Runner für Production und Staging
|
||||
|
||||
```yaml
|
||||
services:
|
||||
runner-production:
|
||||
environment:
|
||||
- GITEA_RUNNER_LABELS=production:docker://ubuntu:22.04
|
||||
|
||||
runner-staging:
|
||||
environment:
|
||||
- GITEA_RUNNER_LABELS=staging:docker://ubuntu:22.04
|
||||
```
|
||||
|
||||
Im Workflow:
|
||||
```yaml
|
||||
jobs:
|
||||
deploy-production:
|
||||
runs-on: production
|
||||
if: github.ref == 'refs/heads/main'
|
||||
```
|
||||
|
||||
### 2. Workflow nur für Conventional Commits
|
||||
|
||||
Der aktuelle Workflow filtert bereits nach Conventional Commits:
|
||||
- `feat:` - Neue Features
|
||||
- `fix:` - Bugfixes
|
||||
- `docs:` - Dokumentation
|
||||
- `chore:` - Wartungsarbeiten
|
||||
- etc.
|
||||
|
||||
### 3. Notifications
|
||||
|
||||
Erweitere den Workflow um Benachrichtigungen:
|
||||
|
||||
```yaml
|
||||
- name: Send Notification
|
||||
if: always()
|
||||
run: |
|
||||
# Telegram, Discord, Email, etc.
|
||||
curl -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
|
||||
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
|
||||
-d text="Deployment status: ${{ job.status }}"
|
||||
```
|
||||
|
||||
### 4. Rollback-Strategie
|
||||
|
||||
Im deployment script ist bereits ein Backup-Mechanismus implementiert:
|
||||
- Datenbank-Backups vor jedem Deployment
|
||||
- Letzte 5 Backups werden aufbewahrt
|
||||
- Automatischer Rollback bei Fehlern
|
||||
|
||||
## Erweiterte Konfiguration
|
||||
|
||||
### Multi-Stage Deployments
|
||||
|
||||
Für staging und production environments:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
deploy-staging:
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
# ... deploy to staging ...
|
||||
|
||||
deploy-production:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
needs: [test, deploy-staging]
|
||||
# ... deploy to production ...
|
||||
```
|
||||
|
||||
### Caching
|
||||
|
||||
Um Build-Zeiten zu verkürzen:
|
||||
|
||||
```yaml
|
||||
- name: Cache Composer
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: vendor
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
|
||||
|
||||
- name: Cache NPM
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
```
|
||||
|
||||
## Weitere Ressourcen
|
||||
|
||||
- [Gitea Actions Dokumentation](https://docs.gitea.com/usage/actions/overview)
|
||||
- [Act Runner Dokumentation](https://gitea.com/gitea/act_runner)
|
||||
- [Docker Compose Dokumentation](https://docs.docker.com/compose/)
|
||||
|
||||
## Support
|
||||
|
||||
Bei Problemen:
|
||||
1. Prüfe Runner Logs: `docker logs gitea-runner`
|
||||
2. Prüfe Workflow Logs in Gitea Actions Tab
|
||||
3. Verifiziere alle Secrets sind korrekt gesetzt
|
||||
4. Teste SSH-Verbindung zur Synology manuell
|
||||
@@ -0,0 +1,435 @@
|
||||
# Synology NAS Deployment Anleitung
|
||||
|
||||
Diese Anleitung beschreibt, wie du diese Laravel-Anwendung auf deiner Synology NAS mit Portainer deployen kannst.
|
||||
|
||||
## 📋 Inhaltsverzeichnis
|
||||
|
||||
1. [Voraussetzungen](#voraussetzungen)
|
||||
2. [Manuelles Deployment](#-vorbereitung)
|
||||
3. [Automatisches CI/CD Deployment](#-automatisches-deployment-mit-gitea-actions)
|
||||
4. [Monitoring & Wartung](#-monitoring--wartung)
|
||||
5. [Troubleshooting](#-troubleshooting)
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Synology NAS mit Docker-Unterstützung
|
||||
- Portainer läuft bereits auf deiner NAS
|
||||
- SSH-Zugriff auf die Synology NAS (optional, aber empfohlen)
|
||||
- Mindestens 2GB freier RAM
|
||||
- Mindestens 5GB freier Speicherplatz
|
||||
- (Optional) Gitea für automatisches CI/CD Deployment
|
||||
|
||||
## 📦 Vorbereitung
|
||||
|
||||
### 1. Projekt-Dateien auf die NAS übertragen
|
||||
|
||||
Es gibt mehrere Möglichkeiten, die Dateien auf deine Synology zu übertragen:
|
||||
|
||||
#### Option A: Via Git (empfohlen)
|
||||
```bash
|
||||
# SSH auf die Synology NAS
|
||||
ssh admin@your-synology-ip
|
||||
|
||||
# Navigiere zu einem geeigneten Verzeichnis (z.B. /volume1/docker/laravel)
|
||||
cd /volume1/docker
|
||||
git clone <dein-repository-url> laravel-app
|
||||
cd laravel-app
|
||||
```
|
||||
|
||||
#### Option B: Via File Station
|
||||
1. Öffne die Synology File Station
|
||||
2. Erstelle einen Ordner: `/docker/laravel-app`
|
||||
3. Lade alle Projekt-Dateien in diesen Ordner hoch
|
||||
|
||||
#### Option C: Via rsync/scp
|
||||
```bash
|
||||
# Vom lokalen Rechner aus
|
||||
rsync -avz --exclude 'node_modules' --exclude 'vendor' \
|
||||
/Users/sebastianfrohlich/Herd/frontend/ \
|
||||
admin@your-synology-ip:/volume1/docker/laravel-app/
|
||||
```
|
||||
|
||||
### 2. Umgebungsvariablen konfigurieren
|
||||
|
||||
```bash
|
||||
# SSH auf der NAS
|
||||
cd /volume1/docker/laravel-app
|
||||
|
||||
# Kopiere die Synology-Beispiel-Datei
|
||||
cp .env.synology.example .env.synology
|
||||
|
||||
# Bearbeite die Datei mit deinen spezifischen Einstellungen
|
||||
nano .env.synology
|
||||
```
|
||||
|
||||
**Wichtige Anpassungen in `.env.synology`:**
|
||||
|
||||
```env
|
||||
# Ersetze mit deiner Synology IP-Adresse
|
||||
APP_URL=http://192.168.1.100:8080
|
||||
|
||||
# Setze APP_DEBUG auf false für Produktion
|
||||
APP_DEBUG=false
|
||||
|
||||
# Generiere einen neuen APP_KEY (wichtig für Sicherheit!)
|
||||
# Dies kann später mit: docker exec laravel_app php artisan key:generate gemacht werden
|
||||
|
||||
# PostgreSQL Passwort - ändere dies!
|
||||
DB_PASSWORD2=dein_sicheres_passwort_hier
|
||||
```
|
||||
|
||||
## 🚀 Deployment mit Portainer
|
||||
|
||||
### Methode 1: Docker Compose Stack (empfohlen)
|
||||
|
||||
1. **Öffne Portainer** in deinem Browser: `http://your-synology-ip:9000`
|
||||
|
||||
2. **Navigiere zu "Stacks"**:
|
||||
- Klicke auf "Stacks" im linken Menü
|
||||
- Klicke auf "+ Add stack"
|
||||
|
||||
3. **Stack konfigurieren**:
|
||||
- **Name**: `laravel-app`
|
||||
- **Build method**: Wähle "Repository"
|
||||
- **Repository URL**: Gib deine Git-Repository-URL ein (falls vorhanden)
|
||||
- Oder wähle "Upload" und lade `docker-compose.synology.yml` hoch
|
||||
- Oder wähle "Web editor" und kopiere den Inhalt von `docker-compose.synology.yml`
|
||||
|
||||
4. **Umgebungsvariablen setzen**:
|
||||
Klicke auf "Add an environment variable" und füge folgende Variablen hinzu:
|
||||
|
||||
```
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=production
|
||||
APP_KEY=base64:L9RVZ3pNFyAvTbMqicT1rL5GbgE+7lJerkU9wyc95H8=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://your-synology-ip:8080
|
||||
DB_CONNECTION2=pgsql
|
||||
DB_HOST2=postgres
|
||||
DB_PORT2=5432
|
||||
DB_DATABASE2=ingest_db
|
||||
DB_USERNAME2=ingest_user
|
||||
DB_PASSWORD2=dein_sicheres_passwort
|
||||
```
|
||||
|
||||
5. **Stack deployen**:
|
||||
- Klicke auf "Deploy the stack"
|
||||
- Warte, bis der Build-Prozess abgeschlossen ist (kann 5-10 Minuten dauern)
|
||||
|
||||
### Methode 2: Build und Deploy manuell via SSH
|
||||
|
||||
```bash
|
||||
# SSH auf die Synology
|
||||
ssh admin@your-synology-ip
|
||||
|
||||
# Navigiere zum Projekt-Verzeichnis
|
||||
cd /volume1/docker/laravel-app
|
||||
|
||||
# Baue das Docker Image
|
||||
docker build -t laravel-app:latest .
|
||||
|
||||
# Starte die Services mit docker-compose
|
||||
docker-compose -f docker-compose.synology.yml up -d
|
||||
```
|
||||
|
||||
## 🔍 Verifikation
|
||||
|
||||
### 1. Überprüfe den Container-Status
|
||||
|
||||
In Portainer:
|
||||
- Gehe zu "Containers"
|
||||
- Du solltest zwei laufende Container sehen:
|
||||
- `laravel_app` (Status: running, Port: 0.0.0.0:8080->80/tcp)
|
||||
- `laravel_postgres` (Status: running, Port: 5432/tcp)
|
||||
|
||||
Via SSH:
|
||||
```bash
|
||||
docker ps
|
||||
```
|
||||
|
||||
### 2. Überprüfe die Logs
|
||||
|
||||
In Portainer:
|
||||
- Klicke auf den Container `laravel_app`
|
||||
- Wähle "Logs"
|
||||
- Du solltest keine Fehler sehen
|
||||
|
||||
Via SSH:
|
||||
```bash
|
||||
# Laravel App Logs
|
||||
docker logs laravel_app
|
||||
|
||||
# PostgreSQL Logs
|
||||
docker logs laravel_postgres
|
||||
```
|
||||
|
||||
### 3. Teste die Anwendung
|
||||
|
||||
Öffne deinen Browser und navigiere zu:
|
||||
```
|
||||
http://your-synology-ip:8080
|
||||
```
|
||||
|
||||
Du solltest die Laravel-Anwendung sehen!
|
||||
|
||||
## 🔧 Nützliche Befehle
|
||||
|
||||
### Artisan-Befehle ausführen
|
||||
|
||||
```bash
|
||||
# Laravel Cache leeren
|
||||
docker exec laravel_app php artisan cache:clear
|
||||
|
||||
# Neuen APP_KEY generieren
|
||||
docker exec laravel_app php artisan key:generate
|
||||
|
||||
# Migrationen ausführen
|
||||
docker exec laravel_app php artisan migrate
|
||||
|
||||
# Seeder ausführen
|
||||
docker exec laravel_app php artisan db:seed
|
||||
```
|
||||
|
||||
### Container neustarten
|
||||
|
||||
Via Portainer:
|
||||
- Gehe zu "Containers"
|
||||
- Wähle den Container aus
|
||||
- Klicke auf "Restart"
|
||||
|
||||
Via SSH:
|
||||
```bash
|
||||
docker-compose -f docker-compose.synology.yml restart
|
||||
```
|
||||
|
||||
### Container stoppen und entfernen
|
||||
|
||||
Via Portainer:
|
||||
- Gehe zu "Stacks"
|
||||
- Wähle den Stack "laravel-app"
|
||||
- Klicke auf "Stop" oder "Delete"
|
||||
|
||||
Via SSH:
|
||||
```bash
|
||||
docker-compose -f docker-compose.synology.yml down
|
||||
|
||||
# Mit Volumes löschen (Achtung: Löscht die Datenbank!)
|
||||
docker-compose -f docker-compose.synology.yml down -v
|
||||
```
|
||||
|
||||
### Logs live verfolgen
|
||||
|
||||
```bash
|
||||
# Alle Container
|
||||
docker-compose -f docker-compose.synology.yml logs -f
|
||||
|
||||
# Nur Laravel App
|
||||
docker logs -f laravel_app
|
||||
|
||||
# Nur PostgreSQL
|
||||
docker logs -f laravel_postgres
|
||||
```
|
||||
|
||||
## 🔐 Sicherheitshinweise
|
||||
|
||||
1. **APP_KEY ändern**: Generiere einen neuen APP_KEY für die Produktion:
|
||||
```bash
|
||||
docker exec laravel_app php artisan key:generate
|
||||
```
|
||||
|
||||
2. **Datenbank-Passwort**: Ändere das Standard-PostgreSQL-Passwort in `.env.synology`
|
||||
|
||||
3. **APP_DEBUG**: Stelle sicher, dass `APP_DEBUG=false` in der Produktion
|
||||
|
||||
4. **Firewall**: Konfiguriere die Synology-Firewall, um nur benötigte Ports zu öffnen
|
||||
|
||||
5. **SSL/HTTPS**: Für den Produktionsbetrieb solltest du einen Reverse Proxy (z.B. Synology DSM Reverse Proxy) mit SSL-Zertifikat einrichten
|
||||
|
||||
## 🌐 Reverse Proxy einrichten (optional, aber empfohlen)
|
||||
|
||||
Für den Zugriff über eine Domain mit HTTPS:
|
||||
|
||||
1. **In Synology DSM**:
|
||||
- Gehe zu "Systemsteuerung" → "Anmeldungsportal" → "Erweitert"
|
||||
- Klicke auf "Reverse Proxy" → "Erstellen"
|
||||
|
||||
2. **Konfiguration**:
|
||||
- **Protokoll**: HTTPS
|
||||
- **Hostname**: your-domain.com
|
||||
- **Port**: 443
|
||||
- **Zielprotokoll**: HTTP
|
||||
- **Zielhost**: localhost
|
||||
- **Zielport**: 8080
|
||||
|
||||
3. **SSL-Zertifikat**:
|
||||
- Gehe zu "Systemsteuerung" → "Sicherheit" → "Zertifikat"
|
||||
- Füge ein Let's Encrypt-Zertifikat hinzu
|
||||
|
||||
## 📊 Monitoring & Wartung
|
||||
|
||||
### Container-Ressourcen überwachen
|
||||
|
||||
In Portainer:
|
||||
- Gehe zu "Containers"
|
||||
- Wähle einen Container
|
||||
- Klicke auf "Stats" für Echtzeit-Metriken (CPU, RAM, Netzwerk)
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# PostgreSQL Datenbank sichern
|
||||
docker exec laravel_postgres pg_dump -U ingest_user ingest_db > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Gesamten Stack sichern (inkl. Volumes)
|
||||
docker run --rm \
|
||||
-v laravel-app_postgres-data:/data \
|
||||
-v /volume1/docker/backups:/backup \
|
||||
alpine tar czf /backup/postgres-backup-$(date +%Y%m%d).tar.gz /data
|
||||
```
|
||||
|
||||
### Updates
|
||||
|
||||
```bash
|
||||
# Projekt-Code aktualisieren (wenn via Git)
|
||||
cd /volume1/docker/laravel-app
|
||||
git pull
|
||||
|
||||
# Neu bauen und deployen
|
||||
docker-compose -f docker-compose.synology.yml up -d --build
|
||||
|
||||
# Migrationen ausführen
|
||||
docker exec laravel_app php artisan migrate --force
|
||||
```
|
||||
|
||||
## ❓ Troubleshooting
|
||||
|
||||
### Problem: Container startet nicht
|
||||
|
||||
```bash
|
||||
# Logs prüfen
|
||||
docker logs laravel_app
|
||||
|
||||
# Container interaktiv starten für Debugging
|
||||
docker exec -it laravel_app sh
|
||||
```
|
||||
|
||||
### Problem: Datenbank-Verbindung fehlgeschlagen
|
||||
|
||||
```bash
|
||||
# PostgreSQL-Container prüfen
|
||||
docker exec laravel_postgres pg_isready -U ingest_user
|
||||
|
||||
# Umgebungsvariablen prüfen
|
||||
docker exec laravel_app env | grep DB_
|
||||
```
|
||||
|
||||
### Problem: Permissions-Fehler
|
||||
|
||||
```bash
|
||||
# Storage-Permissions korrigieren
|
||||
docker exec laravel_app chmod -R 775 storage bootstrap/cache
|
||||
docker exec laravel_app chown -R nginx:nginx storage bootstrap/cache
|
||||
```
|
||||
|
||||
### Problem: Port bereits belegt
|
||||
|
||||
Wenn Port 8080 bereits verwendet wird:
|
||||
1. Öffne `docker-compose.synology.yml`
|
||||
2. Ändere die Port-Mapping: `"8081:80"` statt `"8080:80"`
|
||||
3. Aktualisiere `APP_URL` in `.env.synology` entsprechend
|
||||
|
||||
## 🤖 Automatisches Deployment mit Gitea Actions
|
||||
|
||||
Wenn du Gitea auf deiner Synology NAS verwendest, kannst du den gesamten Build- und Deployment-Prozess automatisieren!
|
||||
|
||||
### Vorteile von CI/CD
|
||||
|
||||
- ✅ Automatischer Build bei jedem Git Push
|
||||
- ✅ Automatische Tests vor Deployment
|
||||
- ✅ Automatisches Deployment auf die NAS
|
||||
- ✅ Rollback bei Fehlern
|
||||
- ✅ Keine manuellen Schritte mehr nötig
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. **Gitea Runner einrichten**
|
||||
- Siehe detaillierte Anleitung: [GITEA_RUNNER_SETUP.md](GITEA_RUNNER_SETUP.md)
|
||||
- Kurz: Runner Docker Container auf NAS deployen
|
||||
- Runner in Gitea registrieren
|
||||
|
||||
2. **Repository Secrets konfigurieren**
|
||||
|
||||
Gehe in Gitea zu: **Repository → Settings → Secrets**
|
||||
|
||||
Füge folgende Secrets hinzu:
|
||||
```
|
||||
SYNOLOGY_HOST=192.168.1.100
|
||||
SYNOLOGY_USER=admin
|
||||
SYNOLOGY_SSH_KEY=<dein-ssh-private-key>
|
||||
APP_KEY=<dein-laravel-app-key>
|
||||
```
|
||||
|
||||
3. **Workflow pushen**
|
||||
|
||||
Der Workflow in [`.gitea/workflows/deploy-synology.yml`](.gitea/workflows/deploy-synology.yml) ist bereits vorkonfiguriert.
|
||||
|
||||
```bash
|
||||
git add .gitea/workflows/deploy-synology.yml
|
||||
git commit -m "ci: add automated deployment workflow"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
4. **Automatisches Deployment genießen! 🎉**
|
||||
|
||||
Bei jedem Push auf `main` oder `develop`:
|
||||
- Tests werden ausgeführt
|
||||
- Docker Image wird gebaut
|
||||
- Deployment auf NAS erfolgt automatisch
|
||||
- Health Check verifiziert Deployment
|
||||
|
||||
### Workflow-Ablauf
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Git Push] --> B[Run Tests]
|
||||
B --> C{Tests OK?}
|
||||
C -->|Ja| D[Build Docker Image]
|
||||
C -->|Nein| E[Abbruch]
|
||||
D --> F[Transfer zu NAS]
|
||||
F --> G[Deploy auf NAS]
|
||||
G --> H[Health Check]
|
||||
H --> I{Healthy?}
|
||||
I -->|Ja| J[✅ Success]
|
||||
I -->|Nein| K[Rollback]
|
||||
```
|
||||
|
||||
### Workflow überwachen
|
||||
|
||||
1. Gehe zu deinem Repository in Gitea
|
||||
2. Klicke auf **Actions**
|
||||
3. Siehe alle Workflow-Runs mit Status
|
||||
4. Klicke auf einen Run für Details und Logs
|
||||
|
||||
### Erweiterte Konfiguration
|
||||
|
||||
Weitere Details zur CI/CD-Konfiguration findest du in:
|
||||
- [GITEA_RUNNER_SETUP.md](GITEA_RUNNER_SETUP.md) - Detaillierte Runner-Setup Anleitung
|
||||
- [`.gitea/workflows/deploy-synology.yml`](.gitea/workflows/deploy-synology.yml) - Workflow-Konfiguration
|
||||
- [`scripts/deploy-synology.sh`](scripts/deploy-synology.sh) - Deployment-Script
|
||||
|
||||
## 📝 Weitere Ressourcen
|
||||
|
||||
- [Laravel Dokumentation](https://laravel.com/docs)
|
||||
- [Docker Dokumentation](https://docs.docker.com/)
|
||||
- [Portainer Dokumentation](https://docs.portainer.io/)
|
||||
- [Synology Docker Anleitung](https://www.synology.com/en-us/dsm/packages/Docker)
|
||||
- [Gitea Actions Dokumentation](https://docs.gitea.com/usage/actions/overview)
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
Bei Problemen:
|
||||
1. Prüfe die Container-Logs
|
||||
2. Überprüfe die Umgebungsvariablen
|
||||
3. Stelle sicher, dass alle Ports verfügbar sind
|
||||
4. Prüfe die Synology-Firewall-Einstellungen
|
||||
5. Bei CI/CD: Prüfe Gitea Actions Logs und Runner Status
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+32
-18
@@ -86,10 +86,10 @@ return [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST2', '127.0.0.1'),
|
||||
'port' => env('DB_PORT2', '5433'),
|
||||
'database' => env('DB_DATABASE2', 'risk_ingest_db'),
|
||||
'username' => env('DB_USERNAME2', 'risk_ingest_user'),
|
||||
'password' => env('DB_PASSWORD2', ''),
|
||||
'port' => env('DB_PORT2', '5432'),
|
||||
'database' => env('DB_DATABASE2', 'ingest_db'),
|
||||
'username' => env('DB_USERNAME2', 'ingest_user'),
|
||||
'password' => env('DB_PASSWORD2', 'ingest_pwd'),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
@@ -98,30 +98,44 @@ return [
|
||||
],
|
||||
|
||||
'backend' => [
|
||||
'driver' => env('DB_CONNECTION2'),
|
||||
'host' => env('DB_HOST2', '127.0.0.1'),
|
||||
'port' => env('DB_PORT2', '5432'),
|
||||
'database' => env('DB_DATABASE2', 'laravel'),
|
||||
'username' => env('DB_USERNAME2', 'root'),
|
||||
'password' => env('DB_PASSWORD2', ''),
|
||||
'driver' => 'pgsql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel_app'),
|
||||
'username' => env('DB_USERNAME', 'pgadmin'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => env('DB_BACKEND_SEARCH_PATH2', 'backend'), // Backend Schema (gleiche DB wie pgsql_second, anderes Schema)
|
||||
'search_path' => 'backend',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'pgsql_second' => [
|
||||
'driver' => env('DB_CONNECTION2'),
|
||||
'host' => env('DB_HOST2', '127.0.0.1'),
|
||||
'port' => env('DB_PORT2', '5432'),
|
||||
'database' => env('DB_DATABASE2', 'laravel'),
|
||||
'username' => env('DB_USERNAME2', 'root'),
|
||||
'password' => env('DB_PASSWORD2', ''),
|
||||
'driver' => 'pgsql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel_app'),
|
||||
'username' => env('DB_USERNAME', 'pgadmin'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => env('DB_SEARCH_PATH2', 'public'),
|
||||
'search_path' => 'public',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
'devbackend' => [
|
||||
'driver' => 'pgsql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel_app'),
|
||||
'username' => env('DB_USERNAME', 'pgadmin'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'devbackend',
|
||||
'sslmode' => 'prefer',
|
||||
],
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
afc-user:$apr1$ti1kco0/$Bp395rESObPoH539Kecvc0
|
||||
@@ -13,6 +13,11 @@ server {
|
||||
# -------------------------------------------------
|
||||
# Livewire + Laravel routes (must come before static block)
|
||||
# -------------------------------------------------
|
||||
# Specific location for livewire.min.js (redirect to non-minified version)
|
||||
location = /livewire/livewire.min.js {
|
||||
return 301 /livewire/livewire.js$is_args$args;
|
||||
}
|
||||
|
||||
location ^~ /livewire/ {
|
||||
try_files $uri /index.php?$query_string;
|
||||
expires off;
|
||||
@@ -37,8 +42,10 @@ server {
|
||||
# Main Laravel entry point
|
||||
# -------------------------------------------------
|
||||
location / {
|
||||
auth_basic "Restricted Access";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
${BASIC_AUTH_DIRECTIVES}
|
||||
}
|
||||
|
||||
# -------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
version: "3.9"
|
||||
|
||||
# Gitea Runner für automatische CI/CD Deployments
|
||||
# Dieses Compose-File wird auf der Synology NAS deployed
|
||||
|
||||
services:
|
||||
gitea-runner:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
# Gitea Instance URL - Passe dies an deine Gitea-Installation an
|
||||
- GITEA_INSTANCE_URL=${GITEA_URL:-http://gitea:3000}
|
||||
|
||||
# Runner Registration Token - Wird aus Gitea UI generiert
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}
|
||||
|
||||
# Runner Name - Wird in Gitea angezeigt
|
||||
- GITEA_RUNNER_NAME=${RUNNER_NAME:-synology-runner}
|
||||
|
||||
# Runner Labels - Definiert welche Workflows dieser Runner ausführen kann
|
||||
# Format: label:docker://image
|
||||
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04,ubuntu-20.04:docker://catthehacker/ubuntu:act-20.04
|
||||
|
||||
# Runner Capacity - Maximale Anzahl gleichzeitiger Jobs
|
||||
- GITEA_RUNNER_CAPACITY=${RUNNER_CAPACITY:-1}
|
||||
|
||||
# Log Level
|
||||
- GITEA_RUNNER_LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
|
||||
volumes:
|
||||
# Runner Daten (Config, Cache, etc.)
|
||||
- runner-data:/data
|
||||
|
||||
# Docker Socket - Ermöglicht dem Runner Docker-Container zu starten
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
# Optional: Workspace für Job-Artefakte
|
||||
- runner-workspace:/workspace
|
||||
|
||||
networks:
|
||||
- gitea-network
|
||||
|
||||
# Health Check
|
||||
healthcheck:
|
||||
test: ["CMD", "pgrep", "-f", "act_runner"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# Resource Limits (optional, aber empfohlen)
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 512M
|
||||
|
||||
volumes:
|
||||
runner-data:
|
||||
driver: local
|
||||
runner-workspace:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
gitea-network:
|
||||
# Wenn Gitea in einem separaten Netzwerk läuft, verwende:
|
||||
# external: true
|
||||
# Ansonsten:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,91 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: laravel-app:latest
|
||||
container_name: laravel_app
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "8080:80" # Angepasst für Synology (Port 81 oft belegt)
|
||||
|
||||
environment:
|
||||
# Laravel Environment
|
||||
- APP_NAME=${APP_NAME:-Laravel}
|
||||
- APP_ENV=${APP_ENV:-production}
|
||||
- APP_KEY=${APP_KEY}
|
||||
- APP_DEBUG=${APP_DEBUG:-false}
|
||||
- APP_URL=${APP_URL:-http://localhost:8080}
|
||||
|
||||
# Database - PostgreSQL
|
||||
- DB_CONNECTION2=${DB_CONNECTION2:-pgsql}
|
||||
- DB_HOST2=${DB_HOST2:-postgres}
|
||||
- DB_PORT2=${DB_PORT2:-5432}
|
||||
- DB_DATABASE2=${DB_DATABASE2:-ingest_db}
|
||||
- DB_USERNAME2=${DB_USERNAME2:-ingest_user}
|
||||
- DB_PASSWORD2=${DB_PASSWORD2:-ingest_pwd}
|
||||
|
||||
# Session & Cache
|
||||
- SESSION_DRIVER=${SESSION_DRIVER:-file}
|
||||
- CACHE_STORE=${CACHE_STORE:-file}
|
||||
- QUEUE_CONNECTION=${QUEUE_CONNECTION:-sync}
|
||||
|
||||
# Logging
|
||||
- LOG_CHANNEL=${LOG_CHANNEL:-stack}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
|
||||
# Mail
|
||||
- MAIL_MAILER=${MAIL_MAILER:-log}
|
||||
|
||||
volumes:
|
||||
- app-storage:/var/www/html/storage
|
||||
- app-bootstrap-cache:/var/www/html/bootstrap/cache
|
||||
- ./database:/var/www/html/database
|
||||
|
||||
networks:
|
||||
- laravel-network
|
||||
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: laravel_postgres
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
- POSTGRES_DB=${DB_DATABASE2:-ingest_db}
|
||||
- POSTGRES_USER=${DB_USERNAME2:-ingest_user}
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD2:-ingest_pwd}
|
||||
- PGDATA=/var/lib/postgresql/data/pgdata
|
||||
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
|
||||
ports:
|
||||
- "5432:5432" # Optional: Direktzugriff auf PostgreSQL
|
||||
|
||||
networks:
|
||||
- laravel-network
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME2:-ingest_user}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
driver: local
|
||||
app-storage:
|
||||
driver: local
|
||||
app-bootstrap-cache:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
laravel-network:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,999 @@
|
||||
# API-Dokumentation: Risk Intelligence Platform
|
||||
|
||||
## 📋 Inhaltsverzeichnis
|
||||
|
||||
1. [Übersicht](#übersicht)
|
||||
2. [API-Spezifikation](#api-spezifikation)
|
||||
3. [Authentication](#authentication)
|
||||
4. [Endpoints](#endpoints)
|
||||
5. [Datenmodelle](#datenmodelle)
|
||||
6. [Beispiele](#beispiele)
|
||||
7. [Error Handling](#error-handling)
|
||||
8. [Rate Limiting](#rate-limiting)
|
||||
9. [Changelog](#changelog)
|
||||
|
||||
---
|
||||
|
||||
## Übersicht
|
||||
|
||||
Die **Trusted AI Analyst Data API** stellt Daten für das Analyst Dashboard, Transaction Review, Company Search und Company Drill-Down bereit.
|
||||
|
||||
### API-Version
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Format:** JSON
|
||||
**Authentifizierung:** Session-basiert (Laravel Session Cookie)
|
||||
|
||||
### Base URLs
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
| **Development** | `http://trusted_ai.test/api` |
|
||||
| **Production** | `https://afc.trai.sft.comstack.de/api` |
|
||||
|
||||
### OpenAPI Spezifikation
|
||||
|
||||
Die vollständige API-Spezifikation ist in [swagger.yaml](../swagger.yaml) dokumentiert.
|
||||
|
||||
**Swagger UI:** Die Spezifikation kann mit [Swagger Editor](https://editor.swagger.io) oder Swagger UI visualisiert werden.
|
||||
|
||||
---
|
||||
|
||||
## API-Spezifikation
|
||||
|
||||
### API-Kategorien
|
||||
|
||||
| Tag | Beschreibung | Endpoints |
|
||||
|-----|--------------|-----------|
|
||||
| **Dashboard** | KPIs und Alert-Übersichten | 1 Endpoint |
|
||||
| **Transactions** | Portfolio-weite Transaction Review | 2 Endpoints |
|
||||
| **Companies** | Company Discovery & Drill-Down | 4 Endpoints |
|
||||
|
||||
**Gesamt:** 7 API-Endpoints
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### Session-basierte Authentifizierung
|
||||
|
||||
Die API nutzt **Laravel Session Cookies** für die Authentifizierung.
|
||||
|
||||
```yaml
|
||||
securitySchemes:
|
||||
sessionCookie:
|
||||
type: apiKey
|
||||
in: cookie
|
||||
name: laravel_session
|
||||
```
|
||||
|
||||
### Login-Flow
|
||||
|
||||
1. **Login via Web-Interface:**
|
||||
```http
|
||||
POST /login HTTP/1.1
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
email=user@example.com&password=secret
|
||||
```
|
||||
|
||||
2. **Session Cookie erhalten:**
|
||||
```http
|
||||
HTTP/1.1 302 Found
|
||||
Set-Cookie: laravel_session=eyJ....; Path=/; HttpOnly; SameSite=lax
|
||||
```
|
||||
|
||||
3. **API-Request mit Cookie:**
|
||||
```http
|
||||
GET /api/dashboard/overview HTTP/1.1
|
||||
Cookie: laravel_session=eyJ....
|
||||
```
|
||||
|
||||
### Authentifizierungs-Fehler
|
||||
|
||||
```json
|
||||
HTTP/1.1 401 Unauthorized
|
||||
{
|
||||
"message": "Unauthenticated."
|
||||
}
|
||||
```
|
||||
|
||||
**Wichtig:** Alle API-Endpoints erfordern eine gültige Session!
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. Dashboard
|
||||
|
||||
#### GET /dashboard/overview
|
||||
|
||||
Liefert aggregierte KPIs und Alert-Übersichten für das Dashboard.
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/dashboard/overview HTTP/1.1
|
||||
Cookie: laravel_session=...
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"generatedAt": "2025-01-19T07:30:00Z",
|
||||
"activeAnalysts": 24,
|
||||
"totalTransactions": 312,
|
||||
"totalVolume": 98765432.10,
|
||||
"alertsToday": 56,
|
||||
"averageAlertsPerAnalyst": 3,
|
||||
"precisionRate": 84,
|
||||
"activeAlerts": {
|
||||
"count": 128,
|
||||
"highRiskCount": 19
|
||||
},
|
||||
"riskSegments": [
|
||||
{
|
||||
"label": "Kritisch (≥80)",
|
||||
"count": 17,
|
||||
"volume": 8900000.0
|
||||
},
|
||||
{
|
||||
"label": "Hoch (60-79)",
|
||||
"count": 42,
|
||||
"volume": 15200000.0
|
||||
},
|
||||
{
|
||||
"label": "Mittel (40-59)",
|
||||
"count": 89,
|
||||
"volume": 32100000.0
|
||||
}
|
||||
],
|
||||
"topCompanies": [
|
||||
{
|
||||
"companyId": 24,
|
||||
"companyName": "Allianz SE",
|
||||
"ticker": "ALV",
|
||||
"sector": "Insurance",
|
||||
"alerts": 6,
|
||||
"alertVolume": 1450000.0,
|
||||
"avgRiskScore": 82
|
||||
}
|
||||
],
|
||||
"runbooksExecutedToday": 41
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Transactions
|
||||
|
||||
#### GET /transactions
|
||||
|
||||
Liste alle Transaktionen mit globalen Filtern (Portfolio-weite Transaction Review).
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Typ | Default | Beschreibung |
|
||||
|-----------|-----|---------|--------------|
|
||||
| `search` | string | - | Match gegen Reference, Counterparty, Company |
|
||||
| `status` | enum | `all` | `all`, `true_positive`, `false_positive`, `cleared` |
|
||||
| `page` | integer | `1` | Seiten-Nummer (min: 1) |
|
||||
| `perPage` | integer | `12` | Items pro Seite (min: 1, max: 100) |
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/transactions?status=true_positive&page=1&perPage=12 HTTP/1.1
|
||||
Cookie: laravel_session=...
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"filters": {
|
||||
"statusOptions": [
|
||||
{
|
||||
"value": "true_positive",
|
||||
"label": "Bestätigte Treffer"
|
||||
},
|
||||
{
|
||||
"value": "false_positive",
|
||||
"label": "Fehlalarme"
|
||||
},
|
||||
{
|
||||
"value": "cleared",
|
||||
"label": "Freigegeben"
|
||||
}
|
||||
],
|
||||
"perPage": 12
|
||||
},
|
||||
"metrics": {
|
||||
"total": {
|
||||
"count": 312,
|
||||
"amount": 98765432.10
|
||||
},
|
||||
"byStatus": {
|
||||
"true_positive": {
|
||||
"count": 42,
|
||||
"amount": 15200000.0
|
||||
},
|
||||
"false_positive": {
|
||||
"count": 89,
|
||||
"amount": 32100000.0
|
||||
},
|
||||
"cleared": {
|
||||
"count": 181,
|
||||
"amount": 51465432.10
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": 512,
|
||||
"company": {
|
||||
"id": 12,
|
||||
"name": "Allianz",
|
||||
"legalName": "Allianz SE",
|
||||
"ticker": "ALV",
|
||||
"sector": "Insurance",
|
||||
"country": "Germany",
|
||||
"headquarters": "Munich",
|
||||
"kycRiskLevel": "high",
|
||||
"summary": "Multinational insurance provider with EMEA focus."
|
||||
},
|
||||
"counterparty": "Alpine Holdings Ltd.",
|
||||
"counterpartyCountry": "Switzerland",
|
||||
"status": "true_positive",
|
||||
"statusLabel": "Bestätigter Treffer",
|
||||
"requiresReview": true,
|
||||
"riskScore": 87,
|
||||
"amount": 245000.75,
|
||||
"currency": "EUR",
|
||||
"reference": "PAY-2024-10-1942",
|
||||
"channel": "SWIFT",
|
||||
"executedAt": "2025-01-18T09:14:00Z",
|
||||
"flaggedReason": "Counterparty on sanctions watchlist"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"perPage": 12,
|
||||
"total": 128,
|
||||
"lastPage": 11
|
||||
},
|
||||
"selectedTransactionId": null,
|
||||
"selectedTransaction": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### GET /transactions/{transactionId}
|
||||
|
||||
Hole detaillierte Transaction Case File.
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
|-----------|-----|--------------|
|
||||
| `transactionId` | integer | Transaction ID |
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/transactions/512 HTTP/1.1
|
||||
Cookie: laravel_session=...
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"id": 512,
|
||||
"company": { ... },
|
||||
"counterparty": "Alpine Holdings Ltd.",
|
||||
"status": "true_positive",
|
||||
"statusLabel": "Bestätigter Treffer",
|
||||
"requiresReview": true,
|
||||
"riskScore": 87,
|
||||
"amount": 245000.75,
|
||||
"currency": "EUR",
|
||||
"reference": "PAY-2024-10-1942",
|
||||
"channel": "SWIFT",
|
||||
"executedAt": "2025-01-18T09:14:00Z",
|
||||
"flaggedBy": "Screening Engine",
|
||||
"flaggedReason": "Counterparty matched to EU sanctions list",
|
||||
"signals": [
|
||||
{
|
||||
"type": "Adverse Media",
|
||||
"value": "Enforcement action reported in 2024-11",
|
||||
"weight": 0.8
|
||||
},
|
||||
{
|
||||
"type": "Sanctions",
|
||||
"value": "EU Sanctions List Match",
|
||||
"weight": 1.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- `401 Unauthorized` - Session ungültig
|
||||
- `404 Not Found` - Transaction existiert nicht
|
||||
|
||||
---
|
||||
|
||||
### 3. Companies
|
||||
|
||||
#### GET /companies/search
|
||||
|
||||
Suche Companies und erhalte Screening-Übersicht.
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Typ | Default | Beschreibung |
|
||||
|-----------|-----|---------|--------------|
|
||||
| `q` | string | - | Freitext-Suche (Name, Legal Name, Ticker, Sector) |
|
||||
| `limit` | integer | `6` | Max. Ergebnisse (min: 1, max: 25) |
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/companies/search?q=Allianz&limit=6 HTTP/1.1
|
||||
Cookie: laravel_session=...
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"overview": {
|
||||
"totalCompanies": 40,
|
||||
"openAlerts": 112,
|
||||
"openAlertVolume": 5640000.0,
|
||||
"averageRiskScore": 71,
|
||||
"watchlistHits": 9,
|
||||
"automationShare": 82
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Allianz",
|
||||
"legalName": "Allianz SE",
|
||||
"ticker": "ALV",
|
||||
"sector": "Insurance",
|
||||
"country": "Germany",
|
||||
"headquarters": "Munich",
|
||||
"kycRiskLevel": "high",
|
||||
"summary": "Multinational insurance provider with EMEA focus.",
|
||||
"alertCount": 7,
|
||||
"alertVolume": 980000.0,
|
||||
"alertRiskScore": 79,
|
||||
"latestAlert": {
|
||||
"transactionId": 731,
|
||||
"counterparty": "Baltic Commodities LLC",
|
||||
"executedAt": "2025-01-17T11:48:00Z",
|
||||
"channel": "SWIFT",
|
||||
"flaggedReason": "Pattern matches sanctions typology",
|
||||
"riskScore": 88
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"query": "Allianz",
|
||||
"resultCount": 6,
|
||||
"limit": 6
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### GET /companies/{companyId}
|
||||
|
||||
Hole Company-Profil.
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
|-----------|-----|--------------|
|
||||
| `companyId` | integer | Company ID |
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/companies/12 HTTP/1.1
|
||||
Cookie: laravel_session=...
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Allianz",
|
||||
"legalName": "Allianz SE",
|
||||
"ticker": "ALV",
|
||||
"sector": "Insurance",
|
||||
"country": "Germany",
|
||||
"headquarters": "Munich",
|
||||
"kycRiskLevel": "high",
|
||||
"summary": "Multinational insurance provider with EMEA focus.",
|
||||
"createdAt": "2024-10-15T08:30:00Z",
|
||||
"updatedAt": "2025-01-18T14:22:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- `401 Unauthorized` - Session ungültig
|
||||
- `404 Not Found` - Company existiert nicht
|
||||
|
||||
---
|
||||
|
||||
#### GET /companies/{companyId}/transactions
|
||||
|
||||
Liste alle Transactions für eine Company.
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
|-----------|-----|--------------|
|
||||
| `companyId` | integer | Company ID |
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Parameter | Typ | Default | Beschreibung |
|
||||
|-----------|-----|---------|--------------|
|
||||
| `status` | enum | `all` | `all`, `true_positive`, `false_positive`, `cleared` |
|
||||
| `channel` | string | `all` | Channel-Filter (z.B. `SWIFT`, `SEPA`) |
|
||||
| `page` | integer | `1` | Seiten-Nummer |
|
||||
| `perPage` | integer | `10` | Items pro Seite (min: 1, max: 100) |
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/companies/12/transactions?status=true_positive&page=1&perPage=10 HTTP/1.1
|
||||
Cookie: laravel_session=...
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"company": {
|
||||
"id": 12,
|
||||
"name": "Allianz",
|
||||
"legalName": "Allianz SE",
|
||||
"ticker": "ALV",
|
||||
"sector": "Insurance",
|
||||
"country": "Germany",
|
||||
"headquarters": "Munich",
|
||||
"kycRiskLevel": "high",
|
||||
"summary": "Multinational insurance provider..."
|
||||
},
|
||||
"filters": {
|
||||
"statusOptions": [
|
||||
{
|
||||
"value": "true_positive",
|
||||
"label": "Bestätigte Treffer"
|
||||
}
|
||||
],
|
||||
"channelOptions": [
|
||||
{
|
||||
"value": "SWIFT",
|
||||
"label": "SWIFT"
|
||||
},
|
||||
{
|
||||
"value": "SEPA",
|
||||
"label": "SEPA"
|
||||
}
|
||||
]
|
||||
},
|
||||
"metrics": {
|
||||
"totalCount": 96,
|
||||
"totalVolume": 7200000.0,
|
||||
"openAlerts": {
|
||||
"count": 12,
|
||||
"amount": 1850000.0
|
||||
},
|
||||
"highRiskShare": 28,
|
||||
"last30Days": {
|
||||
"count": 15,
|
||||
"amount": 980000.0
|
||||
},
|
||||
"byStatus": {
|
||||
"true_positive": {
|
||||
"count": 12,
|
||||
"amount": 1850000.0
|
||||
},
|
||||
"false_positive": {
|
||||
"count": 24,
|
||||
"amount": 2100000.0
|
||||
},
|
||||
"cleared": {
|
||||
"count": 60,
|
||||
"amount": 3250000.0
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": 877,
|
||||
"company": { ... },
|
||||
"counterparty": "Alpine Holdings Ltd.",
|
||||
"status": "true_positive",
|
||||
"riskScore": 87,
|
||||
"amount": 245000.75,
|
||||
"currency": "EUR",
|
||||
"reference": "PAY-2024-10-1942",
|
||||
"channel": "SWIFT",
|
||||
"executedAt": "2025-01-18T09:14:00Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"perPage": 10,
|
||||
"total": 12,
|
||||
"lastPage": 2
|
||||
},
|
||||
"selectedTransactionId": null,
|
||||
"selectedTransaction": null,
|
||||
"recentAlerts": [
|
||||
{
|
||||
"id": 911,
|
||||
"counterparty": "Northbridge Trading Ltd.",
|
||||
"executedAt": "2025-01-18T14:52:00Z",
|
||||
"channel": "SWIFT",
|
||||
"status": "true_positive",
|
||||
"statusLabel": "Bestätigter Treffer",
|
||||
"riskScore": 92
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- `401 Unauthorized` - Session ungültig
|
||||
- `404 Not Found` - Company nicht gefunden oder keine Transactions
|
||||
|
||||
---
|
||||
|
||||
#### GET /companies/{companyId}/transactions/{transactionId}
|
||||
|
||||
Hole Transaction Case File mit Company-Context und History.
|
||||
|
||||
**Path Parameters:**
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
|-----------|-----|--------------|
|
||||
| `companyId` | integer | Company ID |
|
||||
| `transactionId` | integer | Transaction ID |
|
||||
|
||||
**Request:**
|
||||
```http
|
||||
GET /api/companies/12/transactions/877 HTTP/1.1
|
||||
Cookie: laravel_session=...
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"id": 877,
|
||||
"company": { ... },
|
||||
"counterparty": "Alpine Holdings Ltd.",
|
||||
"status": "true_positive",
|
||||
"statusLabel": "Bestätigter Treffer",
|
||||
"requiresReview": true,
|
||||
"riskScore": 87,
|
||||
"amount": 245000.75,
|
||||
"currency": "EUR",
|
||||
"reference": "PAY-2024-10-1942",
|
||||
"channel": "SWIFT",
|
||||
"executedAt": "2025-01-18T09:14:00Z",
|
||||
"flaggedBy": "Screening Engine",
|
||||
"flaggedReason": "Counterparty matched to EU sanctions list",
|
||||
"signals": [
|
||||
{
|
||||
"type": "Adverse Media",
|
||||
"value": "Enforcement action reported in 2024-11",
|
||||
"weight": 0.8
|
||||
}
|
||||
],
|
||||
"recommendedActions": [
|
||||
{
|
||||
"title": "Verdachtsmeldung vorbereiten",
|
||||
"description": "Erstellen Sie den Meldeentwurf für die FIU und sichern Sie Belege."
|
||||
},
|
||||
{
|
||||
"title": "Enhanced Due Diligence",
|
||||
"description": "Prüfen Sie zusätzliche Informationsquellen zur Counterparty."
|
||||
}
|
||||
],
|
||||
"counterpartyHistory": [
|
||||
{
|
||||
"id": 765,
|
||||
"executedAt": "2024-12-22T15:37:00Z",
|
||||
"channel": "SWIFT",
|
||||
"reference": "PAY-2024-12-1187",
|
||||
"amount": 99000.0,
|
||||
"currency": "EUR",
|
||||
"riskScore": 74
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- `401 Unauthorized` - Session ungültig
|
||||
- `404 Not Found` - Transaction nicht gefunden für diese Company
|
||||
|
||||
---
|
||||
|
||||
## Datenmodelle
|
||||
|
||||
### Transaction Status
|
||||
|
||||
```typescript
|
||||
enum TransactionStatus {
|
||||
TRUE_POSITIVE = 'true_positive', // Kritisches Risiko
|
||||
FALSE_POSITIVE = 'false_positive', // Hohes Risiko
|
||||
CLEARED = 'cleared' // Geringes Risiko
|
||||
}
|
||||
```
|
||||
|
||||
### KYC Risk Level
|
||||
|
||||
```typescript
|
||||
enum KycRiskLevel {
|
||||
LOW = 'low',
|
||||
HIGH = 'high',
|
||||
CRITICAL = 'critical'
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination Meta
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"perPage": 12,
|
||||
"total": 128,
|
||||
"lastPage": 11
|
||||
}
|
||||
```
|
||||
|
||||
### Count Amount Summary
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 42,
|
||||
"amount": 2750000.50
|
||||
}
|
||||
```
|
||||
|
||||
### Transaction Signal
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Adverse Media",
|
||||
"value": "Enforcement action reported in 2024-11",
|
||||
"weight": 0.8
|
||||
}
|
||||
```
|
||||
|
||||
### Recommended Action
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Verdachtsmeldung vorbereiten",
|
||||
"description": "Erstellen Sie den Meldeentwurf für die FIU..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiele
|
||||
|
||||
### cURL-Beispiele
|
||||
|
||||
#### Login & Session Cookie erhalten
|
||||
|
||||
```bash
|
||||
# Login
|
||||
curl -c cookies.txt -X POST http://trusted_ai.test/login \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "email=user@example.com&password=secret"
|
||||
|
||||
# Session Cookie ist jetzt in cookies.txt gespeichert
|
||||
```
|
||||
|
||||
#### Dashboard Overview abrufen
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt http://trusted_ai.test/api/dashboard/overview
|
||||
```
|
||||
|
||||
#### Transactions filtern
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt "http://trusted_ai.test/api/transactions?status=true_positive&page=1&perPage=12"
|
||||
```
|
||||
|
||||
#### Company suchen
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt "http://trusted_ai.test/api/companies/search?q=Allianz&limit=6"
|
||||
```
|
||||
|
||||
#### Company Transactions abrufen
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt "http://trusted_ai.test/api/companies/12/transactions?status=all&page=1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### JavaScript (Fetch API)
|
||||
|
||||
```javascript
|
||||
// Session Cookie wird automatisch vom Browser gesendet
|
||||
|
||||
// Dashboard Overview
|
||||
const overview = await fetch('/api/dashboard/overview', {
|
||||
credentials: 'include' // Wichtig: Session Cookie mitsenden
|
||||
}).then(r => r.json());
|
||||
|
||||
console.log(overview);
|
||||
|
||||
// Transactions mit Filter
|
||||
const transactions = await fetch('/api/transactions?status=true_positive&page=1&perPage=12', {
|
||||
credentials: 'include'
|
||||
}).then(r => r.json());
|
||||
|
||||
console.log(transactions.data);
|
||||
|
||||
// Company Search
|
||||
const companies = await fetch('/api/companies/search?q=Allianz', {
|
||||
credentials: 'include'
|
||||
}).then(r => r.json());
|
||||
|
||||
console.log(companies.results);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TypeScript Types
|
||||
|
||||
```typescript
|
||||
// Dashboard Overview Response
|
||||
interface DashboardOverview {
|
||||
generatedAt: string;
|
||||
activeAnalysts: number;
|
||||
totalTransactions: number;
|
||||
totalVolume: number;
|
||||
alertsToday: number;
|
||||
averageAlertsPerAnalyst: number;
|
||||
precisionRate: number;
|
||||
activeAlerts: {
|
||||
count: number;
|
||||
highRiskCount: number;
|
||||
};
|
||||
riskSegments: RiskSegment[];
|
||||
topCompanies: TopCompanyAlert[];
|
||||
runbooksExecutedToday: number;
|
||||
}
|
||||
|
||||
// Transaction Collection Response
|
||||
interface TransactionCollectionResponse {
|
||||
filters: {
|
||||
statusOptions: StatusOption[];
|
||||
perPage: number;
|
||||
};
|
||||
metrics: TransactionMetrics;
|
||||
data: TransactionPreview[];
|
||||
pagination: PaginationMeta;
|
||||
selectedTransactionId: number | null;
|
||||
selectedTransaction: TransactionDetail | null;
|
||||
}
|
||||
|
||||
// Company Search Response
|
||||
interface CompanySearchResponse {
|
||||
overview: CompanySearchOverview;
|
||||
results: CompanySearchResult[];
|
||||
meta: {
|
||||
query: string | null;
|
||||
resultCount: number;
|
||||
limit: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Standard Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Error message",
|
||||
"errors": {
|
||||
"field": ["Validation error"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Status Codes
|
||||
|
||||
| Code | Bedeutung | Beschreibung |
|
||||
|------|-----------|--------------|
|
||||
| `200` | OK | Request erfolgreich |
|
||||
| `401` | Unauthorized | Session ungültig oder abgelaufen |
|
||||
| `404` | Not Found | Resource nicht gefunden |
|
||||
| `422` | Unprocessable Entity | Validierungsfehler |
|
||||
| `500` | Internal Server Error | Server-Fehler |
|
||||
|
||||
### Beispiel: 401 Unauthorized
|
||||
|
||||
```json
|
||||
HTTP/1.1 401 Unauthorized
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"message": "Unauthenticated."
|
||||
}
|
||||
```
|
||||
|
||||
**Aktion:** Redirect zu `/login`
|
||||
|
||||
### Beispiel: 404 Not Found
|
||||
|
||||
```json
|
||||
HTTP/1.1 404 Not Found
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"message": "Company not found."
|
||||
}
|
||||
```
|
||||
|
||||
### Beispiel: 422 Validation Error
|
||||
|
||||
```json
|
||||
HTTP/1.1 422 Unprocessable Entity
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"message": "The given data was invalid.",
|
||||
"errors": {
|
||||
"status": ["The selected status is invalid."],
|
||||
"page": ["The page must be at least 1."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
### Aktueller Status
|
||||
|
||||
**Aktuell:** Kein Rate Limiting implementiert.
|
||||
|
||||
### Geplant (Future)
|
||||
|
||||
```
|
||||
Rate Limit: 60 Requests pro Minute pro User
|
||||
Header: X-RateLimit-Limit, X-RateLimit-Remaining
|
||||
```
|
||||
|
||||
**Response bei Überschreitung:**
|
||||
```json
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Content-Type: application/json
|
||||
Retry-After: 60
|
||||
|
||||
{
|
||||
"message": "Too many requests. Please try again later."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0.0 (2025-01-19)
|
||||
|
||||
**Initial Release**
|
||||
|
||||
**Endpoints:**
|
||||
- ✅ `GET /dashboard/overview` - Dashboard KPIs
|
||||
- ✅ `GET /transactions` - Portfolio-wide Transactions
|
||||
- ✅ `GET /transactions/{id}` - Transaction Detail
|
||||
- ✅ `GET /companies/search` - Company Search
|
||||
- ✅ `GET /companies/{id}` - Company Profile
|
||||
- ✅ `GET /companies/{id}/transactions` - Company Transactions
|
||||
- ✅ `GET /companies/{id}/transactions/{id}` - Company Transaction Detail
|
||||
|
||||
**Features:**
|
||||
- Session-basierte Authentifizierung
|
||||
- Pagination Support
|
||||
- Filter & Search
|
||||
- Metrics & Aggregationen
|
||||
- OpenAPI 3.1 Spezifikation
|
||||
|
||||
---
|
||||
|
||||
## Implementierungs-Status
|
||||
|
||||
### ⚠️ Wichtig: API noch nicht implementiert!
|
||||
|
||||
Die in diesem Dokument beschriebenen Endpoints sind **aktuell noch nicht implementiert**.
|
||||
|
||||
**Status:**
|
||||
- ✅ OpenAPI Spezifikation vorhanden ([swagger.yaml](../swagger.yaml))
|
||||
- ❌ API Routes noch nicht definiert (`routes/api.php` ist leer)
|
||||
- ❌ API Controllers noch nicht erstellt
|
||||
- ❌ API Responses noch nicht implementiert
|
||||
|
||||
### Nächste Schritte zur Implementierung
|
||||
|
||||
1. **API Routes definieren:**
|
||||
```php
|
||||
// routes/api.php
|
||||
Route::middleware(['auth:sanctum'])->group(function () {
|
||||
Route::get('/dashboard/overview', [DashboardController::class, 'overview']);
|
||||
Route::get('/transactions', [TransactionController::class, 'index']);
|
||||
Route::get('/transactions/{transaction}', [TransactionController::class, 'show']);
|
||||
Route::get('/companies/search', [CompanyController::class, 'search']);
|
||||
Route::get('/companies/{company}', [CompanyController::class, 'show']);
|
||||
Route::get('/companies/{company}/transactions', [CompanyController::class, 'transactions']);
|
||||
});
|
||||
```
|
||||
|
||||
2. **API Controllers erstellen:**
|
||||
```bash
|
||||
php artisan make:controller Api/DashboardController
|
||||
php artisan make:controller Api/TransactionController
|
||||
php artisan make:controller Api/CompanyController
|
||||
```
|
||||
|
||||
3. **API Resources erstellen:**
|
||||
```bash
|
||||
php artisan make:resource TransactionResource
|
||||
php artisan make:resource CompanyResource
|
||||
```
|
||||
|
||||
4. **Tests schreiben:**
|
||||
```bash
|
||||
php artisan make:test Api/DashboardTest
|
||||
php artisan make:test Api/TransactionTest
|
||||
php artisan make:test Api/CompanyTest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
### API-Übersicht
|
||||
|
||||
| Kategorie | Endpoints | Status |
|
||||
|-----------|-----------|--------|
|
||||
| Dashboard | 1 | ⚠️ Spezifiziert, nicht implementiert |
|
||||
| Transactions | 2 | ⚠️ Spezifiziert, nicht implementiert |
|
||||
| Companies | 4 | ⚠️ Spezifiziert, nicht implementiert |
|
||||
| **Gesamt** | **7** | - |
|
||||
|
||||
### Datenmodelle
|
||||
|
||||
- **DashboardOverview** - Dashboard-KPIs
|
||||
- **TransactionCollectionResponse** - Paginierte Transactions
|
||||
- **TransactionDetail** - Detaillierte Transaction
|
||||
- **CompanySearchResponse** - Company-Suchergebnisse
|
||||
- **CompanyDetail** - Company-Profil
|
||||
- **CompanyTransactionsResponse** - Company-spezifische Transactions
|
||||
|
||||
### Authentifizierung
|
||||
|
||||
**Session-basiert** via Laravel Session Cookie
|
||||
- Login erforderlich
|
||||
- Cookie-basierte Authentifizierung
|
||||
- CSRF-Protection
|
||||
|
||||
---
|
||||
|
||||
**Erstellt:** 2025-11-24
|
||||
**Version:** 1.0.0
|
||||
**Autor:** Risk Intelligence Platform Team
|
||||
**OpenAPI Spec:** [swagger.yaml](../swagger.yaml)
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,991 @@
|
||||
# Entwickler-Setup-Guide: Risk Intelligence Platform
|
||||
|
||||
## 📋 Inhaltsverzeichnis
|
||||
|
||||
1. [Voraussetzungen](#voraussetzungen)
|
||||
2. [Schnellstart](#schnellstart)
|
||||
3. [Detaillierte Installation](#detaillierte-installation)
|
||||
4. [Datenbank-Setup](#datenbank-setup)
|
||||
5. [Environment-Konfiguration](#environment-konfiguration)
|
||||
6. [Frontend-Assets](#frontend-assets)
|
||||
7. [Erste Schritte](#erste-schritte)
|
||||
8. [Development-Workflow](#development-workflow)
|
||||
9. [Troubleshooting](#troubleshooting)
|
||||
10. [Nützliche Commands](#nützliche-commands)
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
### Erforderliche Software
|
||||
|
||||
| Software | Mindestversion | Empfohlen | Installation |
|
||||
|----------|---------------|-----------|--------------|
|
||||
| **PHP** | 8.2 | 8.4.14+ | [Laravel Herd](https://herd.laravel.com) |
|
||||
| **Composer** | 2.0 | Latest | Via Herd oder [getcomposer.org](https://getcomposer.org) |
|
||||
| **Node.js** | 18.x | 20.x+ | [nodejs.org](https://nodejs.org) |
|
||||
| **npm** | 9.x | Latest | Mit Node.js installiert |
|
||||
| **PostgreSQL** | 14.x | 16.x+ | [postgresql.org](https://www.postgresql.org) |
|
||||
| **Git** | 2.x | Latest | [git-scm.com](https://git-scm.com) |
|
||||
|
||||
### Empfohlene Tools
|
||||
|
||||
- **Laravel Herd** - Lokale PHP-Entwicklungsumgebung (macOS)
|
||||
- **TablePlus** / **pgAdmin** - PostgreSQL GUI
|
||||
- **VS Code** - Editor mit PHP/Laravel Extensions
|
||||
- **Postman** / **Insomnia** - API Testing (optional)
|
||||
|
||||
### PHP Extensions
|
||||
|
||||
Folgende PHP Extensions werden benötigt:
|
||||
|
||||
```bash
|
||||
# Prüfe installierte Extensions
|
||||
php -m
|
||||
|
||||
# Benötigte Extensions:
|
||||
- pdo_pgsql # PostgreSQL Driver
|
||||
- mbstring # Multibyte String
|
||||
- xml # XML Support
|
||||
- curl # HTTP Client
|
||||
- bcmath # Precision Math
|
||||
- fileinfo # File Information
|
||||
- json # JSON Support
|
||||
- openssl # Encryption
|
||||
- tokenizer # PHP Tokenizer
|
||||
- zip # ZIP Archive
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schnellstart
|
||||
|
||||
Für erfahrene Entwickler - vollständige Installation in 5 Minuten:
|
||||
|
||||
```bash
|
||||
# 1. Repository klonen
|
||||
git clone <repository-url> risk-platform
|
||||
cd risk-platform
|
||||
|
||||
# 2. Dependencies installieren & Setup ausführen
|
||||
composer setup
|
||||
|
||||
# 3. PostgreSQL-Datenbank erstellen (siehe unten)
|
||||
# Anpassen: .env mit DB-Credentials
|
||||
|
||||
# 4. Migrations ausführen
|
||||
php artisan migrate --seed
|
||||
|
||||
# 5. Development-Server starten
|
||||
composer run dev
|
||||
```
|
||||
|
||||
**Fertig!** Die Applikation läuft auf [http://localhost:8000](http://localhost:8000)
|
||||
|
||||
---
|
||||
|
||||
## Detaillierte Installation
|
||||
|
||||
### Schritt 1: Repository klonen
|
||||
|
||||
```bash
|
||||
# Via HTTPS
|
||||
git clone https://github.com/your-org/risk-platform.git
|
||||
cd risk-platform
|
||||
|
||||
# Oder via SSH
|
||||
git clone git@github.com:your-org/risk-platform.git
|
||||
cd risk-platform
|
||||
```
|
||||
|
||||
### Schritt 2: PHP Dependencies installieren
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
**Was passiert dabei?**
|
||||
- Installiert alle Laravel-Pakete
|
||||
- Installiert Livewire, Flux UI, Fortify
|
||||
- Installiert Dev-Dependencies (Pest, Pint, Boost)
|
||||
- Generiert Autoload-Files
|
||||
|
||||
### Schritt 3: Environment-Datei erstellen
|
||||
|
||||
```bash
|
||||
# .env.example nach .env kopieren
|
||||
cp .env.example .env
|
||||
|
||||
# Application Key generieren
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
### Schritt 4: Node Dependencies installieren
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
**Was passiert dabei?**
|
||||
- Installiert Vite
|
||||
- Installiert Tailwind CSS v4
|
||||
- Installiert Build-Tools
|
||||
|
||||
---
|
||||
|
||||
## Datenbank-Setup
|
||||
|
||||
### PostgreSQL Installation
|
||||
|
||||
#### macOS (mit Homebrew)
|
||||
|
||||
```bash
|
||||
# PostgreSQL installieren
|
||||
brew install postgresql@16
|
||||
|
||||
# Als Service starten
|
||||
brew services start postgresql@16
|
||||
|
||||
# Verbindung testen
|
||||
psql postgres
|
||||
```
|
||||
|
||||
#### macOS (mit Postgres.app)
|
||||
|
||||
1. Download von [postgresapp.com](https://postgresapp.com)
|
||||
2. App starten
|
||||
3. Default Server initialisieren
|
||||
|
||||
#### Ubuntu/Debian
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install postgresql postgresql-contrib
|
||||
sudo systemctl start postgresql
|
||||
sudo systemctl enable postgresql
|
||||
```
|
||||
|
||||
### Datenbank erstellen
|
||||
|
||||
```bash
|
||||
# Als postgres User einloggen
|
||||
psql postgres
|
||||
|
||||
# Oder direkt mit spezifischem User
|
||||
sudo -u postgres psql
|
||||
```
|
||||
|
||||
```sql
|
||||
-- Datenbank erstellen
|
||||
CREATE DATABASE risk_platform_db;
|
||||
|
||||
-- User erstellen (optional)
|
||||
CREATE USER risk_user WITH ENCRYPTED PASSWORD 'your_secure_password';
|
||||
|
||||
-- Rechte vergeben
|
||||
GRANT ALL PRIVILEGES ON DATABASE risk_platform_db TO risk_user;
|
||||
|
||||
-- In PostgreSQL 15+ auch Schema-Rechte vergeben
|
||||
\c risk_platform_db
|
||||
GRANT ALL ON SCHEMA public TO risk_user;
|
||||
|
||||
-- Exit
|
||||
\q
|
||||
```
|
||||
|
||||
### Schemas erstellen
|
||||
|
||||
```sql
|
||||
-- Verbindung zur DB
|
||||
psql -U risk_user -d risk_platform_db
|
||||
|
||||
-- PUBLIC Schema (wird automatisch erstellt)
|
||||
-- BACKEND Schema erstellen
|
||||
CREATE SCHEMA IF NOT EXISTS backend;
|
||||
|
||||
-- Rechte für BACKEND Schema
|
||||
GRANT ALL ON SCHEMA backend TO risk_user;
|
||||
|
||||
-- Exit
|
||||
\q
|
||||
```
|
||||
|
||||
### Verbindung testen
|
||||
|
||||
```bash
|
||||
# Direkter Connect
|
||||
psql -h 127.0.0.1 -p 5432 -U risk_user -d risk_platform_db
|
||||
|
||||
# Mit Laravel Tinker
|
||||
php artisan tinker
|
||||
>>> DB::connection('pgsql_second')->select('SELECT version()');
|
||||
>>> DB::connection('backend')->select('SELECT current_schema()');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment-Konfiguration
|
||||
|
||||
### .env Datei anpassen
|
||||
|
||||
```bash
|
||||
# .env öffnen und anpassen
|
||||
nano .env
|
||||
```
|
||||
|
||||
#### Grundkonfiguration
|
||||
|
||||
```ini
|
||||
APP_NAME="Risk Intelligence Platform"
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:xxx # Wird von key:generate erstellt
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
APP_LOCALE=de
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=de_DE
|
||||
```
|
||||
|
||||
#### Datenbank-Konfiguration (Wichtig!)
|
||||
|
||||
```ini
|
||||
# Default Connection (PUBLIC Schema)
|
||||
DB_CONNECTION=pgsql
|
||||
DB_CONNECTION2=pgsql
|
||||
DB_HOST2=127.0.0.1
|
||||
DB_PORT2=5432
|
||||
DB_DATABASE2=risk_platform_db
|
||||
DB_USERNAME2=risk_user
|
||||
DB_PASSWORD2=your_secure_password
|
||||
```
|
||||
|
||||
**Wichtig:** Die Applikation nutzt `DB_*2` Variablen für PostgreSQL!
|
||||
|
||||
#### Cache & Session
|
||||
|
||||
```ini
|
||||
CACHE_STORE=database
|
||||
SESSION_DRIVER=database
|
||||
QUEUE_CONNECTION=database
|
||||
```
|
||||
|
||||
**Warum database?**
|
||||
- Einfaches Setup ohne Redis
|
||||
- Funktioniert out-of-the-box
|
||||
- Für Produktion: Wechsel zu Redis empfohlen
|
||||
|
||||
#### Mail-Konfiguration (Development)
|
||||
|
||||
```ini
|
||||
MAIL_MAILER=log
|
||||
MAIL_FROM_ADDRESS="noreply@risk-platform.test"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
Mails werden in `storage/logs/laravel.log` gespeichert.
|
||||
|
||||
#### Optional: Redis (für bessere Performance)
|
||||
|
||||
```ini
|
||||
CACHE_STORE=redis
|
||||
SESSION_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
```
|
||||
|
||||
### Vollständige .env Vorlage
|
||||
|
||||
<details>
|
||||
<summary>Klicken für vollständige .env Beispiel-Datei</summary>
|
||||
|
||||
```ini
|
||||
APP_NAME="Risk Intelligence Platform"
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:xxx
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
APP_LOCALE=de
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=de_DE
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
PHP_CLI_SERVER_WORKERS=4
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# PostgreSQL Configuration
|
||||
DB_CONNECTION=pgsql
|
||||
DB_CONNECTION2=pgsql
|
||||
DB_HOST2=127.0.0.1
|
||||
DB_PORT2=5432
|
||||
DB_DATABASE2=risk_platform_db
|
||||
DB_USERNAME2=risk_user
|
||||
DB_PASSWORD2=your_secure_password
|
||||
|
||||
# Session & Cache
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
CACHE_STORE=database
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
# Broadcasting & Filesystem
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
|
||||
# Mail
|
||||
MAIL_MAILER=log
|
||||
MAIL_FROM_ADDRESS="noreply@risk-platform.test"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
# Vite
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Frontend-Assets
|
||||
|
||||
### Assets kompilieren
|
||||
|
||||
```bash
|
||||
# Development Build (einmalig)
|
||||
npm run build
|
||||
|
||||
# Development mit Hot Reload
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Was macht `npm run dev`?
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ VITE v7.0.4 ready in X ms │
|
||||
│ │
|
||||
│ ➜ Local: http://localhost:5173/ │
|
||||
│ ➜ Network: use --host to expose │
|
||||
│ │
|
||||
│ ➜ Laravel: http://localhost:8000 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
|
||||
✓ compiled successfully
|
||||
```
|
||||
|
||||
- Startet Vite Dev-Server auf Port 5173
|
||||
- Hot Module Replacement (HMR) aktiviert
|
||||
- Tailwind CSS v4 wird kompiliert
|
||||
- Livewire-Assets werden gebundled
|
||||
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
|
||||
# Output:
|
||||
# dist/assets/app-xxxxx.js
|
||||
# dist/assets/app-xxxxx.css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Erste Schritte
|
||||
|
||||
### 1. Migrations ausführen
|
||||
|
||||
```bash
|
||||
# Alle Migrations ausführen
|
||||
php artisan migrate
|
||||
|
||||
# Mit Seeding (Test-Daten)
|
||||
php artisan migrate --seed
|
||||
|
||||
# Migration-Status prüfen
|
||||
php artisan migrate:status
|
||||
```
|
||||
|
||||
**Erwartete Tabellen nach Migration:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────┬────────┐
|
||||
│ Migration │ Ran? │
|
||||
├──────────────────────────────────┼────────┤
|
||||
│ 0001_01_01_000000_create_users │ Yes │
|
||||
│ 0001_01_01_000001_create_cache │ Yes │
|
||||
│ 0001_01_01_000002_create_jobs │ Yes │
|
||||
│ 2025_09_02_075243_add_two_factor │ Yes │
|
||||
│ 2025_10_20_181750_create_companies │ Yes │
|
||||
│ 2025_10_20_181753_create_transactions │ Yes │
|
||||
│ 2025_11_16_140224_add_output_columns │ Yes │
|
||||
│ 2025_11_16_150000_create_backend_data_pool │ Yes │
|
||||
│ ... │ │
|
||||
└──────────────────────────────────┴────────┘
|
||||
```
|
||||
|
||||
### 2. Test-User erstellen
|
||||
|
||||
```bash
|
||||
php artisan tinker
|
||||
```
|
||||
|
||||
```php
|
||||
// Test-User erstellen
|
||||
$user = App\Models\User::create([
|
||||
'name' => 'Admin User',
|
||||
'email' => 'admin@example.com',
|
||||
'password' => bcrypt('password'),
|
||||
'email_verified_at' => now(),
|
||||
]);
|
||||
|
||||
// Oder mit Factory
|
||||
App\Models\User::factory()->create([
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
|
||||
exit
|
||||
```
|
||||
|
||||
### 3. Development-Server starten
|
||||
|
||||
#### Option A: Composer Dev Command (Empfohlen)
|
||||
|
||||
```bash
|
||||
composer run dev
|
||||
```
|
||||
|
||||
**Startet parallel:**
|
||||
- `php artisan serve` → http://localhost:8000
|
||||
- `php artisan queue:listen` → Queue Worker
|
||||
- `php artisan pail` → Live Logs
|
||||
- `npm run dev` → Vite Dev Server
|
||||
|
||||
#### Option B: Einzelne Services manuell
|
||||
|
||||
```bash
|
||||
# Terminal 1: Laravel Server
|
||||
php artisan serve
|
||||
|
||||
# Terminal 2: Vite Dev Server
|
||||
npm run dev
|
||||
|
||||
# Terminal 3 (optional): Queue Worker
|
||||
php artisan queue:work
|
||||
|
||||
# Terminal 4 (optional): Logs
|
||||
php artisan pail
|
||||
```
|
||||
|
||||
#### Option C: Laravel Herd (macOS)
|
||||
|
||||
Wenn du Laravel Herd nutzt:
|
||||
|
||||
1. Projekt in Herd-Verzeichnis verschieben
|
||||
2. Automatische Domain: `risk-platform.test`
|
||||
3. HTTPS automatisch aktiviert
|
||||
4. Kein `php artisan serve` notwendig
|
||||
|
||||
```bash
|
||||
# In Herd-Verzeichnis verschieben
|
||||
mv ~/risk-platform ~/Herd/risk-platform
|
||||
|
||||
# Browser öffnen
|
||||
open https://risk-platform.test
|
||||
```
|
||||
|
||||
### 4. Applikation öffnen
|
||||
|
||||
Browser öffnen: [http://localhost:8000](http://localhost:8000)
|
||||
|
||||
**Login-Credentials:**
|
||||
- Email: `test@example.com`
|
||||
- Password: `password`
|
||||
|
||||
---
|
||||
|
||||
## Development-Workflow
|
||||
|
||||
### Typischer Entwicklungstag
|
||||
|
||||
```bash
|
||||
# 1. Morgen: Repository aktualisieren
|
||||
git pull origin main
|
||||
|
||||
# 2. Dependencies aktualisieren (falls composer.lock geändert)
|
||||
composer install
|
||||
npm install
|
||||
|
||||
# 3. Neue Migrations (falls vorhanden)
|
||||
php artisan migrate
|
||||
|
||||
# 4. Dev-Server starten
|
||||
composer run dev
|
||||
|
||||
# 5. Am Ende: Code formatieren
|
||||
vendor/bin/pint
|
||||
|
||||
# 6. Tests ausführen
|
||||
php artisan test
|
||||
|
||||
# 7. Commit & Push
|
||||
git add .
|
||||
git commit -m "feat: neue feature"
|
||||
git push
|
||||
```
|
||||
|
||||
### Code-Formatierung
|
||||
|
||||
```bash
|
||||
# Alle Dateien formatieren
|
||||
vendor/bin/pint
|
||||
|
||||
# Nur geänderte Dateien
|
||||
vendor/bin/pint --dirty
|
||||
|
||||
# Test-Modus (ohne Änderungen)
|
||||
vendor/bin/pint --test
|
||||
```
|
||||
|
||||
### Tests ausführen
|
||||
|
||||
```bash
|
||||
# Alle Tests
|
||||
php artisan test
|
||||
|
||||
# Nur Feature Tests
|
||||
php artisan test --testsuite=Feature
|
||||
|
||||
# Nur Unit Tests
|
||||
php artisan test --testsuite=Unit
|
||||
|
||||
# Spezifische Test-Datei
|
||||
php artisan test tests/Feature/CompanyTest.php
|
||||
|
||||
# Mit Filter
|
||||
php artisan test --filter=testCompanyCreation
|
||||
|
||||
# Mit Coverage (benötigt Xdebug)
|
||||
php artisan test --coverage
|
||||
```
|
||||
|
||||
### Backend-Daten synchronisieren
|
||||
|
||||
```bash
|
||||
# Stats anzeigen (kein Sync)
|
||||
php artisan backend:sync-data-pool --stats
|
||||
|
||||
# Incremental Sync
|
||||
php artisan backend:sync-data-pool --incremental
|
||||
|
||||
# Full Sync
|
||||
php artisan backend:sync-data-pool --full
|
||||
|
||||
# Transformation
|
||||
php artisan backend:transform-data-pool
|
||||
|
||||
# Kompletter Rebuild
|
||||
php artisan backend:rebuild
|
||||
```
|
||||
|
||||
### Scheduler lokal testen
|
||||
|
||||
```bash
|
||||
# Schedule Worker (läuft dauerhaft)
|
||||
php artisan schedule:work
|
||||
|
||||
# Oder: Nächste Due-Tasks anzeigen
|
||||
php artisan schedule:list
|
||||
|
||||
# Einzelnen Task testen
|
||||
php artisan schedule:test sync-backend-data-pool-incremental
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: "SQLSTATE[08006] Connection refused"
|
||||
|
||||
**Ursache:** PostgreSQL läuft nicht oder falsche Credentials
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# PostgreSQL-Status prüfen
|
||||
brew services list | grep postgresql
|
||||
|
||||
# PostgreSQL starten
|
||||
brew services start postgresql@16
|
||||
|
||||
# Verbindung testen
|
||||
psql -h 127.0.0.1 -p 5432 -U risk_user -d risk_platform_db
|
||||
|
||||
# .env prüfen
|
||||
cat .env | grep DB_
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: "Class 'PDO' not found"
|
||||
|
||||
**Ursache:** PHP PostgreSQL Extension fehlt
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Installierte Extensions prüfen
|
||||
php -m | grep pdo
|
||||
|
||||
# Mit Herd (macOS)
|
||||
# Extensions sind normalerweise vorinstalliert
|
||||
|
||||
# Oder PHP neu installieren via Homebrew
|
||||
brew reinstall php@8.4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: "Vite manifest not found"
|
||||
|
||||
**Ursache:** Frontend-Assets nicht kompiliert
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Development Build
|
||||
npm run build
|
||||
|
||||
# Oder Dev-Server starten
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: "419 Page Expired" bei Forms
|
||||
|
||||
**Ursache:** CSRF-Token ungültig (Session abgelaufen)
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Cache leeren
|
||||
php artisan config:clear
|
||||
php artisan cache:clear
|
||||
php artisan view:clear
|
||||
|
||||
# Browser: Hard Refresh (Cmd+Shift+R / Ctrl+Shift+R)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: Migrations laufen nicht
|
||||
|
||||
**Ursache:** Datenbank-Schema fehlt oder keine Rechte
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Schema-Rechte prüfen
|
||||
psql -U risk_user -d risk_platform_db
|
||||
|
||||
# In psql:
|
||||
SELECT schema_name FROM information_schema.schemata;
|
||||
|
||||
# Rechte vergeben
|
||||
GRANT ALL ON SCHEMA public TO risk_user;
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA public TO risk_user;
|
||||
|
||||
# Migration erneut versuchen
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: Queue-Jobs laufen nicht
|
||||
|
||||
**Ursache:** Queue-Worker läuft nicht
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Queue-Worker starten
|
||||
php artisan queue:work
|
||||
|
||||
# Oder mit Auto-Restart bei Code-Änderungen
|
||||
php artisan queue:listen
|
||||
|
||||
# Failed Jobs anzeigen
|
||||
php artisan queue:failed
|
||||
|
||||
# Failed Jobs erneut versuchen
|
||||
php artisan queue:retry all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: "Class not found" Fehler
|
||||
|
||||
**Ursache:** Autoload-Cache veraltet
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Autoload neu generieren
|
||||
composer dump-autoload
|
||||
|
||||
# Alle Caches leeren
|
||||
php artisan optimize:clear
|
||||
|
||||
# Oder einzeln:
|
||||
php artisan config:clear
|
||||
php artisan cache:clear
|
||||
php artisan route:clear
|
||||
php artisan view:clear
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: Livewire-Components laden nicht
|
||||
|
||||
**Ursache:** Volt-Components nicht registriert
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Volt Provider prüfen
|
||||
cat bootstrap/providers.php | grep Volt
|
||||
|
||||
# Cache leeren
|
||||
php artisan optimize:clear
|
||||
|
||||
# Browser Cache leeren
|
||||
# Cmd+Shift+R (macOS) oder Ctrl+Shift+R (Windows/Linux)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Problem: Styling fehlt / Tailwind funktioniert nicht
|
||||
|
||||
**Ursache:** Vite-Server läuft nicht oder falsche Konfiguration
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Vite Dev-Server neu starten
|
||||
npm run dev
|
||||
|
||||
# Oder Production-Build
|
||||
npm run build
|
||||
|
||||
# Tailwind-Config prüfen
|
||||
cat tailwind.config.js
|
||||
|
||||
# Node-Modules neu installieren
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nützliche Commands
|
||||
|
||||
### Laravel Artisan
|
||||
|
||||
```bash
|
||||
# Liste aller Commands
|
||||
php artisan list
|
||||
|
||||
# Command-Hilfe
|
||||
php artisan help migrate
|
||||
|
||||
# Interaktive Shell
|
||||
php artisan tinker
|
||||
|
||||
# Cache Management
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
php artisan view:clear
|
||||
php artisan optimize:clear # Alle Caches auf einmal
|
||||
|
||||
# Database
|
||||
php artisan db:show # DB-Info anzeigen
|
||||
php artisan migrate:status # Migration-Status
|
||||
php artisan migrate:fresh --seed # DB neu aufsetzen + Seeds
|
||||
|
||||
# Make Commands
|
||||
php artisan make:model Company
|
||||
php artisan make:controller CompanyController
|
||||
php artisan make:migration create_companies_table
|
||||
php artisan make:livewire CompanySearch
|
||||
php artisan make:test CompanyTest
|
||||
php artisan make:class Services/MyService
|
||||
|
||||
# Routes
|
||||
php artisan route:list # Alle Routes anzeigen
|
||||
php artisan route:list --path=companies # Gefiltert
|
||||
```
|
||||
|
||||
### Composer
|
||||
|
||||
```bash
|
||||
# Dependencies installieren
|
||||
composer install
|
||||
|
||||
# Development-Dependencies überspringen
|
||||
composer install --no-dev
|
||||
|
||||
# Einzelnes Package installieren
|
||||
composer require laravel/telescope
|
||||
|
||||
# Package entfernen
|
||||
composer remove laravel/telescope
|
||||
|
||||
# Autoload regenerieren
|
||||
composer dump-autoload
|
||||
|
||||
# Setup ausführen (custom script)
|
||||
composer setup
|
||||
|
||||
# Dev-Server starten (custom script)
|
||||
composer run dev
|
||||
|
||||
# Tests ausführen (custom script)
|
||||
composer test
|
||||
```
|
||||
|
||||
### NPM
|
||||
|
||||
```bash
|
||||
# Dependencies installieren
|
||||
npm install
|
||||
|
||||
# Dev-Server starten
|
||||
npm run dev
|
||||
|
||||
# Production Build
|
||||
npm run build
|
||||
|
||||
# Package installieren
|
||||
npm install alpine.js
|
||||
|
||||
# Package entfernen
|
||||
npm uninstall alpine.js
|
||||
|
||||
# Outdated Packages prüfen
|
||||
npm outdated
|
||||
|
||||
# Updates installieren
|
||||
npm update
|
||||
```
|
||||
|
||||
### Git
|
||||
|
||||
```bash
|
||||
# Aktueller Branch
|
||||
git branch
|
||||
|
||||
# Neuen Branch erstellen
|
||||
git checkout -b feature/neue-funktion
|
||||
|
||||
# Änderungen anzeigen
|
||||
git status
|
||||
git diff
|
||||
|
||||
# Commit
|
||||
git add .
|
||||
git commit -m "feat: neue funktion"
|
||||
git push origin feature/neue-funktion
|
||||
|
||||
# Main aktualisieren
|
||||
git checkout main
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```bash
|
||||
# Verbinden
|
||||
psql -U risk_user -d risk_platform_db
|
||||
|
||||
# In psql:
|
||||
\dt # Alle Tabellen
|
||||
\d companies # Tabellen-Schema anzeigen
|
||||
\dn # Alle Schemas
|
||||
\du # Alle User
|
||||
\l # Alle Datenbanken
|
||||
\q # Exit
|
||||
|
||||
# Backup erstellen
|
||||
pg_dump -U risk_user risk_platform_db > backup.sql
|
||||
|
||||
# Backup einspielen
|
||||
psql -U risk_user risk_platform_db < backup.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
Nach erfolgreichem Setup:
|
||||
|
||||
1. **Architektur-Dokumentation lesen** → [docs/architecture-overview.md](architecture-overview.md)
|
||||
2. **Backend-Sync Commands kennenlernen** → [docs/backend-sync-commands.md](backend-sync-commands.md)
|
||||
3. **Code-Konventionen studieren** → [CLAUDE.md](../CLAUDE.md)
|
||||
4. **Tests schreiben & ausführen** → `php artisan test`
|
||||
5. **Erste Änderungen committen** → `git commit`
|
||||
|
||||
---
|
||||
|
||||
## Hilfe & Support
|
||||
|
||||
### Dokumentation
|
||||
|
||||
- **Architektur-Übersicht:** [docs/architecture-overview.md](architecture-overview.md)
|
||||
- **Backend-Sync:** [docs/backend-sync-commands.md](backend-sync-commands.md)
|
||||
- **Scheduling:** [docs/scheduling-setup.md](scheduling-setup.md)
|
||||
- **Migration:** [docs/sync-migration-instructions.md](sync-migration-instructions.md)
|
||||
|
||||
### Laravel Dokumentation
|
||||
|
||||
- **Laravel 12:** [https://laravel.com/docs/12.x](https://laravel.com/docs/12.x)
|
||||
- **Livewire 3:** [https://livewire.laravel.com](https://livewire.laravel.com)
|
||||
- **Flux UI:** [https://flux.laravel.com](https://flux.laravel.com)
|
||||
- **Pest Testing:** [https://pestphp.com](https://pestphp.com)
|
||||
|
||||
### Logs prüfen
|
||||
|
||||
```bash
|
||||
# Application Logs (Live)
|
||||
tail -f storage/logs/laravel.log
|
||||
|
||||
# Nur Fehler
|
||||
tail -f storage/logs/laravel.log | grep ERROR
|
||||
|
||||
# Mit Laravel Pail (farbig)
|
||||
php artisan pail
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# Laravel Debugbar (installiert)
|
||||
# Aktiviert automatisch wenn APP_DEBUG=true
|
||||
# Erscheint am unteren Bildschirmrand im Browser
|
||||
|
||||
# Tinker für schnelle Tests
|
||||
php artisan tinker
|
||||
>>> App\Models\Company::count()
|
||||
>>> DB::table('transactions')->latest()->first()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Setup-Zeit:** ~15-30 Minuten
|
||||
**Erstellt:** 2025-11-24
|
||||
**Version:** 1.0
|
||||
**Autor:** Risk Intelligence Platform Team
|
||||
|
||||
Viel Erfolg beim Entwickeln! 🚀
|
||||
File diff suppressed because it is too large.
Load diff
@@ -18,5 +18,6 @@
|
||||
</div>
|
||||
</div>
|
||||
@fluxScripts
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,6 +8,15 @@ Route::get('/', function () {
|
||||
return view('welcome');
|
||||
})->name('home');
|
||||
|
||||
Route::get('/health', function () {
|
||||
return response()->json([
|
||||
'status' => 'healthy',
|
||||
'timestamp' => now()->toIso8601String(),
|
||||
'app' => config('app.name'),
|
||||
'env' => config('app.env'),
|
||||
]);
|
||||
})->name('health');
|
||||
|
||||
Route::view('dashboard', 'dashboard')
|
||||
->middleware(['auth', 'verified'])
|
||||
->name('dashboard');
|
||||
|
||||
Executable
+258
@@ -0,0 +1,258 @@
|
||||
#!/bin/bash
|
||||
# Deployment Script für Synology NAS
|
||||
# Dieses Script wird auf der Synology NAS ausgeführt
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# ============================================
|
||||
# Configuration
|
||||
# ============================================
|
||||
PROJECT_DIR="/volume1/docker/laravel-app"
|
||||
DOCKER_IMAGE="laravel-app"
|
||||
COMPOSE_FILE="docker-compose.synology.yml"
|
||||
ENV_FILE=".env.synology"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# ============================================
|
||||
# Helper Functions
|
||||
# ============================================
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Pre-deployment Checks
|
||||
# ============================================
|
||||
log_info "Starting deployment process..."
|
||||
|
||||
# Check if running on Synology
|
||||
if [ ! -d "/volume1" ]; then
|
||||
log_error "This script should be run on Synology NAS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Navigate to project directory
|
||||
cd "$PROJECT_DIR" || {
|
||||
log_error "Project directory not found: $PROJECT_DIR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if Docker is installed
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if docker-compose is installed
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
log_error "docker-compose is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Load Docker Image
|
||||
# ============================================
|
||||
if [ -f "laravel-app.tar.gz" ]; then
|
||||
log_info "Loading Docker image from artifact..."
|
||||
gunzip -c laravel-app.tar.gz | docker load
|
||||
|
||||
# Tag the image appropriately
|
||||
LOADED_IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep laravel-app | head -n1)
|
||||
if [ -n "$LOADED_IMAGE" ]; then
|
||||
docker tag "$LOADED_IMAGE" "${DOCKER_IMAGE}:latest"
|
||||
log_info "Tagged image as ${DOCKER_IMAGE}:latest"
|
||||
fi
|
||||
else
|
||||
log_warn "No Docker image artifact found. Using existing image or building..."
|
||||
|
||||
# Check if Dockerfile exists and build if necessary
|
||||
if [ -f "Dockerfile" ]; then
|
||||
log_info "Building Docker image..."
|
||||
docker build -t "${DOCKER_IMAGE}:latest" .
|
||||
else
|
||||
log_error "Neither Docker artifact nor Dockerfile found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Environment Configuration
|
||||
# ============================================
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
log_warn "Environment file not found: $ENV_FILE"
|
||||
|
||||
if [ -f ".env.synology.example" ]; then
|
||||
log_info "Copying from .env.synology.example..."
|
||||
cp .env.synology.example "$ENV_FILE"
|
||||
log_warn "⚠️ Please configure $ENV_FILE with your settings!"
|
||||
else
|
||||
log_error "No environment configuration found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Backup Current Deployment
|
||||
# ============================================
|
||||
log_info "Creating backup of current deployment..."
|
||||
|
||||
BACKUP_DIR="/volume1/docker/backups/laravel-app"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/backup_${TIMESTAMP}.tar.gz"
|
||||
|
||||
# Backup database if container is running
|
||||
if docker ps --format '{{.Names}}' | grep -q "laravel_postgres"; then
|
||||
log_info "Backing up PostgreSQL database..."
|
||||
docker exec laravel_postgres pg_dump -U ingest_user ingest_db > "$BACKUP_DIR/db_${TIMESTAMP}.sql"
|
||||
fi
|
||||
|
||||
# Keep only last 5 backups
|
||||
log_info "Cleaning old backups (keeping last 5)..."
|
||||
ls -t "$BACKUP_DIR"/db_*.sql 2>/dev/null | tail -n +6 | xargs -r rm
|
||||
|
||||
# ============================================
|
||||
# Stop Current Containers
|
||||
# ============================================
|
||||
log_info "Stopping current containers..."
|
||||
|
||||
if [ -f "$COMPOSE_FILE" ]; then
|
||||
docker-compose -f "$COMPOSE_FILE" down || log_warn "No containers to stop"
|
||||
else
|
||||
log_warn "Compose file not found: $COMPOSE_FILE"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Clean up old images (optional)
|
||||
# ============================================
|
||||
log_info "Cleaning up old Docker images..."
|
||||
docker image prune -f
|
||||
|
||||
# ============================================
|
||||
# Start New Deployment
|
||||
# ============================================
|
||||
log_info "Starting new deployment..."
|
||||
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
log_error "Docker Compose file not found: $COMPOSE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Pull/use latest images and start services
|
||||
docker-compose -f "$COMPOSE_FILE" up -d --build
|
||||
|
||||
# ============================================
|
||||
# Wait for Services to Start
|
||||
# ============================================
|
||||
log_info "Waiting for services to start..."
|
||||
sleep 10
|
||||
|
||||
# Check if containers are running
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "laravel_app"; then
|
||||
log_error "Laravel app container failed to start"
|
||||
|
||||
# Show logs
|
||||
log_info "Container logs:"
|
||||
docker-compose -f "$COMPOSE_FILE" logs --tail=50
|
||||
|
||||
# Rollback
|
||||
log_warn "Attempting rollback..."
|
||||
docker-compose -f "$COMPOSE_FILE" down
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Post-deployment Tasks
|
||||
# ============================================
|
||||
log_info "Running post-deployment tasks..."
|
||||
|
||||
# Wait for database to be ready
|
||||
log_info "Waiting for database to be ready..."
|
||||
sleep 5
|
||||
|
||||
# Run migrations
|
||||
log_info "Running database migrations..."
|
||||
docker exec laravel_app php artisan migrate --force || log_warn "Migrations failed"
|
||||
|
||||
# Clear caches
|
||||
log_info "Clearing caches..."
|
||||
docker exec laravel_app php artisan cache:clear || log_warn "Cache clear failed"
|
||||
docker exec laravel_app php artisan config:clear || log_warn "Config clear failed"
|
||||
docker exec laravel_app php artisan view:clear || log_warn "View clear failed"
|
||||
|
||||
# Optimize
|
||||
log_info "Optimizing application..."
|
||||
docker exec laravel_app php artisan config:cache || log_warn "Config cache failed"
|
||||
docker exec laravel_app php artisan route:cache || log_warn "Route cache failed"
|
||||
docker exec laravel_app php artisan view:cache || log_warn "View cache failed"
|
||||
|
||||
# ============================================
|
||||
# Health Check
|
||||
# ============================================
|
||||
log_info "Performing health check..."
|
||||
|
||||
MAX_RETRIES=30
|
||||
RETRY_COUNT=0
|
||||
HEALTH_CHECK_URL="http://localhost:8080"
|
||||
|
||||
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
||||
if curl -f -s "$HEALTH_CHECK_URL" > /dev/null 2>&1; then
|
||||
log_info "✅ Application is healthy!"
|
||||
break
|
||||
fi
|
||||
|
||||
log_info "⏳ Waiting for application... ($RETRY_COUNT/$MAX_RETRIES)"
|
||||
sleep 5
|
||||
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||
done
|
||||
|
||||
if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
|
||||
log_error "Health check failed after $MAX_RETRIES attempts"
|
||||
|
||||
# Show recent logs
|
||||
log_info "Recent application logs:"
|
||||
docker-compose -f "$COMPOSE_FILE" logs --tail=100
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Display Status
|
||||
# ============================================
|
||||
log_info "Deployment completed successfully! 🚀"
|
||||
log_info "Container status:"
|
||||
docker-compose -f "$COMPOSE_FILE" ps
|
||||
|
||||
log_info ""
|
||||
log_info "==================================="
|
||||
log_info "Application URL: http://$(hostname -I | awk '{print $1}'):8080"
|
||||
log_info "==================================="
|
||||
log_info ""
|
||||
log_info "Useful commands:"
|
||||
log_info " - View logs: docker-compose -f $COMPOSE_FILE logs -f"
|
||||
log_info " - Restart: docker-compose -f $COMPOSE_FILE restart"
|
||||
log_info " - Stop: docker-compose -f $COMPOSE_FILE down"
|
||||
log_info " - Shell access: docker exec -it laravel_app sh"
|
||||
|
||||
# ============================================
|
||||
# Cleanup
|
||||
# ============================================
|
||||
log_info "Cleaning up deployment artifacts..."
|
||||
rm -f laravel-app.tar.gz
|
||||
|
||||
log_info "Deployment script completed!"
|
||||
exit 0
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# Quick Setup Script für Gitea Runner auf Synology NAS
|
||||
# Dieses Script deployt den Gitea Runner automatisch
|
||||
|
||||
set -e
|
||||
|
||||
echo "======================================"
|
||||
echo "Gitea Runner Setup für Synology NAS"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# Konfiguration
|
||||
RUNNER_DIR="/volume1/docker/gitea-runner"
|
||||
GITEA_URL="https://gitea.cbazza.synology.me"
|
||||
|
||||
# Prüfe ob auf Synology
|
||||
if [ ! -d "/volume1" ]; then
|
||||
echo "❌ Dieses Script muss auf der Synology NAS ausgeführt werden!"
|
||||
echo " Führe es via SSH aus: ssh sebastianfrohlich@192.168.178.29"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Frage nach Registration Token
|
||||
echo "📝 Bitte gehe zu:"
|
||||
echo " ${GITEA_URL}/user/settings/actions/runners"
|
||||
echo " und erstelle einen neuen Runner."
|
||||
echo ""
|
||||
read -p "Gib den Registration Token ein: " RUNNER_TOKEN
|
||||
|
||||
if [ -z "$RUNNER_TOKEN" ]; then
|
||||
echo "❌ Kein Token eingegeben!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "⚙️ Erstelle Runner-Verzeichnis..."
|
||||
mkdir -p "$RUNNER_DIR"
|
||||
cd "$RUNNER_DIR"
|
||||
|
||||
echo "📝 Erstelle .env Datei..."
|
||||
cat > .env << EOF
|
||||
# Gitea Runner Configuration
|
||||
GITEA_URL=${GITEA_URL}
|
||||
RUNNER_TOKEN=${RUNNER_TOKEN}
|
||||
RUNNER_NAME=synology-runner
|
||||
RUNNER_CAPACITY=1
|
||||
LOG_LEVEL=info
|
||||
EOF
|
||||
|
||||
echo "📝 Erstelle docker-compose.yml..."
|
||||
cat > docker-compose.yml << 'EOF'
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
gitea-runner:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=${GITEA_URL}
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}
|
||||
- GITEA_RUNNER_NAME=${RUNNER_NAME:-synology-runner}
|
||||
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest
|
||||
- GITEA_RUNNER_CAPACITY=${RUNNER_CAPACITY:-1}
|
||||
- GITEA_RUNNER_LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
|
||||
volumes:
|
||||
- runner-data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
networks:
|
||||
- gitea-network
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "pgrep", "-f", "act_runner"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
volumes:
|
||||
runner-data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
gitea-network:
|
||||
driver: bridge
|
||||
EOF
|
||||
|
||||
echo "🚀 Starte Gitea Runner..."
|
||||
docker-compose up -d
|
||||
|
||||
echo ""
|
||||
echo "✅ Gitea Runner wurde erfolgreich deployed!"
|
||||
echo ""
|
||||
echo "📊 Prüfe Status mit:"
|
||||
echo " docker logs -f gitea-runner"
|
||||
echo ""
|
||||
echo "🌐 Verifiziere in Gitea:"
|
||||
echo " ${GITEA_URL}/user/settings/actions/runners"
|
||||
echo ""
|
||||
echo "Der Runner sollte jetzt als 'Online' angezeigt werden."
|
||||
echo ""
|
||||
@@ -0,0 +1,43 @@
|
||||
# Local .terraform directories
|
||||
**/.terraform/*
|
||||
|
||||
# .tfstate files
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
|
||||
# Crash log files
|
||||
crash.log
|
||||
crash.*.log
|
||||
|
||||
# Exclude all .tfvars files, which are likely to contain sensitive data
|
||||
*.tfvars
|
||||
*.tfvars.json
|
||||
|
||||
# Ignore override files as they are usually used to override resources locally
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
|
||||
# Ignore CLI configuration files
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
|
||||
# Ignore lock files (optional - remove if you want to commit them)
|
||||
.terraform.lock.hcl
|
||||
|
||||
# Ignore backend configuration
|
||||
backend.hcl
|
||||
backend.tfvars
|
||||
|
||||
# Ignore kubeconfig files
|
||||
kubeconfig*
|
||||
*.kubeconfig
|
||||
|
||||
# Ignore backup files
|
||||
*.backup
|
||||
*.bak
|
||||
|
||||
# Ignore plan files
|
||||
tfplan
|
||||
*.tfplan
|
||||
@@ -0,0 +1,474 @@
|
||||
# ✅ Deployment Checklist
|
||||
|
||||
Diese Checkliste führt dich Schritt-für-Schritt durch das erste Deployment.
|
||||
|
||||
## 📋 Pre-Deployment
|
||||
|
||||
### 1. Lokale Umgebung
|
||||
|
||||
- [ ] Terraform >= 1.9.0 installiert
|
||||
- [ ] Azure CLI >= 2.0 installiert
|
||||
- [ ] kubectl >= 1.28 installiert
|
||||
- [ ] Docker >= 20.10 installiert
|
||||
- [ ] PHP >= 8.2 installiert (für Laravel-Befehle)
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
terraform version
|
||||
az version
|
||||
kubectl version
|
||||
docker version
|
||||
php -v
|
||||
```
|
||||
|
||||
### 2. Azure Zugriff
|
||||
|
||||
- [ ] Azure Account vorhanden
|
||||
- [ ] Subscription ID bekannt: `77677a80-2dea-493d-9867-f1c961b80fb3`
|
||||
- [ ] Zugriff auf Resource Group: `trusted_ai_demo_rg`
|
||||
- [ ] Zugriff auf AKS Cluster: `trai_k8s_cluster`
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
az login
|
||||
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
|
||||
az account show
|
||||
az aks show --resource-group trusted_ai_demo_rg --name trai_k8s_cluster
|
||||
```
|
||||
|
||||
## 🔧 Setup
|
||||
|
||||
### 3. Azure Container Registry
|
||||
|
||||
- [ ] ACR erstellt oder vorhanden
|
||||
- [ ] ACR Name: `mylaravelregistry` (oder eigener Name)
|
||||
- [ ] AKS hat Pull-Berechtigung für ACR
|
||||
|
||||
**Erstellen:**
|
||||
```bash
|
||||
# ACR erstellen (falls noch nicht vorhanden)
|
||||
az acr create \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name mylaravelregistry \
|
||||
--sku Basic \
|
||||
--location germanywestcentral
|
||||
|
||||
# AKS Zugriff geben
|
||||
az aks update \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--attach-acr mylaravelregistry
|
||||
|
||||
# Login
|
||||
az acr login --name mylaravelregistry
|
||||
```
|
||||
|
||||
### 4. Docker Image
|
||||
|
||||
- [ ] Docker Image gebaut
|
||||
- [ ] Docker Image zu ACR gepusht
|
||||
- [ ] Image Tag notiert
|
||||
|
||||
**Bauen & Pushen:**
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend
|
||||
|
||||
# Bauen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
|
||||
|
||||
# Pushen
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
|
||||
|
||||
# Image Tag notieren:
|
||||
IMAGE_TAG="mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
|
||||
```
|
||||
|
||||
### 5. Laravel Konfiguration
|
||||
|
||||
- [ ] APP_KEY generiert
|
||||
- [ ] APP_KEY notiert
|
||||
|
||||
**Generieren:**
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend
|
||||
php artisan key:generate --show
|
||||
|
||||
# Output: base64:xyz...
|
||||
# Notieren für terraform.tfvars
|
||||
```
|
||||
|
||||
### 6. Backup-Dateien
|
||||
|
||||
- [ ] Database Backup vorhanden
|
||||
- [ ] Backup-Pfad notiert
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
ls -lh /Users/sebastianfrohlich/Herd/frontend/backups/
|
||||
|
||||
# Wähle ein Backup, z.B.:
|
||||
BACKUP_FILE="../backups/backup_backend_20251203_101741.dump"
|
||||
```
|
||||
|
||||
## ⚙️ Terraform Konfiguration
|
||||
|
||||
### 7. terraform.tfvars erstellen
|
||||
|
||||
- [ ] `terraform.tfvars` aus Example kopiert
|
||||
- [ ] Alle erforderlichen Werte eingetragen
|
||||
|
||||
**Erstellen:**
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend/terraform
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
nano terraform.tfvars
|
||||
```
|
||||
|
||||
**Erforderliche Werte:**
|
||||
|
||||
```hcl
|
||||
# ✅ Docker Image
|
||||
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
|
||||
|
||||
# ✅ Laravel APP_KEY
|
||||
app_key = "base64:YOUR_GENERATED_KEY_HERE"
|
||||
|
||||
# ✅ PostgreSQL Admin Password (sicher wählen!)
|
||||
postgresql_admin_password = "YourVerySecurePassword123!"
|
||||
|
||||
# ✅ Alert Email
|
||||
alert_email_address = "your-email@example.com"
|
||||
|
||||
# ✅ Database Restore (beim ersten Deployment)
|
||||
db_restore_enabled = true
|
||||
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
|
||||
```
|
||||
|
||||
**Optionale Werte:**
|
||||
|
||||
```hcl
|
||||
# Für Custom Domain
|
||||
ingress_host = "app.yourdomain.com"
|
||||
|
||||
# Für SSL/TLS (benötigt Domain)
|
||||
ssl_enabled = true
|
||||
ssl_issuer_email = "admin@yourdomain.com"
|
||||
|
||||
# Ressourcen anpassen
|
||||
app_replicas = 2
|
||||
postgresql_sku_name = "B_Standard_B1ms"
|
||||
```
|
||||
|
||||
### 8. Konfiguration validieren
|
||||
|
||||
- [ ] terraform.tfvars Syntax korrekt
|
||||
- [ ] Alle Secrets/Passwörter sicher
|
||||
- [ ] Backup-Pfad korrekt
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
cd terraform
|
||||
|
||||
# Terraform init
|
||||
terraform init
|
||||
|
||||
# Validate
|
||||
terraform validate
|
||||
|
||||
# Sollte ausgeben: Success! The configuration is valid.
|
||||
```
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### 9. Deployment ausführen
|
||||
|
||||
**Option A: Automatisches Script (Empfohlen)**
|
||||
|
||||
- [ ] Deploy-Script ausgeführt
|
||||
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend/terraform
|
||||
./scripts/deploy.sh
|
||||
```
|
||||
|
||||
Das Script führt automatisch aus:
|
||||
1. ✅ Prerequisite Check
|
||||
2. ✅ Azure Login Status
|
||||
3. ✅ terraform.tfvars Check
|
||||
4. ✅ Docker Image Check
|
||||
5. ✅ APP_KEY Check
|
||||
6. ✅ terraform init
|
||||
7. ✅ terraform validate
|
||||
8. ✅ terraform plan
|
||||
9. ✅ terraform apply (nach Bestätigung)
|
||||
10. ✅ kubectl konfigurieren
|
||||
11. ✅ Pod Status prüfen
|
||||
|
||||
**Option B: Manuell**
|
||||
|
||||
- [ ] Terraform Plan erstellt
|
||||
- [ ] Plan überprüft
|
||||
- [ ] Terraform Apply ausgeführt
|
||||
|
||||
```bash
|
||||
cd terraform
|
||||
|
||||
# Plan
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# Plan überprüfen
|
||||
# Sollte Resources anzeigen: +XX to add, ~0 to change, -0 to destroy
|
||||
|
||||
# Apply
|
||||
terraform apply tfplan
|
||||
|
||||
# Outputs anzeigen
|
||||
terraform output
|
||||
terraform output deployment_instructions
|
||||
```
|
||||
|
||||
### 10. kubectl konfigurieren
|
||||
|
||||
- [ ] kubectl Credentials abgerufen
|
||||
- [ ] Cluster-Zugriff getestet
|
||||
|
||||
```bash
|
||||
az aks get-credentials \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--overwrite-existing
|
||||
|
||||
# Testen
|
||||
kubectl get nodes
|
||||
kubectl get namespaces
|
||||
```
|
||||
|
||||
## 🔄 Database Restore
|
||||
|
||||
### 11. Datenbank wiederherstellen
|
||||
|
||||
**Option A: Automatisches Script (Empfohlen)**
|
||||
|
||||
- [ ] Restore-Script ausgeführt
|
||||
- [ ] Job Status geprüft
|
||||
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend/terraform
|
||||
./scripts/restore-db.sh
|
||||
|
||||
# Script fragt nach:
|
||||
# - Welches Backup-File?
|
||||
# - Bestätigung: yes
|
||||
# Zeigt dann Logs und Status
|
||||
```
|
||||
|
||||
**Option B: Via Terraform**
|
||||
|
||||
- [ ] `db_restore_enabled = true` in terraform.tfvars
|
||||
- [ ] `terraform apply` ausgeführt
|
||||
|
||||
```bash
|
||||
# In terraform.tfvars:
|
||||
db_restore_enabled = true
|
||||
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
|
||||
|
||||
# Apply
|
||||
terraform apply
|
||||
|
||||
# Job Status prüfen
|
||||
kubectl get jobs -n laravel-app
|
||||
kubectl logs -n laravel-app -l job-type=database-restore -f
|
||||
```
|
||||
|
||||
## ✅ Post-Deployment Checks
|
||||
|
||||
### 12. Infrastruktur Status
|
||||
|
||||
- [ ] Pods sind running
|
||||
- [ ] Service ist erreichbar
|
||||
- [ ] Ingress hat External IP
|
||||
- [ ] Database ist connected
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
# Pods
|
||||
kubectl get pods -n laravel-app
|
||||
# Sollte: 2/2 Running anzeigen
|
||||
|
||||
# Service
|
||||
kubectl get svc -n laravel-app
|
||||
|
||||
# Ingress
|
||||
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||
# Sollte: EXTERNAL-IP anzeigen (dauert 5-10 Min)
|
||||
|
||||
# Database
|
||||
kubectl get secret laravel-app-db-credentials -n laravel-app
|
||||
```
|
||||
|
||||
### 13. Application Health
|
||||
|
||||
- [ ] Application ist erreichbar
|
||||
- [ ] HTTP Status 200 oder 302
|
||||
- [ ] Keine Fehler in Logs
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
# URL abrufen
|
||||
terraform output app_url
|
||||
|
||||
# Beispiel Output: http://20.79.123.456
|
||||
|
||||
# Im Browser öffnen oder:
|
||||
curl -I http://20.79.123.456
|
||||
|
||||
# Sollte: HTTP/1.1 200 OK oder 302 Found
|
||||
|
||||
# Logs prüfen
|
||||
kubectl logs -n laravel-app -l app=laravel-app -f
|
||||
|
||||
# Sollte: Keine Errors zeigen
|
||||
```
|
||||
|
||||
### 14. Database Connectivity
|
||||
|
||||
- [ ] Database Restore Job completed
|
||||
- [ ] Laravel kann auf DB zugreifen
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
# Restore Job Status
|
||||
kubectl get jobs -n laravel-app
|
||||
# Sollte: db-restore-* mit COMPLETIONS 1/1
|
||||
|
||||
# Job Logs
|
||||
kubectl logs -n laravel-app -l job-type=database-restore
|
||||
|
||||
# Sollte: "Database restore completed successfully!" enthalten
|
||||
|
||||
# Laravel Migrations Status (in Pod)
|
||||
POD_NAME=$(kubectl get pods -n laravel-app -l app=laravel-app -o jsonpath='{.items[0].metadata.name}')
|
||||
kubectl exec -it -n laravel-app $POD_NAME -- php artisan migrate:status
|
||||
|
||||
# Sollte: Migration table anzeigen
|
||||
```
|
||||
|
||||
## 🎯 Finalisierung
|
||||
|
||||
### 15. Monitoring Setup
|
||||
|
||||
- [ ] Azure Monitor funktioniert
|
||||
- [ ] Alert Rules aktiv
|
||||
- [ ] Email Alerts konfiguriert
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
# Azure Portal öffnen
|
||||
open https://portal.azure.com
|
||||
|
||||
# Gehe zu:
|
||||
# 1. Resource Group: trusted_ai_demo_rg
|
||||
# 2. AKS Cluster: trai_k8s_cluster
|
||||
# 3. Monitoring > Insights
|
||||
|
||||
# Alert Rules prüfen:
|
||||
az monitor metrics alert list \
|
||||
--resource-group trusted_ai_demo_rg
|
||||
```
|
||||
|
||||
### 16. Dokumentation
|
||||
|
||||
- [ ] LoadBalancer IP notiert
|
||||
- [ ] Database Connection String notiert
|
||||
- [ ] Terraform Outputs gespeichert
|
||||
|
||||
**Notieren:**
|
||||
```bash
|
||||
# Alle Outputs anzeigen
|
||||
terraform output
|
||||
|
||||
# Spezifische Outputs
|
||||
terraform output app_url
|
||||
terraform output postgresql_server_fqdn
|
||||
terraform output postgresql_connection_string
|
||||
|
||||
# In Passwort-Manager oder sicheren Ort speichern!
|
||||
```
|
||||
|
||||
### 17. Cleanup & Security
|
||||
|
||||
- [ ] Sensitive Dateien nicht committed (terraform.tfvars)
|
||||
- [ ] Passwörter sicher gespeichert
|
||||
- [ ] .gitignore überprüft
|
||||
|
||||
**Prüfen:**
|
||||
```bash
|
||||
# Git Status
|
||||
git status
|
||||
|
||||
# Sollte NICHT enthalten:
|
||||
# - terraform.tfvars
|
||||
# - *.tfstate
|
||||
# - kubeconfig
|
||||
|
||||
# Falls vorhanden:
|
||||
git rm --cached terraform/terraform.tfvars
|
||||
git rm --cached terraform/*.tfstate
|
||||
```
|
||||
|
||||
## 🚦 Success Criteria
|
||||
|
||||
Deployment ist erfolgreich wenn:
|
||||
|
||||
- ✅ `kubectl get pods -n laravel-app` zeigt 2/2 Running
|
||||
- ✅ `terraform output app_url` zeigt eine URL
|
||||
- ✅ URL im Browser ist erreichbar
|
||||
- ✅ Database Restore Job ist completed
|
||||
- ✅ Keine Fehler in Pod Logs
|
||||
- ✅ Ingress hat External IP
|
||||
|
||||
## 🎉 Fertig!
|
||||
|
||||
Deine Laravel-Anwendung läuft jetzt auf Azure Kubernetes Service!
|
||||
|
||||
### Nächste Schritte:
|
||||
|
||||
1. **Testen**: Funktionalität der Anwendung testen
|
||||
2. **Monitoring**: Azure Monitor regelmäßig prüfen
|
||||
3. **Backups**: PostgreSQL Backup-Strategy überprüfen
|
||||
4. **Domain**: Custom Domain konfigurieren (optional)
|
||||
5. **SSL**: Let's Encrypt aktivieren (optional)
|
||||
6. **CI/CD**: GitHub Actions einrichten (optional)
|
||||
|
||||
### Wichtige Commands:
|
||||
|
||||
```bash
|
||||
# Status prüfen
|
||||
kubectl get all -n laravel-app
|
||||
|
||||
# Logs anzeigen
|
||||
kubectl logs -n laravel-app -l app=laravel-app -f
|
||||
|
||||
# Shell in Pod
|
||||
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
|
||||
|
||||
# Port-forward für lokalen Zugriff
|
||||
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
|
||||
|
||||
# Terraform Outputs
|
||||
terraform output deployment_instructions
|
||||
```
|
||||
|
||||
## 📞 Support
|
||||
|
||||
Bei Problemen:
|
||||
|
||||
1. **Troubleshooting Guide**: [README.md#troubleshooting](README.md#troubleshooting)
|
||||
2. **Kubernetes Events**: `kubectl get events -n laravel-app --sort-by='.lastTimestamp'`
|
||||
3. **Pod Logs**: `kubectl logs -n laravel-app <pod-name>`
|
||||
4. **Terraform State**: `terraform show`
|
||||
|
||||
---
|
||||
|
||||
**Deployment Time**: ~30 Minuten
|
||||
**Status**: ✅ Ready for Production
|
||||
@@ -0,0 +1,410 @@
|
||||
# GitHub Actions CI/CD Setup
|
||||
|
||||
Diese Anleitung zeigt, wie du die automatische Deployment-Pipeline mit GitHub Actions einrichtest.
|
||||
|
||||
## 📋 Übersicht
|
||||
|
||||
Die Pipeline führt automatisch folgende Schritte aus:
|
||||
|
||||
1. **Build & Push**: Docker Image bauen und zu Azure Container Registry pushen
|
||||
2. **Terraform Deploy**: Infrastruktur mit Terraform deployen
|
||||
3. **Smoke Tests**: Basis-Tests nach Deployment ausführen
|
||||
4. **Notifications**: Status-Benachrichtigungen
|
||||
|
||||
## 🔐 Erforderliche GitHub Secrets
|
||||
|
||||
Gehe zu deinem GitHub Repository → Settings → Secrets and variables → Actions → New repository secret
|
||||
|
||||
### Azure Credentials
|
||||
|
||||
```bash
|
||||
# Azure Service Principal erstellen
|
||||
az ad sp create-for-rbac \
|
||||
--name "github-actions-laravel-app" \
|
||||
--role contributor \
|
||||
--scopes /subscriptions/77677a80-2dea-493d-9867-f1c961b80fb3/resourceGroups/trusted_ai_demo_rg \
|
||||
--sdk-auth
|
||||
|
||||
# Output sieht so aus:
|
||||
{
|
||||
"clientId": "xxx",
|
||||
"clientSecret": "xxx",
|
||||
"subscriptionId": "77677a80-2dea-493d-9867-f1c961b80fb3",
|
||||
"tenantId": "xxx",
|
||||
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
|
||||
"resourceManagerEndpointUrl": "https://management.azure.com/",
|
||||
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
|
||||
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
|
||||
"galleryEndpointUrl": "https://gallery.azure.com/",
|
||||
"managementEndpointUrl": "https://management.core.windows.net/"
|
||||
}
|
||||
```
|
||||
|
||||
**Secret Name**: `AZURE_CREDENTIALS`
|
||||
**Secret Value**: Der gesamte JSON Output von oben
|
||||
|
||||
### Azure Container Registry
|
||||
|
||||
```bash
|
||||
# ACR Credentials abrufen
|
||||
az acr credential show --name mylaravelregistry
|
||||
|
||||
# Output:
|
||||
{
|
||||
"passwords": [
|
||||
{
|
||||
"name": "password",
|
||||
"value": "xxx"
|
||||
},
|
||||
{
|
||||
"name": "password2",
|
||||
"value": "yyy"
|
||||
}
|
||||
],
|
||||
"username": "mylaravelregistry"
|
||||
}
|
||||
```
|
||||
|
||||
**Secret Name**: `ACR_USERNAME`
|
||||
**Secret Value**: `mylaravelregistry`
|
||||
|
||||
**Secret Name**: `ACR_PASSWORD`
|
||||
**Secret Value**: Der Wert von `password` (oder `password2`)
|
||||
|
||||
### Azure Subscription
|
||||
|
||||
**Secret Name**: `AZURE_SUBSCRIPTION_ID`
|
||||
**Secret Value**: `77677a80-2dea-493d-9867-f1c961b80fb3`
|
||||
|
||||
### Laravel Application
|
||||
|
||||
**Secret Name**: `LARAVEL_APP_KEY`
|
||||
**Secret Value**: Generiere mit `php artisan key:generate --show`
|
||||
**Beispiel**: `base64:abcdefgh12345...`
|
||||
|
||||
### PostgreSQL Database
|
||||
|
||||
**Secret Name**: `POSTGRESQL_ADMIN_USERNAME`
|
||||
**Secret Value**: `pgadmin`
|
||||
|
||||
**Secret Name**: `POSTGRESQL_ADMIN_PASSWORD`
|
||||
**Secret Value**: Dein sicheres PostgreSQL Passwort
|
||||
|
||||
### Ingress & SSL
|
||||
|
||||
**Secret Name**: `INGRESS_HOST`
|
||||
**Secret Value**: Deine Domain (z.B. `app.yourdomain.com`) oder leer lassen für IP-Zugriff
|
||||
|
||||
**Secret Name**: `SSL_ISSUER_EMAIL`
|
||||
**Secret Value**: Email für Let's Encrypt (z.B. `admin@yourdomain.com`)
|
||||
|
||||
### Monitoring
|
||||
|
||||
**Secret Name**: `ALERT_EMAIL`
|
||||
**Secret Value**: Email für Azure Alerts
|
||||
|
||||
## 📝 Secrets Checkliste
|
||||
|
||||
- [ ] `AZURE_CREDENTIALS` (JSON von Service Principal)
|
||||
- [ ] `ACR_USERNAME` (mylaravelregistry)
|
||||
- [ ] `ACR_PASSWORD` (ACR password)
|
||||
- [ ] `AZURE_SUBSCRIPTION_ID` (77677a80-2dea-493d-9867-f1c961b80fb3)
|
||||
- [ ] `LARAVEL_APP_KEY` (base64:...)
|
||||
- [ ] `POSTGRESQL_ADMIN_USERNAME` (pgadmin)
|
||||
- [ ] `POSTGRESQL_ADMIN_PASSWORD` (sicheres Passwort)
|
||||
- [ ] `INGRESS_HOST` (optional: deine Domain)
|
||||
- [ ] `SSL_ISSUER_EMAIL` (optional: für SSL)
|
||||
- [ ] `ALERT_EMAIL` (deine Email)
|
||||
|
||||
## 🚀 Workflow Trigger
|
||||
|
||||
### Automatisch bei Push
|
||||
|
||||
Die Pipeline wird automatisch ausgeführt bei Push auf:
|
||||
- `main` Branch (Staging Deployment)
|
||||
- `production` Branch (Production Deployment)
|
||||
|
||||
```bash
|
||||
# Code ändern und committen
|
||||
git add .
|
||||
git commit -m "Update feature"
|
||||
|
||||
# Push zu main für Staging
|
||||
git push origin main
|
||||
|
||||
# Push zu production für Production
|
||||
git push origin production
|
||||
```
|
||||
|
||||
### Manuell über GitHub UI
|
||||
|
||||
1. Gehe zu deinem Repository auf GitHub
|
||||
2. Klicke auf "Actions" Tab
|
||||
3. Wähle "Deploy to Azure AKS" Workflow
|
||||
4. Klicke auf "Run workflow"
|
||||
5. Wähle Environment (staging/production)
|
||||
6. Klicke auf "Run workflow"
|
||||
|
||||
### Manuell über GitHub CLI
|
||||
|
||||
```bash
|
||||
# Installiere gh CLI (falls noch nicht vorhanden)
|
||||
brew install gh # macOS
|
||||
# oder: https://cli.github.com/
|
||||
|
||||
# Login
|
||||
gh auth login
|
||||
|
||||
# Workflow manuell triggern
|
||||
gh workflow run deploy-azure.yml \
|
||||
--ref main \
|
||||
--field environment=staging
|
||||
|
||||
# Workflow Status prüfen
|
||||
gh run list --workflow=deploy-azure.yml
|
||||
|
||||
# Logs anzeigen
|
||||
gh run view --log
|
||||
```
|
||||
|
||||
## 🔧 Workflow Konfiguration anpassen
|
||||
|
||||
Die Workflow-Datei liegt in [.github/workflows/deploy-azure.yml](../.github/workflows/deploy-azure.yml).
|
||||
|
||||
### Environment-spezifische Settings
|
||||
|
||||
Im Workflow werden verschiedene Settings basierend auf dem Environment gesetzt:
|
||||
|
||||
**Staging:**
|
||||
- `app_debug = true`
|
||||
- `app_replicas = 2`
|
||||
- `postgresql_sku_name = "B_Standard_B1ms"` (Basic)
|
||||
- `postgresql_storage_mb = 32768` (32 GB)
|
||||
- `ssl_enabled = false`
|
||||
|
||||
**Production:**
|
||||
- `app_debug = false`
|
||||
- `app_replicas = 3`
|
||||
- `postgresql_sku_name = "GP_Standard_D2s_v3"` (General Purpose)
|
||||
- `postgresql_storage_mb = 131072` (128 GB)
|
||||
- `ssl_enabled = true`
|
||||
|
||||
### Weitere Trigger hinzufügen
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: '0 2 * * 0' # Jeden Sonntag um 2 Uhr
|
||||
workflow_dispatch:
|
||||
# ... existing inputs
|
||||
```
|
||||
|
||||
## 📊 Workflow Monitoring
|
||||
|
||||
### In GitHub UI
|
||||
|
||||
1. Gehe zu "Actions" Tab in deinem Repository
|
||||
2. Siehst alle Workflow-Runs
|
||||
3. Klicke auf einen Run für Details
|
||||
4. Siehst Logs für jeden Job/Step
|
||||
|
||||
### Via GitHub CLI
|
||||
|
||||
```bash
|
||||
# Aktuelle Runs anzeigen
|
||||
gh run list --workflow=deploy-azure.yml
|
||||
|
||||
# Spezifischen Run anzeigen
|
||||
gh run view <run-id>
|
||||
|
||||
# Logs anzeigen
|
||||
gh run view <run-id> --log
|
||||
|
||||
# Run erneut starten
|
||||
gh run rerun <run-id>
|
||||
|
||||
# Run abbrechen
|
||||
gh run cancel <run-id>
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Pipeline schlägt bei "Build and Push" fehl
|
||||
|
||||
**Problem**: ACR Authentication fehlgeschlagen
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# Prüfe ACR Credentials
|
||||
az acr credential show --name mylaravelregistry
|
||||
|
||||
# Update GitHub Secrets mit neuen Credentials
|
||||
```
|
||||
|
||||
### Pipeline schlägt bei "Terraform Deploy" fehl
|
||||
|
||||
**Problem**: Azure Credentials ungültig
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# Service Principal neu erstellen
|
||||
az ad sp create-for-rbac \
|
||||
--name "github-actions-laravel-app" \
|
||||
--role contributor \
|
||||
--scopes /subscriptions/77677a80-2dea-493d-9867-f1c961b80fb3/resourceGroups/trusted_ai_demo_rg \
|
||||
--sdk-auth
|
||||
|
||||
# AZURE_CREDENTIALS Secret updaten
|
||||
```
|
||||
|
||||
**Problem**: Terraform State Lock
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# State Lock manuell entfernen
|
||||
cd terraform
|
||||
terraform force-unlock <lock-id>
|
||||
```
|
||||
|
||||
### Pipeline schlägt bei "Smoke Tests" fehl
|
||||
|
||||
**Problem**: Pods nicht ready
|
||||
|
||||
**Lösung**:
|
||||
```bash
|
||||
# kubectl credentials abrufen
|
||||
az aks get-credentials \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster
|
||||
|
||||
# Pod Status prüfen
|
||||
kubectl get pods -n laravel-app
|
||||
kubectl describe pod <pod-name> -n laravel-app
|
||||
kubectl logs <pod-name> -n laravel-app
|
||||
```
|
||||
|
||||
## 🔒 Sicherheit
|
||||
|
||||
### Service Principal Permissions
|
||||
|
||||
Der Service Principal benötigt folgende Berechtigungen:
|
||||
- Contributor auf Resource Group
|
||||
- AcrPush auf Container Registry (optional, wenn über ACR credentials)
|
||||
|
||||
```bash
|
||||
# Berechtigungen prüfen
|
||||
az role assignment list \
|
||||
--assignee <service-principal-client-id> \
|
||||
--resource-group trusted_ai_demo_rg
|
||||
```
|
||||
|
||||
### Secrets Rotation
|
||||
|
||||
Rotiere Secrets regelmäßig:
|
||||
|
||||
```bash
|
||||
# Neues ACR Password generieren
|
||||
az acr credential renew \
|
||||
--name mylaravelregistry \
|
||||
--password-name password
|
||||
|
||||
# Service Principal Secret erneuern
|
||||
az ad sp credential reset \
|
||||
--id <service-principal-object-id>
|
||||
```
|
||||
|
||||
## 🚦 Branch Protection Rules
|
||||
|
||||
Empfohlene Branch Protection Rules für `main` und `production`:
|
||||
|
||||
1. Gehe zu Repository → Settings → Branches
|
||||
2. Füge Branch Protection Rule hinzu für `main` und `production`
|
||||
3. Aktiviere:
|
||||
- ✅ Require status checks to pass before merging
|
||||
- ✅ Require branches to be up to date before merging
|
||||
- ✅ Require deployments to succeed before merging
|
||||
- ✅ Require conversation resolution before merging
|
||||
- ✅ Include administrators
|
||||
|
||||
## 📈 Deployment Environments
|
||||
|
||||
GitHub Environments für bessere Kontrolle:
|
||||
|
||||
1. Gehe zu Repository → Settings → Environments
|
||||
2. Erstelle zwei Environments: `staging` und `production`
|
||||
3. Für `production`:
|
||||
- ✅ Required reviewers: Füge Reviewer hinzu
|
||||
- ✅ Wait timer: 5 minutes
|
||||
- ✅ Deployment branches: Only `production` branch
|
||||
|
||||
## 🔄 CI/CD Best Practices
|
||||
|
||||
### 1. Feature Branch Workflow
|
||||
|
||||
```bash
|
||||
# Feature branch erstellen
|
||||
git checkout -b feature/new-feature
|
||||
|
||||
# Änderungen committen
|
||||
git add .
|
||||
git commit -m "Add new feature"
|
||||
|
||||
# Push und Pull Request erstellen
|
||||
git push origin feature/new-feature
|
||||
|
||||
# Nach Review: Merge in main (automatisches Staging Deployment)
|
||||
# Dann: Merge in production (automatisches Production Deployment)
|
||||
```
|
||||
|
||||
### 2. Semantic Versioning
|
||||
|
||||
Verwende Git Tags für Releases:
|
||||
|
||||
```bash
|
||||
# Tag erstellen
|
||||
git tag -a v1.0.0 -m "Release version 1.0.0"
|
||||
git push origin v1.0.0
|
||||
|
||||
# Workflow wird Docker Image mit diesem Tag bauen
|
||||
```
|
||||
|
||||
### 3. Rollback Strategy
|
||||
|
||||
```bash
|
||||
# Bei Problemen: Zu vorheriger Version zurück
|
||||
gh workflow run deploy-azure.yml \
|
||||
--ref <previous-commit-sha> \
|
||||
--field environment=production
|
||||
|
||||
# Oder: Kubernetes Rollback
|
||||
kubectl rollout undo deployment/laravel-app -n laravel-app
|
||||
```
|
||||
|
||||
## 📚 Weitere Ressourcen
|
||||
|
||||
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||
- [Azure DevOps Documentation](https://docs.microsoft.com/en-us/azure/devops/)
|
||||
- [Terraform Cloud](https://www.terraform.io/cloud) - Alternative für Terraform State Management
|
||||
|
||||
## 🎯 Nächste Schritte
|
||||
|
||||
Nach Setup der CI/CD Pipeline:
|
||||
|
||||
1. [ ] Teste Pipeline mit Dummy-Commit
|
||||
2. [ ] Erstelle Feature Branch und PR
|
||||
3. [ ] Richte Branch Protection Rules ein
|
||||
4. [ ] Konfiguriere GitHub Environments
|
||||
5. [ ] Dokumentiere Team-Workflow
|
||||
6. [ ] Teste Rollback-Prozess
|
||||
7. [ ] Richte Slack/Teams Notifications ein (optional)
|
||||
|
||||
---
|
||||
|
||||
**Support**: Bei Problemen prüfe GitHub Actions Logs und Terraform State
|
||||
@@ -0,0 +1,398 @@
|
||||
# 🚀 Quickstart Guide - Laravel auf Azure AKS
|
||||
|
||||
Diese Anleitung führt dich in ~30 Minuten durch das komplette Deployment deiner Laravel-Anwendung auf Azure Kubernetes Service.
|
||||
|
||||
## ✅ Voraussetzungen Check
|
||||
|
||||
```bash
|
||||
# Prüfe ob alle Tools installiert sind
|
||||
terraform version # >= 1.9.0
|
||||
az version # >= 2.0
|
||||
kubectl version # >= 1.28
|
||||
docker version # >= 20.10
|
||||
php -v # >= 8.2
|
||||
```
|
||||
|
||||
## 📝 Schritt-für-Schritt Anleitung
|
||||
|
||||
### 1️⃣ Azure Login (2 Min)
|
||||
|
||||
```bash
|
||||
# Login
|
||||
az login
|
||||
|
||||
# Subscription setzen
|
||||
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
|
||||
|
||||
# Verify
|
||||
az account show
|
||||
```
|
||||
|
||||
### 2️⃣ Container Registry Setup (5 Min)
|
||||
|
||||
```bash
|
||||
# ACR erstellen (wenn nicht vorhanden)
|
||||
az acr create \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name mylaravelregistry \
|
||||
--sku Basic \
|
||||
--location germanywestcentral
|
||||
|
||||
# AKS Zugriff auf ACR geben
|
||||
az aks update \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--attach-acr mylaravelregistry
|
||||
|
||||
# Login
|
||||
az acr login --name mylaravelregistry
|
||||
```
|
||||
|
||||
### 3️⃣ Docker Image Build & Push (5 Min)
|
||||
|
||||
```bash
|
||||
# Zum Projekt-Root wechseln
|
||||
cd /Users/sebastianfrohlich/Herd/frontend
|
||||
|
||||
# Image bauen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
|
||||
|
||||
# Image pushen
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
|
||||
```
|
||||
|
||||
### 4️⃣ Laravel APP_KEY generieren (1 Min)
|
||||
|
||||
```bash
|
||||
# Im Projekt-Root
|
||||
php artisan key:generate --show
|
||||
|
||||
# Beispiel Output: base64:abcdefgh12345...
|
||||
# Kopiere diesen Wert für den nächsten Schritt
|
||||
```
|
||||
|
||||
### 5️⃣ Terraform Konfiguration (5 Min)
|
||||
|
||||
```bash
|
||||
# Zu Terraform wechseln
|
||||
cd terraform
|
||||
|
||||
# Konfiguration kopieren
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
|
||||
# Bearbeiten
|
||||
nano terraform.tfvars
|
||||
```
|
||||
|
||||
**Wichtige Werte in `terraform.tfvars` ändern:**
|
||||
|
||||
```hcl
|
||||
# Docker Image (von Schritt 3)
|
||||
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
|
||||
|
||||
# Laravel Key (von Schritt 4)
|
||||
app_key = "base64:abcdefgh12345..."
|
||||
|
||||
# PostgreSQL Password (sicheres Passwort wählen!)
|
||||
postgresql_admin_password = "YourSecurePassword123!"
|
||||
|
||||
# Email für Alerts
|
||||
alert_email_address = "your-email@example.com"
|
||||
```
|
||||
|
||||
### 6️⃣ Deployment ausführen (10 Min)
|
||||
|
||||
```bash
|
||||
# Automatisches Deployment mit Script
|
||||
./scripts/deploy.sh
|
||||
```
|
||||
|
||||
Das Script führt automatisch aus:
|
||||
- ✅ Prerequisite-Check
|
||||
- ✅ Terraform init
|
||||
- ✅ Terraform validate
|
||||
- ✅ Terraform plan
|
||||
- ✅ Terraform apply (nach Bestätigung)
|
||||
- ✅ kubectl konfigurieren
|
||||
- ✅ Pod-Status prüfen
|
||||
|
||||
**Oder manuell:**
|
||||
|
||||
```bash
|
||||
# Init
|
||||
terraform init
|
||||
|
||||
# Plan
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# Apply
|
||||
terraform apply tfplan
|
||||
|
||||
# kubectl konfigurieren
|
||||
az aks get-credentials \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--overwrite-existing
|
||||
```
|
||||
|
||||
### 7️⃣ Datenbank wiederherstellen (5 Min)
|
||||
|
||||
```bash
|
||||
# Automatisches Restore-Script
|
||||
./scripts/restore-db.sh
|
||||
|
||||
# Script fragt:
|
||||
# - Welches Backup-File? (z.B. backup_backend_20251203_101741.dump)
|
||||
# - Bestätigung: yes
|
||||
# - Zeigt Fortschritt und Logs
|
||||
```
|
||||
|
||||
**Oder in terraform.tfvars setzen und nochmal apply:**
|
||||
|
||||
```hcl
|
||||
db_restore_enabled = true
|
||||
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
|
||||
```
|
||||
|
||||
```bash
|
||||
terraform apply
|
||||
```
|
||||
|
||||
### 8️⃣ Anwendung testen (2 Min)
|
||||
|
||||
```bash
|
||||
# LoadBalancer IP abrufen
|
||||
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||
|
||||
# Oder über Terraform Output
|
||||
terraform output app_url
|
||||
|
||||
# Example Output:
|
||||
# app_url = "http://20.79.123.456"
|
||||
```
|
||||
|
||||
🎉 **Öffne die URL im Browser!**
|
||||
|
||||
### 9️⃣ Status prüfen
|
||||
|
||||
```bash
|
||||
# Pods prüfen
|
||||
kubectl get pods -n laravel-app
|
||||
|
||||
# Services prüfen
|
||||
kubectl get svc -n laravel-app
|
||||
|
||||
# Ingress prüfen
|
||||
kubectl get ingress -n laravel-app
|
||||
|
||||
# Logs anzeigen
|
||||
kubectl logs -n laravel-app -l app=laravel-app -f
|
||||
|
||||
# Database restore job prüfen (wenn aktiviert)
|
||||
kubectl get jobs -n laravel-app
|
||||
kubectl logs -n laravel-app -l job-type=database-restore
|
||||
```
|
||||
|
||||
## 🔧 Häufige Probleme
|
||||
|
||||
### Problem: Pods starten nicht (ImagePullBackOff)
|
||||
|
||||
```bash
|
||||
# Prüfe ob ACR-Integration funktioniert
|
||||
az aks check-acr \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--acr mylaravelregistry.azurecr.io
|
||||
|
||||
# Fix: ACR Integration neu setzen
|
||||
az aks update \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--attach-acr mylaravelregistry
|
||||
```
|
||||
|
||||
### Problem: LoadBalancer IP bleibt "Pending"
|
||||
|
||||
```bash
|
||||
# Prüfe Service Events
|
||||
kubectl describe svc ingress-nginx-controller -n ingress-nginx
|
||||
|
||||
# Warte 5-10 Minuten - Azure braucht Zeit für LoadBalancer Setup
|
||||
kubectl get svc -n ingress-nginx -w
|
||||
```
|
||||
|
||||
### Problem: Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Prüfe PostgreSQL Firewall Rules
|
||||
az postgres flexible-server firewall-rule list \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name <server-name>
|
||||
|
||||
# Prüfe Secrets
|
||||
kubectl get secret laravel-app-db-credentials -n laravel-app -o yaml | grep DB_
|
||||
```
|
||||
|
||||
### Problem: 502 Bad Gateway
|
||||
|
||||
```bash
|
||||
# Prüfe ob Pods ready sind
|
||||
kubectl get pods -n laravel-app
|
||||
|
||||
# Prüfe Pod Logs
|
||||
kubectl logs -n laravel-app <pod-name>
|
||||
|
||||
# Teste direkt zum Service
|
||||
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
|
||||
# Dann: http://localhost:8080
|
||||
```
|
||||
|
||||
## 📊 Nützliche Befehle
|
||||
|
||||
### Status-Übersicht
|
||||
|
||||
```bash
|
||||
# Alles auf einen Blick
|
||||
kubectl get all -n laravel-app
|
||||
kubectl get all -n ingress-nginx
|
||||
|
||||
# Terraform Outputs
|
||||
terraform output
|
||||
```
|
||||
|
||||
### Logs anzeigen
|
||||
|
||||
```bash
|
||||
# Application Logs (alle Pods)
|
||||
kubectl logs -n laravel-app -l app=laravel-app -f
|
||||
|
||||
# Bestimmter Pod
|
||||
kubectl logs -n laravel-app <pod-name> -f
|
||||
|
||||
# Ingress Controller Logs
|
||||
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f
|
||||
```
|
||||
|
||||
### Scale Up/Down
|
||||
|
||||
```bash
|
||||
# Mehr Replicas
|
||||
kubectl scale deployment/laravel-app --replicas=3 -n laravel-app
|
||||
|
||||
# Prüfen
|
||||
kubectl get pods -n laravel-app
|
||||
```
|
||||
|
||||
### Shell in Pod
|
||||
|
||||
```bash
|
||||
# Shell öffnen
|
||||
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
|
||||
|
||||
# Laravel Artisan in Pod ausführen
|
||||
kubectl exec -it -n laravel-app <pod-name> -- php artisan migrate:status
|
||||
kubectl exec -it -n laravel-app <pod-name> -- php artisan route:list
|
||||
```
|
||||
|
||||
### Port-Forward für lokalen Zugriff
|
||||
|
||||
```bash
|
||||
# App direkt aufrufen (ohne LoadBalancer)
|
||||
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
|
||||
|
||||
# Dann: http://localhost:8080
|
||||
```
|
||||
|
||||
## 🔄 Updates deployen
|
||||
|
||||
### Neue Version deployen
|
||||
|
||||
```bash
|
||||
# 1. Code ändern
|
||||
# 2. Neues Image bauen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 .
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1
|
||||
|
||||
# 3. Deployment updaten (schnell)
|
||||
kubectl set image deployment/laravel-app \
|
||||
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 \
|
||||
-n laravel-app
|
||||
|
||||
# Rollout Status prüfen
|
||||
kubectl rollout status deployment/laravel-app -n laravel-app
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# Zur vorherigen Version zurück
|
||||
kubectl rollout undo deployment/laravel-app -n laravel-app
|
||||
```
|
||||
|
||||
## 🧹 Cleanup
|
||||
|
||||
### Nur App löschen (DB behalten)
|
||||
|
||||
```bash
|
||||
terraform destroy -target=kubernetes_deployment.app
|
||||
terraform destroy -target=kubernetes_service.app
|
||||
```
|
||||
|
||||
### Alles löschen
|
||||
|
||||
```bash
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
## 📚 Weitere Informationen
|
||||
|
||||
- Ausführliche Dokumentation: [README.md](README.md)
|
||||
- Terraform Outputs: `terraform output`
|
||||
- Azure Portal: https://portal.azure.com
|
||||
|
||||
## 🆘 Hilfe benötigt?
|
||||
|
||||
```bash
|
||||
# Terraform Outputs anzeigen
|
||||
terraform output deployment_instructions
|
||||
|
||||
# Kubernetes Events prüfen
|
||||
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
|
||||
|
||||
# Resource Status
|
||||
kubectl describe deployment laravel-app -n laravel-app
|
||||
kubectl describe svc laravel-app -n laravel-app
|
||||
```
|
||||
|
||||
## ✨ Next Steps
|
||||
|
||||
Nach erfolgreichem Deployment:
|
||||
|
||||
1. **Domain konfigurieren** (optional):
|
||||
```hcl
|
||||
# In terraform.tfvars
|
||||
ingress_host = "app.yourdomain.com"
|
||||
ssl_enabled = true
|
||||
ssl_issuer_email = "admin@yourdomain.com"
|
||||
```
|
||||
|
||||
```bash
|
||||
terraform apply
|
||||
```
|
||||
|
||||
2. **Monitoring einrichten**: Prüfe Azure Monitor in Azure Portal
|
||||
|
||||
3. **CI/CD Pipeline**: Erstelle GitHub Actions oder Azure DevOps Pipeline
|
||||
|
||||
4. **Backups testen**: Teste Restore-Prozess
|
||||
|
||||
5. **Security Hardening**:
|
||||
- VNet Integration für PostgreSQL
|
||||
- Private Endpoints
|
||||
- RBAC konfigurieren
|
||||
|
||||
---
|
||||
|
||||
**Deployment Time:** ~30 Minuten
|
||||
**Kosten:** ~50-100€/Monat (abhängig von Ressourcen)
|
||||
**Scaling:** Horizontal (mehr Pods) & Vertikal (größere VMs)
|
||||
@@ -0,0 +1,841 @@
|
||||
# Laravel Application Deployment auf Azure Kubernetes Service (AKS)
|
||||
|
||||
Diese Terraform-Konfiguration stellt deine Laravel-Anwendung auf Azure Kubernetes Service (AKS) bereit und erstellt eine Azure PostgreSQL Datenbank.
|
||||
|
||||
## 📋 Inhaltsverzeichnis
|
||||
|
||||
- [Voraussetzungen](#voraussetzungen)
|
||||
- [Architektur-Übersicht](#architektur-übersicht)
|
||||
- [Schnellstart](#schnellstart)
|
||||
- [Detaillierte Anleitung](#detaillierte-anleitung)
|
||||
- [Konfiguration](#konfiguration)
|
||||
- [Deployment](#deployment)
|
||||
- [Datenbank-Wiederherstellung](#datenbank-wiederherstellung)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Wartung](#wartung)
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
Folgende Tools müssen installiert sein:
|
||||
|
||||
- [Terraform](https://www.terraform.io/downloads.html) >= 1.9.0
|
||||
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) >= 2.0
|
||||
- [kubectl](https://kubernetes.io/docs/tasks/tools/) >= 1.28
|
||||
- [Docker](https://docs.docker.com/get-docker/) >= 20.10
|
||||
- [PHP](https://www.php.net/downloads) >= 8.2 (für lokale Laravel-Befehle)
|
||||
|
||||
## Architektur-Übersicht
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Azure Cloud │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ Resource Group: trusted_ai_demo_rg │ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ AKS Cluster: trai_k8s_cluster │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
|
||||
│ │ │ │ Namespace: laravel-app │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ • Deployment (2+ Pods) │ │ │ │
|
||||
│ │ │ │ • Service (ClusterIP) │ │ │ │
|
||||
│ │ │ │ • ConfigMaps & Secrets │ │ │ │
|
||||
│ │ │ │ • Horizontal Pod Autoscaler │ │ │ │
|
||||
│ │ │ └────────────────────────────────────────────┘ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
|
||||
│ │ │ │ Namespace: ingress-nginx │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ • Nginx Ingress Controller │ │ │ │
|
||||
│ │ │ │ • LoadBalancer Service (Public IP) │ │ │ │
|
||||
│ │ │ └────────────────────────────────────────────┘ │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ Azure PostgreSQL Flexible Server │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ • PostgreSQL 16 │ │ │
|
||||
│ │ │ • 32 GB Storage │ │ │
|
||||
│ │ │ • Automated Backups (7 days) │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Komponenten:
|
||||
|
||||
1. **AKS Cluster**: Bestehender Kubernetes-Cluster
|
||||
2. **Laravel Application**: Containerisierte Laravel-App mit Nginx + PHP-FPM
|
||||
3. **PostgreSQL Database**: Azure Database for PostgreSQL Flexible Server
|
||||
4. **Nginx Ingress Controller**: Load Balancer für externen Zugriff
|
||||
5. **Secrets Management**: Kubernetes Secrets für sensible Daten
|
||||
|
||||
## Schnellstart
|
||||
|
||||
### 1. Azure CLI Login
|
||||
|
||||
```bash
|
||||
az login
|
||||
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
|
||||
```
|
||||
|
||||
### 2. Docker Image erstellen und pushen
|
||||
|
||||
Erstelle zuerst eine Azure Container Registry (falls noch nicht vorhanden):
|
||||
|
||||
```bash
|
||||
# Container Registry erstellen
|
||||
az acr create \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name mylaravelregistry \
|
||||
--sku Basic \
|
||||
--location germanywestcentral
|
||||
|
||||
# In Registry einloggen
|
||||
az acr login --name mylaravelregistry
|
||||
```
|
||||
|
||||
Dann baue und pushe das Docker Image:
|
||||
|
||||
```bash
|
||||
# Zurück zum Projekt-Root
|
||||
cd /Users/sebastianfrohlich/Herd/frontend
|
||||
|
||||
# Docker Image bauen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
|
||||
|
||||
# Image pushen
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
|
||||
```
|
||||
|
||||
### 3. Terraform konfigurieren
|
||||
|
||||
```bash
|
||||
cd terraform
|
||||
|
||||
# Kopiere die Beispiel-Konfiguration
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
|
||||
# Bearbeite terraform.tfvars
|
||||
nano terraform.tfvars
|
||||
```
|
||||
|
||||
Wichtige Werte in `terraform.tfvars`:
|
||||
|
||||
```hcl
|
||||
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
|
||||
app_key = "base64:YOUR_GENERATED_KEY" # Generiere mit: php artisan key:generate --show
|
||||
postgresql_admin_password = "YourSecurePassword123!"
|
||||
alert_email_address = "your-email@example.com"
|
||||
```
|
||||
|
||||
### 4. Deployment ausführen
|
||||
|
||||
```bash
|
||||
# Automatisches Deployment mit Script
|
||||
./scripts/deploy.sh
|
||||
```
|
||||
|
||||
Oder manuell:
|
||||
|
||||
```bash
|
||||
# Terraform initialisieren
|
||||
terraform init
|
||||
|
||||
# Plan überprüfen
|
||||
terraform plan
|
||||
|
||||
# Deployment ausführen
|
||||
terraform apply
|
||||
```
|
||||
|
||||
### 5. Datenbank wiederherstellen
|
||||
|
||||
```bash
|
||||
# Interaktives Restore-Script
|
||||
./scripts/restore-db.sh
|
||||
```
|
||||
|
||||
Oder manuell:
|
||||
|
||||
```bash
|
||||
# Setze db_restore_enabled = true in terraform.tfvars
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Detaillierte Anleitung
|
||||
|
||||
### Schritt 1: Azure Container Registry (ACR) Setup
|
||||
|
||||
Die Laravel-Anwendung muss als Docker Image bereitgestellt werden:
|
||||
|
||||
```bash
|
||||
# ACR erstellen (wenn nicht vorhanden)
|
||||
az acr create \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name mylaravelregistry \
|
||||
--sku Basic
|
||||
|
||||
# Admin-Zugriff aktivieren (optional, für einfachere Handhabung)
|
||||
az acr update -n mylaravelregistry --admin-enabled true
|
||||
|
||||
# Login credentials abrufen
|
||||
az acr credential show --name mylaravelregistry
|
||||
|
||||
# Docker login
|
||||
az acr login --name mylaravelregistry
|
||||
```
|
||||
|
||||
### Schritt 2: Docker Image Build & Push
|
||||
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend
|
||||
|
||||
# Image mit Tag bauen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:latest .
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
|
||||
|
||||
# Image pushen
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:latest
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
|
||||
```
|
||||
|
||||
### Schritt 3: AKS Pull-Berechtigung für ACR
|
||||
|
||||
```bash
|
||||
# AKS Pull-Berechtigung für ACR erteilen
|
||||
az aks update \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--attach-acr mylaravelregistry
|
||||
```
|
||||
|
||||
### Schritt 4: Laravel APP_KEY generieren
|
||||
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend
|
||||
php artisan key:generate --show
|
||||
```
|
||||
|
||||
Kopiere den Output (z.B. `base64:xyz...`) in deine `terraform.tfvars`.
|
||||
|
||||
### Schritt 5: Terraform Konfiguration
|
||||
|
||||
Erstelle `terraform/terraform.tfvars`:
|
||||
|
||||
```hcl
|
||||
# Azure Configuration
|
||||
subscription_id = "77677a80-2dea-493d-9867-f1c961b80fb3"
|
||||
resource_group_name = "trusted_ai_demo_rg"
|
||||
location = "germanywestcentral"
|
||||
aks_cluster_name = "trai_k8s_cluster"
|
||||
|
||||
# Application Configuration
|
||||
app_name = "laravel-app"
|
||||
app_namespace = "laravel-app"
|
||||
app_env = "production"
|
||||
app_debug = false
|
||||
app_replicas = 2
|
||||
|
||||
# Docker Image
|
||||
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
|
||||
|
||||
# Laravel APP_KEY (generiert mit: php artisan key:generate --show)
|
||||
app_key = "base64:YOUR_ACTUAL_KEY_HERE"
|
||||
|
||||
# PostgreSQL Configuration
|
||||
postgresql_admin_username = "pgadmin"
|
||||
postgresql_admin_password = "YourVerySecurePassword123!"
|
||||
postgresql_sku_name = "B_Standard_B1ms"
|
||||
postgresql_storage_mb = 32768
|
||||
postgresql_version = "16"
|
||||
postgresql_backup_retention_days = 7
|
||||
|
||||
# Ingress Configuration
|
||||
ingress_enabled = true
|
||||
ingress_host = "" # Leer lassen für IP-basierten Zugriff
|
||||
|
||||
# SSL/TLS Configuration (optional, später aktivieren)
|
||||
ssl_enabled = false
|
||||
ssl_issuer_email = ""
|
||||
|
||||
# Database Restore Configuration
|
||||
db_restore_enabled = true
|
||||
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
|
||||
|
||||
# Alert Configuration
|
||||
alert_email_address = "your-email@example.com"
|
||||
```
|
||||
|
||||
### Schritt 6: Terraform Deployment
|
||||
|
||||
```bash
|
||||
cd /Users/sebastianfrohlich/Herd/frontend/terraform
|
||||
|
||||
# Terraform initialisieren
|
||||
terraform init
|
||||
|
||||
# Validierung
|
||||
terraform validate
|
||||
|
||||
# Plan erstellen und überprüfen
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# Deployment ausführen
|
||||
terraform apply tfplan
|
||||
|
||||
# Outputs anzeigen
|
||||
terraform output
|
||||
terraform output deployment_instructions
|
||||
```
|
||||
|
||||
### Schritt 7: kubectl konfigurieren
|
||||
|
||||
```bash
|
||||
# kubectl Credentials abrufen
|
||||
az aks get-credentials \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name trai_k8s_cluster \
|
||||
--overwrite-existing
|
||||
|
||||
# Cluster-Zugriff testen
|
||||
kubectl get nodes
|
||||
|
||||
# Pods prüfen
|
||||
kubectl get pods -n laravel-app
|
||||
|
||||
# Services prüfen
|
||||
kubectl get svc -n laravel-app
|
||||
kubectl get svc -n ingress-nginx
|
||||
```
|
||||
|
||||
### Schritt 8: Anwendung testen
|
||||
|
||||
```bash
|
||||
# LoadBalancer IP abrufen
|
||||
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||
|
||||
# External IP sollte angezeigt werden, z.B.:
|
||||
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
|
||||
# ingress-nginx-controller LoadBalancer 10.0.45.123 20.79.123.456 80:31234/TCP,443:31567/TCP
|
||||
|
||||
# Anwendung im Browser öffnen:
|
||||
# http://<EXTERNAL-IP>
|
||||
```
|
||||
|
||||
### HTTP Basic Authentication
|
||||
|
||||
Die Anwendung ist mit HTTP Basic Auth geschützt:
|
||||
|
||||
- **Benutzer**: `afc-user`
|
||||
- **Passwort**: `vDZFrZ+*~4gW=3^?-,:8{ga=7M.o5k,G`
|
||||
|
||||
Die Credentials sind in [`deploy/nginx/.htpasswd`](../deploy/nginx/.htpasswd) konfiguriert und werden während des Docker-Builds in das Image kopiert.
|
||||
|
||||
### Aktueller Deployment-Status
|
||||
|
||||
**Aktuell deployed**: Version `1.0.7`
|
||||
|
||||
**Aktuelle Konfiguration**:
|
||||
|
||||
- URL: `http://72.144.113.194/`
|
||||
- Docker Image: `laravelappreg.azurecr.io/laravel-app:1.0.7`
|
||||
- Replicas: 2
|
||||
- Datenbank: `laravel_app` (PostgreSQL 16) mit 3 Schemas
|
||||
- HTTP Basic Auth: Aktiv
|
||||
|
||||
**Letzte Änderungen (v1.0.7)**:
|
||||
|
||||
- HTTP Basic Authentication hinzugefügt
|
||||
- Nginx Redirect für Livewire JavaScript (`.min.js` → `.js`)
|
||||
- Alle 3 Datenbank-Schemas nach `laravel_app` migriert
|
||||
- Datenbankverbindungen in `config/database.php` aktualisiert
|
||||
- Session-Driver auf `database` umgestellt (Multi-Pod Support)
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### Umgebungsvariablen
|
||||
|
||||
Die Laravel-Umgebungsvariablen werden über Kubernetes ConfigMaps und Secrets verwaltet:
|
||||
|
||||
**ConfigMap** (`kubernetes_config_map.app_config`):
|
||||
- APP_NAME, APP_ENV, APP_DEBUG, APP_URL
|
||||
- Session, Cache, Queue, Mail Konfiguration
|
||||
|
||||
**Secrets** (`kubernetes_secret.app_secrets`):
|
||||
- APP_KEY
|
||||
|
||||
**Database Secrets** (`kubernetes_secret.db_credentials`):
|
||||
- DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE
|
||||
- DB_USERNAME, DB_PASSWORD
|
||||
|
||||
### Resource Limits
|
||||
|
||||
Standard-Einstellungen in `variables.tf`:
|
||||
|
||||
```hcl
|
||||
app_resources_requests_cpu = "100m"
|
||||
app_resources_requests_memory = "256Mi"
|
||||
app_resources_limits_cpu = "500m"
|
||||
app_resources_limits_memory = "512Mi"
|
||||
```
|
||||
|
||||
Diese können in `terraform.tfvars` überschrieben werden.
|
||||
|
||||
### Horizontal Pod Autoscaling
|
||||
|
||||
Die Anwendung skaliert automatisch basierend auf CPU und Memory:
|
||||
|
||||
- Min Replicas: `app_replicas` (default: 2)
|
||||
- Max Replicas: `app_replicas * 3` (default: 6)
|
||||
- CPU Threshold: 80%
|
||||
- Memory Threshold: 80%
|
||||
|
||||
## Datenbank-Konfiguration
|
||||
|
||||
### Datenbank-Struktur
|
||||
|
||||
Die Anwendung verwendet **eine PostgreSQL-Datenbank** (`laravel_app`) mit **drei Schemas**:
|
||||
|
||||
#### 1. **public** Schema (12 Tabellen)
|
||||
Standard Laravel-Tabellen und Application-Daten:
|
||||
- backend_data_pool, cache, cache_locks
|
||||
- companies, failed_jobs, job_batches, jobs
|
||||
- migrations, password_reset_tokens, sessions
|
||||
- transactions, users
|
||||
|
||||
**Connection**: `DB::connection('pgsql_second')` oder `DB::connection('pgsql')`
|
||||
|
||||
#### 2. **backend** Schema (11 Tabellen)
|
||||
Backend-spezifische Daten:
|
||||
- entity_corporate_context, evidence_registry
|
||||
- fallback_events, internal_context_blobs
|
||||
- prompt_results, prompt_runs, prompt_templates, prompt_token_metrics
|
||||
- sources, transaction_outputs, transactions
|
||||
|
||||
**Connection**: `DB::connection('backend')`
|
||||
|
||||
#### 3. **devbackend** Schema (13 Tabellen)
|
||||
Development/Testing Backend-Daten:
|
||||
- document_facts, entity_corporate_context
|
||||
- evidence_registry, fallback_events
|
||||
- internal_context_blobs, pdf_documents
|
||||
- prompt_results, prompt_runs, prompt_templates, prompt_token_metrics
|
||||
- sources, transaction_outputs, transactions
|
||||
|
||||
**Connection**: `DB::connection('devbackend')`
|
||||
|
||||
### Verwendung in Laravel
|
||||
|
||||
```php
|
||||
// Public Schema (Standard)
|
||||
$users = DB::connection('pgsql')->table('users')->get();
|
||||
$users = DB::connection('pgsql_second')->table('users')->get();
|
||||
|
||||
// Backend Schema
|
||||
$transactions = DB::connection('backend')->table('transactions')->get();
|
||||
|
||||
// DevBackend Schema
|
||||
$documents = DB::connection('devbackend')->table('pdf_documents')->get();
|
||||
```
|
||||
|
||||
Alle Connections sind in [config/database.php](../config/database.php) konfiguriert und zeigen auf dieselbe Datenbank `laravel_app` mit unterschiedlichen `search_path` Einstellungen.
|
||||
|
||||
## Datenbank-Wiederherstellung
|
||||
|
||||
### Automatische Wiederherstellung während Deployment
|
||||
|
||||
Die Datenbank-Wiederherstellung importiert alle drei Schemas in die `laravel_app` Datenbank:
|
||||
|
||||
```bash
|
||||
# Backups befinden sich in:
|
||||
/Users/sebastianfrohlich/Herd/frontend/backups/
|
||||
- backup_public_20251203_101723.dump # Public Schema
|
||||
- backup_backend_20251203_101741.dump # Backend Schema
|
||||
- backup_devbackend_20251203_103857.dump # DevBackend Schema
|
||||
```
|
||||
|
||||
Setze in `terraform.tfvars`:
|
||||
|
||||
```hcl
|
||||
db_restore_enabled = true
|
||||
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
|
||||
```
|
||||
|
||||
Das Backup wird als Kubernetes Job ausgeführt.
|
||||
|
||||
### Manuelle Wiederherstellung
|
||||
|
||||
```bash
|
||||
# Interaktives Script
|
||||
./scripts/restore-db.sh
|
||||
|
||||
# Script fragt nach:
|
||||
# - Welche Backup-Datei verwendet werden soll
|
||||
# - Bestätigung zum Überschreiben der Datenbank
|
||||
# - Zeigt Fortschritt und Logs an
|
||||
```
|
||||
|
||||
### Manuelles DB-Restore (kubectl)
|
||||
|
||||
```bash
|
||||
# ConfigMap mit Backup erstellen
|
||||
kubectl create configmap db-restore-backup \
|
||||
--from-file=backup.dump=/Users/sebastianfrohlich/Herd/frontend/backups/backup_backend_20251203_101741.dump \
|
||||
-n laravel-app
|
||||
|
||||
# Job aus db-restore.tf verwenden oder manuell erstellen
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: db-restore-manual
|
||||
namespace: laravel-app
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: db-restore
|
||||
image: postgres:16-alpine
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
export PGPASSWORD="\$DB_PASSWORD"
|
||||
pg_restore -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME -d \$DB_DATABASE \
|
||||
--verbose --clean --if-exists --no-owner --no-privileges /backup/backup.dump
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: laravel-app-db-credentials
|
||||
volumeMounts:
|
||||
- name: backup-volume
|
||||
mountPath: /backup
|
||||
volumes:
|
||||
- name: backup-volume
|
||||
configMap:
|
||||
name: db-restore-backup
|
||||
restartPolicy: OnFailure
|
||||
EOF
|
||||
|
||||
# Job-Status prüfen
|
||||
kubectl get jobs -n laravel-app
|
||||
|
||||
# Logs anzeigen
|
||||
kubectl logs -n laravel-app -l job-name=db-restore-manual -f
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Neues Image deployen
|
||||
|
||||
```bash
|
||||
# 1. Neues Image bauen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 .
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1
|
||||
|
||||
# 2. terraform.tfvars aktualisieren
|
||||
# docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.1"
|
||||
|
||||
# 3. Terraform apply
|
||||
terraform apply
|
||||
|
||||
# Oder direkt kubectl verwenden für schnelleres Update:
|
||||
kubectl set image deployment/laravel-app \
|
||||
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 \
|
||||
-n laravel-app
|
||||
|
||||
# Rollout Status prüfen
|
||||
kubectl rollout status deployment/laravel-app -n laravel-app
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# Rollout Historie anzeigen
|
||||
kubectl rollout history deployment/laravel-app -n laravel-app
|
||||
|
||||
# Zu vorheriger Version zurückkehren
|
||||
kubectl rollout undo deployment/laravel-app -n laravel-app
|
||||
|
||||
# Zu spezifischer Revision zurückkehren
|
||||
kubectl rollout undo deployment/laravel-app --to-revision=2 -n laravel-app
|
||||
```
|
||||
|
||||
### Replicas skalieren
|
||||
|
||||
```bash
|
||||
# Über kubectl
|
||||
kubectl scale deployment/laravel-app --replicas=3 -n laravel-app
|
||||
|
||||
# Über Terraform
|
||||
# Ändere app_replicas in terraform.tfvars und führe aus:
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Pods starten nicht
|
||||
|
||||
```bash
|
||||
# Pod-Status prüfen
|
||||
kubectl get pods -n laravel-app
|
||||
|
||||
# Detaillierte Pod-Informationen
|
||||
kubectl describe pod <pod-name> -n laravel-app
|
||||
|
||||
# Pod-Logs anzeigen
|
||||
kubectl logs <pod-name> -n laravel-app
|
||||
|
||||
# Vorherige Pod-Logs (bei CrashLoopBackOff)
|
||||
kubectl logs <pod-name> -n laravel-app --previous
|
||||
```
|
||||
|
||||
### Datenbank-Verbindungsprobleme
|
||||
|
||||
```bash
|
||||
# Secrets prüfen
|
||||
kubectl get secret laravel-app-db-credentials -n laravel-app -o yaml
|
||||
|
||||
# PostgreSQL Server Firewall prüfen
|
||||
az postgres flexible-server firewall-rule list \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name <postgresql-server-name>
|
||||
|
||||
# Verbindung vom Pod aus testen
|
||||
kubectl run -it --rm debug --image=postgres:16-alpine -n laravel-app -- \
|
||||
psql "postgresql://user:pass@host:5432/database"
|
||||
```
|
||||
|
||||
### Ingress funktioniert nicht
|
||||
|
||||
```bash
|
||||
# Ingress Status prüfen
|
||||
kubectl get ingress -n laravel-app
|
||||
|
||||
# Ingress Controller Logs
|
||||
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller
|
||||
|
||||
# LoadBalancer Service prüfen
|
||||
kubectl get svc -n ingress-nginx
|
||||
|
||||
# Ingress Events prüfen
|
||||
kubectl describe ingress laravel-app-ingress -n laravel-app
|
||||
```
|
||||
|
||||
### SSL/TLS Zertifikat-Probleme
|
||||
|
||||
```bash
|
||||
# cert-manager Pods prüfen
|
||||
kubectl get pods -n cert-manager
|
||||
|
||||
# Certificate Status prüfen
|
||||
kubectl get certificate -n laravel-app
|
||||
|
||||
# Certificate Details
|
||||
kubectl describe certificate laravel-app-tls -n laravel-app
|
||||
|
||||
# cert-manager Logs
|
||||
kubectl logs -n cert-manager -l app=cert-manager
|
||||
```
|
||||
|
||||
### Performance-Probleme
|
||||
|
||||
```bash
|
||||
# Resource Usage prüfen
|
||||
kubectl top pods -n laravel-app
|
||||
kubectl top nodes
|
||||
|
||||
# HPA Status prüfen
|
||||
kubectl get hpa -n laravel-app
|
||||
|
||||
# Events prüfen
|
||||
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
|
||||
```
|
||||
|
||||
## Wartung
|
||||
|
||||
### Backups
|
||||
|
||||
PostgreSQL Flexible Server erstellt automatische Backups:
|
||||
|
||||
```bash
|
||||
# Backup-Konfiguration prüfen
|
||||
az postgres flexible-server show \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name <postgresql-server-name> \
|
||||
--query "{backupRetentionDays:backup.backupRetentionDays,geoRedundantBackup:backup.geoRedundantBackup}"
|
||||
|
||||
# Manuelles Backup erstellen
|
||||
az postgres flexible-server backup create \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--name <postgresql-server-name> \
|
||||
--backup-name manual-backup-$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
### Updates
|
||||
|
||||
#### Terraform Updates
|
||||
|
||||
```bash
|
||||
# Terraform Zustand aktualisieren
|
||||
terraform refresh
|
||||
|
||||
# Änderungen planen
|
||||
terraform plan
|
||||
|
||||
# Änderungen anwenden
|
||||
terraform apply
|
||||
```
|
||||
|
||||
#### Laravel Updates
|
||||
|
||||
```bash
|
||||
# Composer Dependencies aktualisieren
|
||||
composer update
|
||||
|
||||
# NPM Dependencies aktualisieren
|
||||
npm update
|
||||
|
||||
# Neues Image bauen und deployen
|
||||
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.2 .
|
||||
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.2
|
||||
|
||||
# Deployment aktualisieren
|
||||
kubectl set image deployment/laravel-app \
|
||||
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.2 \
|
||||
-n laravel-app
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
|
||||
```bash
|
||||
# Log Analytics Workspace
|
||||
az monitor log-analytics workspace show \
|
||||
--resource-group trusted_ai_demo_rg \
|
||||
--workspace-name <workspace-name>
|
||||
|
||||
# Azure Monitor für Container
|
||||
# Im Azure Portal: AKS Cluster > Monitoring > Insights
|
||||
|
||||
# Prometheus Metrics (wenn aktiviert)
|
||||
kubectl port-forward -n ingress-nginx \
|
||||
svc/ingress-nginx-controller-metrics 10254:10254
|
||||
# Dann: http://localhost:10254/metrics
|
||||
```
|
||||
|
||||
### Cleanup
|
||||
|
||||
```bash
|
||||
# Einzelne Resources löschen
|
||||
terraform destroy -target=kubernetes_deployment.app
|
||||
terraform destroy -target=azurerm_postgresql_flexible_server.main
|
||||
|
||||
# Alles löschen
|
||||
terraform destroy
|
||||
|
||||
# Namespace löschen (löscht alle Resources im Namespace)
|
||||
kubectl delete namespace laravel-app
|
||||
```
|
||||
|
||||
## Kosten-Optimierung
|
||||
|
||||
### Development/Staging
|
||||
|
||||
Für Dev/Staging Umgebungen kannst du Kosten sparen:
|
||||
|
||||
```hcl
|
||||
# terraform.tfvars für Staging
|
||||
postgresql_sku_name = "B_Standard_B1ms" # Burstable tier
|
||||
postgresql_storage_mb = 32768 # 32 GB
|
||||
app_replicas = 1 # Weniger Replicas
|
||||
aks_node_pool_min_count = 1 # Weniger Nodes
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
Für Production empfohlen:
|
||||
|
||||
```hcl
|
||||
postgresql_sku_name = "GP_Standard_D2s_v3" # General Purpose
|
||||
postgresql_storage_mb = 131072 # 128 GB
|
||||
postgresql_backup_retention_days = 35 # Längere Retention
|
||||
app_replicas = 3 # Mehr Replicas
|
||||
```
|
||||
|
||||
## Sicherheit
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Secrets Management**:
|
||||
- Verwende Azure Key Vault für Production
|
||||
- Rotiere Passwörter regelmäßig
|
||||
- Verwende starke, generierte Passwörter
|
||||
|
||||
2. **Network Security**:
|
||||
- Aktiviere VNet Integration für PostgreSQL
|
||||
- Verwende Private Endpoints
|
||||
- Beschränke Firewall-Regeln
|
||||
|
||||
3. **SSL/TLS**:
|
||||
- Aktiviere SSL für Production:
|
||||
```hcl
|
||||
ssl_enabled = true
|
||||
ssl_issuer_email = "admin@yourdomain.com"
|
||||
ingress_host = "app.yourdomain.com"
|
||||
```
|
||||
|
||||
4. **RBAC**:
|
||||
- Verwende Kubernetes RBAC
|
||||
- Minimale Berechtigungen für Service Accounts
|
||||
|
||||
## Support und Kontakt
|
||||
|
||||
Bei Fragen oder Problemen:
|
||||
|
||||
1. Prüfe die [Troubleshooting](#troubleshooting) Sektion
|
||||
2. Prüfe Kubernetes Events: `kubectl get events -n laravel-app`
|
||||
3. Prüfe Logs: `kubectl logs -n laravel-app -l app=laravel-app`
|
||||
|
||||
## Anhang
|
||||
|
||||
### Nützliche Befehle
|
||||
|
||||
```bash
|
||||
# Alle Resources in einem Namespace anzeigen
|
||||
kubectl get all -n laravel-app
|
||||
|
||||
# Port-forward für lokalen Zugriff
|
||||
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
|
||||
|
||||
# Shell in einem Pod öffnen
|
||||
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
|
||||
|
||||
# ConfigMap/Secret bearbeiten
|
||||
kubectl edit configmap laravel-app-config -n laravel-app
|
||||
|
||||
# Resource Usage live monitoren
|
||||
watch kubectl top pods -n laravel-app
|
||||
|
||||
# Cluster Info
|
||||
kubectl cluster-info
|
||||
kubectl get nodes -o wide
|
||||
```
|
||||
|
||||
### Terraform State Management
|
||||
|
||||
Für Team-Arbeit solltest du Remote State verwenden:
|
||||
|
||||
```hcl
|
||||
# backend.hcl
|
||||
resource_group_name = "trusted_ai_demo_rg"
|
||||
storage_account_name = "tfstate<random>"
|
||||
container_name = "tfstate"
|
||||
key = "laravel-app.terraform.tfstate"
|
||||
```
|
||||
|
||||
```bash
|
||||
# Backend initialisieren
|
||||
terraform init -backend-config=backend.hcl
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
# Note: Database backup files are too large for ConfigMaps (>1MB limit)
|
||||
# Use manual restore via kubectl after deployment instead:
|
||||
#
|
||||
# kubectl cp ../backups/backup_backend_20251203_101741.dump \
|
||||
# laravel-app/postgresql-0:/tmp/backup.dump
|
||||
#
|
||||
# kubectl exec -it -n laravel-app postgresql-0 -- \
|
||||
# pg_restore -U pgadmin -d laravel_app --clean --if-exists \
|
||||
# /tmp/backup.dump
|
||||
#
|
||||
# Or use the restore script: ./scripts/restore-db.sh
|
||||
|
||||
# Disabled: Automatic database restore via Terraform
|
||||
# The ConfigMap approach doesn't work for files >1MB
|
||||
# Use manual restore after deployment (see comments above)
|
||||
@@ -0,0 +1,230 @@
|
||||
# Install nginx-ingress-controller via Helm
|
||||
resource "helm_release" "nginx_ingress" {
|
||||
count = var.ingress_enabled ? 1 : 0
|
||||
|
||||
name = "ingress-nginx"
|
||||
repository = "https://kubernetes.github.io/ingress-nginx"
|
||||
chart = "ingress-nginx"
|
||||
version = "4.11.3"
|
||||
namespace = "ingress-nginx"
|
||||
create_namespace = true
|
||||
|
||||
set {
|
||||
name = "controller.service.type"
|
||||
value = "LoadBalancer"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "controller.service.annotations.service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path"
|
||||
value = "/healthz"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "controller.metrics.enabled"
|
||||
value = "true"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "controller.podAnnotations.prometheus\\.io/scrape"
|
||||
value = "true"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "controller.podAnnotations.prometheus\\.io/port"
|
||||
value = "10254"
|
||||
}
|
||||
|
||||
# Resource limits
|
||||
set {
|
||||
name = "controller.resources.requests.cpu"
|
||||
value = "100m"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "controller.resources.requests.memory"
|
||||
value = "128Mi"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "controller.resources.limits.cpu"
|
||||
value = "500m"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "controller.resources.limits.memory"
|
||||
value = "512Mi"
|
||||
}
|
||||
|
||||
# Enable rate limiting
|
||||
set {
|
||||
name = "controller.config.limit-req-status-code"
|
||||
value = "429"
|
||||
}
|
||||
|
||||
timeout = 600
|
||||
}
|
||||
|
||||
# Wait for nginx-ingress to be ready and get LoadBalancer IP
|
||||
data "kubernetes_service" "ingress_nginx" {
|
||||
count = var.ingress_enabled ? 1 : 0
|
||||
|
||||
metadata {
|
||||
name = "ingress-nginx-controller"
|
||||
namespace = "ingress-nginx"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
helm_release.nginx_ingress
|
||||
]
|
||||
}
|
||||
|
||||
# Install cert-manager for SSL/TLS (optional)
|
||||
resource "helm_release" "cert_manager" {
|
||||
count = var.ssl_enabled ? 1 : 0
|
||||
|
||||
name = "cert-manager"
|
||||
repository = "https://charts.jetstack.io"
|
||||
chart = "cert-manager"
|
||||
version = "v1.16.2"
|
||||
namespace = "cert-manager"
|
||||
create_namespace = true
|
||||
|
||||
set {
|
||||
name = "crds.enabled"
|
||||
value = "true"
|
||||
}
|
||||
|
||||
set {
|
||||
name = "global.leaderElection.namespace"
|
||||
value = "cert-manager"
|
||||
}
|
||||
|
||||
timeout = 600
|
||||
}
|
||||
|
||||
# ClusterIssuer for Let's Encrypt (production)
|
||||
resource "kubernetes_manifest" "letsencrypt_prod" {
|
||||
count = var.ssl_enabled && var.ssl_issuer_email != "" ? 1 : 0
|
||||
|
||||
manifest = {
|
||||
apiVersion = "cert-manager.io/v1"
|
||||
kind = "ClusterIssuer"
|
||||
metadata = {
|
||||
name = "letsencrypt-prod"
|
||||
}
|
||||
spec = {
|
||||
acme = {
|
||||
server = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
email = var.ssl_issuer_email
|
||||
privateKeySecretRef = {
|
||||
name = "letsencrypt-prod"
|
||||
}
|
||||
solvers = [
|
||||
{
|
||||
http01 = {
|
||||
ingress = {
|
||||
class = "nginx"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
helm_release.cert_manager
|
||||
]
|
||||
}
|
||||
|
||||
# ClusterIssuer for Let's Encrypt (staging) - for testing
|
||||
resource "kubernetes_manifest" "letsencrypt_staging" {
|
||||
count = var.ssl_enabled && var.ssl_issuer_email != "" ? 1 : 0
|
||||
|
||||
manifest = {
|
||||
apiVersion = "cert-manager.io/v1"
|
||||
kind = "ClusterIssuer"
|
||||
metadata = {
|
||||
name = "letsencrypt-staging"
|
||||
}
|
||||
spec = {
|
||||
acme = {
|
||||
server = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
email = var.ssl_issuer_email
|
||||
privateKeySecretRef = {
|
||||
name = "letsencrypt-staging"
|
||||
}
|
||||
solvers = [
|
||||
{
|
||||
http01 = {
|
||||
ingress = {
|
||||
class = "nginx"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
helm_release.cert_manager
|
||||
]
|
||||
}
|
||||
|
||||
# Kubernetes Ingress for Laravel Application
|
||||
resource "kubernetes_ingress_v1" "app" {
|
||||
count = var.ingress_enabled ? 1 : 0
|
||||
|
||||
metadata {
|
||||
name = "${var.app_name}-ingress"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
|
||||
annotations = merge(
|
||||
{
|
||||
"kubernetes.io/ingress.class" = "nginx"
|
||||
"nginx.ingress.kubernetes.io/rewrite-target" = "/"
|
||||
"nginx.ingress.kubernetes.io/ssl-redirect" = var.ssl_enabled ? "true" : "false"
|
||||
},
|
||||
var.ssl_enabled && var.ingress_host != "" ? {
|
||||
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
|
||||
} : {}
|
||||
)
|
||||
}
|
||||
|
||||
spec {
|
||||
dynamic "tls" {
|
||||
for_each = var.ssl_enabled && var.ingress_host != "" ? [1] : []
|
||||
content {
|
||||
hosts = [var.ingress_host]
|
||||
secret_name = "${var.app_name}-tls"
|
||||
}
|
||||
}
|
||||
|
||||
rule {
|
||||
host = var.ingress_host != "" ? var.ingress_host : null
|
||||
|
||||
http {
|
||||
path {
|
||||
path = "/"
|
||||
path_type = "Prefix"
|
||||
|
||||
backend {
|
||||
service {
|
||||
name = kubernetes_service.app.metadata[0].name
|
||||
port {
|
||||
number = 80
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
helm_release.nginx_ingress,
|
||||
kubernetes_service.app
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
# Kubernetes Namespace
|
||||
resource "kubernetes_namespace" "app" {
|
||||
metadata {
|
||||
name = var.app_namespace
|
||||
labels = merge(
|
||||
local.common_labels,
|
||||
{
|
||||
name = var.app_namespace
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# Kubernetes Secret for Database Connection
|
||||
resource "kubernetes_secret" "db_credentials" {
|
||||
metadata {
|
||||
name = "${var.app_name}-db-credentials"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
data = {
|
||||
# Primary connection uses DB_HOST2 variables (for pgsql_second connection)
|
||||
DB_CONNECTION2 = "pgsql"
|
||||
DB_HOST2 = "postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local"
|
||||
DB_PORT2 = "5432"
|
||||
DB_DATABASE2 = local.postgresql_db_name
|
||||
DB_USERNAME2 = var.postgresql_admin_username
|
||||
DB_PASSWORD2 = local.postgresql_admin_pass
|
||||
|
||||
# Also set standard DB_ variables for pgsql connection (used by sessions)
|
||||
DB_CONNECTION = "pgsql"
|
||||
DB_HOST = "postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local"
|
||||
DB_PORT = "5432"
|
||||
DB_DATABASE = local.postgresql_db_name
|
||||
DB_USERNAME = var.postgresql_admin_username
|
||||
DB_PASSWORD = local.postgresql_admin_pass
|
||||
}
|
||||
|
||||
type = "Opaque"
|
||||
|
||||
depends_on = [
|
||||
kubernetes_service.postgresql
|
||||
]
|
||||
}
|
||||
|
||||
# Kubernetes Secret for Laravel Application
|
||||
resource "kubernetes_secret" "app_secrets" {
|
||||
metadata {
|
||||
name = "${var.app_name}-secrets"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
data = {
|
||||
APP_KEY = var.app_key
|
||||
}
|
||||
|
||||
type = "Opaque"
|
||||
}
|
||||
|
||||
# Kubernetes ConfigMap for Laravel Application
|
||||
resource "kubernetes_config_map" "app_config" {
|
||||
metadata {
|
||||
name = "${var.app_name}-config"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
data = {
|
||||
APP_NAME = var.app_name
|
||||
APP_ENV = var.app_env
|
||||
APP_DEBUG = tostring(var.app_debug)
|
||||
APP_URL = local.app_url
|
||||
APP_LOCALE = "en"
|
||||
APP_FALLBACK_LOCALE = "en"
|
||||
APP_FAKER_LOCALE = "en_US"
|
||||
LOG_CHANNEL = "stack"
|
||||
LOG_STACK = "single"
|
||||
LOG_LEVEL = "info"
|
||||
SESSION_DRIVER = "database"
|
||||
SESSION_CONNECTION = "pgsql"
|
||||
SESSION_LIFETIME = "120"
|
||||
SESSION_ENCRYPT = "false"
|
||||
CACHE_STORE = "database"
|
||||
QUEUE_CONNECTION = "database"
|
||||
BROADCAST_CONNECTION = "log"
|
||||
FILESYSTEM_DISK = "local"
|
||||
MAIL_MAILER = "log"
|
||||
VITE_APP_NAME = var.app_name
|
||||
}
|
||||
}
|
||||
|
||||
# Kubernetes Deployment for Laravel Application
|
||||
resource "kubernetes_deployment" "app" {
|
||||
metadata {
|
||||
name = var.app_name
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
spec {
|
||||
replicas = var.app_replicas
|
||||
|
||||
selector {
|
||||
match_labels = {
|
||||
app = var.app_name
|
||||
}
|
||||
}
|
||||
|
||||
template {
|
||||
metadata {
|
||||
labels = merge(
|
||||
local.common_labels,
|
||||
{
|
||||
version = "latest"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
spec {
|
||||
image_pull_secrets {
|
||||
name = "acr-secret"
|
||||
}
|
||||
|
||||
container {
|
||||
name = var.app_name
|
||||
image = var.docker_image
|
||||
|
||||
port {
|
||||
name = "http"
|
||||
container_port = 80
|
||||
protocol = "TCP"
|
||||
}
|
||||
|
||||
env_from {
|
||||
config_map_ref {
|
||||
name = kubernetes_config_map.app_config.metadata[0].name
|
||||
}
|
||||
}
|
||||
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = kubernetes_secret.app_secrets.metadata[0].name
|
||||
}
|
||||
}
|
||||
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = kubernetes_secret.db_credentials.metadata[0].name
|
||||
}
|
||||
}
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = var.app_resources_requests_cpu
|
||||
memory = var.app_resources_requests_memory
|
||||
}
|
||||
limits = {
|
||||
cpu = var.app_resources_limits_cpu
|
||||
memory = var.app_resources_limits_memory
|
||||
}
|
||||
}
|
||||
|
||||
liveness_probe {
|
||||
exec {
|
||||
command = ["sh", "-c", "ps aux | grep -v grep | grep -q nginx && ps aux | grep -v grep | grep -q php-fpm"]
|
||||
}
|
||||
initial_delay_seconds = 30
|
||||
period_seconds = 10
|
||||
timeout_seconds = 5
|
||||
failure_threshold = 3
|
||||
}
|
||||
|
||||
readiness_probe {
|
||||
exec {
|
||||
command = ["sh", "-c", "ps aux | grep -v grep | grep -q nginx && ps aux | grep -v grep | grep -q php-fpm"]
|
||||
}
|
||||
initial_delay_seconds = 10
|
||||
period_seconds = 5
|
||||
timeout_seconds = 3
|
||||
failure_threshold = 3
|
||||
}
|
||||
}
|
||||
|
||||
restart_policy = "Always"
|
||||
}
|
||||
}
|
||||
|
||||
strategy {
|
||||
type = "RollingUpdate"
|
||||
rolling_update {
|
||||
max_surge = "1"
|
||||
max_unavailable = "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
kubernetes_secret.db_credentials,
|
||||
kubernetes_secret.app_secrets,
|
||||
kubernetes_config_map.app_config
|
||||
]
|
||||
}
|
||||
|
||||
# Kubernetes Service for Laravel Application
|
||||
resource "kubernetes_service" "app" {
|
||||
metadata {
|
||||
name = var.app_name
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
spec {
|
||||
selector = {
|
||||
app = var.app_name
|
||||
}
|
||||
|
||||
port {
|
||||
name = "http"
|
||||
port = 80
|
||||
target_port = 80
|
||||
protocol = "TCP"
|
||||
}
|
||||
|
||||
type = "ClusterIP"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
kubernetes_deployment.app
|
||||
]
|
||||
}
|
||||
|
||||
# Horizontal Pod Autoscaler
|
||||
resource "kubernetes_horizontal_pod_autoscaler_v2" "app" {
|
||||
metadata {
|
||||
name = "${var.app_name}-hpa"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
spec {
|
||||
scale_target_ref {
|
||||
api_version = "apps/v1"
|
||||
kind = "Deployment"
|
||||
name = kubernetes_deployment.app.metadata[0].name
|
||||
}
|
||||
|
||||
min_replicas = var.app_replicas
|
||||
max_replicas = var.app_replicas * 3
|
||||
|
||||
metric {
|
||||
type = "Resource"
|
||||
resource {
|
||||
name = "cpu"
|
||||
target {
|
||||
type = "Utilization"
|
||||
average_utilization = 80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metric {
|
||||
type = "Resource"
|
||||
resource {
|
||||
name = "memory"
|
||||
target {
|
||||
type = "Utilization"
|
||||
average_utilization = 80
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Data sources for existing resources
|
||||
data "azurerm_resource_group" "main" {
|
||||
name = var.resource_group_name
|
||||
}
|
||||
|
||||
data "azurerm_kubernetes_cluster" "main" {
|
||||
name = var.aks_cluster_name
|
||||
resource_group_name = var.resource_group_name
|
||||
}
|
||||
|
||||
data "azurerm_subscription" "current" {}
|
||||
|
||||
# Generate random suffix for unique resource names
|
||||
resource "random_string" "suffix" {
|
||||
length = 8
|
||||
special = false
|
||||
upper = false
|
||||
}
|
||||
|
||||
# Generate random password for PostgreSQL if not provided
|
||||
resource "random_password" "postgresql_admin_password" {
|
||||
count = var.postgresql_admin_password == null ? 1 : 0
|
||||
length = 32
|
||||
special = true
|
||||
}
|
||||
|
||||
# Local variables
|
||||
locals {
|
||||
app_name_safe = replace(var.app_name, "_", "-")
|
||||
postgresql_server_name = "${local.app_name_safe}-psql-${random_string.suffix.result}"
|
||||
postgresql_db_name = replace(var.app_name, "-", "_")
|
||||
postgresql_admin_pass = var.postgresql_admin_password != null ? var.postgresql_admin_password : random_password.postgresql_admin_password[0].result
|
||||
app_url = var.app_url != "" ? var.app_url : (var.ingress_host != "" ? "https://${var.ingress_host}" : (var.ingress_enabled ? "http://${try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending")}" : "ingress-not-enabled"))
|
||||
|
||||
common_labels = {
|
||||
app = var.app_name
|
||||
environment = var.app_env
|
||||
managed-by = "terraform"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
output "resource_group_name" {
|
||||
description = "Name of the resource group"
|
||||
value = data.azurerm_resource_group.main.name
|
||||
}
|
||||
|
||||
output "aks_cluster_name" {
|
||||
description = "Name of the AKS cluster"
|
||||
value = data.azurerm_kubernetes_cluster.main.name
|
||||
}
|
||||
|
||||
output "postgresql_service_name" {
|
||||
description = "Name of the PostgreSQL service (in-cluster)"
|
||||
value = kubernetes_service.postgresql.metadata[0].name
|
||||
}
|
||||
|
||||
output "postgresql_service_fqdn" {
|
||||
description = "Internal FQDN of the PostgreSQL service (in-cluster)"
|
||||
value = "postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local"
|
||||
}
|
||||
|
||||
output "postgresql_database_name" {
|
||||
description = "Name of the PostgreSQL database"
|
||||
value = local.postgresql_db_name
|
||||
}
|
||||
|
||||
output "postgresql_admin_username" {
|
||||
description = "Administrator username for PostgreSQL"
|
||||
value = var.postgresql_admin_username
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "postgresql_connection_string" {
|
||||
description = "PostgreSQL connection string (from within cluster)"
|
||||
value = "postgresql://${var.postgresql_admin_username}:${nonsensitive(local.postgresql_admin_pass)}@postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local:5432/${local.postgresql_db_name}"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "app_namespace" {
|
||||
description = "Kubernetes namespace for the application"
|
||||
value = kubernetes_namespace.app.metadata[0].name
|
||||
}
|
||||
|
||||
output "app_service_name" {
|
||||
description = "Kubernetes service name for the application"
|
||||
value = kubernetes_service.app.metadata[0].name
|
||||
}
|
||||
|
||||
output "ingress_enabled" {
|
||||
description = "Whether ingress is enabled"
|
||||
value = var.ingress_enabled
|
||||
}
|
||||
|
||||
output "ingress_ip" {
|
||||
description = "IP address of the ingress controller load balancer"
|
||||
value = var.ingress_enabled ? try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending") : "not enabled"
|
||||
}
|
||||
|
||||
output "app_url" {
|
||||
description = "URL of the Laravel application"
|
||||
value = var.ingress_enabled ? (var.ingress_host != "" ? "https://${var.ingress_host}" : "http://${try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending")}") : "ingress not enabled"
|
||||
}
|
||||
|
||||
output "ssl_enabled" {
|
||||
description = "Whether SSL/TLS is enabled"
|
||||
value = var.ssl_enabled
|
||||
}
|
||||
|
||||
output "db_restore_info" {
|
||||
description = "Database restore information"
|
||||
value = "Database restore disabled in Terraform. Use manual restore via kubectl (see db-restore.tf for instructions)"
|
||||
}
|
||||
|
||||
output "deployment_instructions" {
|
||||
description = "Next steps after deployment"
|
||||
value = <<-EOT
|
||||
========================================
|
||||
Deployment Complete!
|
||||
========================================
|
||||
|
||||
1. Get kubectl credentials:
|
||||
az aks get-credentials --resource-group ${data.azurerm_resource_group.main.name} --name ${data.azurerm_kubernetes_cluster.main.name}
|
||||
|
||||
2. Check application status:
|
||||
kubectl get pods -n ${kubernetes_namespace.app.metadata[0].name}
|
||||
kubectl get svc -n ${kubernetes_namespace.app.metadata[0].name}
|
||||
kubectl get ingress -n ${kubernetes_namespace.app.metadata[0].name}
|
||||
|
||||
3. Check database restore job (if enabled):
|
||||
kubectl get jobs -n ${kubernetes_namespace.app.metadata[0].name}
|
||||
kubectl logs -n ${kubernetes_namespace.app.metadata[0].name} -l job-type=database-restore
|
||||
|
||||
4. Access the application:
|
||||
${var.ingress_enabled ? (var.ingress_host != "" ? "https://${var.ingress_host}" : "http://${try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending")}") : "ingress not enabled - use port-forward"}
|
||||
|
||||
5. Port-forward (if ingress not ready):
|
||||
kubectl port-forward -n ${kubernetes_namespace.app.metadata[0].name} svc/${kubernetes_service.app.metadata[0].name} 8080:80
|
||||
|
||||
6. View logs:
|
||||
kubectl logs -n ${kubernetes_namespace.app.metadata[0].name} -l app=${var.app_name} -f
|
||||
|
||||
7. Database connection details (in-cluster):
|
||||
Host: postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local
|
||||
Database: ${local.postgresql_db_name}
|
||||
Username: ${var.postgresql_admin_username}
|
||||
|
||||
8. Restore database manually:
|
||||
kubectl cp ../backups/backup_backend_20251203_101741.dump ${kubernetes_namespace.app.metadata[0].name}/postgresql-0:/tmp/backup.dump
|
||||
kubectl exec -it -n ${kubernetes_namespace.app.metadata[0].name} postgresql-0 -- pg_restore -U ${var.postgresql_admin_username} -d ${local.postgresql_db_name} --clean --if-exists /tmp/backup.dump
|
||||
|
||||
========================================
|
||||
EOT
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
# PostgreSQL in Kubernetes Cluster
|
||||
# Using StatefulSet with PersistentVolume for data persistence
|
||||
|
||||
# Kubernetes Secret for PostgreSQL
|
||||
resource "kubernetes_secret" "postgresql" {
|
||||
metadata {
|
||||
name = "postgresql-credentials"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
data = {
|
||||
POSTGRES_USER = var.postgresql_admin_username
|
||||
POSTGRES_PASSWORD = var.postgresql_admin_password
|
||||
POSTGRES_DB = local.postgresql_db_name
|
||||
}
|
||||
|
||||
type = "Opaque"
|
||||
}
|
||||
|
||||
# PersistentVolumeClaim for PostgreSQL data
|
||||
resource "kubernetes_persistent_volume_claim" "postgresql" {
|
||||
metadata {
|
||||
name = "postgresql-pvc"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
spec {
|
||||
access_modes = ["ReadWriteOnce"]
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
storage = "10Gi"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wait_until_bound = false
|
||||
}
|
||||
|
||||
# PostgreSQL StatefulSet
|
||||
resource "kubernetes_stateful_set" "postgresql" {
|
||||
metadata {
|
||||
name = "postgresql"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
spec {
|
||||
service_name = "postgresql"
|
||||
replicas = 1
|
||||
|
||||
selector {
|
||||
match_labels = {
|
||||
app = "postgresql"
|
||||
}
|
||||
}
|
||||
|
||||
template {
|
||||
metadata {
|
||||
labels = merge(
|
||||
local.common_labels,
|
||||
{
|
||||
app = "postgresql"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
spec {
|
||||
container {
|
||||
name = "postgresql"
|
||||
image = "postgres:16-alpine"
|
||||
|
||||
port {
|
||||
container_port = 5432
|
||||
name = "postgresql"
|
||||
}
|
||||
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = kubernetes_secret.postgresql.metadata[0].name
|
||||
}
|
||||
}
|
||||
|
||||
volume_mount {
|
||||
name = "postgresql-storage"
|
||||
mount_path = "/var/lib/postgresql/data"
|
||||
sub_path = "postgres"
|
||||
}
|
||||
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "250m"
|
||||
memory = "512Mi"
|
||||
}
|
||||
limits = {
|
||||
cpu = "1000m"
|
||||
memory = "1Gi"
|
||||
}
|
||||
}
|
||||
|
||||
liveness_probe {
|
||||
exec {
|
||||
command = ["pg_isready", "-U", var.postgresql_admin_username]
|
||||
}
|
||||
initial_delay_seconds = 30
|
||||
period_seconds = 10
|
||||
timeout_seconds = 5
|
||||
failure_threshold = 3
|
||||
}
|
||||
|
||||
readiness_probe {
|
||||
exec {
|
||||
command = ["pg_isready", "-U", var.postgresql_admin_username]
|
||||
}
|
||||
initial_delay_seconds = 5
|
||||
period_seconds = 5
|
||||
timeout_seconds = 3
|
||||
failure_threshold = 3
|
||||
}
|
||||
}
|
||||
|
||||
volume {
|
||||
name = "postgresql-storage"
|
||||
persistent_volume_claim {
|
||||
claim_name = kubernetes_persistent_volume_claim.postgresql.metadata[0].name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
kubernetes_persistent_volume_claim.postgresql,
|
||||
kubernetes_secret.postgresql
|
||||
]
|
||||
}
|
||||
|
||||
# PostgreSQL Service
|
||||
resource "kubernetes_service" "postgresql" {
|
||||
metadata {
|
||||
name = "postgresql"
|
||||
namespace = kubernetes_namespace.app.metadata[0].name
|
||||
labels = local.common_labels
|
||||
}
|
||||
|
||||
spec {
|
||||
selector = {
|
||||
app = "postgresql"
|
||||
}
|
||||
|
||||
port {
|
||||
name = "postgresql"
|
||||
port = 5432
|
||||
target_port = 5432
|
||||
protocol = "TCP"
|
||||
}
|
||||
|
||||
type = "ClusterIP"
|
||||
cluster_ip = "None" # Headless service for StatefulSet
|
||||
session_affinity = "None"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
kubernetes_stateful_set.postgresql
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
# Azure Database for PostgreSQL Flexible Server
|
||||
resource "azurerm_postgresql_flexible_server" "main" {
|
||||
name = local.postgresql_server_name
|
||||
resource_group_name = data.azurerm_resource_group.main.name
|
||||
location = var.location
|
||||
|
||||
administrator_login = var.postgresql_admin_username
|
||||
administrator_password = local.postgresql_admin_pass
|
||||
|
||||
sku_name = var.postgresql_sku_name
|
||||
version = var.postgresql_version
|
||||
storage_mb = var.postgresql_storage_mb
|
||||
|
||||
backup_retention_days = var.postgresql_backup_retention_days
|
||||
geo_redundant_backup_enabled = false
|
||||
|
||||
# Public access for initial setup (can be restricted later)
|
||||
# For production, consider using private endpoints or VNet integration
|
||||
public_network_access_enabled = true
|
||||
|
||||
zone = "1"
|
||||
|
||||
tags = merge(
|
||||
local.common_labels,
|
||||
{
|
||||
purpose = "database"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
# PostgreSQL Firewall Rule - Allow Azure Services
|
||||
resource "azurerm_postgresql_flexible_server_firewall_rule" "allow_azure_services" {
|
||||
name = "AllowAzureServices"
|
||||
server_id = azurerm_postgresql_flexible_server.main.id
|
||||
start_ip_address = "0.0.0.0"
|
||||
end_ip_address = "0.0.0.0"
|
||||
}
|
||||
|
||||
# PostgreSQL Firewall Rule - Allow AKS Outbound IPs
|
||||
# Note: In production, consider using VNet integration or private endpoints
|
||||
resource "azurerm_postgresql_flexible_server_firewall_rule" "allow_all_temporary" {
|
||||
name = "AllowAllTemporary"
|
||||
server_id = azurerm_postgresql_flexible_server.main.id
|
||||
start_ip_address = "0.0.0.0"
|
||||
end_ip_address = "255.255.255.255"
|
||||
|
||||
# This is a temporary rule for initial setup
|
||||
# Replace with specific IP ranges or VNet integration in production
|
||||
}
|
||||
|
||||
# PostgreSQL Database
|
||||
resource "azurerm_postgresql_flexible_server_database" "main" {
|
||||
name = local.postgresql_db_name
|
||||
server_id = azurerm_postgresql_flexible_server.main.id
|
||||
collation = "en_US.utf8"
|
||||
charset = "utf8"
|
||||
}
|
||||
|
||||
# PostgreSQL Configuration - Optimize for Laravel
|
||||
resource "azurerm_postgresql_flexible_server_configuration" "max_connections" {
|
||||
name = "max_connections"
|
||||
server_id = azurerm_postgresql_flexible_server.main.id
|
||||
value = "200"
|
||||
}
|
||||
|
||||
resource "azurerm_postgresql_flexible_server_configuration" "timezone" {
|
||||
name = "timezone"
|
||||
server_id = azurerm_postgresql_flexible_server.main.id
|
||||
value = "UTC"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
provider "azurerm" {
|
||||
features {
|
||||
resource_group {
|
||||
prevent_deletion_if_contains_resources = false
|
||||
}
|
||||
}
|
||||
subscription_id = var.subscription_id
|
||||
environment = "public"
|
||||
use_cli = true
|
||||
resource_provider_registrations = "none"
|
||||
}
|
||||
|
||||
provider "kubernetes" {
|
||||
host = data.azurerm_kubernetes_cluster.main.kube_config[0].host
|
||||
client_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_certificate)
|
||||
client_key = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_key)
|
||||
cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate)
|
||||
}
|
||||
|
||||
provider "helm" {
|
||||
kubernetes {
|
||||
host = data.azurerm_kubernetes_cluster.main.kube_config[0].host
|
||||
client_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_certificate)
|
||||
client_key = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_key)
|
||||
cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate)
|
||||
}
|
||||
}
|
||||
Executable
+178
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TERRAFORM_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
PROJECT_ROOT="$(dirname "$TERRAFORM_DIR")"
|
||||
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Laravel Application Deployment Script${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Check prerequisites
|
||||
echo -e "${YELLOW}Checking prerequisites...${NC}"
|
||||
|
||||
if ! command -v terraform &> /dev/null; then
|
||||
echo -e "${RED}Error: Terraform is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v az &> /dev/null; then
|
||||
echo -e "${RED}Error: Azure CLI is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo -e "${RED}Error: kubectl is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo -e "${RED}Error: Docker is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ All prerequisites are installed${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if logged into Azure
|
||||
echo -e "${YELLOW}Checking Azure login status...${NC}"
|
||||
if ! az account show &> /dev/null; then
|
||||
echo -e "${RED}Error: Not logged into Azure. Please run 'az login'${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_SUBSCRIPTION=$(az account show --query name -o tsv)
|
||||
echo -e "${GREEN}✓ Logged into Azure (Subscription: $CURRENT_SUBSCRIPTION)${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if terraform.tfvars exists
|
||||
if [ ! -f "$TERRAFORM_DIR/terraform.tfvars" ]; then
|
||||
echo -e "${RED}Error: terraform.tfvars not found${NC}"
|
||||
echo -e "${YELLOW}Please create terraform.tfvars from terraform.tfvars.example${NC}"
|
||||
echo -e "${YELLOW}cp $TERRAFORM_DIR/terraform.tfvars.example $TERRAFORM_DIR/terraform.tfvars${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Docker image is specified
|
||||
DOCKER_IMAGE=$(grep 'docker_image' "$TERRAFORM_DIR/terraform.tfvars" | cut -d'"' -f2)
|
||||
if [ -z "$DOCKER_IMAGE" ] || [ "$DOCKER_IMAGE" == "myregistry.azurecr.io/laravel-app:latest" ]; then
|
||||
echo -e "${YELLOW}Warning: Docker image not configured in terraform.tfvars${NC}"
|
||||
echo -e "${YELLOW}Please update the docker_image variable before deploying${NC}"
|
||||
read -p "Do you want to build and push the Docker image now? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo -e "${YELLOW}Please provide the following information:${NC}"
|
||||
read -p "Azure Container Registry name (e.g., myregistry): " ACR_NAME
|
||||
read -p "Image name (e.g., laravel-app): " IMAGE_NAME
|
||||
read -p "Image tag (default: latest): " IMAGE_TAG
|
||||
IMAGE_TAG=${IMAGE_TAG:-latest}
|
||||
|
||||
DOCKER_IMAGE="$ACR_NAME.azurecr.io/$IMAGE_NAME:$IMAGE_TAG"
|
||||
|
||||
echo -e "${YELLOW}Building Docker image...${NC}"
|
||||
cd "$PROJECT_ROOT"
|
||||
docker build -t "$DOCKER_IMAGE" .
|
||||
|
||||
echo -e "${YELLOW}Logging into Azure Container Registry...${NC}"
|
||||
az acr login --name "$ACR_NAME"
|
||||
|
||||
echo -e "${YELLOW}Pushing Docker image...${NC}"
|
||||
docker push "$DOCKER_IMAGE"
|
||||
|
||||
echo -e "${GREEN}✓ Docker image pushed successfully${NC}"
|
||||
|
||||
# Update terraform.tfvars
|
||||
sed -i.bak "s|docker_image = \".*\"|docker_image = \"$DOCKER_IMAGE\"|" "$TERRAFORM_DIR/terraform.tfvars"
|
||||
echo -e "${GREEN}✓ Updated terraform.tfvars with new Docker image${NC}"
|
||||
echo ""
|
||||
else
|
||||
echo -e "${RED}Deployment cancelled. Please configure docker_image in terraform.tfvars${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate Laravel APP_KEY if needed
|
||||
APP_KEY=$(grep 'app_key' "$TERRAFORM_DIR/terraform.tfvars" | cut -d'"' -f2)
|
||||
if [ -z "$APP_KEY" ] || [ "$APP_KEY" == "base64:YOUR_APP_KEY_HERE" ]; then
|
||||
echo -e "${YELLOW}Generating Laravel APP_KEY...${NC}"
|
||||
cd "$PROJECT_ROOT"
|
||||
NEW_APP_KEY=$(php artisan key:generate --show)
|
||||
sed -i.bak "s|app_key = \".*\"|app_key = \"$NEW_APP_KEY\"|" "$TERRAFORM_DIR/terraform.tfvars"
|
||||
echo -e "${GREEN}✓ Generated and saved APP_KEY${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Change to Terraform directory
|
||||
cd "$TERRAFORM_DIR"
|
||||
|
||||
# Initialize Terraform
|
||||
echo -e "${YELLOW}Initializing Terraform...${NC}"
|
||||
terraform init
|
||||
|
||||
# Validate Terraform configuration
|
||||
echo -e "${YELLOW}Validating Terraform configuration...${NC}"
|
||||
terraform validate
|
||||
|
||||
# Plan deployment
|
||||
echo -e "${YELLOW}Planning deployment...${NC}"
|
||||
terraform plan -out=tfplan
|
||||
|
||||
# Confirm deployment
|
||||
echo ""
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
echo -e "${YELLOW}Ready to deploy!${NC}"
|
||||
echo -e "${YELLOW}========================================${NC}"
|
||||
read -p "Do you want to proceed with the deployment? (yes/no) " -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
echo -e "${RED}Deployment cancelled${NC}"
|
||||
rm -f tfplan
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Apply Terraform
|
||||
echo -e "${YELLOW}Applying Terraform configuration...${NC}"
|
||||
terraform apply tfplan
|
||||
|
||||
# Clean up plan file
|
||||
rm -f tfplan
|
||||
|
||||
# Get outputs
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Deployment completed successfully!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
terraform output deployment_instructions
|
||||
|
||||
# Configure kubectl
|
||||
echo ""
|
||||
echo -e "${YELLOW}Configuring kubectl...${NC}"
|
||||
RESOURCE_GROUP=$(terraform output -raw resource_group_name)
|
||||
AKS_CLUSTER=$(terraform output -raw aks_cluster_name)
|
||||
az aks get-credentials --resource-group "$RESOURCE_GROUP" --name "$AKS_CLUSTER" --overwrite-existing
|
||||
echo -e "${GREEN}✓ kubectl configured${NC}"
|
||||
|
||||
# Wait for pods to be ready
|
||||
echo ""
|
||||
echo -e "${YELLOW}Waiting for application pods to be ready...${NC}"
|
||||
APP_NAMESPACE=$(terraform output -raw app_namespace)
|
||||
kubectl wait --for=condition=ready pod -l app=$(terraform output -raw app_service_name | sed 's/-service//') -n "$APP_NAMESPACE" --timeout=300s || true
|
||||
|
||||
# Show pod status
|
||||
echo ""
|
||||
echo -e "${YELLOW}Application pod status:${NC}"
|
||||
kubectl get pods -n "$APP_NAMESPACE"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Deployment script completed!${NC}"
|
||||
Executable
+224
@@ -0,0 +1,224 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TERRAFORM_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
PROJECT_ROOT="$(dirname "$TERRAFORM_DIR")"
|
||||
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Database Restore Script${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Check prerequisites
|
||||
echo -e "${YELLOW}Checking prerequisites...${NC}"
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo -e "${RED}Error: kubectl is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v az &> /dev/null; then
|
||||
echo -e "${RED}Error: Azure CLI is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ All prerequisites are installed${NC}"
|
||||
echo ""
|
||||
|
||||
# Change to Terraform directory
|
||||
cd "$TERRAFORM_DIR"
|
||||
|
||||
# Check if Terraform is initialized
|
||||
if [ ! -d ".terraform" ]; then
|
||||
echo -e "${RED}Error: Terraform not initialized. Please run terraform init first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get Terraform outputs
|
||||
echo -e "${YELLOW}Getting Terraform outputs...${NC}"
|
||||
APP_NAMESPACE=$(terraform output -raw app_namespace 2>/dev/null)
|
||||
RESOURCE_GROUP=$(terraform output -raw resource_group_name 2>/dev/null)
|
||||
AKS_CLUSTER=$(terraform output -raw aks_cluster_name 2>/dev/null)
|
||||
|
||||
if [ -z "$APP_NAMESPACE" ] || [ -z "$RESOURCE_GROUP" ] || [ -z "$AKS_CLUSTER" ]; then
|
||||
echo -e "${RED}Error: Could not get Terraform outputs. Please run terraform apply first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configure kubectl
|
||||
echo -e "${YELLOW}Configuring kubectl...${NC}"
|
||||
az aks get-credentials --resource-group "$RESOURCE_GROUP" --name "$AKS_CLUSTER" --overwrite-existing
|
||||
echo -e "${GREEN}✓ kubectl configured${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if backup file exists
|
||||
BACKUP_DIR="$PROJECT_ROOT/backups"
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
echo -e "${RED}Error: Backup directory not found: $BACKUP_DIR${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# List available backups
|
||||
echo -e "${YELLOW}Available backup files:${NC}"
|
||||
ls -lh "$BACKUP_DIR"/*.dump 2>/dev/null || {
|
||||
echo -e "${RED}No backup files found in $BACKUP_DIR${NC}"
|
||||
exit 1
|
||||
}
|
||||
echo ""
|
||||
|
||||
# Select backup file
|
||||
read -p "Enter the backup file name (or full path): " BACKUP_FILE
|
||||
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
# Try in backup directory
|
||||
BACKUP_FILE="$BACKUP_DIR/$BACKUP_FILE"
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo -e "${RED}Error: Backup file not found: $BACKUP_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Found backup file: $BACKUP_FILE${NC}"
|
||||
echo ""
|
||||
|
||||
# Confirm restore
|
||||
echo -e "${RED}========================================${NC}"
|
||||
echo -e "${RED}WARNING: This will restore the database!${NC}"
|
||||
echo -e "${RED}All existing data will be replaced!${NC}"
|
||||
echo -e "${RED}========================================${NC}"
|
||||
read -p "Are you sure you want to continue? (yes/no) " -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
echo -e "${YELLOW}Database restore cancelled${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create ConfigMap with backup file
|
||||
echo -e "${YELLOW}Creating ConfigMap with backup file...${NC}"
|
||||
kubectl create configmap db-restore-backup \
|
||||
--from-file=backup.dump="$BACKUP_FILE" \
|
||||
-n "$APP_NAMESPACE" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
echo -e "${GREEN}✓ ConfigMap created${NC}"
|
||||
echo ""
|
||||
|
||||
# Create restore job
|
||||
echo -e "${YELLOW}Creating database restore job...${NC}"
|
||||
JOB_NAME="db-restore-manual-$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: $JOB_NAME
|
||||
namespace: $APP_NAMESPACE
|
||||
labels:
|
||||
app: laravel-app
|
||||
job-type: database-restore
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: laravel-app
|
||||
job-type: database-restore
|
||||
spec:
|
||||
containers:
|
||||
- name: db-restore
|
||||
image: postgres:16-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Waiting for PostgreSQL to be ready..."
|
||||
until pg_isready -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME; do
|
||||
echo "Waiting for database..."
|
||||
sleep 5
|
||||
done
|
||||
echo "PostgreSQL is ready!"
|
||||
|
||||
echo "Restoring database backup..."
|
||||
export PGPASSWORD="\$DB_PASSWORD"
|
||||
|
||||
# Create database if not exists
|
||||
psql -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME -d postgres -c "CREATE DATABASE \$DB_DATABASE;" || echo "Database already exists"
|
||||
|
||||
# Restore from backup
|
||||
pg_restore -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME -d \$DB_DATABASE --verbose --clean --if-exists --no-owner --no-privileges /backup/backup.dump || echo "Restore completed with warnings (this is normal)"
|
||||
|
||||
echo "Database restore completed successfully!"
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: laravel-app-db-credentials
|
||||
volumeMounts:
|
||||
- name: backup-volume
|
||||
mountPath: /backup
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
volumes:
|
||||
- name: backup-volume
|
||||
configMap:
|
||||
name: db-restore-backup
|
||||
restartPolicy: OnFailure
|
||||
backoffLimit: 3
|
||||
ttlSecondsAfterFinished: 86400
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ Restore job created: $JOB_NAME${NC}"
|
||||
echo ""
|
||||
|
||||
# Monitor job progress
|
||||
echo -e "${YELLOW}Monitoring restore job progress...${NC}"
|
||||
echo -e "${YELLOW}Press Ctrl+C to stop monitoring (job will continue in background)${NC}"
|
||||
echo ""
|
||||
|
||||
# Wait for pod to be created
|
||||
sleep 5
|
||||
|
||||
# Get pod name
|
||||
POD_NAME=$(kubectl get pods -n "$APP_NAMESPACE" -l job-name="$JOB_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
||||
|
||||
if [ -n "$POD_NAME" ]; then
|
||||
echo -e "${YELLOW}Following logs from pod: $POD_NAME${NC}"
|
||||
kubectl logs -n "$APP_NAMESPACE" -f "$POD_NAME" || true
|
||||
else
|
||||
echo -e "${YELLOW}Waiting for pod to be created...${NC}"
|
||||
kubectl wait --for=condition=ready pod -l job-name="$JOB_NAME" -n "$APP_NAMESPACE" --timeout=60s || true
|
||||
POD_NAME=$(kubectl get pods -n "$APP_NAMESPACE" -l job-name="$JOB_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
||||
if [ -n "$POD_NAME" ]; then
|
||||
kubectl logs -n "$APP_NAMESPACE" -f "$POD_NAME" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check job status
|
||||
echo ""
|
||||
echo -e "${YELLOW}Checking job status...${NC}"
|
||||
JOB_STATUS=$(kubectl get job "$JOB_NAME" -n "$APP_NAMESPACE" -o jsonpath='{.status.conditions[?(@.type=="Complete")].status}' 2>/dev/null)
|
||||
|
||||
if [ "$JOB_STATUS" == "True" ]; then
|
||||
echo -e "${GREEN}✓ Database restore completed successfully!${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Job status:${NC}"
|
||||
kubectl get job "$JOB_NAME" -n "$APP_NAMESPACE"
|
||||
echo ""
|
||||
echo -e "${YELLOW}To check logs again, run:${NC}"
|
||||
echo -e "${YELLOW}kubectl logs -n $APP_NAMESPACE -l job-name=$JOB_NAME${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Database restore script completed!${NC}"
|
||||
@@ -0,0 +1,29 @@
|
||||
terraform {
|
||||
required_version = ">= 1.9.0"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 4.0"
|
||||
}
|
||||
kubernetes = {
|
||||
source = "hashicorp/kubernetes"
|
||||
version = "~> 2.33"
|
||||
}
|
||||
helm = {
|
||||
source = "hashicorp/helm"
|
||||
version = "~> 2.16"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.6"
|
||||
}
|
||||
}
|
||||
|
||||
# Use local backend for initial deployment
|
||||
# For production, consider using remote backend (Azure Storage or Terraform Cloud)
|
||||
# backend "azurerm" {
|
||||
# # Backend configuration will be provided via backend-config file or CLI
|
||||
# # Example: terraform init -backend-config=backend.hcl
|
||||
# }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# Azure Configuration
|
||||
subscription_id = "77677a80-2dea-493d-9867-f1c961b80fb3"
|
||||
resource_group_name = "trusted_ai_demo_rg"
|
||||
location = "germanywestcentral"
|
||||
aks_cluster_name = "trai_k8s_cluster"
|
||||
|
||||
# Application Configuration
|
||||
app_name = "laravel-app"
|
||||
app_namespace = "laravel-app"
|
||||
app_env = "production"
|
||||
app_debug = false
|
||||
app_replicas = 2
|
||||
|
||||
# Docker Image (Update this with your actual image)
|
||||
docker_image = "myregistry.azurecr.io/laravel-app:latest"
|
||||
|
||||
# Laravel Application Key (Generate with: php artisan key:generate --show)
|
||||
app_key = "base64:YOUR_APP_KEY_HERE"
|
||||
|
||||
# PostgreSQL Configuration
|
||||
postgresql_admin_username = "pgadmin"
|
||||
postgresql_admin_password = "ChangeThisPassword123!"
|
||||
postgresql_sku_name = "B_Standard_B1ms"
|
||||
postgresql_storage_mb = 32768
|
||||
postgresql_version = "16"
|
||||
postgresql_backup_retention_days = 7
|
||||
|
||||
# Ingress Configuration
|
||||
ingress_enabled = true
|
||||
ingress_host = "" # Leave empty for IP-based access, or set to your domain
|
||||
|
||||
# SSL/TLS Configuration
|
||||
ssl_enabled = false
|
||||
ssl_issuer_email = "" # Required if ssl_enabled = true
|
||||
|
||||
# Database Restore Configuration
|
||||
db_restore_enabled = false
|
||||
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
|
||||
|
||||
# Resource Limits
|
||||
app_resources_requests_cpu = "100m"
|
||||
app_resources_requests_memory = "256Mi"
|
||||
app_resources_limits_cpu = "500m"
|
||||
app_resources_limits_memory = "512Mi"
|
||||
|
||||
# Alert Configuration
|
||||
alert_email_address = "your-email@example.com"
|
||||
@@ -0,0 +1,180 @@
|
||||
variable "subscription_id" {
|
||||
description = "Azure subscription ID"
|
||||
type = string
|
||||
default = "77677a80-2dea-493d-9867-f1c961b80fb3"
|
||||
}
|
||||
|
||||
variable "resource_group_name" {
|
||||
description = "Name of the existing resource group"
|
||||
type = string
|
||||
default = "trusted_ai_demo_rg"
|
||||
}
|
||||
|
||||
variable "location" {
|
||||
description = "Azure region for resources"
|
||||
type = string
|
||||
default = "germanywestcentral"
|
||||
}
|
||||
|
||||
variable "aks_cluster_name" {
|
||||
description = "Name of the existing AKS cluster"
|
||||
type = string
|
||||
default = "trai_k8s_cluster"
|
||||
}
|
||||
|
||||
variable "app_name" {
|
||||
description = "Name of the application"
|
||||
type = string
|
||||
default = "laravel-app"
|
||||
}
|
||||
|
||||
variable "app_namespace" {
|
||||
description = "Kubernetes namespace for the application"
|
||||
type = string
|
||||
default = "laravel-app"
|
||||
}
|
||||
|
||||
variable "docker_image" {
|
||||
description = "Docker image for the Laravel application"
|
||||
type = string
|
||||
# Format: <registry>/<image>:<tag>
|
||||
# Example: "myregistry.azurecr.io/laravel-app:latest"
|
||||
}
|
||||
|
||||
variable "app_replicas" {
|
||||
description = "Number of application replicas"
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "app_resources_requests_cpu" {
|
||||
description = "CPU resource requests for application pods"
|
||||
type = string
|
||||
default = "100m"
|
||||
}
|
||||
|
||||
variable "app_resources_requests_memory" {
|
||||
description = "Memory resource requests for application pods"
|
||||
type = string
|
||||
default = "256Mi"
|
||||
}
|
||||
|
||||
variable "app_resources_limits_cpu" {
|
||||
description = "CPU resource limits for application pods"
|
||||
type = string
|
||||
default = "500m"
|
||||
}
|
||||
|
||||
variable "app_resources_limits_memory" {
|
||||
description = "Memory resource limits for application pods"
|
||||
type = string
|
||||
default = "512Mi"
|
||||
}
|
||||
|
||||
# PostgreSQL Variables
|
||||
variable "postgresql_admin_username" {
|
||||
description = "Administrator username for PostgreSQL"
|
||||
type = string
|
||||
default = "pgadmin"
|
||||
}
|
||||
|
||||
variable "postgresql_admin_password" {
|
||||
description = "Administrator password for PostgreSQL"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "postgresql_sku_name" {
|
||||
description = "SKU name for PostgreSQL (e.g., B_Standard_B1ms, GP_Standard_D2s_v3)"
|
||||
type = string
|
||||
default = "B_Standard_B1ms"
|
||||
}
|
||||
|
||||
variable "postgresql_storage_mb" {
|
||||
description = "Storage size in MB for PostgreSQL"
|
||||
type = number
|
||||
default = 32768 # 32 GB
|
||||
}
|
||||
|
||||
variable "postgresql_version" {
|
||||
description = "PostgreSQL version"
|
||||
type = string
|
||||
default = "16"
|
||||
}
|
||||
|
||||
variable "postgresql_backup_retention_days" {
|
||||
description = "Backup retention days for PostgreSQL"
|
||||
type = number
|
||||
default = 7
|
||||
}
|
||||
|
||||
# Laravel Application Variables
|
||||
variable "app_key" {
|
||||
description = "Laravel application key (base64 encoded)"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "app_env" {
|
||||
description = "Laravel application environment"
|
||||
type = string
|
||||
default = "production"
|
||||
}
|
||||
|
||||
variable "app_debug" {
|
||||
description = "Enable Laravel debug mode"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "app_url" {
|
||||
description = "Laravel application URL"
|
||||
type = string
|
||||
# Will be set based on ingress if not provided
|
||||
default = ""
|
||||
}
|
||||
|
||||
# Ingress Variables
|
||||
variable "ingress_enabled" {
|
||||
description = "Enable ingress controller"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "ingress_host" {
|
||||
description = "Hostname for the ingress (leave empty for IP-based access)"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "ssl_enabled" {
|
||||
description = "Enable SSL/TLS (requires cert-manager)"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "ssl_issuer_email" {
|
||||
description = "Email for Let's Encrypt certificate issuer"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
# Database Restore Variables
|
||||
variable "db_restore_enabled" {
|
||||
description = "Enable database restore job on deployment"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "db_backup_file_path" {
|
||||
description = "Path to the database backup file (local path that will be uploaded)"
|
||||
type = string
|
||||
default = "../backups/backup_backend_20251203_101741.dump"
|
||||
}
|
||||
|
||||
# Alert Variables
|
||||
variable "alert_email_address" {
|
||||
description = "Email address for alert notifications"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
@@ -9,7 +9,7 @@ test('can connect to backend schema', function () {
|
||||
$count = Transaction::count();
|
||||
|
||||
expect($count)->toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('can read transactions from backend schema', function () {
|
||||
$transaction = Transaction::first();
|
||||
@@ -22,7 +22,7 @@ test('can read transactions from backend schema', function () {
|
||||
} else {
|
||||
expect(true)->toBeTrue(); // No transactions yet
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('can read transaction outputs', function () {
|
||||
$output = TransactionOutput::first();
|
||||
@@ -35,7 +35,7 @@ test('can read transaction outputs', function () {
|
||||
} else {
|
||||
expect(true)->toBeTrue(); // No outputs yet
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('transaction has outputs relationship', function () {
|
||||
$transaction = Transaction::with('outputs')->first();
|
||||
@@ -45,7 +45,7 @@ test('transaction has outputs relationship', function () {
|
||||
} else {
|
||||
expect(true)->toBeTrue(); // No transactions yet
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('can get specific output by key', function () {
|
||||
$transaction = Transaction::with('outputs')->first();
|
||||
@@ -58,7 +58,7 @@ test('can get specific output by key', function () {
|
||||
} else {
|
||||
expect(true)->toBeTrue(); // No transactions with outputs yet
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('can get company info from transaction', function () {
|
||||
$transaction = Transaction::with('outputs')->first();
|
||||
@@ -71,7 +71,7 @@ test('can get company info from transaction', function () {
|
||||
} else {
|
||||
expect(true)->toBeTrue();
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('can get risk assessment from transaction', function () {
|
||||
$transaction = Transaction::with('outputs')->first();
|
||||
@@ -88,7 +88,7 @@ test('can get risk assessment from transaction', function () {
|
||||
} else {
|
||||
expect(true)->toBeTrue();
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('can check if transaction requires review', function () {
|
||||
$transaction = Transaction::with('outputs')->first();
|
||||
@@ -100,7 +100,7 @@ test('can check if transaction requires review', function () {
|
||||
} else {
|
||||
expect(true)->toBeTrue();
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('transaction output keys constants exist', function () {
|
||||
expect(TransactionOutput::KEY_COMPANY_INFO)->toBe('company_info');
|
||||
|
||||
@@ -43,7 +43,7 @@ test('full sync truncates and rebuilds backend_data_pool', function () {
|
||||
->first();
|
||||
|
||||
expect($oldRecord)->toBeNull();
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('incremental sync only adds new records', function () {
|
||||
// Arrange: Perform initial sync
|
||||
@@ -62,7 +62,7 @@ test('incremental sync only adds new records', function () {
|
||||
// Assert: Count should not increase significantly (only if new records in backend)
|
||||
$newCount = DB::table('backend_data_pool')->count();
|
||||
expect($newCount)->toBeGreaterThanOrEqual($initialCount);
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('sync correctly maps backend.transactions and backend.transaction_outputs', function () {
|
||||
// Act: Perform sync
|
||||
@@ -84,7 +84,7 @@ test('sync correctly maps backend.transactions and backend.transaction_outputs',
|
||||
expect($record)->toHaveProperty('content');
|
||||
expect($record)->toHaveProperty('synced_at');
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('sync only includes transactions with status done', function () {
|
||||
// Act: Perform sync
|
||||
@@ -97,7 +97,7 @@ test('sync only includes transactions with status done', function () {
|
||||
->count();
|
||||
|
||||
expect($nonDoneRecords)->toBe(0);
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('sync processes records in batches', function () {
|
||||
// Act: Perform sync with small batch size
|
||||
@@ -107,7 +107,7 @@ test('sync processes records in batches', function () {
|
||||
// Assert: Should complete without errors
|
||||
$recordCount = DB::table('backend_data_pool')->count();
|
||||
expect($recordCount)->toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('getStats returns correct statistics', function () {
|
||||
// Arrange: Perform sync
|
||||
@@ -125,7 +125,7 @@ test('getStats returns correct statistics', function () {
|
||||
expect($stats)->toHaveKey('last_synced_at');
|
||||
|
||||
expect($stats['backend_data_pool_records'])->toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('sync handles empty backend gracefully', function () {
|
||||
// This test assumes backend might be empty or have no 'done' transactions
|
||||
@@ -137,7 +137,7 @@ test('sync handles empty backend gracefully', function () {
|
||||
// Assert: Should complete without errors
|
||||
$recordCount = DB::table('backend_data_pool')->count();
|
||||
expect($recordCount)->toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
})->group('backend-db');
|
||||
|
||||
test('sync orders records by transaction_id and prompt_id', function () {
|
||||
// Act: Perform sync
|
||||
@@ -167,4 +167,4 @@ test('sync orders records by transaction_id and prompt_id', function () {
|
||||
$previousPromptId = $record->prompt_id;
|
||||
}
|
||||
}
|
||||
});
|
||||
})->group('backend-db');
|
||||
Reference in new issue
Block a user