helm
This commit is contained in:
60
CLAUDE.md
Normal file
60
CLAUDE.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Kubernetes deployment manifests for "Memelord Jake" — a Django meme-sharing application deployed on a cloud-native Kubernetes cluster. This repo contains **no application source code**, only infrastructure-as-code YAML manifests.
|
||||||
|
|
||||||
|
The Django app image is `ghcr.io/l4rm4nd/memelord:latest`. The cluster domain is `ee-lte-1.codemowers.io`.
|
||||||
|
|
||||||
|
## Deploying
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f deployment.yaml # Backing services: PostgreSQL, Redis, S3, networking
|
||||||
|
kubectl apply -f config.yaml # Django settings.py ConfigMap
|
||||||
|
kubectl apply -f app.yaml # Memelord Deployment
|
||||||
|
kubectl apply -f oidc.yaml # OIDC client configuration
|
||||||
|
kubectl apply -f grafana.yaml # Grafana monitoring (includes namespace)
|
||||||
|
kubectl apply -f monitoring.yaml # Prometheus Probe
|
||||||
|
```
|
||||||
|
|
||||||
|
The cluster requires these operators pre-installed: CloudNativePG, DragonflyDB, Onyxia S3, cert-manager, Traefik, Codemowers Cloud OIDC, Prometheus operator.
|
||||||
|
|
||||||
|
## File Map
|
||||||
|
|
||||||
|
- **`deployment.yaml`** — Backing services: StringSecret + Dragonfly (Redis), StringSecret + Cluster + Database (PostgreSQL), Policy + S3User + Bucket (S3), Service + Certificate + Ingress
|
||||||
|
- **`config.yaml`** — ConfigMap containing the full Django `settings.py`; the largest and most complex file. Configures DB, cache, security headers (CSP/HSTS), storage backends, OIDC, logging
|
||||||
|
- **`app.yaml`** — Deployment for the Django app (1 replica, port 8000). Mounts `settings.py` from ConfigMap via `subPath`. All config injected via environment variables from Secrets
|
||||||
|
- **`grafana.yaml`** — Complete Grafana stack: Namespace, ConfigMaps (Prometheus + Loki datasources, dashboard JSON), StatefulSet (5Gi SQLite), OIDC auth, Ingress with TLS
|
||||||
|
- **`oidc.yaml`** — OIDCClient CR for Memelord app authentication via Passmower
|
||||||
|
- **`monitoring.yaml`** — Prometheus Probe CR
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Namespace: memelord-jake
|
||||||
|
|
||||||
|
Memelord (Deployment) ──► PostgreSQL (CloudNativePG Cluster)
|
||||||
|
──► DragonflyDB (Redis-compatible cache/sessions)
|
||||||
|
──► MinIO S3 (media storage via Onyxia operator)
|
||||||
|
──► Passmower OIDC (authentication)
|
||||||
|
|
||||||
|
Grafana (StatefulSet) ──► Prometheus (monitoring ns)
|
||||||
|
──► Loki (monitoring ns)
|
||||||
|
──► Passmower OIDC (authentication)
|
||||||
|
|
||||||
|
External access: Traefik Ingress + cert-manager TLS
|
||||||
|
- memelord-jake.ee-lte-1.codemowers.io
|
||||||
|
- grafana-jake.ee-lte-1.codemowers.io
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Conventions
|
||||||
|
|
||||||
|
- Resource naming: prefix `memelord-jake-` for all backing services
|
||||||
|
- Secrets auto-generated via `StringSecret` CRs (mittwald secret generator)
|
||||||
|
- Django settings are fully environment-driven (12-factor); `config.yaml` reads everything from env vars
|
||||||
|
- Storage class `postgres` for DB, `sqlite` for Grafana
|
||||||
|
- Node selector: `codemowers.io/lvm-ubuntu-vg: enterprise-ssd`
|
||||||
|
- ArgoCD destination cluster: `https://10.254.10.31:6443`
|
||||||
|
- S3 uses path-style addressing (`AWS_S3_ADDRESSING_STYLE = 'path'`)
|
||||||
3
helm/Chart.yaml
Normal file
3
helm/Chart.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: memelord
|
||||||
|
version: 0.1.0
|
||||||
@@ -1,131 +1,130 @@
|
|||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord
|
name: {{ .Release.Name }}
|
||||||
namespace: memelord-jake
|
spec:
|
||||||
spec:
|
replicas: 1
|
||||||
replicas: 1
|
selector:
|
||||||
selector:
|
matchLabels:
|
||||||
matchLabels:
|
app: {{ .Release.Name }}
|
||||||
app: memelord
|
template:
|
||||||
template:
|
metadata:
|
||||||
metadata:
|
labels:
|
||||||
labels:
|
app: {{ .Release.Name }}
|
||||||
app: memelord
|
spec:
|
||||||
spec:
|
containers:
|
||||||
containers:
|
- name: {{ .Release.Name }}
|
||||||
- name: memelord
|
image: ghcr.io/l4rm4nd/memelord:latest
|
||||||
image: ghcr.io/l4rm4nd/memelord:latest
|
imagePullPolicy: Always
|
||||||
imagePullPolicy: Always
|
ports:
|
||||||
ports:
|
- name: http
|
||||||
- name: http
|
containerPort: 8000
|
||||||
containerPort: 8000
|
|
||||||
|
env:
|
||||||
env:
|
- name: DOMAIN
|
||||||
- name: DOMAIN
|
value: {{ .Values.hostname | quote }}
|
||||||
value: "memelord-jake.ee-lte-1.codemowers.io"
|
|
||||||
|
# Database Configuration
|
||||||
# Database Configuration
|
- name: DB_ENGINE
|
||||||
- name: DB_ENGINE
|
value: "postgres"
|
||||||
value: "postgres"
|
- name: POSTGRES_HOST
|
||||||
- name: POSTGRES_HOST
|
value: "{{ .Release.Name }}-database-rw"
|
||||||
value: "memelord-jake-database-rw"
|
- name: POSTGRES_PORT
|
||||||
- name: POSTGRES_PORT
|
value: "5432"
|
||||||
value: "5432"
|
- name: POSTGRES_DB
|
||||||
- name: POSTGRES_DB
|
value: {{ .Release.Name | quote }}
|
||||||
value: "memelord-jake"
|
- name: POSTGRES_USER
|
||||||
- name: POSTGRES_USER
|
valueFrom:
|
||||||
valueFrom:
|
secretKeyRef:
|
||||||
secretKeyRef:
|
name: {{ .Release.Name }}-database
|
||||||
name: memelord-jake-database
|
key: username
|
||||||
key: username
|
- name: POSTGRES_PASSWORD
|
||||||
- name: POSTGRES_PASSWORD
|
valueFrom:
|
||||||
valueFrom:
|
secretKeyRef:
|
||||||
secretKeyRef:
|
name: {{ .Release.Name }}-database
|
||||||
name: memelord-jake-database
|
key: password
|
||||||
key: password
|
|
||||||
|
# Redis Configuration
|
||||||
# Redis Configuration
|
- name: REDIS_HOST
|
||||||
- name: REDIS_HOST
|
value: "{{ .Release.Name }}-redis"
|
||||||
value: "memelord-jake-redis"
|
- name: REDIS_PORT
|
||||||
- name: REDIS_PORT
|
value: "6379"
|
||||||
value: "6379"
|
- name: REDIS_PASSWORD
|
||||||
- name: REDIS_PASSWORD
|
valueFrom:
|
||||||
valueFrom:
|
secretKeyRef:
|
||||||
secretKeyRef:
|
name: {{ .Release.Name }}-redis
|
||||||
name: memelord-jake-redis
|
key: redis-password
|
||||||
key: redis-password
|
|
||||||
|
# S3/MinIO Storage Configuration
|
||||||
# S3/MinIO Storage Configuration
|
- name: STORAGE_BACKEND
|
||||||
- name: STORAGE_BACKEND
|
value: "s3"
|
||||||
value: "s3"
|
- name: AWS_ACCESS_KEY_ID
|
||||||
- name: AWS_ACCESS_KEY_ID
|
valueFrom:
|
||||||
valueFrom:
|
secretKeyRef:
|
||||||
secretKeyRef:
|
name: {{ .Release.Name }}-bucket
|
||||||
name: memelord-jake-bucket
|
key: accessKey
|
||||||
key: accessKey
|
- name: AWS_SECRET_ACCESS_KEY
|
||||||
- name: AWS_SECRET_ACCESS_KEY
|
valueFrom:
|
||||||
valueFrom:
|
secretKeyRef:
|
||||||
secretKeyRef:
|
name: {{ .Release.Name }}-bucket
|
||||||
name: memelord-jake-bucket
|
key: secretKey
|
||||||
key: secretKey
|
- name: AWS_S3_ADDRESSING_STYLE
|
||||||
- name: AWS_S3_ADDRESSING_STYLE
|
value: path
|
||||||
value: path
|
- name: AWS_STORAGE_BUCKET_NAME
|
||||||
- name: AWS_STORAGE_BUCKET_NAME
|
value: {{ .Release.Name | quote }}
|
||||||
value: "memelord-jake"
|
- name: AWS_S3_ENDPOINT_URL
|
||||||
- name: AWS_S3_ENDPOINT_URL
|
value: "https://minio.ee-lte-1.codemowers.io"
|
||||||
value: "https://minio.ee-lte-1.codemowers.io"
|
- name: AWS_S3_REGION_NAME
|
||||||
- name: AWS_S3_REGION_NAME
|
value: "ee-lte-1"
|
||||||
value: "ee-lte-1"
|
|
||||||
|
# OIDC Configuration
|
||||||
# OIDC Configuration
|
- name: OIDC_ENABLED
|
||||||
- name: OIDC_ENABLED
|
value: "True"
|
||||||
value: "True"
|
- name: OIDC_CREATE_USER
|
||||||
- name: OIDC_CREATE_USER
|
value: "True"
|
||||||
value: "True"
|
- name: OIDC_RP_CLIENT_ID
|
||||||
- name: OIDC_RP_CLIENT_ID
|
valueFrom:
|
||||||
valueFrom:
|
secretKeyRef:
|
||||||
secretKeyRef:
|
name: oidc-client-{{ .Release.Name }}-owner-secrets
|
||||||
name: oidc-client-memelord-jake-owner-secrets
|
key: OIDC_CLIENT_ID
|
||||||
key: OIDC_CLIENT_ID
|
- name: OIDC_RP_CLIENT_SECRET
|
||||||
- name: OIDC_RP_CLIENT_SECRET
|
valueFrom:
|
||||||
valueFrom:
|
secretKeyRef:
|
||||||
secretKeyRef:
|
name: oidc-client-{{ .Release.Name }}-owner-secrets
|
||||||
name: oidc-client-memelord-jake-owner-secrets
|
key: OIDC_CLIENT_SECRET
|
||||||
key: OIDC_CLIENT_SECRET
|
|
||||||
|
# Browser-facing endpoint (external URL)
|
||||||
# Browser-facing endpoint (external URL)
|
- name: OIDC_OP_AUTHORIZATION_ENDPOINT
|
||||||
- name: OIDC_OP_AUTHORIZATION_ENDPOINT
|
value: "https://auth.ee-lte-1.codemowers.io/auth"
|
||||||
value: "https://auth.ee-lte-1.codemowers.io/auth"
|
|
||||||
|
# Server-to-server endpoints (internal URLs)
|
||||||
# Server-to-server endpoints (internal URLs)
|
- name: OIDC_OP_TOKEN_ENDPOINT
|
||||||
- name: OIDC_OP_TOKEN_ENDPOINT
|
value: "http://passmower.passmower.svc.cluster.local/token"
|
||||||
value: "http://passmower.passmower.svc.cluster.local/token"
|
- name: OIDC_OP_USER_ENDPOINT
|
||||||
- name: OIDC_OP_USER_ENDPOINT
|
value: "http://passmower.passmower.svc.cluster.local/me"
|
||||||
value: "http://passmower.passmower.svc.cluster.local/me"
|
- name: OIDC_OP_JWKS_ENDPOINT
|
||||||
- name: OIDC_OP_JWKS_ENDPOINT
|
value: "http://passmower.passmower.svc.cluster.local/jwks"
|
||||||
value: "http://passmower.passmower.svc.cluster.local/jwks"
|
|
||||||
|
- name: OIDC_RP_SIGN_ALGO
|
||||||
- name: OIDC_RP_SIGN_ALGO
|
value: "RS256"
|
||||||
value: "RS256"
|
- name: OIDC_AUTOLOGIN
|
||||||
- name: OIDC_AUTOLOGIN
|
value: "False"
|
||||||
value: "False"
|
|
||||||
|
# General Configuration
|
||||||
# General Configuration
|
- name: DEBUG
|
||||||
- name: DEBUG
|
value: "True"
|
||||||
value: "True"
|
- name: SECURE_COOKIES
|
||||||
- name: SECURE_COOKIES
|
value: "True"
|
||||||
value: "True"
|
|
||||||
|
# Use the patched app code (including patched settings.py)
|
||||||
# Use the patched app code (including patched settings.py)
|
volumeMounts:
|
||||||
volumeMounts:
|
- name: settings
|
||||||
- name: settings
|
mountPath: /opt/app/myproject/settings.py
|
||||||
mountPath: /opt/app/myproject/settings.py
|
subPath: settings.py
|
||||||
subPath: settings.py
|
readOnly: true
|
||||||
readOnly: true
|
|
||||||
|
volumes:
|
||||||
volumes:
|
- name: settings
|
||||||
- name: settings
|
configMap:
|
||||||
configMap:
|
name: settings
|
||||||
name: settings
|
|
||||||
@@ -2,21 +2,20 @@ apiVersion: v1
|
|||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: settings
|
name: settings
|
||||||
namespace: memelord-jake
|
|
||||||
data:
|
data:
|
||||||
settings.py: |
|
settings.py: |
|
||||||
"""
|
"""
|
||||||
Django settings for myproject project.
|
Django settings for myproject project.
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 3.2.16.
|
Generated by 'django-admin startproject' using Django 3.2.16.
|
||||||
|
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/3.2/topics/settings/
|
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/3.2/ref/settings/
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import os
|
import os
|
||||||
@@ -25,33 +24,33 @@ data:
|
|||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from csp.constants import NONE, SELF, UNSAFE_INLINE, UNSAFE_EVAL
|
from csp.constants import NONE, SELF, UNSAFE_INLINE, UNSAFE_EVAL
|
||||||
|
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
# get debug modus from env
|
# get debug modus from env
|
||||||
DEBUG = os.environ.get('DEBUG', 'False').lower() in ['true']
|
DEBUG = os.environ.get('DEBUG', 'False').lower() in ['true']
|
||||||
|
|
||||||
# get container version from env
|
# get container version from env
|
||||||
VERSION = escape(os.environ.get("VERSION", ''))
|
VERSION = escape(os.environ.get("VERSION", ''))
|
||||||
|
|
||||||
# Enable/disable public meme feed feature (must be after import os)
|
# Enable/disable public meme feed feature (must be after import os)
|
||||||
ENABLE_PUBLIC_FEED = os.environ.get('ENABLE_PUBLIC_FEED', 'False').lower() in ['true', '1', 'yes']
|
ENABLE_PUBLIC_FEED = os.environ.get('ENABLE_PUBLIC_FEED', 'False').lower() in ['true', '1', 'yes']
|
||||||
|
|
||||||
# auto-generate a secure secret key or use from env variable
|
# auto-generate a secure secret key or use from env variable
|
||||||
SECRET_KEY = os.environ.get("SECRET_KEY", secrets.token_urlsafe(32))
|
SECRET_KEY = os.environ.get("SECRET_KEY", secrets.token_urlsafe(32))
|
||||||
|
|
||||||
# define allowed hosts and trusted domains via env variables
|
# define allowed hosts and trusted domains via env variables
|
||||||
DOMAIN = ""
|
DOMAIN = ""
|
||||||
ALLOWED_HOSTS = ["127.0.0.1"]
|
ALLOWED_HOSTS = ["127.0.0.1"]
|
||||||
CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1:8000"]
|
CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1:8000"]
|
||||||
|
|
||||||
DOMAIN = str(os.environ.get("DOMAIN", "localhost"))
|
DOMAIN = str(os.environ.get("DOMAIN", "localhost"))
|
||||||
TRUSTED_PORT = str(os.environ.get("PORT", "8000"))
|
TRUSTED_PORT = str(os.environ.get("PORT", "8000"))
|
||||||
|
|
||||||
if DOMAIN:
|
if DOMAIN:
|
||||||
domains = DOMAIN.split(',')
|
domains = DOMAIN.split(',')
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
@@ -63,14 +62,14 @@ data:
|
|||||||
TRUSTED_USER_DOMAIN_HTTPS = f"https://{domain}:{TRUSTED_PORT}"
|
TRUSTED_USER_DOMAIN_HTTPS = f"https://{domain}:{TRUSTED_PORT}"
|
||||||
TRUSTED_USER_DOMAIN_HTTPS_443_DEFAULT = f"https://{domain}"
|
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])
|
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
|
#Session Management
|
||||||
CSRF_COOKIE_HTTPONLY = True
|
CSRF_COOKIE_HTTPONLY = True
|
||||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = os.environ.get('SESSION_EXPIRE_AT_BROWSER_CLOSE', 'True').lower() in ['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_AGE = int(os.environ.get('SESSION_COOKIE_AGE', '30')) * 60
|
||||||
SESSION_COOKIE_NAME = 'Session'
|
SESSION_COOKIE_NAME = 'Session'
|
||||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# REDIS CACHE CONFIGURATION (for sessions and general caching)
|
# REDIS CACHE CONFIGURATION (for sessions and general caching)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -79,7 +78,7 @@ data:
|
|||||||
REDIS_PORT = os.environ.get("REDIS_PORT", "6379")
|
REDIS_PORT = os.environ.get("REDIS_PORT", "6379")
|
||||||
REDIS_DB = os.environ.get("REDIS_DB", "0")
|
REDIS_DB = os.environ.get("REDIS_DB", "0")
|
||||||
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", "")
|
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", "")
|
||||||
|
|
||||||
if REDIS_HOST:
|
if REDIS_HOST:
|
||||||
# Redis is available - use it for caching and sessions
|
# Redis is available - use it for caching and sessions
|
||||||
CACHES = {
|
CACHES = {
|
||||||
@@ -103,7 +102,7 @@ data:
|
|||||||
"TIMEOUT": 300, # 5 minutes default
|
"TIMEOUT": 300, # 5 minutes default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use Redis for session storage (cloud-native)
|
# Use Redis for session storage (cloud-native)
|
||||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||||
SESSION_CACHE_ALIAS = "default"
|
SESSION_CACHE_ALIAS = "default"
|
||||||
@@ -111,9 +110,9 @@ data:
|
|||||||
# Redis not configured - use database sessions (backward compatible)
|
# Redis not configured - use database sessions (backward compatible)
|
||||||
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
||||||
# No CACHES configuration - Django will use local memory cache
|
# No CACHES configuration - Django will use local memory cache
|
||||||
|
|
||||||
SECURE_COOKIES = os.environ.get('SECURE_COOKIES', 'False').lower() in ['true']
|
SECURE_COOKIES = os.environ.get('SECURE_COOKIES', 'False').lower() in ['true']
|
||||||
|
|
||||||
if SECURE_COOKIES:
|
if SECURE_COOKIES:
|
||||||
# transmit cookies over encrypted https only
|
# transmit cookies over encrypted https only
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
@@ -126,22 +125,22 @@ data:
|
|||||||
# transmit cookies over unencrypted http
|
# transmit cookies over unencrypted http
|
||||||
SESSION_COOKIE_SECURE = False
|
SESSION_COOKIE_SECURE = False
|
||||||
CSRF_COOKIE_SECURE = False
|
CSRF_COOKIE_SECURE = False
|
||||||
|
|
||||||
# http security response headers
|
# http security response headers
|
||||||
SECURE_BROWSER_XSS_FILTER = True
|
SECURE_BROWSER_XSS_FILTER = True
|
||||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||||
X_FRAME_OPTIONS = 'DENY'
|
X_FRAME_OPTIONS = 'DENY'
|
||||||
REFERRER_POLICY = 'same-origin'
|
REFERRER_POLICY = 'same-origin'
|
||||||
|
|
||||||
# Load from environment, default to "'self'"
|
# Load from environment, default to "'self'"
|
||||||
raw_frame_ancestors = os.environ.get("CSP_FRAME_ANCESTORS", "'none'")
|
raw_frame_ancestors = os.environ.get("CSP_FRAME_ANCESTORS", "'none'")
|
||||||
# Split by comma, strip spaces, and keep properly quoted entries
|
# Split by comma, strip spaces, and keep properly quoted entries
|
||||||
FRAME_ANCESTORS = [item.strip() for item in raw_frame_ancestors.split(',') if item.strip()]
|
FRAME_ANCESTORS = [item.strip() for item in raw_frame_ancestors.split(',') if item.strip()]
|
||||||
|
|
||||||
# Build CSP img-src list dynamically based on storage backend
|
# Build CSP img-src list dynamically based on storage backend
|
||||||
STORAGE_BACKEND = os.environ.get('STORAGE_BACKEND', 'local').lower()
|
STORAGE_BACKEND = os.environ.get('STORAGE_BACKEND', 'local').lower()
|
||||||
IMG_SRC_LIST = ["'self'", "data:", "blob:", "https://img.logo.dev"]
|
IMG_SRC_LIST = ["'self'", "data:", "blob:", "https://img.logo.dev"]
|
||||||
|
|
||||||
# Add S3 domains to CSP if using S3 storage
|
# Add S3 domains to CSP if using S3 storage
|
||||||
if STORAGE_BACKEND == 's3':
|
if STORAGE_BACKEND == 's3':
|
||||||
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
|
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
|
||||||
@@ -149,11 +148,11 @@ data:
|
|||||||
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'us-east-1')
|
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_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']
|
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)
|
# Always add custom domain if specified (CDN like CloudFront)
|
||||||
if AWS_S3_CUSTOM_DOMAIN:
|
if AWS_S3_CUSTOM_DOMAIN:
|
||||||
IMG_SRC_LIST.append(f"https://{AWS_S3_CUSTOM_DOMAIN}")
|
IMG_SRC_LIST.append(f"https://{AWS_S3_CUSTOM_DOMAIN}")
|
||||||
|
|
||||||
# Detect S3 provider based on endpoint URL
|
# Detect S3 provider based on endpoint URL
|
||||||
if AWS_S3_ENDPOINT_URL:
|
if AWS_S3_ENDPOINT_URL:
|
||||||
# S3-compatible service detected
|
# S3-compatible service detected
|
||||||
@@ -161,92 +160,92 @@ data:
|
|||||||
parsed_url = urlparse(AWS_S3_ENDPOINT_URL)
|
parsed_url = urlparse(AWS_S3_ENDPOINT_URL)
|
||||||
endpoint_domain = parsed_url.netloc
|
endpoint_domain = parsed_url.netloc
|
||||||
endpoint_scheme = parsed_url.scheme or 'https'
|
endpoint_scheme = parsed_url.scheme or 'https'
|
||||||
|
|
||||||
# Add the endpoint domain
|
# Add the endpoint domain
|
||||||
IMG_SRC_LIST.append(f"{endpoint_scheme}://{endpoint_domain}")
|
IMG_SRC_LIST.append(f"{endpoint_scheme}://{endpoint_domain}")
|
||||||
|
|
||||||
# Add bucket-based subdomain format if applicable
|
# Add bucket-based subdomain format if applicable
|
||||||
if endpoint_domain and AWS_STORAGE_BUCKET_NAME:
|
if endpoint_domain and AWS_STORAGE_BUCKET_NAME:
|
||||||
IMG_SRC_LIST.append(f"{endpoint_scheme}://{AWS_STORAGE_BUCKET_NAME}.{endpoint_domain}")
|
IMG_SRC_LIST.append(f"{endpoint_scheme}://{AWS_STORAGE_BUCKET_NAME}.{endpoint_domain}")
|
||||||
|
|
||||||
# Provider-specific URL patterns
|
# Provider-specific URL patterns
|
||||||
endpoint_lower = endpoint_domain.lower() if endpoint_domain else ""
|
endpoint_lower = endpoint_domain.lower() if endpoint_domain else ""
|
||||||
|
|
||||||
# DigitalOcean Spaces: also add CDN domain
|
# DigitalOcean Spaces: also add CDN domain
|
||||||
if 'digitaloceanspaces.com' in endpoint_lower:
|
if 'digitaloceanspaces.com' in endpoint_lower:
|
||||||
# DigitalOcean Spaces CDN format: bucket-name.region.cdn.digitaloceanspaces.com
|
# DigitalOcean Spaces CDN format: bucket-name.region.cdn.digitaloceanspaces.com
|
||||||
if AWS_STORAGE_BUCKET_NAME and AWS_S3_REGION_NAME:
|
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")
|
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
|
# Cloudflare R2: also add public bucket URL format
|
||||||
elif 'r2.cloudflarestorage.com' in endpoint_lower:
|
elif 'r2.cloudflarestorage.com' in endpoint_lower:
|
||||||
# Cloudflare R2 can use custom domains via public.r2.dev
|
# Cloudflare R2 can use custom domains via public.r2.dev
|
||||||
# Format: https://bucket-name.account-id.r2.dev (if public)
|
# Format: https://bucket-name.account-id.r2.dev (if public)
|
||||||
# This is typically set via AWS_S3_CUSTOM_DOMAIN, but we note it here
|
# This is typically set via AWS_S3_CUSTOM_DOMAIN, but we note it here
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Wasabi: supports path-style and virtual-hosted-style
|
# Wasabi: supports path-style and virtual-hosted-style
|
||||||
elif 'wasabisys.com' in endpoint_lower:
|
elif 'wasabisys.com' in endpoint_lower:
|
||||||
# Already covered by endpoint_domain and bucket subdomain
|
# Already covered by endpoint_domain and bucket subdomain
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Linode Object Storage: supports path-style and virtual-hosted-style
|
# Linode Object Storage: supports path-style and virtual-hosted-style
|
||||||
elif 'linodeobjects.com' in endpoint_lower:
|
elif 'linodeobjects.com' in endpoint_lower:
|
||||||
# Already covered by endpoint_domain and bucket subdomain
|
# Already covered by endpoint_domain and bucket subdomain
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Backblaze B2: supports path-style and virtual-hosted-style
|
# Backblaze B2: supports path-style and virtual-hosted-style
|
||||||
elif 'backblazeb2.com' in endpoint_lower:
|
elif 'backblazeb2.com' in endpoint_lower:
|
||||||
# Already covered by endpoint_domain and bucket subdomain
|
# Already covered by endpoint_domain and bucket subdomain
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# MinIO: custom deployment, already covered
|
# MinIO: custom deployment, already covered
|
||||||
# Other S3-compatible services: already covered
|
# Other S3-compatible services: already covered
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# No endpoint URL = Standard AWS S3
|
# No endpoint URL = Standard AWS S3
|
||||||
if AWS_STORAGE_BUCKET_NAME:
|
if AWS_STORAGE_BUCKET_NAME:
|
||||||
# Add AWS S3 virtual-hosted-style URLs
|
# 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.amazonaws.com")
|
||||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.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)
|
# Add path-style URL format (legacy but still supported)
|
||||||
IMG_SRC_LIST.append("https://s3.amazonaws.com")
|
IMG_SRC_LIST.append("https://s3.amazonaws.com")
|
||||||
IMG_SRC_LIST.append(f"https://s3.{AWS_S3_REGION_NAME}.amazonaws.com")
|
IMG_SRC_LIST.append(f"https://s3.{AWS_S3_REGION_NAME}.amazonaws.com")
|
||||||
|
|
||||||
# Add dual-stack endpoints (IPv6 support)
|
# Add dual-stack endpoints (IPv6 support)
|
||||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3.dualstack.{AWS_S3_REGION_NAME}.amazonaws.com")
|
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
|
# Add S3 Transfer Acceleration endpoint if enabled
|
||||||
if AWS_S3_USE_ACCELERATE_ENDPOINT:
|
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.amazonaws.com")
|
||||||
IMG_SRC_LIST.append(f"https://{AWS_STORAGE_BUCKET_NAME}.s3-accelerate.dualstack.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
|
# Add Azure domains to CSP if using Azure storage
|
||||||
elif STORAGE_BACKEND == 'azure':
|
elif STORAGE_BACKEND == 'azure':
|
||||||
AZURE_ACCOUNT_NAME = os.environ.get('AZURE_ACCOUNT_NAME')
|
AZURE_ACCOUNT_NAME = os.environ.get('AZURE_ACCOUNT_NAME')
|
||||||
AZURE_CUSTOM_DOMAIN = os.environ.get('AZURE_CUSTOM_DOMAIN')
|
AZURE_CUSTOM_DOMAIN = os.environ.get('AZURE_CUSTOM_DOMAIN')
|
||||||
|
|
||||||
if AZURE_CUSTOM_DOMAIN:
|
if AZURE_CUSTOM_DOMAIN:
|
||||||
IMG_SRC_LIST.append(f"https://{AZURE_CUSTOM_DOMAIN}")
|
IMG_SRC_LIST.append(f"https://{AZURE_CUSTOM_DOMAIN}")
|
||||||
|
|
||||||
if AZURE_ACCOUNT_NAME:
|
if AZURE_ACCOUNT_NAME:
|
||||||
IMG_SRC_LIST.append(f"https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net")
|
IMG_SRC_LIST.append(f"https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net")
|
||||||
|
|
||||||
# Add GCS domains to CSP if using GCS storage
|
# Add GCS domains to CSP if using GCS storage
|
||||||
elif STORAGE_BACKEND == 'gcs':
|
elif STORAGE_BACKEND == 'gcs':
|
||||||
GS_BUCKET_NAME = os.environ.get('GS_BUCKET_NAME')
|
GS_BUCKET_NAME = os.environ.get('GS_BUCKET_NAME')
|
||||||
GS_CUSTOM_ENDPOINT = os.environ.get('GS_CUSTOM_ENDPOINT')
|
GS_CUSTOM_ENDPOINT = os.environ.get('GS_CUSTOM_ENDPOINT')
|
||||||
|
|
||||||
if GS_CUSTOM_ENDPOINT:
|
if GS_CUSTOM_ENDPOINT:
|
||||||
IMG_SRC_LIST.append(GS_CUSTOM_ENDPOINT)
|
IMG_SRC_LIST.append(GS_CUSTOM_ENDPOINT)
|
||||||
|
|
||||||
if GS_BUCKET_NAME:
|
if GS_BUCKET_NAME:
|
||||||
# GCS can use multiple URL formats
|
# GCS can use multiple URL formats
|
||||||
IMG_SRC_LIST.append("https://storage.googleapis.com")
|
IMG_SRC_LIST.append("https://storage.googleapis.com")
|
||||||
IMG_SRC_LIST.append("https://storage.cloud.google.com")
|
IMG_SRC_LIST.append("https://storage.cloud.google.com")
|
||||||
IMG_SRC_LIST.append(f"https://{GS_BUCKET_NAME}.storage.googleapis.com")
|
IMG_SRC_LIST.append(f"https://{GS_BUCKET_NAME}.storage.googleapis.com")
|
||||||
|
|
||||||
# SFTP/Dropbox typically serve through Django (using 'self' origin)
|
# SFTP/Dropbox typically serve through Django (using 'self' origin)
|
||||||
elif STORAGE_BACKEND == 'sftp':
|
elif STORAGE_BACKEND == 'sftp':
|
||||||
# SFTP files are retrieved by Django and served through Django views
|
# SFTP files are retrieved by Django and served through Django views
|
||||||
@@ -254,17 +253,17 @@ data:
|
|||||||
SFTP_CUSTOM_DOMAIN = os.environ.get('SFTP_CUSTOM_DOMAIN')
|
SFTP_CUSTOM_DOMAIN = os.environ.get('SFTP_CUSTOM_DOMAIN')
|
||||||
if SFTP_CUSTOM_DOMAIN:
|
if SFTP_CUSTOM_DOMAIN:
|
||||||
IMG_SRC_LIST.append(f"https://{SFTP_CUSTOM_DOMAIN}")
|
IMG_SRC_LIST.append(f"https://{SFTP_CUSTOM_DOMAIN}")
|
||||||
|
|
||||||
elif STORAGE_BACKEND == 'dropbox':
|
elif STORAGE_BACKEND == 'dropbox':
|
||||||
# Dropbox files are retrieved by Django and served through Django views
|
# Dropbox files are retrieved by Django and served through Django views
|
||||||
# No additional CSP configuration needed
|
# No additional CSP configuration needed
|
||||||
pass
|
pass
|
||||||
|
|
||||||
CSP_IMG_SRC_EXTRA = os.environ.get('CSP_IMG_SRC_EXTRA', '')
|
CSP_IMG_SRC_EXTRA = os.environ.get('CSP_IMG_SRC_EXTRA', '')
|
||||||
if CSP_IMG_SRC_EXTRA:
|
if CSP_IMG_SRC_EXTRA:
|
||||||
extra_domains = [domain.strip() for domain in CSP_IMG_SRC_EXTRA.split(',') if domain.strip()]
|
extra_domains = [domain.strip() for domain in CSP_IMG_SRC_EXTRA.split(',') if domain.strip()]
|
||||||
IMG_SRC_LIST.extend(extra_domains)
|
IMG_SRC_LIST.extend(extra_domains)
|
||||||
|
|
||||||
CONTENT_SECURITY_POLICY = {
|
CONTENT_SECURITY_POLICY = {
|
||||||
"DIRECTIVES": {
|
"DIRECTIVES": {
|
||||||
"default-src": ["'self'"],
|
"default-src": ["'self'"],
|
||||||
@@ -277,7 +276,7 @@ data:
|
|||||||
"frame-ancestors": FRAME_ANCESTORS,
|
"frame-ancestors": FRAME_ANCESTORS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'myapp',
|
'myapp',
|
||||||
@@ -291,7 +290,7 @@ data:
|
|||||||
'csp',
|
'csp',
|
||||||
'storages',
|
'storages',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
@@ -303,9 +302,9 @@ data:
|
|||||||
'django_http_referrer_policy.middleware.ReferrerPolicyMiddleware',
|
'django_http_referrer_policy.middleware.ReferrerPolicyMiddleware',
|
||||||
'csp.middleware.CSPMiddleware',
|
'csp.middleware.CSPMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'myproject.urls'
|
ROOT_URLCONF = 'myproject.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
@@ -321,29 +320,29 @@ data:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite3")
|
DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite3")
|
||||||
|
|
||||||
if DB_ENGINE == "sqlite3":
|
if DB_ENGINE == "sqlite3":
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': os.path.join(BASE_DIR, 'database', 'db.sqlite3'),
|
'NAME': os.path.join(BASE_DIR, 'database', 'db.sqlite3'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
elif DB_ENGINE == "postgres":
|
elif DB_ENGINE == "postgres":
|
||||||
|
|
||||||
DB_HOST = os.environ.get("POSTGRES_HOST", "db")
|
DB_HOST = os.environ.get("POSTGRES_HOST", "db")
|
||||||
DB_PORT = os.environ.get("POSTGRES_PORT", "5432")
|
DB_PORT = os.environ.get("POSTGRES_PORT", "5432")
|
||||||
DB_USER = os.environ.get("POSTGRES_USER", "memelord")
|
DB_USER = os.environ.get("POSTGRES_USER", "memelord")
|
||||||
DB_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "memelord")
|
DB_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "memelord")
|
||||||
DB_NAME = os.environ.get("POSTGRES_DB", "memelord")
|
DB_NAME = os.environ.get("POSTGRES_DB", "memelord")
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
@@ -354,10 +353,10 @@ data:
|
|||||||
'PASSWORD': DB_PASSWORD,
|
'PASSWORD': DB_PASSWORD,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
@@ -372,7 +371,7 @@ data:
|
|||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
@@ -380,23 +379,23 @@ data:
|
|||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
LANGUAGES = [
|
LANGUAGES = [
|
||||||
('en', _('English')),
|
('en', _('English')),
|
||||||
('de', _('German')),
|
('de', _('German')),
|
||||||
('fr', _('French')),
|
('fr', _('French')),
|
||||||
('it', _('Italian')),
|
('it', _('Italian')),
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCALE_PATHS = [
|
LOCALE_PATHS = [
|
||||||
os.path.join(BASE_DIR, 'locale')
|
os.path.join(BASE_DIR, 'locale')
|
||||||
]
|
]
|
||||||
|
|
||||||
# Celery configuration
|
# Celery configuration
|
||||||
# http://docs.celeryproject.org/en/latest/configuration.html
|
# http://docs.celeryproject.org/en/latest/configuration.html
|
||||||
|
|
||||||
LOGS_DIR = os.path.join(BASE_DIR, 'logs')
|
LOGS_DIR = os.path.join(BASE_DIR, 'logs')
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# LOGGING CONFIGURATION
|
# LOGGING CONFIGURATION
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -458,30 +457,30 @@ data:
|
|||||||
'level': 'INFO',
|
'level': 'INFO',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create logs directory if it doesn't exist
|
# Create logs directory if it doesn't exist
|
||||||
os.makedirs(LOGS_DIR, exist_ok=True)
|
os.makedirs(LOGS_DIR, exist_ok=True)
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'myapp', 'static')
|
STATIC_ROOT = os.path.join(BASE_DIR, 'myapp', 'static')
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
LOGIN_URL = '/accounts/login/'
|
LOGIN_URL = '/accounts/login/'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
LOGOUT_REDIRECT_URL = '/post-logout/'
|
LOGOUT_REDIRECT_URL = '/post-logout/'
|
||||||
ALLOW_LOGOUT_GET_METHOD = True
|
ALLOW_LOGOUT_GET_METHOD = True
|
||||||
|
|
||||||
WSGI_APPLICATION = 'myproject.wsgi.application'
|
WSGI_APPLICATION = 'myproject.wsgi.application'
|
||||||
|
|
||||||
# check if oidc is enabled
|
# check if oidc is enabled
|
||||||
OIDC_ENABLED = os.environ.get('OIDC_ENABLED', 'False').lower() in ['true']
|
OIDC_ENABLED = os.environ.get('OIDC_ENABLED', 'False').lower() in ['true']
|
||||||
OIDC_AUTOLOGIN = os.environ.get('OIDC_AUTOLOGIN', 'False').lower() in ['true']
|
OIDC_AUTOLOGIN = os.environ.get('OIDC_AUTOLOGIN', 'False').lower() in ['true']
|
||||||
|
|
||||||
# Max file upload size in MB (default: 10MB)
|
# Max file upload size in MB (default: 10MB)
|
||||||
# Can be configured via MAX_UPLOAD_SIZE_MB environment variable
|
# 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
|
MAX_UPLOAD_SIZE_MB = int(os.environ.get('MAX_UPLOAD_SIZE_MB', '10')) * 1024 * 1024
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# STORAGE BACKEND CONFIGURATION
|
# STORAGE BACKEND CONFIGURATION
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -489,9 +488,9 @@ data:
|
|||||||
# Configure storage backend via STORAGE_BACKEND environment variable
|
# Configure storage backend via STORAGE_BACKEND environment variable
|
||||||
# Supported backends: local, s3, azure, gcs, sftp, dropbox
|
# Supported backends: local, s3, azure, gcs, sftp, dropbox
|
||||||
# Default: local (filesystem storage)
|
# Default: local (filesystem storage)
|
||||||
|
|
||||||
STORAGE_BACKEND = os.environ.get('STORAGE_BACKEND', 'local').lower()
|
STORAGE_BACKEND = os.environ.get('STORAGE_BACKEND', 'local').lower()
|
||||||
|
|
||||||
# Django 5.0+ uses STORAGES instead of DEFAULT_FILE_STORAGE
|
# Django 5.0+ uses STORAGES instead of DEFAULT_FILE_STORAGE
|
||||||
# Set both for compatibility
|
# Set both for compatibility
|
||||||
if STORAGE_BACKEND == 's3':
|
if STORAGE_BACKEND == 's3':
|
||||||
@@ -509,14 +508,14 @@ data:
|
|||||||
AWS_QUERYSTRING_AUTH = os.environ.get('AWS_QUERYSTRING_AUTH', 'True').lower() in ['true']
|
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_S3_FILE_OVERWRITE = os.environ.get('AWS_S3_FILE_OVERWRITE', 'False').lower() in ['true']
|
||||||
AWS_LOCATION = os.environ.get('AWS_LOCATION', 'media')
|
AWS_LOCATION = os.environ.get('AWS_LOCATION', 'media')
|
||||||
|
|
||||||
# Use signature version 4 (required for all regions)
|
# Use signature version 4 (required for all regions)
|
||||||
AWS_S3_SIGNATURE_VERSION = 's3v4'
|
AWS_S3_SIGNATURE_VERSION = 's3v4'
|
||||||
|
|
||||||
# Use virtual-hosted-style URLs (bucket-name.s3.region.amazonaws.com)
|
# Use virtual-hosted-style URLs (bucket-name.s3.region.amazonaws.com)
|
||||||
# This is the default and recommended format
|
# This is the default and recommended format
|
||||||
AWS_S3_ADDRESSING_STYLE = 'path'
|
AWS_S3_ADDRESSING_STYLE = 'path'
|
||||||
|
|
||||||
# Django 5.0+ STORAGES configuration
|
# Django 5.0+ STORAGES configuration
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
@@ -526,10 +525,10 @@ data:
|
|||||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Legacy setting for older Django versions
|
# Legacy setting for older Django versions
|
||||||
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||||
|
|
||||||
# Update MEDIA_URL
|
# Update MEDIA_URL
|
||||||
if AWS_S3_CUSTOM_DOMAIN:
|
if AWS_S3_CUSTOM_DOMAIN:
|
||||||
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/'
|
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/'
|
||||||
@@ -541,9 +540,9 @@ data:
|
|||||||
MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com/{AWS_LOCATION}/'
|
MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.{AWS_S3_REGION_NAME}.amazonaws.com/{AWS_LOCATION}/'
|
||||||
else:
|
else:
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
elif STORAGE_BACKEND == 'azure':
|
elif STORAGE_BACKEND == 'azure':
|
||||||
# Microsoft Azure Blob Storage
|
# Microsoft Azure Blob Storage
|
||||||
AZURE_ACCOUNT_NAME = os.environ.get('AZURE_ACCOUNT_NAME')
|
AZURE_ACCOUNT_NAME = os.environ.get('AZURE_ACCOUNT_NAME')
|
||||||
@@ -557,7 +556,7 @@ data:
|
|||||||
AZURE_OVERWRITE_FILES = os.environ.get('AZURE_OVERWRITE_FILES', 'False').lower() in ['true']
|
AZURE_OVERWRITE_FILES = os.environ.get('AZURE_OVERWRITE_FILES', 'False').lower() in ['true']
|
||||||
AZURE_LOCATION = os.environ.get('AZURE_LOCATION', '')
|
AZURE_LOCATION = os.environ.get('AZURE_LOCATION', '')
|
||||||
AZURE_CUSTOM_DOMAIN = os.environ.get('AZURE_CUSTOM_DOMAIN')
|
AZURE_CUSTOM_DOMAIN = os.environ.get('AZURE_CUSTOM_DOMAIN')
|
||||||
|
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "storages.backends.azure_storage.AzureStorage",
|
"BACKEND": "storages.backends.azure_storage.AzureStorage",
|
||||||
@@ -566,18 +565,18 @@ data:
|
|||||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_FILE_STORAGE = 'storages.backends.azure_storage.AzureStorage'
|
DEFAULT_FILE_STORAGE = 'storages.backends.azure_storage.AzureStorage'
|
||||||
|
|
||||||
if AZURE_CUSTOM_DOMAIN:
|
if AZURE_CUSTOM_DOMAIN:
|
||||||
MEDIA_URL = f'https://{AZURE_CUSTOM_DOMAIN}/{AZURE_LOCATION}'
|
MEDIA_URL = f'https://{AZURE_CUSTOM_DOMAIN}/{AZURE_LOCATION}'
|
||||||
elif AZURE_ACCOUNT_NAME:
|
elif AZURE_ACCOUNT_NAME:
|
||||||
MEDIA_URL = f'https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net/{AZURE_CONTAINER}/{AZURE_LOCATION}'
|
MEDIA_URL = f'https://{AZURE_ACCOUNT_NAME}.blob.core.windows.net/{AZURE_CONTAINER}/{AZURE_LOCATION}'
|
||||||
else:
|
else:
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
elif STORAGE_BACKEND == 'gcs':
|
elif STORAGE_BACKEND == 'gcs':
|
||||||
# Google Cloud Storage
|
# Google Cloud Storage
|
||||||
GS_BUCKET_NAME = os.environ.get('GS_BUCKET_NAME')
|
GS_BUCKET_NAME = os.environ.get('GS_BUCKET_NAME')
|
||||||
@@ -588,7 +587,7 @@ data:
|
|||||||
GS_LOCATION = os.environ.get('GS_LOCATION', 'media')
|
GS_LOCATION = os.environ.get('GS_LOCATION', 'media')
|
||||||
GS_CUSTOM_ENDPOINT = os.environ.get('GS_CUSTOM_ENDPOINT')
|
GS_CUSTOM_ENDPOINT = os.environ.get('GS_CUSTOM_ENDPOINT')
|
||||||
GS_QUERYSTRING_AUTH = os.environ.get('GS_QUERYSTRING_AUTH', 'True').lower() in ['true']
|
GS_QUERYSTRING_AUTH = os.environ.get('GS_QUERYSTRING_AUTH', 'True').lower() in ['true']
|
||||||
|
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
|
"BACKEND": "storages.backends.gcloud.GoogleCloudStorage",
|
||||||
@@ -597,18 +596,18 @@ data:
|
|||||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_FILE_STORAGE = 'storages.backends.gcloud.GoogleCloudStorage'
|
DEFAULT_FILE_STORAGE = 'storages.backends.gcloud.GoogleCloudStorage'
|
||||||
|
|
||||||
if GS_CUSTOM_ENDPOINT:
|
if GS_CUSTOM_ENDPOINT:
|
||||||
MEDIA_URL = f'{GS_CUSTOM_ENDPOINT}/{GS_LOCATION}/'
|
MEDIA_URL = f'{GS_CUSTOM_ENDPOINT}/{GS_LOCATION}/'
|
||||||
elif GS_BUCKET_NAME:
|
elif GS_BUCKET_NAME:
|
||||||
MEDIA_URL = f'https://storage.googleapis.com/{GS_BUCKET_NAME}/{GS_LOCATION}/'
|
MEDIA_URL = f'https://storage.googleapis.com/{GS_BUCKET_NAME}/{GS_LOCATION}/'
|
||||||
else:
|
else:
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
elif STORAGE_BACKEND == 'sftp':
|
elif STORAGE_BACKEND == 'sftp':
|
||||||
# SFTP Storage
|
# SFTP Storage
|
||||||
SFTP_STORAGE_HOST = os.environ.get('SFTP_STORAGE_HOST')
|
SFTP_STORAGE_HOST = os.environ.get('SFTP_STORAGE_HOST')
|
||||||
@@ -625,7 +624,7 @@ data:
|
|||||||
SFTP_STORAGE_UID = os.environ.get('SFTP_STORAGE_UID')
|
SFTP_STORAGE_UID = os.environ.get('SFTP_STORAGE_UID')
|
||||||
SFTP_STORAGE_GID = os.environ.get('SFTP_STORAGE_GID')
|
SFTP_STORAGE_GID = os.environ.get('SFTP_STORAGE_GID')
|
||||||
SFTP_KNOWN_HOST_FILE = os.environ.get('SFTP_KNOWN_HOST_FILE')
|
SFTP_KNOWN_HOST_FILE = os.environ.get('SFTP_KNOWN_HOST_FILE')
|
||||||
|
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "storages.backends.sftpstorage.SFTPStorage",
|
"BACKEND": "storages.backends.sftpstorage.SFTPStorage",
|
||||||
@@ -634,18 +633,18 @@ data:
|
|||||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_FILE_STORAGE = 'storages.backends.sftpstorage.SFTPStorage'
|
DEFAULT_FILE_STORAGE = 'storages.backends.sftpstorage.SFTPStorage'
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
elif STORAGE_BACKEND == 'dropbox':
|
elif STORAGE_BACKEND == 'dropbox':
|
||||||
# Dropbox Storage
|
# Dropbox Storage
|
||||||
DROPBOX_OAUTH2_TOKEN = os.environ.get('DROPBOX_OAUTH2_TOKEN')
|
DROPBOX_OAUTH2_TOKEN = os.environ.get('DROPBOX_OAUTH2_TOKEN')
|
||||||
DROPBOX_ROOT_PATH = os.environ.get('DROPBOX_ROOT_PATH', '/media')
|
DROPBOX_ROOT_PATH = os.environ.get('DROPBOX_ROOT_PATH', '/media')
|
||||||
DROPBOX_TIMEOUT = int(os.environ.get('DROPBOX_TIMEOUT', '100'))
|
DROPBOX_TIMEOUT = int(os.environ.get('DROPBOX_TIMEOUT', '100'))
|
||||||
DROPBOX_WRITE_MODE = os.environ.get('DROPBOX_WRITE_MODE', 'add')
|
DROPBOX_WRITE_MODE = os.environ.get('DROPBOX_WRITE_MODE', 'add')
|
||||||
|
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "storages.backends.dropbox.DropBoxStorage",
|
"BACKEND": "storages.backends.dropbox.DropBoxStorage",
|
||||||
@@ -654,11 +653,11 @@ data:
|
|||||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_FILE_STORAGE = 'storages.backends.dropbox.DropBoxStorage'
|
DEFAULT_FILE_STORAGE = 'storages.backends.dropbox.DropBoxStorage'
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Local filesystem storage (default)
|
# Local filesystem storage (default)
|
||||||
STORAGES = {
|
STORAGES = {
|
||||||
@@ -669,11 +668,11 @@ data:
|
|||||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
if OIDC_ENABLED:
|
if OIDC_ENABLED:
|
||||||
# get oidc config from env
|
# get oidc config from env
|
||||||
OIDC_CREATE_USER = os.environ.get('OIDC_CREATE_USER', 'True').lower() in ['true']
|
OIDC_CREATE_USER = os.environ.get('OIDC_CREATE_USER', 'True').lower() in ['true']
|
||||||
@@ -687,20 +686,20 @@ data:
|
|||||||
OIDC_OP_USER_ENDPOINT = os.environ.get('OIDC_OP_USER_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_RENEW_ID_TOKEN_EXPIRY_SECONDS = float(os.environ.get('OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS', 900))
|
||||||
OIDC_USERNAME_ALGO = 'myapp.utils.generate_username'
|
OIDC_USERNAME_ALGO = 'myapp.utils.generate_username'
|
||||||
|
|
||||||
# Add 'mozilla_django_oidc.middleware.SessionRefresh' to INSTALLED_APPS
|
# Add 'mozilla_django_oidc.middleware.SessionRefresh' to INSTALLED_APPS
|
||||||
INSTALLED_APPS.append('mozilla_django_oidc')
|
INSTALLED_APPS.append('mozilla_django_oidc')
|
||||||
|
|
||||||
# Add 'mozilla_django_oidc' authentication backend
|
# Add 'mozilla_django_oidc' authentication backend
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
'mozilla_django_oidc.auth.OIDCAuthenticationBackend',
|
'mozilla_django_oidc.auth.OIDCAuthenticationBackend',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add 'mozilla_django_oidc.middleware.SessionRefresh' to MIDDLEWARE
|
# Add 'mozilla_django_oidc.middleware.SessionRefresh' to MIDDLEWARE
|
||||||
# https://mozilla-django-oidc.readthedocs.io/en/stable/installation.html#validate-id-tokens-by-renewing-them
|
# https://mozilla-django-oidc.readthedocs.io/en/stable/installation.html#validate-id-tokens-by-renewing-them
|
||||||
MIDDLEWARE.append('mozilla_django_oidc.middleware.SessionRefresh')
|
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
|
# 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
|
# OIDC should only be setup behind a TLS reverse proxy anyways
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
@@ -1,177 +1,177 @@
|
|||||||
---
|
---
|
||||||
apiVersion: secretgenerator.mittwald.de/v1alpha1
|
apiVersion: secretgenerator.mittwald.de/v1alpha1
|
||||||
kind: StringSecret
|
kind: StringSecret
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake-redis
|
name: {{ .Release.Name }}-redis
|
||||||
spec:
|
spec:
|
||||||
fields:
|
fields:
|
||||||
- fieldName: redis-password
|
- fieldName: redis-password
|
||||||
length: "32"
|
length: "32"
|
||||||
encoding: hex
|
encoding: hex
|
||||||
---
|
---
|
||||||
apiVersion: dragonflydb.io/v1alpha1
|
apiVersion: dragonflydb.io/v1alpha1
|
||||||
kind: Dragonfly
|
kind: Dragonfly
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake-redis
|
name: {{ .Release.Name }}-redis
|
||||||
spec:
|
spec:
|
||||||
authentication:
|
authentication:
|
||||||
passwordFromSecret:
|
passwordFromSecret:
|
||||||
name: memelord-jake-redis
|
name: {{ .Release.Name }}-redis
|
||||||
key: redis-password
|
key: redis-password
|
||||||
replicas: 1
|
replicas: 1
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 500m
|
cpu: 500m
|
||||||
memory: 500Mi
|
memory: 500Mi
|
||||||
limits:
|
limits:
|
||||||
cpu: 600m
|
cpu: 600m
|
||||||
memory: 750Mi
|
memory: 750Mi
|
||||||
---
|
---
|
||||||
apiVersion: secretgenerator.mittwald.de/v1alpha1
|
apiVersion: secretgenerator.mittwald.de/v1alpha1
|
||||||
kind: StringSecret
|
kind: StringSecret
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake-database
|
name: {{ .Release.Name }}-database
|
||||||
labels:
|
labels:
|
||||||
cnpg.io/reload: "true"
|
cnpg.io/reload: "true"
|
||||||
spec:
|
spec:
|
||||||
data:
|
data:
|
||||||
username: memelord-jake
|
username: {{ .Release.Name }}
|
||||||
fields:
|
fields:
|
||||||
- fieldName: password
|
- fieldName: password
|
||||||
length: "32"
|
length: "32"
|
||||||
encoding: hex
|
encoding: hex
|
||||||
---
|
---
|
||||||
apiVersion: postgresql.cnpg.io/v1
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
kind: Cluster
|
kind: Cluster
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake-database
|
name: {{ .Release.Name }}-database
|
||||||
spec:
|
spec:
|
||||||
instances: 1
|
instances: 1
|
||||||
imageName: ghcr.io/cloudnative-pg/postgresql:17
|
imageName: ghcr.io/cloudnative-pg/postgresql:17
|
||||||
storage:
|
storage:
|
||||||
size: 1Gi
|
size: 1Gi
|
||||||
storageClass: postgres
|
storageClass: postgres
|
||||||
affinity:
|
affinity:
|
||||||
podAntiAffinityType: required
|
podAntiAffinityType: required
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
codemowers.io/lvm-ubuntu-vg: enterprise-ssd
|
codemowers.io/lvm-ubuntu-vg: enterprise-ssd
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "1"
|
cpu: "1"
|
||||||
memory: "4Gi"
|
memory: "4Gi"
|
||||||
postgresql:
|
postgresql:
|
||||||
parameters:
|
parameters:
|
||||||
max_connections: "300"
|
max_connections: "300"
|
||||||
shared_buffers: "512MB"
|
shared_buffers: "512MB"
|
||||||
effective_cache_size: "2GB"
|
effective_cache_size: "2GB"
|
||||||
managed:
|
managed:
|
||||||
roles:
|
roles:
|
||||||
- name: memelord-jake
|
- name: {{ .Release.Name }}
|
||||||
ensure: present
|
ensure: present
|
||||||
login: true
|
login: true
|
||||||
passwordSecret:
|
passwordSecret:
|
||||||
name: memelord-jake-database
|
name: {{ .Release.Name }}-database
|
||||||
---
|
---
|
||||||
apiVersion: postgresql.cnpg.io/v1
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
kind: Database
|
kind: Database
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake
|
name: {{ .Release.Name }}
|
||||||
spec:
|
spec:
|
||||||
name: memelord-jake
|
name: {{ .Release.Name }}
|
||||||
owner: memelord-jake
|
owner: {{ .Release.Name }}
|
||||||
cluster:
|
cluster:
|
||||||
name: memelord-jake-database
|
name: {{ .Release.Name }}-database
|
||||||
---
|
---
|
||||||
apiVersion: s3.onyxia.sh/v1alpha1
|
apiVersion: s3.onyxia.sh/v1alpha1
|
||||||
kind: Policy
|
kind: Policy
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake-policy
|
name: {{ .Release.Name }}-policy
|
||||||
spec:
|
spec:
|
||||||
name: memelord-jake-policy
|
name: {{ .Release.Name }}-policy
|
||||||
s3InstanceRef: minio/default
|
s3InstanceRef: minio/default
|
||||||
policyContent: >-
|
policyContent: >-
|
||||||
{
|
{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"s3:*"
|
"s3:*"
|
||||||
],
|
],
|
||||||
"Resource": [
|
"Resource": [
|
||||||
"arn:aws:s3:::memelord-jake",
|
"arn:aws:s3:::{{ .Release.Name }}",
|
||||||
"arn:aws:s3:::memelord-jake/*"
|
"arn:aws:s3:::{{ .Release.Name }}/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
apiVersion: s3.onyxia.sh/v1alpha1
|
apiVersion: s3.onyxia.sh/v1alpha1
|
||||||
kind: S3User
|
kind: S3User
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake-bucket
|
name: {{ .Release.Name }}-bucket
|
||||||
spec:
|
spec:
|
||||||
accessKey: memelord-jake-bucket
|
accessKey: {{ .Release.Name }}-bucket
|
||||||
policies:
|
policies:
|
||||||
- memelord-jake-policy
|
- {{ .Release.Name }}-policy
|
||||||
s3InstanceRef: minio/default
|
s3InstanceRef: minio/default
|
||||||
---
|
---
|
||||||
apiVersion: s3.onyxia.sh/v1alpha1
|
apiVersion: s3.onyxia.sh/v1alpha1
|
||||||
kind: Bucket
|
kind: Bucket
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake
|
name: {{ .Release.Name }}
|
||||||
spec:
|
spec:
|
||||||
name: memelord-jake
|
name: {{ .Release.Name }}
|
||||||
s3InstanceRef: minio/default
|
s3InstanceRef: minio/default
|
||||||
quota:
|
quota:
|
||||||
default: 100000000
|
default: 100000000
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord
|
name: {{ .Release.Name }}
|
||||||
spec:
|
spec:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
selector:
|
selector:
|
||||||
app: memelord
|
app: {{ .Release.Name }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
port: 80
|
port: 80
|
||||||
targetPort: 8000
|
targetPort: 8000
|
||||||
---
|
---
|
||||||
apiVersion: cert-manager.io/v1
|
apiVersion: cert-manager.io/v1
|
||||||
kind: Certificate
|
kind: Certificate
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake
|
name: {{ .Release.Name }}
|
||||||
spec:
|
spec:
|
||||||
secretName: memelord-jake-tls
|
secretName: {{ .Release.Name }}-tls
|
||||||
dnsNames:
|
dnsNames:
|
||||||
- memelord-jake.ee-lte-1.codemowers.io
|
- {{ .Values.hostname }}
|
||||||
issuerRef:
|
issuerRef:
|
||||||
name: letsencrypt
|
name: letsencrypt
|
||||||
kind: ClusterIssuer
|
kind: ClusterIssuer
|
||||||
---
|
---
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake
|
name: {{ .Release.Name }}
|
||||||
annotations:
|
annotations:
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: traefik
|
ingressClassName: traefik
|
||||||
rules:
|
rules:
|
||||||
- host: memelord-jake.ee-lte-1.codemowers.io
|
- host: {{ .Values.hostname }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- pathType: Prefix
|
- pathType: Prefix
|
||||||
path: "/"
|
path: "/"
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: memelord
|
name: {{ .Release.Name }}
|
||||||
port:
|
port:
|
||||||
number: 80
|
number: 80
|
||||||
tls:
|
tls:
|
||||||
- secretName: memelord-jake-tls
|
- secretName: {{ .Release.Name }}-tls
|
||||||
@@ -1,484 +1,469 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Namespace
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-jake
|
name: grafana-provisioning
|
||||||
---
|
data:
|
||||||
apiVersion: v1
|
datasources.yaml: |
|
||||||
kind: ConfigMap
|
apiVersion: 1
|
||||||
metadata:
|
datasources:
|
||||||
name: grafana-provisioning
|
- name: Prometheus
|
||||||
namespace: memelord-jake
|
type: prometheus
|
||||||
data:
|
access: proxy
|
||||||
datasources.yaml: |
|
url: http://prometheus-operated.monitoring.svc.cluster.local:9090
|
||||||
apiVersion: 1
|
isDefault: true
|
||||||
datasources:
|
- name: Loki
|
||||||
- name: Prometheus
|
type: loki
|
||||||
type: prometheus
|
access: proxy
|
||||||
access: proxy
|
url: http://loki.monitoring.svc.cluster.local:3100
|
||||||
url: http://prometheus-operated.monitoring.svc.cluster.local:9090
|
dashboards.yaml: |
|
||||||
isDefault: true
|
apiVersion: 1
|
||||||
- name: Loki
|
providers:
|
||||||
type: loki
|
- name: 'Default'
|
||||||
access: proxy
|
orgId: 1
|
||||||
url: http://loki.monitoring.svc.cluster.local:3100
|
folder: ''
|
||||||
dashboards.yaml: |
|
type: file
|
||||||
apiVersion: 1
|
disableDeletion: false
|
||||||
providers:
|
editable: true
|
||||||
- name: 'Default'
|
options:
|
||||||
orgId: 1
|
path: /var/lib/grafana/dashboards
|
||||||
folder: ''
|
---
|
||||||
type: file
|
apiVersion: v1
|
||||||
disableDeletion: false
|
kind: ConfigMap
|
||||||
editable: true
|
metadata:
|
||||||
options:
|
name: grafana-dashboards
|
||||||
path: /var/lib/grafana/dashboards
|
data:
|
||||||
---
|
log-aggregator.json: |
|
||||||
apiVersion: v1
|
{
|
||||||
kind: ConfigMap
|
"annotations": {
|
||||||
metadata:
|
"list": [
|
||||||
name: grafana-dashboards
|
{
|
||||||
namespace: memelord-jake
|
"builtIn": 1,
|
||||||
data:
|
"datasource": {
|
||||||
log-aggregator.json: |
|
"type": "grafana",
|
||||||
{
|
"uid": "-- Grafana --"
|
||||||
"annotations": {
|
},
|
||||||
"list": [
|
"enable": true,
|
||||||
{
|
"hide": true,
|
||||||
"builtIn": 1,
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
"datasource": {
|
"name": "Annotations & Alerts",
|
||||||
"type": "grafana",
|
"type": "dashboard"
|
||||||
"uid": "-- Grafana --"
|
}
|
||||||
},
|
]
|
||||||
"enable": true,
|
},
|
||||||
"hide": true,
|
"editable": true,
|
||||||
"iconColor": "rgba(0, 211, 255, 1)",
|
"fiscalYearStartMonth": 0,
|
||||||
"name": "Annotations & Alerts",
|
"graphTooltip": 0,
|
||||||
"type": "dashboard"
|
"id": 1,
|
||||||
}
|
"links": [],
|
||||||
]
|
"panels": [
|
||||||
},
|
{
|
||||||
"editable": true,
|
"datasource": {
|
||||||
"fiscalYearStartMonth": 0,
|
"type": "loki",
|
||||||
"graphTooltip": 0,
|
"uid": "P8E80F9AEF21F6940"
|
||||||
"id": 1,
|
},
|
||||||
"links": [],
|
"fieldConfig": {
|
||||||
"panels": [
|
"defaults": {
|
||||||
{
|
"color": {
|
||||||
"datasource": {
|
"mode": "palette-classic"
|
||||||
"type": "loki",
|
},
|
||||||
"uid": "P8E80F9AEF21F6940"
|
"custom": {
|
||||||
},
|
"axisBorderShow": false,
|
||||||
"fieldConfig": {
|
"axisCenteredZero": false,
|
||||||
"defaults": {
|
"axisColorMode": "text",
|
||||||
"color": {
|
"axisLabel": "",
|
||||||
"mode": "palette-classic"
|
"axisPlacement": "auto",
|
||||||
},
|
"barAlignment": 0,
|
||||||
"custom": {
|
"barWidthFactor": 0.6,
|
||||||
"axisBorderShow": false,
|
"drawStyle": "line",
|
||||||
"axisCenteredZero": false,
|
"fillOpacity": 0,
|
||||||
"axisColorMode": "text",
|
"gradientMode": "opacity",
|
||||||
"axisLabel": "",
|
"hideFrom": {
|
||||||
"axisPlacement": "auto",
|
"legend": false,
|
||||||
"barAlignment": 0,
|
"tooltip": false,
|
||||||
"barWidthFactor": 0.6,
|
"viz": false
|
||||||
"drawStyle": "line",
|
},
|
||||||
"fillOpacity": 0,
|
"insertNulls": false,
|
||||||
"gradientMode": "opacity",
|
"lineInterpolation": "linear",
|
||||||
"hideFrom": {
|
"lineWidth": 1,
|
||||||
"legend": false,
|
"pointSize": 5,
|
||||||
"tooltip": false,
|
"scaleDistribution": {
|
||||||
"viz": false
|
"type": "linear"
|
||||||
},
|
},
|
||||||
"insertNulls": false,
|
"showPoints": "auto",
|
||||||
"lineInterpolation": "linear",
|
"showValues": false,
|
||||||
"lineWidth": 1,
|
"spanNulls": false,
|
||||||
"pointSize": 5,
|
"stacking": {
|
||||||
"scaleDistribution": {
|
"group": "A",
|
||||||
"type": "linear"
|
"mode": "normal"
|
||||||
},
|
},
|
||||||
"showPoints": "auto",
|
"thresholdsStyle": {
|
||||||
"showValues": false,
|
"mode": "off"
|
||||||
"spanNulls": false,
|
}
|
||||||
"stacking": {
|
},
|
||||||
"group": "A",
|
"mappings": [],
|
||||||
"mode": "normal"
|
"thresholds": {
|
||||||
},
|
"mode": "absolute",
|
||||||
"thresholdsStyle": {
|
"steps": [
|
||||||
"mode": "off"
|
{
|
||||||
}
|
"color": "green",
|
||||||
},
|
"value": 0
|
||||||
"mappings": [],
|
},
|
||||||
"thresholds": {
|
{
|
||||||
"mode": "absolute",
|
"color": "red",
|
||||||
"steps": [
|
"value": 80
|
||||||
{
|
}
|
||||||
"color": "green",
|
]
|
||||||
"value": 0
|
},
|
||||||
},
|
"unit": "msg/s"
|
||||||
{
|
},
|
||||||
"color": "red",
|
"overrides": []
|
||||||
"value": 80
|
},
|
||||||
}
|
"gridPos": {
|
||||||
]
|
"h": 8,
|
||||||
},
|
"w": 24,
|
||||||
"unit": "msg/s"
|
"x": 0,
|
||||||
},
|
"y": 0
|
||||||
"overrides": []
|
},
|
||||||
},
|
"id": 2,
|
||||||
"gridPos": {
|
"options": {
|
||||||
"h": 8,
|
"legend": {
|
||||||
"w": 24,
|
"calcs": [],
|
||||||
"x": 0,
|
"displayMode": "list",
|
||||||
"y": 0
|
"placement": "bottom",
|
||||||
},
|
"showLegend": true
|
||||||
"id": 2,
|
},
|
||||||
"options": {
|
"tooltip": {
|
||||||
"legend": {
|
"hideZeros": false,
|
||||||
"calcs": [],
|
"mode": "single",
|
||||||
"displayMode": "list",
|
"sort": "none"
|
||||||
"placement": "bottom",
|
}
|
||||||
"showLegend": true
|
},
|
||||||
},
|
"pluginVersion": "12.2.1",
|
||||||
"tooltip": {
|
"targets": [
|
||||||
"hideZeros": false,
|
{
|
||||||
"mode": "single",
|
"datasource": {
|
||||||
"sort": "none"
|
"type": "loki",
|
||||||
}
|
"uid": "P8E80F9AEF21F6940"
|
||||||
},
|
},
|
||||||
"pluginVersion": "12.2.1",
|
"direction": "backward",
|
||||||
"targets": [
|
"editorMode": "code",
|
||||||
{
|
"expr": "sum by (detected_level) (count_over_time ({app=~\"$app\",namespace=~\"$namespace\"}[1m]))",
|
||||||
"datasource": {
|
"legendFormat": "{{`{{detected_level}}`}}",
|
||||||
"type": "loki",
|
"queryType": "range",
|
||||||
"uid": "P8E80F9AEF21F6940"
|
"refId": "A"
|
||||||
},
|
}
|
||||||
"direction": "backward",
|
],
|
||||||
"editorMode": "code",
|
"title": "Log records",
|
||||||
"expr": "sum by (detected_level) (count_over_time ({app=~\"$app\",namespace=~\"$namespace\"}[1m]))",
|
"type": "timeseries"
|
||||||
"legendFormat": "{{detected_level}}",
|
},
|
||||||
"queryType": "range",
|
{
|
||||||
"refId": "A"
|
"datasource": {
|
||||||
}
|
"type": "loki",
|
||||||
],
|
"uid": "P8E80F9AEF21F6940"
|
||||||
"title": "Log records",
|
},
|
||||||
"type": "timeseries"
|
"fieldConfig": {
|
||||||
},
|
"defaults": {},
|
||||||
{
|
"overrides": []
|
||||||
"datasource": {
|
},
|
||||||
"type": "loki",
|
"gridPos": {
|
||||||
"uid": "P8E80F9AEF21F6940"
|
"h": 20,
|
||||||
},
|
"w": 24,
|
||||||
"fieldConfig": {
|
"x": 0,
|
||||||
"defaults": {},
|
"y": 8
|
||||||
"overrides": []
|
},
|
||||||
},
|
"id": 1,
|
||||||
"gridPos": {
|
"options": {
|
||||||
"h": 20,
|
"dedupStrategy": "none",
|
||||||
"w": 24,
|
"enableInfiniteScrolling": false,
|
||||||
"x": 0,
|
"enableLogDetails": true,
|
||||||
"y": 8
|
"prettifyLogMessage": true,
|
||||||
},
|
"showCommonLabels": true,
|
||||||
"id": 1,
|
"showLabels": true,
|
||||||
"options": {
|
"showTime": true,
|
||||||
"dedupStrategy": "none",
|
"sortOrder": "Descending",
|
||||||
"enableInfiniteScrolling": false,
|
"wrapLogMessage": true
|
||||||
"enableLogDetails": true,
|
},
|
||||||
"prettifyLogMessage": true,
|
"pluginVersion": "12.2.1",
|
||||||
"showCommonLabels": true,
|
"targets": [
|
||||||
"showLabels": true,
|
{
|
||||||
"showTime": true,
|
"datasource": {
|
||||||
"sortOrder": "Descending",
|
"type": "loki",
|
||||||
"wrapLogMessage": true
|
"uid": "P8E80F9AEF21F6940"
|
||||||
},
|
},
|
||||||
"pluginVersion": "12.2.1",
|
"direction": "backward",
|
||||||
"targets": [
|
"editorMode": "code",
|
||||||
{
|
"expr": "{app=~\"$app\",namespace=~\"$namespace\"}",
|
||||||
"datasource": {
|
"queryType": "range",
|
||||||
"type": "loki",
|
"refId": "A"
|
||||||
"uid": "P8E80F9AEF21F6940"
|
}
|
||||||
},
|
],
|
||||||
"direction": "backward",
|
"title": "Loki",
|
||||||
"editorMode": "code",
|
"type": "logs"
|
||||||
"expr": "{app=~\"$app\",namespace=~\"$namespace\"}",
|
}
|
||||||
"queryType": "range",
|
],
|
||||||
"refId": "A"
|
"preload": false,
|
||||||
}
|
"refresh": "30s",
|
||||||
],
|
"schemaVersion": 42,
|
||||||
"title": "Loki",
|
"tags": [],
|
||||||
"type": "logs"
|
"templating": {
|
||||||
}
|
"list": [
|
||||||
],
|
{
|
||||||
"preload": false,
|
"allValue": ".*",
|
||||||
"refresh": "30s",
|
"current": {
|
||||||
"schemaVersion": 42,
|
"text": "All",
|
||||||
"tags": [],
|
"value": [
|
||||||
"templating": {
|
"$__all"
|
||||||
"list": [
|
]
|
||||||
{
|
},
|
||||||
"allValue": ".*",
|
"datasource": {
|
||||||
"current": {
|
"type": "loki",
|
||||||
"text": "All",
|
"uid": "P8E80F9AEF21F6940"
|
||||||
"value": [
|
},
|
||||||
"$__all"
|
"definition": "",
|
||||||
]
|
"includeAll": true,
|
||||||
},
|
"multi": true,
|
||||||
"datasource": {
|
"name": "app",
|
||||||
"type": "loki",
|
"options": [],
|
||||||
"uid": "P8E80F9AEF21F6940"
|
"query": {
|
||||||
},
|
"label": "app",
|
||||||
"definition": "",
|
"refId": "LokiVariableQueryEditor-VariableQuery",
|
||||||
"includeAll": true,
|
"stream": "",
|
||||||
"multi": true,
|
"type": 1
|
||||||
"name": "app",
|
},
|
||||||
"options": [],
|
"refresh": 1,
|
||||||
"query": {
|
"regex": "",
|
||||||
"label": "app",
|
"sort": 5,
|
||||||
"refId": "LokiVariableQueryEditor-VariableQuery",
|
"type": "query"
|
||||||
"stream": "",
|
},
|
||||||
"type": 1
|
{
|
||||||
},
|
"allValue": ".+",
|
||||||
"refresh": 1,
|
"current": {
|
||||||
"regex": "",
|
"text": "All",
|
||||||
"sort": 5,
|
"value": [
|
||||||
"type": "query"
|
"$__all"
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
"allValue": ".+",
|
"datasource": {
|
||||||
"current": {
|
"type": "loki",
|
||||||
"text": "All",
|
"uid": "P8E80F9AEF21F6940"
|
||||||
"value": [
|
},
|
||||||
"$__all"
|
"definition": "",
|
||||||
]
|
"includeAll": true,
|
||||||
},
|
"label": "namespace",
|
||||||
"datasource": {
|
"multi": true,
|
||||||
"type": "loki",
|
"name": "namespace",
|
||||||
"uid": "P8E80F9AEF21F6940"
|
"options": [],
|
||||||
},
|
"query": {
|
||||||
"definition": "",
|
"label": "namespace",
|
||||||
"includeAll": true,
|
"refId": "LokiVariableQueryEditor-VariableQuery",
|
||||||
"label": "namespace",
|
"stream": "",
|
||||||
"multi": true,
|
"type": 1
|
||||||
"name": "namespace",
|
},
|
||||||
"options": [],
|
"refresh": 1,
|
||||||
"query": {
|
"regex": "",
|
||||||
"label": "namespace",
|
"type": "query"
|
||||||
"refId": "LokiVariableQueryEditor-VariableQuery",
|
}
|
||||||
"stream": "",
|
]
|
||||||
"type": 1
|
},
|
||||||
},
|
"time": {
|
||||||
"refresh": 1,
|
"from": "now-5m",
|
||||||
"regex": "",
|
"to": "now"
|
||||||
"type": "query"
|
},
|
||||||
}
|
"timepicker": {},
|
||||||
]
|
"timezone": "browser",
|
||||||
},
|
"title": "Log Aggregator",
|
||||||
"time": {
|
"uid": "lawf6g2",
|
||||||
"from": "now-5m",
|
"version": 1
|
||||||
"to": "now"
|
}
|
||||||
},
|
---
|
||||||
"timepicker": {},
|
apiVersion: apps/v1
|
||||||
"timezone": "browser",
|
kind: StatefulSet
|
||||||
"title": "Log Aggregator",
|
metadata:
|
||||||
"uid": "lawf6g2",
|
name: grafana
|
||||||
"version": 1
|
labels:
|
||||||
}
|
app: grafana
|
||||||
---
|
spec:
|
||||||
apiVersion: apps/v1
|
serviceName: grafana
|
||||||
kind: StatefulSet
|
replicas: 1
|
||||||
metadata:
|
selector:
|
||||||
name: grafana
|
matchLabels:
|
||||||
namespace: memelord-jake
|
app: grafana
|
||||||
labels:
|
template:
|
||||||
app: grafana
|
metadata:
|
||||||
spec:
|
labels:
|
||||||
serviceName: grafana
|
app: grafana
|
||||||
replicas: 1
|
spec:
|
||||||
selector:
|
securityContext:
|
||||||
matchLabels:
|
fsGroup: 472
|
||||||
app: grafana
|
containers:
|
||||||
template:
|
- name: grafana
|
||||||
metadata:
|
image: grafana/grafana:latest
|
||||||
labels:
|
imagePullPolicy: IfNotPresent
|
||||||
app: grafana
|
ports:
|
||||||
spec:
|
- containerPort: 3000
|
||||||
securityContext:
|
name: http
|
||||||
fsGroup: 472
|
env:
|
||||||
containers:
|
# sqlite DB on PVC
|
||||||
- name: grafana
|
- name: GF_DATABASE_TYPE
|
||||||
image: grafana/grafana:latest
|
value: sqlite3
|
||||||
imagePullPolicy: IfNotPresent
|
- name: GF_DATABASE_PATH
|
||||||
ports:
|
value: /var/lib/grafana/grafana.db
|
||||||
- containerPort: 3000
|
|
||||||
name: http
|
# Ingress URL (important for OAuth callback + links)
|
||||||
env:
|
- name: GF_SERVER_ROOT_URL
|
||||||
# sqlite DB on PVC
|
value: https://{{ .Values.grafanaHostname }}/
|
||||||
- name: GF_DATABASE_TYPE
|
- name: GF_SERVER_SERVE_FROM_SUB_PATH
|
||||||
value: sqlite3
|
value: "false"
|
||||||
- name: GF_DATABASE_PATH
|
|
||||||
value: /var/lib/grafana/grafana.db
|
# ---- OIDC (Passmower) via Generic OAuth ----
|
||||||
|
- name: GF_AUTH_GENERIC_OAUTH_ENABLED
|
||||||
# Ingress URL (important for OAuth callback + links)
|
value: "true"
|
||||||
- name: GF_SERVER_ROOT_URL
|
- name: GF_AUTH_GENERIC_OAUTH_NAME
|
||||||
value: https://grafana-jake.ee-lte-1.codemowers.io/
|
value: "Passmower"
|
||||||
- name: GF_SERVER_SERVE_FROM_SUB_PATH
|
- name: GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP
|
||||||
value: "false"
|
value: "true"
|
||||||
|
|
||||||
# ---- OIDC (Passmower) via Generic OAuth ----
|
- name: GF_AUTH_GENERIC_OAUTH_USE_ID_TOKEN
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_ENABLED
|
value: "false"
|
||||||
value: "true"
|
- name: GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_NAME
|
value: "contains(groups[*], 'github.com:codemowers:admins') && 'Admin' || 'Viewer'"
|
||||||
value: "Passmower"
|
# matches OIDCClient pkce: false
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP
|
- name: GF_AUTH_GENERIC_OAUTH_USE_PKCE
|
||||||
value: "true"
|
value: "false"
|
||||||
|
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_USE_ID_TOKEN
|
- name: GF_AUTH_GENERIC_OAUTH_CLIENT_ID
|
||||||
value: "false"
|
valueFrom:
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH
|
secretKeyRef:
|
||||||
value: "contains(groups[*], 'github.com:codemowers:admins') && 'Admin' || 'Viewer'"
|
name: oidc-client-grafana-{{ .Release.Name }}-owner-secrets
|
||||||
# matches OIDCClient pkce: false
|
key: OIDC_CLIENT_ID
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_USE_PKCE
|
- name: GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET
|
||||||
value: "false"
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
# IMPORTANT:
|
name: oidc-client-grafana-{{ .Release.Name }}-owner-secrets
|
||||||
# After OIDCClient grafana-jake is created successfully,
|
key: OIDC_CLIENT_SECRET
|
||||||
# set this secret name to the generated one (likely oidc-client-grafana-jake-owner-secrets)
|
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_CLIENT_ID
|
- name: GF_AUTH_GENERIC_OAUTH_SCOPES
|
||||||
valueFrom:
|
value: "openid profile groups"
|
||||||
secretKeyRef:
|
|
||||||
name: oidc-client-grafana-jake-owner-secrets
|
# From your existing OIDC secret: auth/token/me endpoints
|
||||||
key: OIDC_CLIENT_ID
|
- name: GF_AUTH_GENERIC_OAUTH_AUTH_URL
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET
|
value: "https://auth.ee-lte-1.codemowers.io/auth"
|
||||||
valueFrom:
|
- name: GF_AUTH_GENERIC_OAUTH_TOKEN_URL
|
||||||
secretKeyRef:
|
value: "https://auth.ee-lte-1.codemowers.io/token"
|
||||||
name: oidc-client-grafana-jake-owner-secrets
|
- name: GF_AUTH_GENERIC_OAUTH_API_URL
|
||||||
key: OIDC_CLIENT_SECRET
|
value: "https://auth.ee-lte-1.codemowers.io/me"
|
||||||
|
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_SCOPES
|
- name: GF_AUTH_GENERIC_OAUTH_SIGNOUT_REDIRECT_URL
|
||||||
value: "openid profile groups"
|
value: https://{{ .Values.grafanaHostname }}/
|
||||||
|
|
||||||
# From your existing OIDC secret: auth/token/me endpoints
|
volumeMounts:
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_AUTH_URL
|
- name: grafana-storage
|
||||||
value: "https://auth.ee-lte-1.codemowers.io/auth"
|
mountPath: /var/lib/grafana
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_TOKEN_URL
|
- name: grafana-provisioning
|
||||||
value: "https://auth.ee-lte-1.codemowers.io/token"
|
mountPath: /etc/grafana/provisioning/datasources/datasources.yaml
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_API_URL
|
subPath: datasources.yaml
|
||||||
value: "https://auth.ee-lte-1.codemowers.io/me"
|
readOnly: true
|
||||||
|
- name: grafana-provisioning
|
||||||
- name: GF_AUTH_GENERIC_OAUTH_SIGNOUT_REDIRECT_URL
|
mountPath: /etc/grafana/provisioning/dashboards/dashboards.yaml
|
||||||
value: https://grafana-jake.ee-lte-1.codemowers.io/
|
subPath: dashboards.yaml
|
||||||
|
readOnly: true
|
||||||
volumeMounts:
|
- name: grafana-dashboards
|
||||||
- name: grafana-storage
|
mountPath: /var/lib/grafana/dashboards
|
||||||
mountPath: /var/lib/grafana
|
readOnly: true
|
||||||
- name: grafana-provisioning
|
readinessProbe:
|
||||||
mountPath: /etc/grafana/provisioning/datasources/datasources.yaml
|
httpGet:
|
||||||
subPath: datasources.yaml
|
path: /api/health
|
||||||
readOnly: true
|
port: 3000
|
||||||
- name: grafana-provisioning
|
initialDelaySeconds: 10
|
||||||
mountPath: /etc/grafana/provisioning/dashboards/dashboards.yaml
|
periodSeconds: 10
|
||||||
subPath: dashboards.yaml
|
livenessProbe:
|
||||||
readOnly: true
|
httpGet:
|
||||||
- name: grafana-dashboards
|
path: /api/health
|
||||||
mountPath: /var/lib/grafana/dashboards
|
port: 3000
|
||||||
readOnly: true
|
initialDelaySeconds: 30
|
||||||
readinessProbe:
|
periodSeconds: 10
|
||||||
httpGet:
|
volumes:
|
||||||
path: /api/health
|
- name: grafana-provisioning
|
||||||
port: 3000
|
configMap:
|
||||||
initialDelaySeconds: 10
|
name: grafana-provisioning
|
||||||
periodSeconds: 10
|
- name: grafana-dashboards
|
||||||
livenessProbe:
|
configMap:
|
||||||
httpGet:
|
name: grafana-dashboards
|
||||||
path: /api/health
|
volumeClaimTemplates:
|
||||||
port: 3000
|
- metadata:
|
||||||
initialDelaySeconds: 30
|
name: grafana-storage
|
||||||
periodSeconds: 10
|
spec:
|
||||||
volumes:
|
accessModes: [ReadWriteOnce]
|
||||||
- name: grafana-provisioning
|
storageClassName: sqlite
|
||||||
configMap:
|
resources:
|
||||||
name: grafana-provisioning
|
requests:
|
||||||
- name: grafana-dashboards
|
storage: 5Gi
|
||||||
configMap:
|
---
|
||||||
name: grafana-dashboards
|
apiVersion: v1
|
||||||
volumeClaimTemplates:
|
kind: Service
|
||||||
- metadata:
|
metadata:
|
||||||
name: grafana-storage
|
name: grafana
|
||||||
spec:
|
labels:
|
||||||
accessModes: [ReadWriteOnce]
|
app: grafana
|
||||||
storageClassName: sqlite
|
spec:
|
||||||
resources:
|
type: ClusterIP
|
||||||
requests:
|
selector:
|
||||||
storage: 5Gi
|
app: grafana
|
||||||
---
|
ports:
|
||||||
apiVersion: v1
|
- name: http
|
||||||
kind: Service
|
port: 3000
|
||||||
metadata:
|
targetPort: 3000
|
||||||
name: grafana
|
---
|
||||||
namespace: memelord-jake
|
apiVersion: cert-manager.io/v1
|
||||||
labels:
|
kind: Certificate
|
||||||
app: grafana
|
metadata:
|
||||||
spec:
|
name: grafana-{{ .Release.Name }}
|
||||||
type: ClusterIP
|
spec:
|
||||||
selector:
|
secretName: grafana-{{ .Release.Name }}-tls
|
||||||
app: grafana
|
dnsNames:
|
||||||
ports:
|
- {{ .Values.grafanaHostname }}
|
||||||
- name: http
|
issuerRef:
|
||||||
port: 3000
|
name: letsencrypt
|
||||||
targetPort: 3000
|
kind: ClusterIssuer
|
||||||
---
|
---
|
||||||
apiVersion: cert-manager.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Certificate
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: grafana-jake
|
name: grafana-{{ .Release.Name }}
|
||||||
namespace: memelord-jake
|
annotations:
|
||||||
spec:
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
secretName: grafana-jake-tls
|
spec:
|
||||||
dnsNames:
|
ingressClassName: traefik
|
||||||
- grafana-jake.ee-lte-1.codemowers.io
|
rules:
|
||||||
issuerRef:
|
- host: {{ .Values.grafanaHostname }}
|
||||||
name: letsencrypt
|
http:
|
||||||
kind: ClusterIssuer
|
paths:
|
||||||
---
|
- pathType: Prefix
|
||||||
apiVersion: networking.k8s.io/v1
|
path: "/"
|
||||||
kind: Ingress
|
backend:
|
||||||
metadata:
|
service:
|
||||||
name: grafana-jake
|
name: grafana
|
||||||
namespace: memelord-jake
|
port:
|
||||||
annotations:
|
number: 3000
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
tls:
|
||||||
spec:
|
- secretName: grafana-{{ .Release.Name }}-tls
|
||||||
ingressClassName: traefik
|
---
|
||||||
rules:
|
apiVersion: codemowers.cloud/v1beta1
|
||||||
- host: grafana-jake.ee-lte-1.codemowers.io
|
kind: OIDCClient
|
||||||
http:
|
metadata:
|
||||||
paths:
|
name: grafana-{{ .Release.Name }}
|
||||||
- pathType: Prefix
|
spec:
|
||||||
path: "/"
|
displayName: Grafana {{ .Release.Name }}
|
||||||
backend:
|
uri: https://{{ .Values.grafanaHostname }}/login/generic_oauth
|
||||||
service:
|
redirectUris:
|
||||||
name: grafana
|
- https://{{ .Values.grafanaHostname }}/login/generic_oauth
|
||||||
port:
|
grantTypes:
|
||||||
number: 3000
|
- authorization_code
|
||||||
tls:
|
- refresh_token
|
||||||
- secretName: grafana-jake-tls
|
responseTypes:
|
||||||
---
|
- code
|
||||||
apiVersion: codemowers.cloud/v1beta1
|
availableScopes:
|
||||||
kind: OIDCClient
|
- openid
|
||||||
metadata:
|
- profile
|
||||||
name: grafana-jake
|
- offline_access
|
||||||
namespace: memelord-jake
|
pkce: false
|
||||||
spec:
|
|
||||||
displayName: Grafana jake
|
|
||||||
uri: https://grafana-jake.ee-lte-1.codemowers.io/login/generic_oauth
|
|
||||||
redirectUris:
|
|
||||||
- https://grafana-jake.ee-lte-1.codemowers.io/login/generic_oauth
|
|
||||||
grantTypes:
|
|
||||||
- authorization_code
|
|
||||||
- refresh_token
|
|
||||||
responseTypes:
|
|
||||||
- code
|
|
||||||
availableScopes:
|
|
||||||
- openid
|
|
||||||
- profile
|
|
||||||
- offline_access
|
|
||||||
pkce: false
|
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
apiVersion: monitoring.coreos.com/v1
|
apiVersion: monitoring.coreos.com/v1
|
||||||
kind: Probe
|
kind: Probe
|
||||||
metadata:
|
metadata:
|
||||||
name: memelord-probe
|
name: {{ .Release.Name }}-probe
|
||||||
namespace: memelord-jake
|
labels:
|
||||||
labels:
|
app: {{ .Release.Name }}
|
||||||
app: memelord
|
spec:
|
||||||
spec:
|
module: http_2xx
|
||||||
module: http_2xx
|
prober:
|
||||||
prober:
|
url: blackbox-exporter.monitoring.svc.cluster.local
|
||||||
url: blackbox-exporter.monitoring.svc.cluster.local
|
targets:
|
||||||
targets:
|
staticConfig:
|
||||||
staticConfig:
|
static:
|
||||||
static:
|
- {{ .Values.hostname }}
|
||||||
- google.com
|
|
||||||
19
helm/templates/oidc.yaml
Normal file
19
helm/templates/oidc.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
apiVersion: codemowers.cloud/v1beta1
|
||||||
|
kind: OIDCClient
|
||||||
|
metadata:
|
||||||
|
name: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
displayName: Memelord {{ .Release.Name }}
|
||||||
|
uri: https://{{ .Values.hostname }}/
|
||||||
|
redirectUris:
|
||||||
|
- https://{{ .Values.hostname }}/oidc/callback/
|
||||||
|
grantTypes:
|
||||||
|
- authorization_code
|
||||||
|
- refresh_token
|
||||||
|
responseTypes:
|
||||||
|
- code
|
||||||
|
availableScopes:
|
||||||
|
- openid
|
||||||
|
- profile
|
||||||
|
pkce: false
|
||||||
2
helm/values.yaml
Normal file
2
helm/values.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
hostname: memelord-jake.ee-lte-1.codemowers.io
|
||||||
|
grafanaHostname: grafana-jake.ee-lte-1.codemowers.io
|
||||||
20
oidc.yaml
20
oidc.yaml
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
apiVersion: codemowers.cloud/v1beta1
|
|
||||||
kind: OIDCClient
|
|
||||||
metadata:
|
|
||||||
name: memelord-jake
|
|
||||||
namespace: memelord-jake
|
|
||||||
spec:
|
|
||||||
displayName: Memelord jake
|
|
||||||
uri: https://memelord-jake.ee-lte-1.codemowers.io/
|
|
||||||
redirectUris:
|
|
||||||
- https://memelord-jake.ee-lte-1.codemowers.io/oidc/callback/
|
|
||||||
grantTypes:
|
|
||||||
- authorization_code
|
|
||||||
- refresh_token
|
|
||||||
responseTypes:
|
|
||||||
- code
|
|
||||||
availableScopes:
|
|
||||||
- openid
|
|
||||||
- profile
|
|
||||||
pkce: false
|
|
||||||
Reference in New Issue
Block a user