feat: add production container support with health check endpoint
Implements Phase 5 containerization specification: - Add /health endpoint for container monitoring - Create multi-stage Containerfile (Podman/Docker compatible) - Add compose.yaml for orchestration - Add Caddyfile.example for reverse proxy (auto-HTTPS) - Add nginx.conf.example as alternative - Update .env.example with container and RSS feed variables - Add gunicorn WSGI server to requirements.txt Container features: - Multi-stage build for smaller image size - Non-root user (starpunk:1000) - Health check with database connectivity test - Volume mount for data persistence - Resource limits and logging configuration - Security headers and HTTPS configuration examples Health check endpoint: - Tests database connectivity - Verifies filesystem access - Returns JSON with status, version, and environment Following Phase 5 design in docs/designs/phase-5-rss-and-container.md
This commit is contained in:
78
.containerignore
Normal file
78
.containerignore
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Container Build Exclusions
|
||||||
|
# Exclude files not needed in production container image
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
*.so
|
||||||
|
*.egg
|
||||||
|
*.egg-info
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
.pytest_cache
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
.tox
|
||||||
|
.hypothesis
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv
|
||||||
|
env
|
||||||
|
.venv
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Development data
|
||||||
|
data
|
||||||
|
container-data
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Documentation (optional - include if needed for offline docs)
|
||||||
|
docs
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# Tests (not needed in production)
|
||||||
|
tests
|
||||||
|
.pytest_cache
|
||||||
|
|
||||||
|
# Development scripts
|
||||||
|
dev_auth.py
|
||||||
|
test_*.py
|
||||||
|
|
||||||
|
# Container files
|
||||||
|
Containerfile
|
||||||
|
compose.yaml
|
||||||
|
.containerignore
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github
|
||||||
|
.gitlab-ci.yml
|
||||||
|
.travis.yml
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
tmp
|
||||||
|
temp
|
||||||
|
*.tmp
|
||||||
30
.env.example
30
.env.example
@@ -64,6 +64,36 @@ FLASK_DEBUG=1
|
|||||||
# Flask secret key (falls back to SESSION_SECRET if not set)
|
# Flask secret key (falls back to SESSION_SECRET if not set)
|
||||||
FLASK_SECRET_KEY=
|
FLASK_SECRET_KEY=
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RSS FEED CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Maximum number of items in RSS feed (default: 50)
|
||||||
|
FEED_MAX_ITEMS=50
|
||||||
|
|
||||||
|
# Feed cache duration in seconds (default: 300 = 5 minutes)
|
||||||
|
FEED_CACHE_SECONDS=300
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CONTAINER CONFIGURATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Application version (for health check endpoint)
|
||||||
|
VERSION=0.6.0
|
||||||
|
|
||||||
|
# Environment: development or production
|
||||||
|
ENVIRONMENT=production
|
||||||
|
|
||||||
|
# Number of Gunicorn workers (default: 4)
|
||||||
|
# Recommendation: (2 x CPU cores) + 1
|
||||||
|
WORKERS=4
|
||||||
|
|
||||||
|
# Worker timeout in seconds (default: 30)
|
||||||
|
WORKER_TIMEOUT=30
|
||||||
|
|
||||||
|
# Max requests per worker before restart (prevents memory leaks)
|
||||||
|
MAX_REQUESTS=1000
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# DEVELOPMENT OPTIONS
|
# DEVELOPMENT OPTIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
96
Caddyfile.example
Normal file
96
Caddyfile.example
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Caddyfile for StarPunk Reverse Proxy
|
||||||
|
# Caddy automatically handles HTTPS with Let's Encrypt
|
||||||
|
#
|
||||||
|
# Installation:
|
||||||
|
# 1. Install Caddy: https://caddyserver.com/docs/install
|
||||||
|
# 2. Copy this file: cp Caddyfile.example Caddyfile
|
||||||
|
# 3. Update your-domain.com to your actual domain
|
||||||
|
# 4. Run: caddy run --config Caddyfile
|
||||||
|
#
|
||||||
|
# Systemd service:
|
||||||
|
# sudo systemctl enable --now caddy
|
||||||
|
|
||||||
|
# Replace with your actual domain
|
||||||
|
your-domain.com {
|
||||||
|
# Reverse proxy to StarPunk container
|
||||||
|
# Container must be running on localhost:8000
|
||||||
|
reverse_proxy localhost:8000
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/starpunk.log {
|
||||||
|
roll_size 10MiB
|
||||||
|
roll_keep 10
|
||||||
|
}
|
||||||
|
format console
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
header {
|
||||||
|
# Remove server identification
|
||||||
|
-Server
|
||||||
|
|
||||||
|
# HSTS - force HTTPS for 1 year
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
|
||||||
|
# Prevent MIME type sniffing
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
|
||||||
|
# Prevent clickjacking
|
||||||
|
X-Frame-Options "DENY"
|
||||||
|
|
||||||
|
# XSS protection (legacy browsers)
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
|
||||||
|
# Referrer policy
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
|
||||||
|
# Content Security Policy (adjust as needed)
|
||||||
|
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compression
|
||||||
|
encode gzip zstd
|
||||||
|
|
||||||
|
# Static file caching
|
||||||
|
@static {
|
||||||
|
path /static/*
|
||||||
|
}
|
||||||
|
header @static {
|
||||||
|
Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
}
|
||||||
|
|
||||||
|
# RSS feed caching
|
||||||
|
@feed {
|
||||||
|
path /feed.xml
|
||||||
|
}
|
||||||
|
header @feed {
|
||||||
|
Cache-Control "public, max-age=300"
|
||||||
|
}
|
||||||
|
|
||||||
|
# API routes (no caching)
|
||||||
|
@api {
|
||||||
|
path /api/*
|
||||||
|
}
|
||||||
|
header @api {
|
||||||
|
Cache-Control "no-store, no-cache, must-revalidate"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check endpoint (monitoring systems)
|
||||||
|
@health {
|
||||||
|
path /health
|
||||||
|
}
|
||||||
|
header @health {
|
||||||
|
Cache-Control "no-store, no-cache, must-revalidate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional: Redirect www to non-www
|
||||||
|
# www.your-domain.com {
|
||||||
|
# redir https://your-domain.com{uri} permanent
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Optional: Multiple domains
|
||||||
|
# another-domain.com {
|
||||||
|
# reverse_proxy localhost:8000
|
||||||
|
# }
|
||||||
83
Containerfile
Normal file
83
Containerfile
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# Multi-stage build for StarPunk production container
|
||||||
|
# Podman and Docker compatible
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Build Stage - Install dependencies in virtual environment
|
||||||
|
# ============================================================================
|
||||||
|
FROM python:3.11-slim AS builder
|
||||||
|
|
||||||
|
# Install uv for fast dependency installation
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy dependency files
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Create virtual environment and install dependencies
|
||||||
|
# Using uv for fast, reproducible installs
|
||||||
|
RUN uv venv /opt/venv && \
|
||||||
|
. /opt/venv/bin/activate && \
|
||||||
|
uv pip install --no-cache -r requirements.txt
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Runtime Stage - Minimal production image
|
||||||
|
# ============================================================================
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Create non-root user for security
|
||||||
|
# UID/GID 1000 is standard for first user on most systems
|
||||||
|
RUN useradd --uid 1000 --create-home --shell /bin/bash starpunk && \
|
||||||
|
mkdir -p /app /data/notes && \
|
||||||
|
chown -R starpunk:starpunk /app /data
|
||||||
|
|
||||||
|
# Copy virtual environment from builder stage
|
||||||
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH" \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
FLASK_APP=app.py \
|
||||||
|
DATA_PATH=/data \
|
||||||
|
NOTES_PATH=/data/notes \
|
||||||
|
DATABASE_PATH=/data/starpunk.db
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY --chown=starpunk:starpunk . .
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER starpunk
|
||||||
|
|
||||||
|
# Expose application port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
# Uses httpx (already in requirements) to verify app is responding
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||||
|
CMD python3 -c "import httpx; httpx.get('http://localhost:8000/health', timeout=2.0)" || exit 1
|
||||||
|
|
||||||
|
# Run gunicorn WSGI server
|
||||||
|
# - 4 workers for concurrency (adjust based on CPU cores)
|
||||||
|
# - Sync worker class (simple, reliable)
|
||||||
|
# - Worker tmp dir in /dev/shm (shared memory, faster)
|
||||||
|
# - Worker recycling to prevent memory leaks
|
||||||
|
# - 30s timeout for slow requests
|
||||||
|
# - Log to stdout/stderr for container log collection
|
||||||
|
CMD ["gunicorn", \
|
||||||
|
"--bind", "0.0.0.0:8000", \
|
||||||
|
"--workers", "4", \
|
||||||
|
"--worker-class", "sync", \
|
||||||
|
"--worker-tmp-dir", "/dev/shm", \
|
||||||
|
"--max-requests", "1000", \
|
||||||
|
"--max-requests-jitter", "50", \
|
||||||
|
"--timeout", "30", \
|
||||||
|
"--graceful-timeout", "30", \
|
||||||
|
"--access-logfile", "-", \
|
||||||
|
"--error-logfile", "-", \
|
||||||
|
"--log-level", "info", \
|
||||||
|
"app:app"]
|
||||||
107
compose.yaml
Normal file
107
compose.yaml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# StarPunk Container Composition
|
||||||
|
# Podman Compose and Docker Compose compatible
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# podman-compose up -d # Start in background
|
||||||
|
# podman-compose logs -f # Follow logs
|
||||||
|
# podman-compose down # Stop and remove
|
||||||
|
#
|
||||||
|
# Docker:
|
||||||
|
# docker compose up -d
|
||||||
|
# docker compose logs -f
|
||||||
|
# docker compose down
|
||||||
|
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
starpunk:
|
||||||
|
# Container configuration
|
||||||
|
image: starpunk:0.6.0
|
||||||
|
container_name: starpunk
|
||||||
|
|
||||||
|
# Build configuration
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile
|
||||||
|
|
||||||
|
# Restart policy - always restart unless explicitly stopped
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Port mapping
|
||||||
|
# Only expose to localhost for security (reverse proxy handles external access)
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8000:8000"
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
# Load from .env file in project root
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
# Override specific environment variables for container
|
||||||
|
environment:
|
||||||
|
# Flask configuration
|
||||||
|
- FLASK_APP=app.py
|
||||||
|
- FLASK_ENV=production
|
||||||
|
- FLASK_DEBUG=0
|
||||||
|
|
||||||
|
# Data paths (container internal)
|
||||||
|
- DATA_PATH=/data
|
||||||
|
- NOTES_PATH=/data/notes
|
||||||
|
- DATABASE_PATH=/data/starpunk.db
|
||||||
|
|
||||||
|
# Application metadata
|
||||||
|
- VERSION=0.6.0
|
||||||
|
- ENVIRONMENT=production
|
||||||
|
|
||||||
|
# Volume mounts for persistent data
|
||||||
|
# All application data stored in ./container-data on host
|
||||||
|
volumes:
|
||||||
|
- ./container-data:/data:rw
|
||||||
|
# Note: Use :Z suffix for SELinux systems (Fedora, RHEL, CentOS)
|
||||||
|
# - ./container-data:/data:rw,Z
|
||||||
|
|
||||||
|
# Health check configuration
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python3", "-c", "import httpx; httpx.get('http://localhost:8000/health', timeout=2.0)"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
# Resource limits (optional but recommended)
|
||||||
|
# Adjust based on your server capacity
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '1.0'
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
cpus: '0.25'
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
# Rotate logs to prevent disk space issues
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# Network configuration
|
||||||
|
networks:
|
||||||
|
- starpunk-net
|
||||||
|
|
||||||
|
# Network definition
|
||||||
|
networks:
|
||||||
|
starpunk-net:
|
||||||
|
driver: bridge
|
||||||
|
# Optional: specify subnet for predictable IPs
|
||||||
|
# ipam:
|
||||||
|
# config:
|
||||||
|
# - subnet: 172.20.0.0/16
|
||||||
|
|
||||||
|
# Optional: Named volumes for data persistence
|
||||||
|
# Uncomment if you prefer named volumes over bind mounts
|
||||||
|
# volumes:
|
||||||
|
# starpunk-data:
|
||||||
|
# driver: local
|
||||||
188
nginx.conf.example
Normal file
188
nginx.conf.example
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Nginx Configuration for StarPunk
|
||||||
|
# Alternative to Caddy for reverse proxy
|
||||||
|
#
|
||||||
|
# Installation:
|
||||||
|
# 1. Install Nginx: sudo apt install nginx
|
||||||
|
# 2. Install Certbot: sudo apt install certbot python3-certbot-nginx
|
||||||
|
# 3. Copy this file: sudo cp nginx.conf.example /etc/nginx/sites-available/starpunk
|
||||||
|
# 4. Update your-domain.com to your actual domain
|
||||||
|
# 5. Create symlink: sudo ln -s /etc/nginx/sites-available/starpunk /etc/nginx/sites-enabled/
|
||||||
|
# 6. Test config: sudo nginx -t
|
||||||
|
# 7. Get SSL cert: sudo certbot --nginx -d your-domain.com
|
||||||
|
# 8. Reload: sudo systemctl reload nginx
|
||||||
|
|
||||||
|
# Upstream definition for StarPunk container
|
||||||
|
upstream starpunk {
|
||||||
|
server localhost:8000;
|
||||||
|
keepalive 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP server - redirect to HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# ACME challenge for Let's Encrypt
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect all other HTTP to HTTPS
|
||||||
|
location / {
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS server
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# SSL certificates (managed by certbot)
|
||||||
|
# Update paths after running certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||||||
|
|
||||||
|
# SSL configuration (Mozilla Intermediate)
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||||
|
ssl_prefer_server_ciphers off;
|
||||||
|
|
||||||
|
# SSL session cache
|
||||||
|
ssl_session_timeout 1d;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
|
||||||
|
# OCSP stapling
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
ssl_trusted_certificate /etc/letsencrypt/live/your-domain.com/chain.pem;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "DENY" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always;
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
access_log /var/log/nginx/starpunk-access.log;
|
||||||
|
error_log /var/log/nginx/starpunk-error.log;
|
||||||
|
|
||||||
|
# Max upload size (for future media uploads)
|
||||||
|
client_max_body_size 10M;
|
||||||
|
|
||||||
|
# Root location - proxy to StarPunk
|
||||||
|
location / {
|
||||||
|
# Proxy to upstream
|
||||||
|
proxy_pass http://starpunk;
|
||||||
|
|
||||||
|
# Proxy headers
|
||||||
|
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_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
# WebSocket support (for future features)
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
proxy_connect_timeout 30s;
|
||||||
|
proxy_send_timeout 30s;
|
||||||
|
proxy_read_timeout 30s;
|
||||||
|
|
||||||
|
# Buffering
|
||||||
|
proxy_buffering on;
|
||||||
|
proxy_buffer_size 4k;
|
||||||
|
proxy_buffers 8 4k;
|
||||||
|
proxy_busy_buffers_size 8k;
|
||||||
|
|
||||||
|
# No caching for dynamic content
|
||||||
|
add_header Cache-Control "no-cache, private" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static files - aggressive caching
|
||||||
|
location /static/ {
|
||||||
|
proxy_pass http://starpunk;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
|
# Long-term caching for static assets
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||||
|
|
||||||
|
# Compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_types text/css application/javascript image/svg+xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
# RSS feed - short-term caching
|
||||||
|
location /feed.xml {
|
||||||
|
proxy_pass http://starpunk;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
|
# Cache for 5 minutes
|
||||||
|
add_header Cache-Control "public, max-age=300";
|
||||||
|
|
||||||
|
# Compression
|
||||||
|
gzip on;
|
||||||
|
gzip_types application/rss+xml application/xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health check endpoint - no caching
|
||||||
|
location /health {
|
||||||
|
proxy_pass http://starpunk;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
|
# No caching
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
|
||||||
|
|
||||||
|
# Allow monitoring systems access
|
||||||
|
# Optional: restrict to specific IPs
|
||||||
|
# allow 10.0.0.0/8; # Internal network
|
||||||
|
# deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admin routes - no caching, security
|
||||||
|
location /admin/ {
|
||||||
|
proxy_pass http://starpunk;
|
||||||
|
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;
|
||||||
|
|
||||||
|
# No caching for admin
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
|
||||||
|
|
||||||
|
# Optional: IP whitelist for admin
|
||||||
|
# allow 1.2.3.4; # Your IP
|
||||||
|
# deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deny access to hidden files
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
access_log off;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional: Redirect www to non-www
|
||||||
|
# server {
|
||||||
|
# listen 80;
|
||||||
|
# listen [::]:80;
|
||||||
|
# listen 443 ssl http2;
|
||||||
|
# listen [::]:443 ssl http2;
|
||||||
|
# server_name www.your-domain.com;
|
||||||
|
#
|
||||||
|
# ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||||||
|
# ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||||||
|
#
|
||||||
|
# return 301 https://your-domain.com$request_uri;
|
||||||
|
# }
|
||||||
@@ -4,6 +4,9 @@
|
|||||||
# Web Framework
|
# Web Framework
|
||||||
Flask==3.0.*
|
Flask==3.0.*
|
||||||
|
|
||||||
|
# WSGI Server (Production)
|
||||||
|
gunicorn==21.2.*
|
||||||
|
|
||||||
# Content Processing
|
# Content Processing
|
||||||
markdown==3.5.*
|
markdown==3.5.*
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,54 @@ def create_app(config=None):
|
|||||||
return {"error": "Internal server error"}, 500
|
return {"error": "Internal server error"}, 500
|
||||||
return render_template("500.html"), 500
|
return render_template("500.html"), 500
|
||||||
|
|
||||||
|
# Health check endpoint for containers and monitoring
|
||||||
|
@app.route("/health")
|
||||||
|
def health_check():
|
||||||
|
"""
|
||||||
|
Health check endpoint for containers and monitoring
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON with status and basic info
|
||||||
|
|
||||||
|
Response codes:
|
||||||
|
200: Application healthy
|
||||||
|
500: Application unhealthy
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
- Database connectivity
|
||||||
|
- File system access
|
||||||
|
- Basic application state
|
||||||
|
"""
|
||||||
|
from flask import jsonify
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check database connectivity
|
||||||
|
from starpunk.database import get_db
|
||||||
|
|
||||||
|
db = get_db(app)
|
||||||
|
db.execute("SELECT 1").fetchone()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
# Check filesystem access
|
||||||
|
data_path = app.config.get("DATA_PATH", "data")
|
||||||
|
if not os.path.exists(data_path):
|
||||||
|
raise Exception("Data path not accessible")
|
||||||
|
|
||||||
|
return (
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"version": app.config.get("VERSION", __version__),
|
||||||
|
"environment": app.config.get("ENV", "unknown"),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"status": "unhealthy", "error": str(e)}), 500
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user