Files
timemaster/setup_server.sh
T
patrickandClaude Sonnet 5 5ba5a99e02
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s
docs: README erstellt, Debian statt Ubuntu korrigiert
README.md war leer (0 Zeilen). Projektübersicht, Setup, Deploy-Workflow
ergänzt. Server läuft tatsächlich auf Debian 13, nicht Ubuntu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
2026-09-03 00:03:06 +02:00

187 lines
8.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ============================================================
# TimeMaster Nativer Server-Setup (Debian 13)
# Führe dieses Script als root oder mit sudo aus
# ============================================================
set -e
echo "==> [1/7] System-Pakete aktualisieren"
apt-get update && apt-get upgrade -y
echo "==> [2/7] Abhängigkeiten installieren"
apt-get install -y \
python3 python3-venv python3-dev python3-pip \
postgresql postgresql-contrib \
redis-server \
nginx \
git curl build-essential libpq-dev
echo "==> [3/7] PostgreSQL einrichten"
systemctl enable postgresql && systemctl start postgresql
# Datenbank + User anlegen (Produktiv-DB + Test-DB)
sudo -u postgres psql <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'timemaster') THEN
CREATE ROLE timemaster LOGIN PASSWORD 'timemaster_secret_change_me';
END IF;
END
\$\$;
SELECT 'CREATE DATABASE timemaster_db OWNER timemaster'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'timemaster_db')\gexec
SELECT 'CREATE DATABASE timemaster_test OWNER timemaster'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'timemaster_test')\gexec
GRANT ALL PRIVILEGES ON DATABASE timemaster_db TO timemaster;
GRANT ALL PRIVILEGES ON DATABASE timemaster_test TO timemaster;
SQL
echo "==> [4/7] Redis einrichten"
systemctl enable redis-server && systemctl start redis-server
echo "==> [5/7] Python venv + Abhängigkeiten"
cd /opt/timemaster/backend
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
echo "==> [6/7] Alembic Migrations ausführen"
alembic upgrade head
echo "==> [7/7] nginx für Frontend konfigurieren"
mkdir -p /opt/timemaster/frontend/dist /opt/timemaster/backend/static
cat > /etc/nginx/sites-available/timemaster << 'NGINX'
# HTTP-only Konfiguration (SSL/HTTPS noch nicht eingerichtet)
# Sobald ein TLS-Zertifikat vorhanden ist:
# 1. Listen-Block auf 443 ssl http2 erweitern
# 2. ssl_certificate / ssl_certificate_key einkommentieren
# 3. HSTS-Header hinzufügen
# 4. HTTP->HTTPS-Redirect aktivieren
server {
listen 80;
server_name _;
client_max_body_size 20M;
# API Backend
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
}
# FastAPI Docs (nur in dev aktiv docs_url ist None in Production)
location /docs {
proxy_pass http://127.0.0.1:8000/docs;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
}
location /redoc {
proxy_pass http://127.0.0.1:8000/redoc;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
}
location /openapi.json {
proxy_pass http://127.0.0.1:8000/openapi.json;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
}
# React Frontend (statische Dateien)
# HINWEIS: nginx-Regel: add_header in einem location-Block ueberschreibt
# alle add_header-Direktiven des parent server-Blocks. Daher Security-Header
# in jede location wiederholen.
location / {
root /opt/timemaster/frontend/dist;
index index.html;
try_files $uri $uri/ /index.html;
expires 1d;
add_header Cache-Control "public, must-revalidate";
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
}
# Swagger/ReDoc-Assets (app/static/swagger-ui im Repo) + Uploads
location /static/ {
alias /opt/timemaster/backend/static/;
expires 7d;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" always;
}
}
NGINX
ln -sf /etc/nginx/sites-available/timemaster /etc/nginx/sites-enabled/timemaster
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl enable nginx && systemctl reload nginx
echo ""
echo "==> [Optional] Tests ausführen"
echo " python -m pytest tests/ -q"
echo ""
echo "✓ Setup abgeschlossen!"
echo " Backend: sudo systemctl start timemaster"
echo " Frontend: http://$(hostname -I | awk '{print $1}')/"
echo " API-Docs: http://$(hostname -I | awk '{print $1}')/docs (und /redoc)"
echo ""
echo ""
echo "Frontend deployen (nach lokalem Build):"
echo " npm run build (im frontend/ Verzeichnis)"
echo " rsync -avz dist/ root@SERVER:/opt/timemaster/frontend/dist/"
echo ""
echo "Hinweise:"
echo " - passlib wurde durch direktes bcrypt ersetzt (kompatibel mit bcrypt >= 4.0)"
echo " - pytest nutzt PostgreSQL test-DB (timemaster_test) kein SQLite"
echo " - pytest.ini: asyncio_mode=auto, loop_scope=session für alle Fixtures"
echo " - openpyxl >= 3.1 für XLSX-Export (Dashboard/Reports)"
echo ""
echo "Verfügbare API-Endpunkte:"
echo " GET /api/v1/dashboard/me Mitarbeiter-Dashboard"
echo " GET /api/v1/dashboard/team Team-Dashboard (Manager+)"
echo " GET /api/v1/dashboard/company Unternehmens-Dashboard (Admin)"
echo " GET /api/v1/reports/time Zeiterfassungsbericht"
echo " GET /api/v1/reports/absences Abwesenheitsbericht"
echo " GET /api/v1/reports/overtime Überstundenbericht"
echo " GET /api/v1/reports/time/export?format=csv|xlsx"
echo " GET /api/v1/reports/absences/export?format=csv|xlsx"
echo " GET /api/v1/reports/overtime/export?format=csv|xlsx"