feat: add supabase deployer
This commit is contained in:
483
roles/supabase/tasks/main.yml
Normal file
483
roles/supabase/tasks/main.yml
Normal file
@@ -0,0 +1,483 @@
|
||||
---
|
||||
# Deploy Supabase on OpenShift via the supabase-community Helm chart.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - KUBECONFIG set in environment (or ocp_kubeconfig host var)
|
||||
# - cert-manager installed with a ClusterIssuer matching supabase_tls_cluster_issuer
|
||||
# - A ReadWriteOnce-capable StorageClass set via supabase_storage_class
|
||||
# - Vault variables defined (see meta/argument_specs.yml for the full list)
|
||||
#
|
||||
# Architecture:
|
||||
# - Kong API gateway is the single ingress point (Route: supabase_studio_host)
|
||||
# - Studio dashboard is served through Kong
|
||||
# - DB runs as root — anyuid SCC granted to the db ServiceAccount
|
||||
# - Vector reads /var/log/pods — privileged SCC granted to vector SA
|
||||
# - All secrets are created as K8s Secrets and referenced via secretRef
|
||||
#
|
||||
# OpenShift SCC notes:
|
||||
# - supabase-db ServiceAccount → anyuid SCC (postgres runs as root)
|
||||
# - supabase-vector ServiceAccount → privileged SCC (hostPath to /var/log/pods)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 0: Resolve secrets from Vault (generate and store if missing)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Resolve Supabase secrets from Vault
|
||||
ansible.builtin.include_tasks: vault_secrets.yml
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 1: Namespace
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create Supabase namespace
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ supabase_namespace }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 2: SCC grants (ClusterRoleBindings before helm so pods start clean)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create ClusterRole granting anyuid SCC (supabase db)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-db-anyuid-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [anyuid]
|
||||
|
||||
- name: Bind anyuid SCC to supabase db ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-db-anyuid-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_db_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-db-anyuid-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
- name: Create ClusterRole granting anyuid SCC (supabase kong)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-kong-anyuid-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [anyuid]
|
||||
|
||||
- name: Bind anyuid SCC to supabase kong ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-kong-anyuid-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_kong_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-kong-anyuid-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
- name: Create ClusterRole granting anyuid SCC (supabase functions)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-functions-anyuid-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [anyuid]
|
||||
|
||||
- name: Bind anyuid SCC to supabase functions ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-functions-anyuid-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_functions_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-functions-anyuid-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
- name: Create ClusterRole granting privileged SCC (supabase vector)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: supabase-vector-privileged-scc
|
||||
rules:
|
||||
- apiGroups: [security.openshift.io]
|
||||
resources: [securitycontextconstraints]
|
||||
verbs: [use]
|
||||
resourceNames: [privileged]
|
||||
|
||||
- name: Bind privileged SCC to supabase vector ServiceAccount
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: supabase-vector-privileged-scc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: "{{ supabase_vector_fullname }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: supabase-vector-privileged-scc
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 3: Kubernetes Secrets (no_log — referenced by helm via secretRef)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create JWT secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_jwt }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
anonKey: "{{ __supabase_anon_key }}"
|
||||
serviceKey: "{{ __supabase_service_key }}"
|
||||
secret: "{{ __supabase_jwt_secret }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create DB secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_db }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
password: "{{ __supabase_db_password }}"
|
||||
database: "{{ supabase_db_name }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create dashboard secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_dashboard }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: "{{ __supabase_dashboard_username }}"
|
||||
password: "{{ __supabase_dashboard_password }}"
|
||||
openAiApiKey: "{{ __supabase_openai_api_key }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create analytics secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_analytics }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
publicAccessToken: "{{ __supabase_analytics_public_token }}"
|
||||
privateAccessToken: "{{ __supabase_analytics_private_token }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create realtime secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_realtime }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
secretKeyBase: "{{ __supabase_realtime_secret_key_base }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create meta secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_meta }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
cryptoKey: "{{ __supabase_meta_crypto_key }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Create SMTP secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: "{{ __supabase_secret_smtp }}"
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: "{{ vault_supabase_smtp_username | default('') }}" # set in host_vars
|
||||
password: "{{ vault_supabase_smtp_password | default('') }}" # set in host_vars
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 4: Optional Docker Hub pull secret
|
||||
# Avoids anonymous rate limiting for docker.io images (kong, vector, etc.)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create Docker Hub pull secret
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supabase-dockerhub
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: >-
|
||||
{{ {'auths': {'https://index.docker.io/v1/': {
|
||||
'username': supabase_dockerhub_username,
|
||||
'password': vault_supabase_dockerhub_token,
|
||||
'auth': (supabase_dockerhub_username + ':' + vault_supabase_dockerhub_token) | b64encode
|
||||
}}} | to_json | b64encode }}
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
when: supabase_dockerhub_enabled | bool
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 5: Helm install / upgrade
|
||||
# ------------------------------------------------------------------
|
||||
- name: Deploy Supabase via Helm
|
||||
kubernetes.core.helm:
|
||||
release_name: "{{ supabase_release_name }}"
|
||||
chart_ref: supabase
|
||||
chart_repo_url: "{{ supabase_chart_repo_url }}"
|
||||
chart_version: "{{ supabase_chart_version | default(omit, true) }}"
|
||||
release_namespace: "{{ supabase_namespace }}"
|
||||
create_namespace: false
|
||||
wait: false
|
||||
values:
|
||||
# Use predictable names so SCC bindings and Route target can reference them
|
||||
deployment:
|
||||
db:
|
||||
fullnameOverride: "{{ supabase_db_fullname }}"
|
||||
kong:
|
||||
fullnameOverride: "{{ supabase_kong_fullname }}"
|
||||
functions:
|
||||
fullnameOverride: "{{ supabase_functions_fullname }}"
|
||||
vector:
|
||||
fullnameOverride: "{{ supabase_vector_fullname }}"
|
||||
|
||||
# Secrets via secretRef — keep secret material out of Helm values
|
||||
secret:
|
||||
jwt:
|
||||
secretRef: "{{ __supabase_secret_jwt }}"
|
||||
db:
|
||||
secretRef: "{{ __supabase_secret_db }}"
|
||||
dashboard:
|
||||
secretRef: "{{ __supabase_secret_dashboard }}"
|
||||
analytics:
|
||||
secretRef: "{{ __supabase_secret_analytics }}"
|
||||
realtime:
|
||||
secretRef: "{{ __supabase_secret_realtime }}"
|
||||
meta:
|
||||
secretRef: "{{ __supabase_secret_meta }}"
|
||||
smtp:
|
||||
secretRef: "{{ __supabase_secret_smtp }}"
|
||||
|
||||
# Storage — all PVCs use the configured StorageClass
|
||||
persistence:
|
||||
db:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_db_storage_size }}"
|
||||
functions:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_functions_storage_size }}"
|
||||
imgproxy:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_imgproxy_storage_size }}"
|
||||
minio:
|
||||
storageClassName: "{{ supabase_storage_class }}"
|
||||
size: "{{ supabase_minio_storage_size }}"
|
||||
|
||||
# Explicit docker.io registry prefix on all docker.io images.
|
||||
# OpenShift ImageContentSourcePolicy rewrites bare image refs (e.g. kong/kong)
|
||||
# to the configured mirror; supplying the full docker.io/ hostname forces the
|
||||
# pull against the real registry and lets the pull secret take effect.
|
||||
image:
|
||||
analytics:
|
||||
repository: docker.io/supabase/logflare
|
||||
auth:
|
||||
repository: docker.io/supabase/gotrue
|
||||
db:
|
||||
repository: docker.io/supabase/postgres
|
||||
functions:
|
||||
repository: docker.io/supabase/edge-runtime
|
||||
imgproxy:
|
||||
repository: docker.io/darthsim/imgproxy
|
||||
kong:
|
||||
repository: docker.io/kong/kong
|
||||
meta:
|
||||
repository: docker.io/supabase/postgres-meta
|
||||
realtime:
|
||||
repository: docker.io/supabase/realtime
|
||||
rest:
|
||||
repository: docker.io/postgrest/postgrest
|
||||
storage:
|
||||
repository: docker.io/supabase/storage-api
|
||||
studio:
|
||||
repository: docker.io/supabase/studio
|
||||
vector:
|
||||
repository: docker.io/timberio/vector
|
||||
|
||||
# Docker Hub pull secret (only wired in when enabled)
|
||||
imagePullSecrets: "{{ [{'name': 'supabase-dockerhub'}] if supabase_dockerhub_enabled | bool else [] }}"
|
||||
|
||||
# Disable chart's nginx ingress — we create an OCP Route below
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# Studio public URL (used by auth callbacks and the browser)
|
||||
environment:
|
||||
studio:
|
||||
SUPABASE_PUBLIC_URL: "https://{{ supabase_studio_host }}"
|
||||
STUDIO_DEFAULT_ORGANIZATION: "{{ supabase_org_name }}"
|
||||
STUDIO_DEFAULT_PROJECT: "{{ supabase_project_name }}"
|
||||
NEXT_PUBLIC_ENABLE_LOGS: "true"
|
||||
|
||||
# SMTP
|
||||
auth:
|
||||
environment:
|
||||
GOTRUE_SMTP_HOST: "{{ supabase_smtp_host }}"
|
||||
GOTRUE_SMTP_PORT: "{{ supabase_smtp_port | string }}"
|
||||
GOTRUE_SMTP_ADMIN_EMAIL: "{{ supabase_smtp_admin_email }}"
|
||||
GOTRUE_SMTP_SENDER_NAME: "{{ supabase_smtp_sender_name }}"
|
||||
GOTRUE_MAILER_AUTOCONFIRM: "{{ 'false' if supabase_smtp_enabled else 'true' }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 6: OpenShift Route (edge TLS, cert-manager certificate)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Create TLS Certificate for Supabase
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: supabase-tls
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
spec:
|
||||
secretName: supabase-tls
|
||||
issuerRef:
|
||||
name: "{{ supabase_tls_cluster_issuer }}"
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- "{{ supabase_studio_host }}"
|
||||
|
||||
- name: Create OpenShift Route for Supabase (Kong gateway)
|
||||
kubernetes.core.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: route.openshift.io/v1
|
||||
kind: Route
|
||||
metadata:
|
||||
name: supabase
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "{{ supabase_tls_cluster_issuer }}"
|
||||
spec:
|
||||
host: "{{ supabase_studio_host }}"
|
||||
to:
|
||||
kind: Service
|
||||
name: "{{ supabase_kong_fullname }}"
|
||||
weight: 100
|
||||
port:
|
||||
targetPort: http
|
||||
tls:
|
||||
termination: edge
|
||||
insecureEdgeTerminationPolicy: Redirect
|
||||
certificate: ""
|
||||
key: ""
|
||||
caCertificate: ""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 7: Wait for Kong deployment to be healthy
|
||||
# ------------------------------------------------------------------
|
||||
- name: Wait for Kong deployment to be ready
|
||||
kubernetes.core.k8s_info:
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
namespace: "{{ supabase_namespace }}"
|
||||
name: "{{ supabase_kong_fullname }}"
|
||||
register: __supabase_kong_deploy
|
||||
until: >-
|
||||
__supabase_kong_deploy.resources | length > 0 and
|
||||
(__supabase_kong_deploy.resources[0].status.readyReplicas | default(0)) >= 1
|
||||
retries: "{{ __supabase_wait_retries }}"
|
||||
delay: 10
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 8: Summary
|
||||
# ------------------------------------------------------------------
|
||||
- name: Display Supabase deployment summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Supabase deployment complete!"
|
||||
- " Namespace : {{ supabase_namespace }}"
|
||||
- " Studio URL : https://{{ supabase_studio_host }}"
|
||||
- " Kong service : {{ supabase_kong_fullname }}:8000"
|
||||
- " DB PVC : {{ supabase_db_storage_size }} ({{ supabase_storage_class }})"
|
||||
- " SMTP enabled : {{ supabase_smtp_enabled }}"
|
||||
- ""
|
||||
- " Login with vault_supabase_dashboard_username / vault_supabase_dashboard_password"
|
||||
152
roles/supabase/tasks/vault_secrets.yml
Normal file
152
roles/supabase/tasks/vault_secrets.yml
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
# Read Supabase secrets from Vault; generate and store any that are missing.
|
||||
#
|
||||
# All __supabase_* facts set here are consumed by the K8s secret tasks in main.yml.
|
||||
#
|
||||
# JWT note: if jwt_secret is absent OR either JWT token is absent, all three are
|
||||
# regenerated together — a partial JWT state (e.g. tokens signed by a different
|
||||
# secret) would break auth across all services.
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Read existing secrets (may fail if path does not exist yet)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Read supabase secrets from Vault
|
||||
community.hashi_vault.vault_kv2_get:
|
||||
path: "{{ supabase_vault_path }}"
|
||||
engine_mount_point: "{{ supabase_vault_mount }}"
|
||||
register: __supabase_vault_read
|
||||
failed_when: false
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Set existing vault data fact
|
||||
ansible.builtin.set_fact:
|
||||
__sv: "{{ __supabase_vault_read.secret | default({}) }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Resolve non-JWT secrets: use existing or generate random values
|
||||
# ------------------------------------------------------------------
|
||||
- name: Resolve non-JWT secrets (generate any that are missing)
|
||||
ansible.builtin.set_fact:
|
||||
__supabase_db_password: >-
|
||||
{{ __sv.db_password
|
||||
| default(lookup('community.general.random_string', length=32, special=false)) }}
|
||||
__supabase_dashboard_username: >-
|
||||
{{ __sv.dashboard_username | default('supabase') }}
|
||||
__supabase_dashboard_password: >-
|
||||
{{ __sv.dashboard_password
|
||||
| default(lookup('community.general.random_string', length=24, special=false)) }}
|
||||
__supabase_analytics_public_token: >-
|
||||
{{ __sv.analytics_public_token
|
||||
| default(lookup('community.general.random_string', length=32, special=false)) }}
|
||||
__supabase_analytics_private_token: >-
|
||||
{{ __sv.analytics_private_token
|
||||
| default(lookup('community.general.random_string', length=32, special=false)) }}
|
||||
__supabase_realtime_secret_key_base: >-
|
||||
{{ __sv.realtime_secret_key_base
|
||||
| default(lookup('community.general.random_string', length=64, special=false)) }}
|
||||
__supabase_meta_crypto_key: >-
|
||||
{{ __sv.meta_crypto_key
|
||||
| default(lookup('community.general.random_string', length=32, special=false)) }}
|
||||
__supabase_openai_api_key: >-
|
||||
{{ __sv.openai_api_key | default('') }}
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# JWT secret + signed tokens (must be generated as a coherent set)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Check whether JWT values need to be (re)generated
|
||||
ansible.builtin.set_fact:
|
||||
__supabase_needs_jwt_gen: >-
|
||||
{{ not (__sv.jwt_secret | default('') | string | length > 0)
|
||||
or not (__sv.anon_key | default('') | string | length > 0)
|
||||
or not (__sv.service_key | default('') | string | length > 0) }}
|
||||
|
||||
- name: Generate JWT signing secret
|
||||
ansible.builtin.set_fact:
|
||||
__supabase_jwt_secret: >-
|
||||
{{ lookup('community.general.random_string', length=64, special=false) }}
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
when: __supabase_needs_jwt_gen | bool
|
||||
|
||||
- name: Use existing JWT signing secret
|
||||
ansible.builtin.set_fact:
|
||||
__supabase_jwt_secret: "{{ __sv.jwt_secret }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
when: not __supabase_needs_jwt_gen | bool
|
||||
|
||||
- name: Generate anon and service_role JWT tokens
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- python3
|
||||
- -c
|
||||
- |
|
||||
import hmac, hashlib, base64, json, time, sys
|
||||
|
||||
def b64url(data):
|
||||
if isinstance(data, str):
|
||||
data = data.encode()
|
||||
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
|
||||
|
||||
def make_jwt(secret, role):
|
||||
now = int(time.time())
|
||||
header = b64url(json.dumps({'alg': 'HS256', 'typ': 'JWT'}, separators=(',', ':')))
|
||||
payload = b64url(json.dumps(
|
||||
{'role': role, 'iss': 'supabase', 'iat': now, 'exp': now + 157680000},
|
||||
separators=(',', ':')
|
||||
))
|
||||
msg = '{}.{}'.format(header, payload)
|
||||
sig = b64url(hmac.new(secret.encode(), msg.encode(), hashlib.sha256).digest())
|
||||
return '{}.{}'.format(msg, sig)
|
||||
|
||||
secret = sys.argv[1]
|
||||
print(make_jwt(secret, 'anon'))
|
||||
print(make_jwt(secret, 'service_role'))
|
||||
- "{{ __supabase_jwt_secret }}"
|
||||
register: __supabase_jwt_output
|
||||
changed_when: false
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
when: __supabase_needs_jwt_gen | bool
|
||||
|
||||
- name: Set generated JWT token facts
|
||||
ansible.builtin.set_fact:
|
||||
__supabase_anon_key: "{{ __supabase_jwt_output.stdout_lines[0] }}"
|
||||
__supabase_service_key: "{{ __supabase_jwt_output.stdout_lines[1] }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
when: __supabase_needs_jwt_gen | bool
|
||||
|
||||
- name: Use existing JWT tokens from Vault
|
||||
ansible.builtin.set_fact:
|
||||
__supabase_anon_key: "{{ __sv.anon_key }}"
|
||||
__supabase_service_key: "{{ __sv.service_key }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
when: not __supabase_needs_jwt_gen | bool
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Write back to Vault (always — ensures all fields are present and
|
||||
# any newly generated values are persisted before K8s secrets are written)
|
||||
# ------------------------------------------------------------------
|
||||
- name: Write supabase secrets to Vault
|
||||
community.hashi_vault.vault_kv2_write:
|
||||
path: "{{ supabase_vault_path }}"
|
||||
engine_mount_point: "{{ supabase_vault_mount }}"
|
||||
data:
|
||||
jwt_secret: "{{ __supabase_jwt_secret }}"
|
||||
anon_key: "{{ __supabase_anon_key }}"
|
||||
service_key: "{{ __supabase_service_key }}"
|
||||
db_password: "{{ __supabase_db_password }}"
|
||||
dashboard_username: "{{ __supabase_dashboard_username }}"
|
||||
dashboard_password: "{{ __supabase_dashboard_password }}"
|
||||
analytics_public_token: "{{ __supabase_analytics_public_token }}"
|
||||
analytics_private_token: "{{ __supabase_analytics_private_token }}"
|
||||
realtime_secret_key_base: "{{ __supabase_realtime_secret_key_base }}"
|
||||
meta_crypto_key: "{{ __supabase_meta_crypto_key }}"
|
||||
openai_api_key: "{{ __supabase_openai_api_key }}"
|
||||
no_log: "{{ supabase_no_log }}"
|
||||
|
||||
- name: Report vault secret status
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ 'Generated and stored new Supabase secrets in Vault'
|
||||
if __supabase_needs_jwt_gen | bool
|
||||
else 'Using existing Supabase secrets from Vault' }}
|
||||
Reference in New Issue
Block a user