Upload files to "/"
This commit is contained in:
862
memelord.yaml
Normal file
862
memelord.yaml
Normal file
@@ -0,0 +1,862 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: memelord
|
||||
namespace: memelord-sala
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: memelord
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: memelord
|
||||
spec:
|
||||
volumes:
|
||||
- name: logs
|
||||
emptyDir: {}
|
||||
- name: admin
|
||||
emptyDir: {}
|
||||
- name: settings
|
||||
configMap:
|
||||
name: memelord-settings
|
||||
|
||||
containers:
|
||||
- name: memelord
|
||||
image: ghcr.io/l4rm4nd/memelord:latest
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
runAsUser: 33
|
||||
runAsGroup: 33
|
||||
volumeMounts:
|
||||
- name: logs
|
||||
mountPath: /opt/app/logs
|
||||
- name: admin
|
||||
mountPath: /opt/app/myapp/static/admin
|
||||
- name: settings
|
||||
mountPath: /opt/app/myproject/settings.py
|
||||
subPath: settings.py
|
||||
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
env:
|
||||
# PostgreSQL credentials from secret
|
||||
- name: DB_ENGINE
|
||||
value: postgres
|
||||
- name: DOMAIN
|
||||
value: memelord-sala.ee-lte-1.codemowers.io
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: memelord-sala-database
|
||||
key: username
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: memelord-sala-database
|
||||
key: password
|
||||
- name: POSTGRES_DB
|
||||
value: memelord-sala
|
||||
- name: POSTGRES_HOST
|
||||
value: memelord-sala-database-rw.memelord-sala.svc.cluster.local
|
||||
- name: POSTGRES_PORT
|
||||
value: "5432"
|
||||
|
||||
# Redis password
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: memelord-sala-redis
|
||||
key: redis-password
|
||||
|
||||
- name: STORAGE_BACKEND
|
||||
value: s3
|
||||
- name: AWS_S3_ENDPOINT_URL
|
||||
value: https://minio.ee-lte-1.codemowers.io/
|
||||
- name: AWS_S3_REGION_NAME
|
||||
value: ee-lte-1
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: memelord-sala-bucket
|
||||
key: accessKey
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: memelord-sala-bucket
|
||||
key: secretKey
|
||||
- name: AWS_STORAGE_BUCKET_NAME
|
||||
value: memelord-sala
|
||||
|
||||
- name: OIDC_ENABLED
|
||||
value: "True"
|
||||
- name: OIDC_RP_SIGN_ALGO
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: oidc-client-memelord-sala-owner-secrets
|
||||
key: OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG
|
||||
- name: OIDC_OP_JWKS_ENDPOINT
|
||||
value: https://auth.ee-lte-1.codemowers.io/jwks
|
||||
- name: OIDC_RP_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: oidc-client-memelord-sala-owner-secrets
|
||||
key: OIDC_CLIENT_ID
|
||||
- name: OIDC_RP_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: oidc-client-memelord-sala-owner-secrets
|
||||
key: OIDC_CLIENT_SECRET
|
||||
- name: OIDC_OP_AUTHORIZATION_ENDPOINT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: oidc-client-memelord-sala-owner-secrets
|
||||
key: OIDC_IDP_AUTH_URI
|
||||
- name: OIDC_OP_TOKEN_ENDPOINT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: oidc-client-memelord-sala-owner-secrets
|
||||
key: OIDC_IDP_TOKEN_URI
|
||||
- name: OIDC_OP_USER_ENDPOINT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: oidc-client-memelord-sala-owner-secrets
|
||||
key: OIDC_IDP_USERINFO_URI
|
||||
- name: SESSION_COOKIE_AGE
|
||||
value: "30"
|
||||
- name: SESSION_EXPIRE_AT_BROWSER_CLOSE
|
||||
value: "False"
|
||||
- name: TZ
|
||||
value: "Europe/Berlin"
|
||||
|
||||
---
|
||||
apiVersion: codemowers.cloud/v1beta1
|
||||
kind: OIDCClient
|
||||
metadata:
|
||||
name: memelord-sala
|
||||
spec:
|
||||
displayName: Memelord sala
|
||||
uri: https://memelord-sala.ee-lte-1.codemowers.io/
|
||||
redirectUris:
|
||||
- https://memelord-sala.ee-lte-1.codemowers.io/oidc/callback/
|
||||
grantTypes:
|
||||
- authorization_code
|
||||
- refresh_token
|
||||
responseTypes:
|
||||
- code
|
||||
availableScopes:
|
||||
- openid
|
||||
- profile
|
||||
pkce: false
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: memelord-settings
|
||||
namespace: memelord-sala
|
||||
data:
|
||||
settings.py: |
|
||||
"""
|
||||
Django settings for myproject project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.2.16.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import pytz
|
||||
import secrets
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from csp.constants import NONE, SELF, UNSAFE_INLINE, UNSAFE_EVAL
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# get debug modus from env
|
||||
DEBUG = os.environ.get('DEBUG', 'False').lower() in ['true']
|
||||
|
||||
# get container version from env
|
||||
VERSION = escape(os.environ.get("VERSION", ''))
|
||||
|
||||
# Enable/disable public meme feed feature (must be after import os)
|
||||
ENABLE_PUBLIC_FEED = os.environ.get('ENABLE_PUBLIC_FEED', 'True').lower() in ['true', '1', 'yes']
|
||||
|
||||
# auto-generate a secure secret key or use from env variable
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", secrets.token_urlsafe(32))
|
||||
|
||||
# define allowed hosts and trusted domains via env variables
|
||||
DOMAIN = ""
|
||||
ALLOWED_HOSTS = ["127.0.0.1"]
|
||||
CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1:8000"]
|
||||
|
||||
DOMAIN = str(os.environ.get("DOMAIN", "localhost"))
|
||||
TRUSTED_PORT = str(os.environ.get("PORT", "8000"))
|
||||
|
||||
if DOMAIN:
|
||||
domains = DOMAIN.split(',')
|
||||
for domain in domains:
|
||||
domain = domain.strip().rstrip('/').replace('http://', '').replace('https://', '')
|
||||
if domain:
|
||||
ALLOWED_HOSTS.append(domain)
|
||||
TRUSTED_USER_DOMAIN_HTTP = f"http://{domain}:{TRUSTED_PORT}"
|
||||
TRUSTED_USER_DOMAIN_HTTP_80_DEFAULT = f"http://{domain}"
|
||||
TRUSTED_USER_DOMAIN_HTTPS = f"https://{domain}:{TRUSTED_PORT}"
|
||||
TRUSTED_USER_DOMAIN_HTTPS_443_DEFAULT = f"https://{domain}"
|
||||
CSRF_TRUSTED_ORIGINS.extend([TRUSTED_USER_DOMAIN_HTTP, TRUSTED_USER_DOMAIN_HTTPS, TRUSTED_USER_DOMAIN_HTTP_80_DEFAULT, TRUSTED_USER_DOMAIN_HTTPS_443_DEFAULT])
|
||||
|
||||
#Session Management
|
||||
CSRF_COOKIE_HTTPONLY = True
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = os.environ.get('SESSION_EXPIRE_AT_BROWSER_CLOSE', 'True').lower() in ['true']
|
||||
SESSION_COOKIE_AGE = int(os.environ.get('SESSION_COOKIE_AGE', '30')) * 60
|
||||
SESSION_COOKIE_NAME = 'Session'
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
|
||||
# =============================================================================
|
||||
# REDIS CACHE CONFIGURATION (for sessions and general caching)
|
||||
# =============================================================================
|
||||
# If REDIS_HOST is not set, sessions will use database (backward compatible)
|
||||
REDIS_HOST = os.environ.get("REDIS_HOST", "")
|
||||
REDIS_PORT = os.environ.get("REDIS_PORT", "6379")
|
||||
REDIS_DB = os.environ.get("REDIS_DB", "0")
|
||||
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", "")
|
||||
|
||||
if REDIS_HOST:
|
||||
# Redis is available - use it for caching and sessions
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}",
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
"PASSWORD": REDIS_PASSWORD if REDIS_PASSWORD else None,
|
||||
"SOCKET_CONNECT_TIMEOUT": 5, # seconds
|
||||
"SOCKET_TIMEOUT": 5, # seconds
|
||||
"RETRY_ON_TIMEOUT": True,
|
||||
"CONNECTION_POOL_KWARGS": {
|
||||
"max_connections": 50,
|
||||
"retry_on_timeout": True,
|
||||
},
|
||||
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
|
||||
"IGNORE_EXCEPTIONS": True, # Don't crash if Redis is down
|
||||
},
|
||||
"KEY_PREFIX": "memelord",
|
||||
"TIMEOUT": 300, # 5 minutes default
|
||||
}
|
||||
}
|
||||
|
||||
# Use Redis for session storage (cloud-native)
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
else:
|
||||
# Redis not configured - use database sessions (backward compatible)
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
||||
# No CACHES configuration - Django will use local memory cache
|
||||
|
||||
SECURE_COOKIES = os.environ.get('SECURE_COOKIES', 'False').lower() in ['true']
|
||||
|
||||
if SECURE_COOKIES:
|
||||
# transmit cookies over encrypted https only
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
# also set hsts response header
|
||||
SECURE_HSTS_SECONDS = "31536000"
|
||||
SECURE_HSTS_PRELOAD = True
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
else:
|
||||
# transmit cookies over unencrypted http
|
||||
SESSION_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
|
||||
# http security response headers
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
X_FRAME_OPTIONS = 'DENY'
|
||||
REFERRER_POLICY = 'same-origin'
|
||||
|
||||
# Load from environment, default to "'self'"
|
||||
raw_frame_ancestors = os.environ.get("CSP_FRAME_ANCESTORS", "'none'")
|
||||
# Split by comma, strip spaces, and keep properly quoted entries
|
||||
FRAME_ANCESTORS = [item.strip() for item in raw_frame_ancestors.split(',') if item.strip()]
|
||||
|
||||
# Build CSP img-src list dynamically based on storage backend
|
||||
STORAGE_BACKEND = os.environ.get('STORAGE_BACKEND', 'local').lower()
|
||||
IMG_SRC_LIST = ["'self'", "data:", "blob:", "https://img.logo.dev"]
|
||||
|
||||
# Add S3 domains to CSP if using S3 storage
|
||||
if STORAGE_BACKEND == 's3':
|
||||
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
|
||||
AWS_S3_CUSTOM_DOMAIN = os.environ.get('AWS_S3_CUSTOM_DOMAIN')
|
||||
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'us-east-1')
|
||||
AWS_S3_ENDPOINT_URL = os.environ.get('AWS_S3_ENDPOINT_URL')
|
||||
AWS_S3_USE_ACCELERATE_ENDPOINT = os.environ.get('AWS_S3_USE_ACCELERATE_ENDPOINT', 'False').lower() in ['true']
|
||||
|
||||
# Always add custom domain if specified (CDN like CloudFront)
|
||||
if AWS_S3_CUSTOM_DOMAIN:
|
||||
IMG_SRC_LIST.append(f"https://{AWS_S3_CUSTOM_DOMAIN}")
|
||||
|
||||
# Detect S3 provider based on endpoint URL
|
||||
if AWS_S3_ENDPOINT_URL:
|
||||
# S3-compatible service detected
|
||||
from urllib.parse import urlparse
|
||||
parsed_url = urlparse(AWS_S3_ENDPOINT_URL)
|
||||
endpoint_domain = parsed_url.netloc
|
||||
endpoint_scheme = parsed_url.scheme or 'https'
|
||||
|
||||
# Add the endpoint domain
|
||||
IMG_SRC_LIST.append(f"{endpoint_scheme}://{endpoint_domain}")
|
||||
|
||||
# Add bucket-based subdomain format if applicable
|
||||
if endpoint_domain and AWS_STORAGE_BUCKET_NAME:
|
||||
IMG_SRC_LIST.append(f"{endpoint_scheme}://{AWS_STORAGE_BUCKET_NAME}.{endpoint_domain}")
|
||||
|
||||
# Provider-specific URL patterns
|
||||
endpoint_lower = endpoint_domain.lower() if endpoint_domain else ""
|
||||
|
||||
# DigitalOcean Spaces: also add CDN domain
|
||||
if 'digitaloceanspaces.com' in endpoint_lower:
|
||||
# DigitalOcean Spaces CDN format: bucket-name.region.cdn.digitaloceanspaces.com
|
||||
if AWS_STORAGE_BUCKET_NAME and AWS_S3_REGION_NAME:
|
||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.{AWS_S3_REGION_NAME}.cdn.digitaloceanspaces.com")
|
||||
|
||||
# Cloudflare R2: also add public bucket URL format
|
||||
elif 'r2.cloudflarestorage.com' in endpoint_lower:
|
||||
# Cloudflare R2 can use custom domains via public.r2.dev
|
||||
# Format: https://bucket-name.account-id.r2.dev (if public)
|
||||
# This is typically set via AWS_S3_CUSTOM_DOMAIN, but we note it here
|
||||
pass
|
||||
|
||||
# Wasabi: supports path-style and virtual-hosted-style
|
||||
elif 'wasabisys.com' in endpoint_lower:
|
||||
# Already covered by endpoint_domain and bucket subdomain
|
||||
pass
|
||||
|
||||
# Linode Object Storage: supports path-style and virtual-hosted-style
|
||||
elif 'linodeobjects.com' in endpoint_lower:
|
||||
# Already covered by endpoint_domain and bucket subdomain
|
||||
pass
|
||||
|
||||
# Backblaze B2: supports path-style and virtual-hosted-style
|
||||
elif 'backblazeb2.com' in endpoint_lower:
|
||||
# Already covered by endpoint_domain and bucket subdomain
|
||||
pass
|
||||
|
||||
# MinIO: custom deployment, already covered
|
||||
# Other S3-compatible services: already covered
|
||||
|
||||
else:
|
||||
# No endpoint URL = Standard AWS S3
|
||||
if AWS_STORAGE_BUCKET_NAME:
|
||||
# Add AWS S3 virtual-hosted-style URLs
|
||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com")
|
||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com")
|
||||
|
||||
# Add path-style URL format (legacy but still supported)
|
||||
IMG_SRC_LIST.append("https://s3.amazonaws.com")
|
||||
IMG_SRC_LIST.append(f"https://s3.{AWS_S3_REGION_NAME}.amazonaws.com")
|
||||
|
||||
# Add dual-stack endpoints (IPv6 support)
|
||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3.dualstack.{AWS_S3_REGION_NAME}.amazonaws.com")
|
||||
|
||||
# Add S3 Transfer Acceleration endpoint if enabled
|
||||
if AWS_S3_USE_ACCELERATE_ENDPOINT:
|
||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3-accelerate.amazonaws.com")
|
||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3-accelerate.dualstack.amazonaws.com")
|
||||
|
||||
# Add Azure domains to CSP if using Azure storage
|
||||
elif STORAGE_BACKEND == 'azure':
|
||||
AZURE_ACCOUNT_NAME = os.environ.get('AZURE_ACCOUNT_NAME')
|
||||
AZURE_CUSTOM_DOMAIN = os.environ.get('AZURE_CUSTOM_DOMAIN')
|
||||
|
||||
if AZURE_CUSTOM_DOMAIN:
|
||||
IMG_SRC_LIST.append(f"https://{AZURE_CUSTOM_DOMAIN}")
|
||||
|
||||
if AZURE_ACCOUNT_NAME:
|
||||
IMG_SRC_LIST.append(f"https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net")
|
||||
|
||||
# Add GCS domains to CSP if using GCS storage
|
||||
elif STORAGE_BACKEND == 'gcs':
|
||||
GS_BUCKET_NAME = os.environ.get('GS_BUCKET_NAME')
|
||||
GS_CUSTOM_ENDPOINT = os.environ.get('GS_CUSTOM_ENDPOINT')
|
||||
|
||||
if GS_CUSTOM_ENDPOINT:
|
||||
IMG_SRC_LIST.append(GS_CUSTOM_ENDPOINT)
|
||||
|
||||
if GS_BUCKET_NAME:
|
||||
# GCS can use multiple URL formats
|
||||
IMG_SRC_LIST.append("https://storage.googleapis.com")
|
||||
IMG_SRC_LIST.append("https://storage.cloud.google.com")
|
||||
IMG_SRC_LIST.append(f"https://{GS_BUCKET_NAME}.storage.googleapis.com")
|
||||
|
||||
# SFTP/Dropbox typically serve through Django (using 'self' origin)
|
||||
elif STORAGE_BACKEND == 'sftp':
|
||||
# SFTP files are retrieved by Django and served through Django views
|
||||
# Optional: If you have nginx serving the same SFTP directory via HTTP
|
||||
SFTP_CUSTOM_DOMAIN = os.environ.get('SFTP_CUSTOM_DOMAIN')
|
||||
if SFTP_CUSTOM_DOMAIN:
|
||||
IMG_SRC_LIST.append(f"https://{SFTP_CUSTOM_DOMAIN}")
|
||||
|
||||
elif STORAGE_BACKEND == 'dropbox':
|
||||
# Dropbox files are retrieved by Django and served through Django views
|
||||
# No additional CSP configuration needed
|
||||
pass
|
||||
|
||||
CSP_IMG_SRC_EXTRA = os.environ.get('CSP_IMG_SRC_EXTRA', '')
|
||||
if CSP_IMG_SRC_EXTRA:
|
||||
extra_domains = [domain.strip() for domain in CSP_IMG_SRC_EXTRA.split(',') if domain.strip()]
|
||||
IMG_SRC_LIST.extend(extra_domains)
|
||||
|
||||
CONTENT_SECURITY_POLICY = {
|
||||
"DIRECTIVES": {
|
||||
"default-src": ["'self'"],
|
||||
"style-src": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdn.jsdelivr.net"],
|
||||
"script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://cdn.jsdelivr.net"],
|
||||
"font-src": ["'self'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],
|
||||
"img-src": IMG_SRC_LIST,
|
||||
"object-src": ["'none'"],
|
||||
"connect-src": ["'self'"],
|
||||
"frame-ancestors": FRAME_ANCESTORS,
|
||||
},
|
||||
}
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = [
|
||||
'myapp',
|
||||
'django_celery_beat',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'csp',
|
||||
'storages',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
#'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django_http_referrer_policy.middleware.ReferrerPolicyMiddleware',
|
||||
'csp.middleware.CSPMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'myproject.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'myapp/templates/registration')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
|
||||
DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite3")
|
||||
|
||||
if DB_ENGINE == "sqlite3":
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'database', 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
elif DB_ENGINE == "postgres":
|
||||
|
||||
DB_HOST = os.environ.get("POSTGRES_HOST", "db")
|
||||
DB_PORT = os.environ.get("POSTGRES_PORT", "5432")
|
||||
DB_USER = os.environ.get("POSTGRES_USER", "memelord")
|
||||
DB_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "memelord")
|
||||
DB_NAME = os.environ.get("POSTGRES_DB", "memelord")
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': DB_NAME,
|
||||
'HOST': DB_HOST,
|
||||
'PORT': DB_PORT,
|
||||
'USER': DB_USER,
|
||||
'PASSWORD': DB_PASSWORD,
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = os.environ.get('TZ', 'Europe/Berlin')
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
LANGUAGES = [
|
||||
('en', _('English')),
|
||||
('de', _('German')),
|
||||
('fr', _('French')),
|
||||
('it', _('Italian')),
|
||||
]
|
||||
|
||||
LOCALE_PATHS = [
|
||||
os.path.join(BASE_DIR, 'locale')
|
||||
]
|
||||
|
||||
# Celery configuration
|
||||
# http://docs.celeryproject.org/en/latest/configuration.html
|
||||
|
||||
LOGS_DIR = os.path.join(BASE_DIR, 'logs')
|
||||
|
||||
# =============================================================================
|
||||
# LOGGING CONFIGURATION
|
||||
# =============================================================================
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '{levelname} {asctime} {module} {message}',
|
||||
'style': '{',
|
||||
},
|
||||
'simple': {
|
||||
'format': '{levelname} {message}',
|
||||
'style': '{',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': os.path.join(LOGS_DIR, 'django.log'),
|
||||
'maxBytes': 1024 * 1024 * 15, # 15MB
|
||||
'backupCount': 10,
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': os.environ.get('DJANGO_LOG_LEVEL', 'INFO'),
|
||||
'propagate': False,
|
||||
},
|
||||
'myapp': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': os.environ.get('DJANGO_LOG_LEVEL', 'INFO'),
|
||||
'propagate': False,
|
||||
},
|
||||
'storages': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'boto3': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
'botocore': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG' if DEBUG else 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
}
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
os.makedirs(LOGS_DIR, exist_ok=True)
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'myapp', 'static')
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
LOGIN_URL = '/accounts/login/'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGOUT_REDIRECT_URL = '/post-logout/'
|
||||
ALLOW_LOGOUT_GET_METHOD = True
|
||||
|
||||
WSGI_APPLICATION = 'myproject.wsgi.application'
|
||||
|
||||
# check if oidc is enabled
|
||||
OIDC_ENABLED = os.environ.get('OIDC_ENABLED', 'False').lower() in ['true']
|
||||
OIDC_AUTOLOGIN = os.environ.get('OIDC_AUTOLOGIN', 'False').lower() in ['true']
|
||||
|
||||
# Max file upload size in MB (default: 10MB)
|
||||
# Can be configured via MAX_UPLOAD_SIZE_MB environment variable
|
||||
MAX_UPLOAD_SIZE_MB = int(os.environ.get('MAX_UPLOAD_SIZE_MB', '10')) * 1024 * 1024
|
||||
|
||||
# =============================================================================
|
||||
# STORAGE BACKEND CONFIGURATION
|
||||
# =============================================================================
|
||||
# IMPORTANT: This must be set BEFORE MEDIA_URL and MEDIA_ROOT
|
||||
# Configure storage backend via STORAGE_BACKEND environment variable
|
||||
# Supported backends: local, s3, azure, gcs, sftp, dropbox
|
||||
# Default: local (filesystem storage)
|
||||
|
||||
STORAGE_BACKEND = os.environ.get('STORAGE_BACKEND', 'local').lower()
|
||||
|
||||
# Django 5.0+ uses STORAGES instead of DEFAULT_FILE_STORAGE
|
||||
# Set both for compatibility
|
||||
if STORAGE_BACKEND == 's3':
|
||||
# Amazon S3 / Compatible S3 Storage (MinIO, DigitalOcean Spaces, etc.)
|
||||
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
|
||||
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
|
||||
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'us-east-1')
|
||||
AWS_S3_CUSTOM_DOMAIN = os.environ.get('AWS_S3_CUSTOM_DOMAIN')
|
||||
AWS_S3_ENDPOINT_URL = os.environ.get('AWS_S3_ENDPOINT_URL') # Only set for S3-compatible services (MinIO, etc.)
|
||||
AWS_DEFAULT_ACL = os.environ.get('AWS_DEFAULT_ACL', 'private')
|
||||
AWS_S3_OBJECT_PARAMETERS = {
|
||||
'CacheControl': 'max-age=86400',
|
||||
}
|
||||
AWS_QUERYSTRING_AUTH = os.environ.get('AWS_QUERYSTRING_AUTH', 'True').lower() in ['true']
|
||||
AWS_S3_FILE_OVERWRITE = os.environ.get('AWS_S3_FILE_OVERWRITE', 'False').lower() in ['true']
|
||||
AWS_LOCATION = os.environ.get('AWS_LOCATION', 'media')
|
||||
|
||||
# Use signature version 4 (required for all regions)
|
||||
AWS_S3_SIGNATURE_VERSION = 's3v4'
|
||||
|
||||
# Use virtual-hosted-style URLs (bucket-name.s3.region.amazonaws.com)
|
||||
# This is the default and recommended format
|
||||
AWS_S3_ADDRESSING_STYLE = 'path'
|
||||
|
||||
# Django 5.0+ STORAGES configuration
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
# Legacy setting for older Django versions
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||
|
||||
# Update MEDIA_URL
|
||||
if AWS_S3_CUSTOM_DOMAIN:
|
||||
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/'
|
||||
elif AWS_STORAGE_BUCKET_NAME:
|
||||
# Use region-specific virtual-hosted-style URL
|
||||
if AWS_S3_REGION_NAME == 'us-east-1':
|
||||
MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/{AWS_LOCATION}/'
|
||||
else:
|
||||
MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com/{AWS_LOCATION}/'
|
||||
else:
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
elif STORAGE_BACKEND == 'azure':
|
||||
# Microsoft Azure Blob Storage
|
||||
AZURE_ACCOUNT_NAME = os.environ.get('AZURE_ACCOUNT_NAME')
|
||||
AZURE_ACCOUNT_KEY = os.environ.get('AZURE_ACCOUNT_KEY')
|
||||
AZURE_CONTAINER = os.environ.get('AZURE_CONTAINER', 'media')
|
||||
AZURE_SSL = os.environ.get('AZURE_SSL', 'True').lower() in ['true']
|
||||
AZURE_UPLOAD_MAX_CONN = int(os.environ.get('AZURE_UPLOAD_MAX_CONN', '2'))
|
||||
AZURE_CONNECTION_TIMEOUT_SECS = int(os.environ.get('AZURE_CONNECTION_TIMEOUT_SECS', '20'))
|
||||
AZURE_BLOB_MAX_MEMORY_SIZE = os.environ.get('AZURE_BLOB_MAX_MEMORY_SIZE', '2MB')
|
||||
AZURE_URL_EXPIRATION_SECS = int(os.environ.get('AZURE_URL_EXPIRATION_SECS', '3600'))
|
||||
AZURE_OVERWRITE_FILES = os.environ.get('AZURE_OVERWRITE_FILES', 'False').lower() in ['true']
|
||||
AZURE_LOCATION = os.environ.get('AZURE_LOCATION', '')
|
||||
AZURE_CUSTOM_DOMAIN = os.environ.get('AZURE_CUSTOM_DOMAIN')
|
||||
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "storages.backends.azure_storage.AzureStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.azure_storage.AzureStorage'
|
||||
|
||||
if AZURE_CUSTOM_DOMAIN:
|
||||
MEDIA_URL = f'https://{AZURE_CUSTOM_DOMAIN}/{AZURE_LOCATION}'
|
||||
elif AZURE_ACCOUNT_NAME:
|
||||
MEDIA_URL = f'https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net/{AZURE_CONTAINER}/{AZURE_LOCATION}'
|
||||
else:
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
elif STORAGE_BACKEND == 'gcs':
|
||||
# Google Cloud Storage
|
||||
GS_BUCKET_NAME = os.environ.get('GS_BUCKET_NAME')
|
||||
GS_PROJECT_ID = os.environ.get('GS_PROJECT_ID')
|
||||
GS_CREDENTIALS = os.environ.get('GS_CREDENTIALS')
|
||||
GS_DEFAULT_ACL = os.environ.get('GS_DEFAULT_ACL', 'private')
|
||||
GS_FILE_OVERWRITE = os.environ.get('GS_FILE_OVERWRITE', 'False').lower() in ['true']
|
||||
GS_LOCATION = os.environ.get('GS_LOCATION', 'media')
|
||||
GS_CUSTOM_ENDPOINT = os.environ.get('GS_CUSTOM_ENDPOINT')
|
||||
GS_QUERYSTRING_AUTH = os.environ.get('GS_QUERYSTRING_AUTH', 'True').lower() in ['true']
|
||||
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.gcloud.GoogleCloudStorage'
|
||||
|
||||
if GS_CUSTOM_ENDPOINT:
|
||||
MEDIA_URL = f'{GS_CUSTOM_ENDPOINT}/{GS_LOCATION}/'
|
||||
elif GS_BUCKET_NAME:
|
||||
MEDIA_URL = f'https://storage.googleapis.com/{GS_BUCKET_NAME}/{GS_LOCATION}/'
|
||||
else:
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
elif STORAGE_BACKEND == 'sftp':
|
||||
# SFTP Storage
|
||||
SFTP_STORAGE_HOST = os.environ.get('SFTP_STORAGE_HOST')
|
||||
SFTP_STORAGE_ROOT = os.environ.get('SFTP_STORAGE_ROOT', '/media/')
|
||||
SFTP_STORAGE_PARAMS = {
|
||||
'port': int(os.environ.get('SFTP_STORAGE_PORT', '22')),
|
||||
'username': os.environ.get('SFTP_STORAGE_USERNAME'),
|
||||
'password': os.environ.get('SFTP_STORAGE_PASSWORD'),
|
||||
'pkey': os.environ.get('SFTP_STORAGE_PRIVATE_KEY'),
|
||||
}
|
||||
SFTP_STORAGE_INTERACTIVE = os.environ.get('SFTP_STORAGE_INTERACTIVE', 'False').lower() in ['true']
|
||||
SFTP_STORAGE_FILE_MODE = os.environ.get('SFTP_STORAGE_FILE_MODE')
|
||||
SFTP_STORAGE_DIR_MODE = os.environ.get('SFTP_STORAGE_DIR_MODE')
|
||||
SFTP_STORAGE_UID = os.environ.get('SFTP_STORAGE_UID')
|
||||
SFTP_STORAGE_GID = os.environ.get('SFTP_STORAGE_GID')
|
||||
SFTP_KNOWN_HOST_FILE = os.environ.get('SFTP_KNOWN_HOST_FILE')
|
||||
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "storages.backends.sftpstorage.SFTPStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.sftpstorage.SFTPStorage'
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
elif STORAGE_BACKEND == 'dropbox':
|
||||
# Dropbox Storage
|
||||
DROPBOX_OAUTH2_TOKEN = os.environ.get('DROPBOX_OAUTH2_TOKEN')
|
||||
DROPBOX_ROOT_PATH = os.environ.get('DROPBOX_ROOT_PATH', '/media')
|
||||
DROPBOX_TIMEOUT = int(os.environ.get('DROPBOX_TIMEOUT', '100'))
|
||||
DROPBOX_WRITE_MODE = os.environ.get('DROPBOX_WRITE_MODE', 'add')
|
||||
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "storages.backends.dropbox.DropBoxStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.dropbox.DropBoxStorage'
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
else:
|
||||
# Local filesystem storage (default)
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
if OIDC_ENABLED:
|
||||
# get oidc config from env
|
||||
OIDC_CREATE_USER = os.environ.get('OIDC_CREATE_USER', 'True').lower() in ['true']
|
||||
OIDC_RP_SIGN_ALGO = os.environ.get('OIDC_RP_SIGN_ALGO', 'HS256')
|
||||
OIDC_OP_JWKS_ENDPOINT = os.environ.get('OIDC_OP_JWKS_ENDPOINT')
|
||||
OIDC_RP_IDP_SIGN_KEY = os.environ.get('OIDC_RP_IDP_SIGN_KEY')
|
||||
OIDC_RP_CLIENT_ID = os.environ.get('OIDC_RP_CLIENT_ID')
|
||||
OIDC_RP_CLIENT_SECRET = os.environ.get('OIDC_RP_CLIENT_SECRET')
|
||||
OIDC_OP_AUTHORIZATION_ENDPOINT = os.environ.get('OIDC_OP_AUTHORIZATION_ENDPOINT')
|
||||
OIDC_OP_TOKEN_ENDPOINT = os.environ.get('OIDC_OP_TOKEN_ENDPOINT')
|
||||
OIDC_OP_USER_ENDPOINT = os.environ.get('OIDC_OP_USER_ENDPOINT')
|
||||
OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS = float(os.environ.get('OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS', 900))
|
||||
OIDC_USERNAME_ALGO = 'myapp.utils.generate_username'
|
||||
|
||||
# Add 'mozilla_django_oidc.middleware.SessionRefresh' to INSTALLED_APPS
|
||||
INSTALLED_APPS.append('mozilla_django_oidc')
|
||||
|
||||
# Add 'mozilla_django_oidc' authentication backend
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'mozilla_django_oidc.auth.OIDCAuthenticationBackend',
|
||||
)
|
||||
|
||||
# Add 'mozilla_django_oidc.middleware.SessionRefresh' to MIDDLEWARE
|
||||
# https://mozilla-django-oidc.readthedocs.io/en/stable/installation.html#validate-id-tokens-by-renewing-them
|
||||
MIDDLEWARE.append('mozilla_django_oidc.middleware.SessionRefresh')
|
||||
|
||||
# Fix http callback issue in mozilla-django-oidc by forcing https; https://github.com/mozilla/mozilla-django-oidc/issues/417
|
||||
# OIDC should only be setup behind a TLS reverse proxy anyways
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
Reference in New Issue
Block a user