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=
|
||||
|
||||
# =============================================================================
|
||||
# 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
|
||||
# =============================================================================
|
||||
|
||||
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
|
||||
Flask==3.0.*
|
||||
|
||||
# WSGI Server (Production)
|
||||
gunicorn==21.2.*
|
||||
|
||||
# Content Processing
|
||||
markdown==3.5.*
|
||||
|
||||
|
||||
@@ -52,6 +52,54 @@ def create_app(config=None):
|
||||
return {"error": "Internal server error"}, 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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user