added docker setup
This commit is contained in:
parent
dd4d57fa1b
commit
89cf92eaf5
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
*/node_modules
|
||||||
|
|
||||||
|
# Build output (will be built in container)
|
||||||
|
frontend/dist
|
||||||
|
backend/dist
|
||||||
|
|
||||||
|
# Development files
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.md
|
||||||
|
!docker/README.md
|
||||||
|
|
||||||
|
# Environment files (use Docker environment instead)
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
backend/.env
|
||||||
|
frontend/.env
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
*.test.ts
|
||||||
|
*.test.tsx
|
||||||
|
*.spec.ts
|
||||||
|
*.spec.tsx
|
||||||
|
__tests__
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Docker files in wrong location
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
|
# Uploads and backups (mounted as volumes)
|
||||||
|
uploads
|
||||||
|
backups
|
||||||
|
backend/uploads
|
||||||
|
backend/backups
|
||||||
|
|
||||||
|
# Prisma migrations (included, but not dev db)
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
|
@ -66,6 +66,20 @@ app.use('/api', contractHistory_routes_js_1.default);
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
});
|
});
|
||||||
|
// Production: Serve frontend static files
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
const publicPath = path_1.default.join(process.cwd(), 'public');
|
||||||
|
// Serve static files
|
||||||
|
app.use(express_1.default.static(publicPath));
|
||||||
|
// SPA fallback: serve index.html for all non-API routes
|
||||||
|
app.get('*', (req, res, next) => {
|
||||||
|
// Skip API routes
|
||||||
|
if (req.path.startsWith('/api')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
res.sendFile(path_1.default.join(publicPath, 'index.html'));
|
||||||
|
});
|
||||||
|
}
|
||||||
// Error handling
|
// Error handling
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
||||||
|
|
@ -69,6 +69,23 @@ app.get('/api/health', (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Production: Serve frontend static files
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
const publicPath = path.join(process.cwd(), 'public');
|
||||||
|
|
||||||
|
// Serve static files
|
||||||
|
app.use(express.static(publicPath));
|
||||||
|
|
||||||
|
// SPA fallback: serve index.html for all non-API routes
|
||||||
|
app.get('*', (req, res, next) => {
|
||||||
|
// Skip API routes
|
||||||
|
if (req.path.startsWith('/api')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
res.sendFile(path.join(publicPath, 'index.html'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Error handling
|
// Error handling
|
||||||
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# OpenCRM Docker Environment
|
||||||
|
# ============================
|
||||||
|
# Copy this file to .env and adjust the values
|
||||||
|
|
||||||
|
# Domain (for Caddy SSL certificate)
|
||||||
|
# Use your actual domain, e.g., crm.example.com
|
||||||
|
DOMAIN=localhost
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DB_ROOT_PASSWORD=change-this-root-password
|
||||||
|
DB_NAME=opencrm
|
||||||
|
DB_USER=opencrm
|
||||||
|
DB_PASSWORD=change-this-password
|
||||||
|
|
||||||
|
# JWT Authentication
|
||||||
|
# Generate with: openssl rand -base64 32
|
||||||
|
JWT_SECRET=change-this-to-a-secure-random-string
|
||||||
|
JWT_EXPIRES_IN=7d
|
||||||
|
|
||||||
|
# Encryption Key (for portal credentials)
|
||||||
|
# Generate with: openssl rand -hex 32
|
||||||
|
ENCRYPTION_KEY=change-this-to-a-32-byte-hex-key
|
||||||
|
|
||||||
|
# First Install: Set to "true" to seed database on first startup
|
||||||
|
# After first successful start, set back to "false"
|
||||||
|
RUN_SEED=true
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# OpenCRM Caddyfile
|
||||||
|
# ===================
|
||||||
|
# Replace {$DOMAIN} with your actual domain or use environment variable
|
||||||
|
# For local development without SSL, use localhost:80
|
||||||
|
|
||||||
|
{$DOMAIN:localhost} {
|
||||||
|
# Reverse proxy to OpenCRM app
|
||||||
|
reverse_proxy app:3001
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
header {
|
||||||
|
# Clickjacking protection
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
# XSS protection
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
# Referrer policy
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
# Handle file uploads (increase body limit)
|
||||||
|
request_body {
|
||||||
|
max_size 50MB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
# ===================================
|
||||||
|
# Stage 1: Build Frontend
|
||||||
|
# ===================================
|
||||||
|
FROM node:20-alpine AS frontend-builder
|
||||||
|
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy frontend source
|
||||||
|
COPY frontend/ ./
|
||||||
|
|
||||||
|
# Build frontend
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 2: Build Backend
|
||||||
|
# ===================================
|
||||||
|
FROM node:20-alpine AS backend-builder
|
||||||
|
|
||||||
|
WORKDIR /app/backend
|
||||||
|
|
||||||
|
# Install OpenSSL for Prisma
|
||||||
|
RUN apk add --no-cache openssl
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies (including dev for build)
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy backend source
|
||||||
|
COPY backend/ ./
|
||||||
|
|
||||||
|
# Generate Prisma client
|
||||||
|
RUN npx prisma generate
|
||||||
|
|
||||||
|
# Build TypeScript
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 3: Production Runtime
|
||||||
|
# ===================================
|
||||||
|
FROM node:20-alpine AS production
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install OpenSSL for Prisma runtime and netcat for DB health check
|
||||||
|
RUN apk add --no-cache openssl netcat-openbsd
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
|
adduser -S opencrm -u 1001 -G nodejs
|
||||||
|
|
||||||
|
# Copy backend package files
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
|
||||||
|
# Install production dependencies + tsx for seed script
|
||||||
|
RUN npm ci --omit=dev && npm install tsx
|
||||||
|
|
||||||
|
# Copy Prisma schema and generate client
|
||||||
|
COPY backend/prisma ./prisma
|
||||||
|
RUN npx prisma generate
|
||||||
|
|
||||||
|
# Copy built backend
|
||||||
|
COPY --from=backend-builder /app/backend/dist ./dist
|
||||||
|
|
||||||
|
# Copy built frontend to public directory
|
||||||
|
COPY --from=frontend-builder /app/frontend/dist ./public
|
||||||
|
|
||||||
|
# Copy seed file for factory reset
|
||||||
|
COPY backend/prisma/seed.ts ./prisma/
|
||||||
|
|
||||||
|
# Copy entrypoint script
|
||||||
|
COPY docker/entrypoint.sh ./entrypoint.sh
|
||||||
|
|
||||||
|
# Create directories for uploads and backups
|
||||||
|
RUN mkdir -p uploads backups && \
|
||||||
|
chmod +x entrypoint.sh && \
|
||||||
|
chown -R opencrm:nodejs /app
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER opencrm
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/api/health || exit 1
|
||||||
|
|
||||||
|
# Start the application with entrypoint
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
# OpenCRM Docker Deployment
|
||||||
|
|
||||||
|
## Schnellstart
|
||||||
|
|
||||||
|
1. **Umgebungsvariablen konfigurieren:**
|
||||||
|
```bash
|
||||||
|
cd docker
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env # Sichere Werte setzen
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Container starten (erster Start mit RUN_SEED=true):**
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Beim ersten Start wird automatisch:
|
||||||
|
- Auf die Datenbank gewartet
|
||||||
|
- Migrationen ausgeführt
|
||||||
|
- Seed-Daten geladen (wenn `RUN_SEED=true`)
|
||||||
|
|
||||||
|
3. **Nach erfolgreicher Installation:**
|
||||||
|
```bash
|
||||||
|
# RUN_SEED in .env auf false setzen
|
||||||
|
sed -i 's/RUN_SEED=true/RUN_SEED=false/' .env
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Anwendung aufrufen:**
|
||||||
|
- Mit Domain: `https://your-domain.com`
|
||||||
|
- Lokal: `http://localhost`
|
||||||
|
|
||||||
|
5. **Login:**
|
||||||
|
- E-Mail: `admin@admin.com`
|
||||||
|
- Passwort: `admin`
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐
|
||||||
|
│ Caddy │
|
||||||
|
│ (SSL/TLS) │
|
||||||
|
│ :80/:443 │
|
||||||
|
└──────┬──────┘
|
||||||
|
│
|
||||||
|
┌──────▼──────┐
|
||||||
|
│ OpenCRM │
|
||||||
|
│ (Node.js) │
|
||||||
|
│ :3001 │
|
||||||
|
└──────┬──────┘
|
||||||
|
│
|
||||||
|
┌──────▼──────┐
|
||||||
|
│ MariaDB │
|
||||||
|
│ :3306 │
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Befehle
|
||||||
|
|
||||||
|
### Container verwalten
|
||||||
|
```bash
|
||||||
|
# Starten
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Stoppen
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# Logs anzeigen
|
||||||
|
docker compose logs -f app
|
||||||
|
|
||||||
|
# Neustart
|
||||||
|
docker compose restart app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Datenbank
|
||||||
|
```bash
|
||||||
|
# Migration ausführen
|
||||||
|
docker compose exec app npx prisma migrate deploy
|
||||||
|
|
||||||
|
# Seed-Daten laden
|
||||||
|
docker compose exec app npx tsx prisma/seed.ts
|
||||||
|
|
||||||
|
# Prisma Studio (Datenbank-UI)
|
||||||
|
docker compose exec app npx prisma studio
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup & Restore
|
||||||
|
```bash
|
||||||
|
# Backup-Verzeichnis ist unter /app/backups gemountet
|
||||||
|
# Backups werden über die Anwendung erstellt/wiederhergestellt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update
|
||||||
|
```bash
|
||||||
|
# Image neu bauen und Container aktualisieren
|
||||||
|
docker compose build --no-cache
|
||||||
|
docker compose up -d
|
||||||
|
# Migrationen werden automatisch beim Start ausgeführt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Volumes
|
||||||
|
|
||||||
|
| Volume | Beschreibung |
|
||||||
|
|--------|--------------|
|
||||||
|
| `mariadb_data` | Datenbank-Dateien |
|
||||||
|
| `uploads_data` | Hochgeladene Dokumente |
|
||||||
|
| `backups_data` | Backup-Dateien |
|
||||||
|
| `caddy_data` | SSL-Zertifikate |
|
||||||
|
| `caddy_config` | Caddy-Konfiguration |
|
||||||
|
|
||||||
|
## SSL-Zertifikat
|
||||||
|
|
||||||
|
Caddy holt automatisch ein Let's Encrypt Zertifikat wenn:
|
||||||
|
- Die Domain in `.env` korrekt gesetzt ist
|
||||||
|
- Port 80 und 443 von außen erreichbar sind
|
||||||
|
- DNS auf den Server zeigt
|
||||||
|
|
||||||
|
Für lokale Entwicklung mit `DOMAIN=localhost` wird ein selbstsigniertes Zertifikat verwendet.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Container startet nicht
|
||||||
|
```bash
|
||||||
|
docker compose logs app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Datenbank-Verbindung fehlgeschlagen
|
||||||
|
```bash
|
||||||
|
# Warten bis MariaDB bereit ist
|
||||||
|
docker compose logs db
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSL-Zertifikat Probleme
|
||||||
|
```bash
|
||||||
|
docker compose logs caddy
|
||||||
|
# Caddy-Daten zurücksetzen
|
||||||
|
docker compose down
|
||||||
|
docker volume rm opencrm_caddy_data opencrm_caddy_config
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# ===================================
|
||||||
|
# MariaDB Database
|
||||||
|
# ===================================
|
||||||
|
db:
|
||||||
|
image: mariadb:10.11
|
||||||
|
container_name: opencrm-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootpassword}
|
||||||
|
MYSQL_DATABASE: ${DB_NAME:-opencrm}
|
||||||
|
MYSQL_USER: ${DB_USER:-opencrm}
|
||||||
|
MYSQL_PASSWORD: ${DB_PASSWORD:-opencrm123}
|
||||||
|
volumes:
|
||||||
|
- mariadb_data:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
- opencrm-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
start_period: 10s
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# OpenCRM Application
|
||||||
|
# ===================================
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: docker/Dockerfile
|
||||||
|
container_name: opencrm-app
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: 3001
|
||||||
|
DATABASE_URL: mysql://${DB_USER:-opencrm}:${DB_PASSWORD:-opencrm123}@db:3306/${DB_NAME:-opencrm}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required}
|
||||||
|
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-7d}
|
||||||
|
ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}
|
||||||
|
RUN_SEED: ${RUN_SEED:-false}
|
||||||
|
volumes:
|
||||||
|
- uploads_data:/app/uploads
|
||||||
|
- backups_data:/app/backups
|
||||||
|
networks:
|
||||||
|
- opencrm-network
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/api/health"]
|
||||||
|
start_period: 60s
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Caddy Reverse Proxy (with auto SSL)
|
||||||
|
# ===================================
|
||||||
|
caddy:
|
||||||
|
image: caddy:2-alpine
|
||||||
|
container_name: opencrm-caddy
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy_data:/data
|
||||||
|
- caddy_config:/config
|
||||||
|
networks:
|
||||||
|
- opencrm-network
|
||||||
|
depends_on:
|
||||||
|
app:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
opencrm-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mariadb_data:
|
||||||
|
uploads_data:
|
||||||
|
backups_data:
|
||||||
|
caddy_data:
|
||||||
|
caddy_config:
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== OpenCRM Startup ==="
|
||||||
|
|
||||||
|
# Wait for database to be ready
|
||||||
|
echo "Waiting for database connection..."
|
||||||
|
MAX_RETRIES=30
|
||||||
|
RETRY_COUNT=0
|
||||||
|
|
||||||
|
while ! nc -z db 3306 2>/dev/null; do
|
||||||
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||||
|
if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then
|
||||||
|
echo "Error: Database not available after $MAX_RETRIES attempts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Attempt $RETRY_COUNT/$MAX_RETRIES - waiting..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
echo "Database is ready!"
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
echo "Running database migrations..."
|
||||||
|
npx prisma migrate deploy
|
||||||
|
|
||||||
|
# Seed database if RUN_SEED is set (first install)
|
||||||
|
if [ "$RUN_SEED" = "true" ]; then
|
||||||
|
echo "Seeding database..."
|
||||||
|
npx tsx prisma/seed.ts
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
echo "Starting OpenCRM server..."
|
||||||
|
exec node dist/index.js
|
||||||
Loading…
Reference in New Issue