feat: initial public release

ConsentOS — a privacy-first cookie consent management platform.

Self-hosted, source-available alternative to OneTrust, Cookiebot, and
CookieYes. Full standards coverage (IAB TCF v2.2, GPP v1, Google
Consent Mode v2, GPC, Shopify Customer Privacy API), multi-tenant
architecture with role-based access, configuration cascade
(system → org → group → site → region), dark-pattern detection in
the scanner, and a tamper-evident consent record audit trail.

This is the initial public release. Prior development history is
retained internally.

See README.md for the feature list, architecture overview, and
quick-start instructions. Licensed under the Elastic Licence 2.0 —
self-host freely; do not resell as a managed service.
This commit is contained in:
James Cottrill
2026-04-13 14:20:15 +00:00
commit fbf26453f2
341 changed files with 62807 additions and 0 deletions

20
helm/consentos/Chart.yaml Normal file
View File

@@ -0,0 +1,20 @@
apiVersion: v2
name: consentos
description: ConsentOS — Helm chart for Kubernetes deployment of the consent management platform
type: application
version: 0.1.0
appVersion: "0.1.0"
home: https://consentos.dev
sources:
- https://github.com/consentos/consentos
keywords:
- consent
- cookie
- gdpr
- privacy
- tcf
- gpp
maintainers:
- name: ConsentOS
email: hello@consentos.dev
url: https://consentos.dev

View File

@@ -0,0 +1,59 @@
{{/*
Common labels for all resources.
*/}}
{{- define "consentos.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
{{- end }}
{{/*
Selector labels for a specific component.
*/}}
{{- define "consentos.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: {{ .component }}
{{- end }}
{{/*
Full name helper.
*/}}
{{- define "consentos.fullname" -}}
{{ .Release.Name }}-{{ .Chart.Name }}
{{- end }}
{{/*
Secret name use existing or generated.
*/}}
{{- define "consentos.secretName" -}}
{{- if .Values.secrets.existingSecret }}
{{- .Values.secrets.existingSecret }}
{{- else }}
{{- include "consentos.fullname" . }}-secrets
{{- end }}
{{- end }}
{{/*
Database URL internal PostgreSQL or external.
*/}}
{{- define "consentos.databaseUrl" -}}
{{- if .Values.postgresql.enabled }}
postgresql+asyncpg://{{ .Values.postgresql.auth.username }}:$(POSTGRES_PASSWORD)@{{ include "consentos.fullname" . }}-postgresql:5432/{{ .Values.postgresql.auth.database }}
{{- else }}
{{- .Values.postgresql.externalUrl }}
{{- end }}
{{- end }}
{{/*
Redis URL internal or external.
*/}}
{{- define "consentos.redisUrl" -}}
{{- if .Values.redis.enabled }}
redis://{{ include "consentos.fullname" . }}-redis:6379/0
{{- else }}
{{- .Values.redis.externalUrl }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,43 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "consentos.fullname" . }}-admin-ui
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: admin-ui
spec:
replicas: {{ .Values.adminUi.replicaCount }}
selector:
matchLabels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "admin-ui") | nindent 6 }}
template:
metadata:
labels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "admin-ui") | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: admin-ui
image: "{{ .Values.adminUi.image.repository }}:{{ .Values.adminUi.image.tag }}"
imagePullPolicy: {{ .Values.adminUi.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 3
periodSeconds: 10
resources:
{{- toYaml .Values.adminUi.resources | nindent 12 }}

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "consentos.fullname" . }}-admin-ui
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: admin-ui
spec:
type: {{ .Values.adminUi.service.type }}
ports:
- port: {{ .Values.adminUi.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "admin-ui") | nindent 4 }}

View File

@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "consentos.fullname" . }}-api
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: api
spec:
replicas: {{ .Values.api.replicaCount }}
selector:
matchLabels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "api") | nindent 6 }}
template:
metadata:
labels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "api") | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}"
imagePullPolicy: {{ .Values.api.image.pullPolicy }}
ports:
- name: http
containerPort: 8000
protocol: TCP
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "consentos.secretName" . }}
key: postgresql-password
- name: DATABASE_URL
value: {{ include "consentos.databaseUrl" . | quote }}
- name: REDIS_URL
value: {{ include "consentos.redisUrl" . | quote }}
- name: JWT_SECRET_KEY
valueFrom:
secretKeyRef:
name: {{ include "consentos.secretName" . }}
key: jwt-secret-key
- name: CDN_BASE_URL
value: {{ .Values.cdn.baseUrl | quote }}
{{- range $key, $value := .Values.api.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 10
resources:
{{- toYaml .Values.api.resources | nindent 12 }}

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "consentos.fullname" . }}-api
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: api
spec:
type: {{ .Values.api.service.type }}
ports:
- port: {{ .Values.api.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "api") | nindent 4 }}

View File

@@ -0,0 +1,47 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "consentos.fullname" . }}
labels:
{{- include "consentos.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
{{- if eq .service "api" }}
name: {{ include "consentos.fullname" $ }}-api
port:
number: {{ $.Values.api.service.port }}
{{- else if eq .service "admin-ui" }}
name: {{ include "consentos.fullname" $ }}-admin-ui
port:
number: {{ $.Values.adminUi.service.port }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,92 @@
{{- if .Values.postgresql.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "consentos.fullname" . }}-postgresql
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: postgresql
spec:
serviceName: {{ include "consentos.fullname" . }}-postgresql
replicas: 1
selector:
matchLabels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "postgresql") | nindent 6 }}
template:
metadata:
labels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "postgresql") | nindent 8 }}
spec:
containers:
- name: postgresql
image: "{{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }}"
ports:
- name: postgresql
containerPort: 5432
protocol: TCP
env:
- name: POSTGRES_DB
value: {{ .Values.postgresql.auth.database | quote }}
- name: POSTGRES_USER
value: {{ .Values.postgresql.auth.username | quote }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "consentos.secretName" . }}
key: postgresql-password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
livenessProbe:
exec:
command:
- pg_isready
- -U
- {{ .Values.postgresql.auth.username }}
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- {{ .Values.postgresql.auth.username }}
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.postgresql.resources | nindent 12 }}
{{- if .Values.postgresql.persistence.enabled }}
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
{{- end }}
{{- if .Values.postgresql.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
{{- if .Values.postgresql.persistence.storageClass }}
storageClassName: {{ .Values.postgresql.persistence.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.postgresql.persistence.size }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "consentos.fullname" . }}-postgresql
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: postgresql
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: postgresql
protocol: TCP
name: postgresql
selector:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "postgresql") | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,59 @@
{{- if .Values.redis.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "consentos.fullname" . }}-redis
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: redis
spec:
replicas: 1
selector:
matchLabels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "redis") | nindent 6 }}
template:
metadata:
labels:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "redis") | nindent 8 }}
spec:
containers:
- name: redis
image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}"
ports:
- name: redis
containerPort: 6379
protocol: TCP
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.redis.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "consentos.fullname" . }}-redis
labels:
{{- include "consentos.labels" . | nindent 4 }}
app.kubernetes.io/component: redis
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: redis
protocol: TCP
name: redis
selector:
{{- include "consentos.selectorLabels" (dict "Chart" .Chart "Release" .Release "component" "redis") | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,12 @@
{{- if not .Values.secrets.existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "consentos.secretName" . }}
labels:
{{- include "consentos.labels" . | nindent 4 }}
type: Opaque
data:
jwt-secret-key: {{ .Values.secrets.jwtSecretKey | b64enc | quote }}
postgresql-password: {{ .Values.secrets.postgresqlPassword | b64enc | quote }}
{{- end }}

134
helm/consentos/values.yaml Normal file
View File

@@ -0,0 +1,134 @@
# Default values for ConsentOS.
# ── API service ──────────────────────────────────────────────────────
api:
replicaCount: 2
image:
repository: ghcr.io/consentos/consentos-api
tag: "latest"
pullPolicy: IfNotPresent
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
env:
LOG_LEVEL: INFO
RATE_LIMIT_ENABLED: "true"
RATE_LIMIT_PER_MINUTE: "120"
service:
type: ClusterIP
port: 8000
# ── Scanner service ──────────────────────────────────────────────────
scanner:
replicaCount: 1
image:
repository: ghcr.io/consentos/consentos-scanner
tag: "latest"
pullPolicy: IfNotPresent
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 1Gi
# ── Admin UI ─────────────────────────────────────────────────────────
adminUi:
replicaCount: 1
image:
repository: ghcr.io/consentos/consentos-admin-ui
tag: "latest"
pullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
service:
type: ClusterIP
port: 80
# ── PostgreSQL ───────────────────────────────────────────────────────
postgresql:
# Set to false to use an external database (e.g. RDS, Cloud SQL)
enabled: true
image:
repository: postgres
tag: "16-alpine"
auth:
database: consentos
username: consentos
existingSecret: ""
secretKeys:
password: postgresql-password
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
persistence:
enabled: true
size: 10Gi
storageClass: ""
# External database URL — used when postgresql.enabled is false
externalUrl: ""
# ── Redis ────────────────────────────────────────────────────────────
redis:
enabled: true
image:
repository: redis
tag: "7-alpine"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
persistence:
enabled: false
# External Redis URL — used when redis.enabled is false
externalUrl: ""
# ── Ingress ──────────────────────────────────────────────────────────
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: consentos.example.com
paths:
- path: /api
pathType: Prefix
service: api
- path: /
pathType: Prefix
service: admin-ui
tls: []
# ── Secrets ──────────────────────────────────────────────────────────
secrets:
# Provide an existing secret name, or leave empty to create one
existingSecret: ""
# Values used when creating the secret (ignored if existingSecret is set)
jwtSecretKey: "CHANGE-ME-in-production"
postgresqlPassword: "consentos"
# ── CDN ──────────────────────────────────────────────────────────────
cdn:
baseUrl: "https://cdn.example.com"
# ── Image pull secrets ───────────────────────────────────────────────
imagePullSecrets: []