feat: add supabase deployer
This commit is contained in:
@@ -333,29 +333,86 @@
|
|||||||
- never
|
- never
|
||||||
- sno_deploy_service_accounts
|
- sno_deploy_service_accounts
|
||||||
|
|
||||||
roles:
|
tasks:
|
||||||
- role: ocp_service_account
|
# ---- supabase-deployer ----
|
||||||
ocp_service_account_name: aap-deployer
|
- name: Provision supabase-deployer ServiceAccount
|
||||||
ocp_service_account_namespace: aap
|
ansible.builtin.include_role:
|
||||||
ocp_service_account_cluster_role_rules:
|
name: ocp_service_account
|
||||||
- apiGroups: [""]
|
vars:
|
||||||
resources: ["namespaces"]
|
ocp_service_account_name: supabase-deployer
|
||||||
verbs: ["get", "list", "create", "patch"]
|
ocp_service_account_namespace: supabase
|
||||||
- apiGroups: [""]
|
ocp_service_account_cluster_role_rules:
|
||||||
resources: ["secrets"]
|
- apiGroups: [""]
|
||||||
verbs: ["get", "list", "watch", "create", "patch"]
|
resources: ["namespaces"]
|
||||||
- apiGroups: [""]
|
verbs: ["get", "list", "create", "patch", "delete"]
|
||||||
resources: ["serviceaccounts"]
|
- apiGroups: [""]
|
||||||
verbs: ["get", "list", "watch"]
|
resources: ["secrets", "configmaps", "services", "serviceaccounts",
|
||||||
- apiGroups: ["apps"]
|
"persistentvolumeclaims", "pods", "endpoints"]
|
||||||
resources: ["deployments"]
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
verbs: ["get", "list", "watch"]
|
- apiGroups: ["apps"]
|
||||||
- apiGroups: ["operators.coreos.com"]
|
resources: ["deployments", "statefulsets", "replicasets"]
|
||||||
resources: ["operatorgroups", "subscriptions", "clusterserviceversions"]
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
verbs: ["get", "list", "create", "patch", "watch"]
|
- apiGroups: ["rbac.authorization.k8s.io"]
|
||||||
- apiGroups: ["apiextensions.k8s.io"]
|
resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
|
||||||
resources: ["customresourcedefinitions"]
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "escalate", "bind"]
|
||||||
verbs: ["get", "list", "watch"]
|
- apiGroups: ["security.openshift.io"]
|
||||||
- apiGroups: ["aap.ansible.com"]
|
resources: ["securitycontextconstraints"]
|
||||||
resources: ["ansibleautomationplatforms"]
|
verbs: ["get", "list", "use"]
|
||||||
verbs: ["get", "list", "create", "patch", "watch"]
|
- apiGroups: ["route.openshift.io"]
|
||||||
|
resources: ["routes", "routes/custom-host"]
|
||||||
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
|
- apiGroups: ["cert-manager.io"]
|
||||||
|
resources: ["certificates", "clusterissuers", "issuers"]
|
||||||
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
|
- apiGroups: ["networking.k8s.io"]
|
||||||
|
resources: ["ingresses"]
|
||||||
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
|
- apiGroups: ["storage.k8s.io"]
|
||||||
|
resources: ["storageclasses"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
|
||||||
|
- name: Store supabase-deployer token in Vault
|
||||||
|
community.hashi_vault.vault_kv2_write:
|
||||||
|
path: toallab/supabase-deployer
|
||||||
|
engine_mount_point: kv
|
||||||
|
data:
|
||||||
|
token: "{{ __ocp_service_account_token }}"
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
# ---- aap-deployer ----
|
||||||
|
- name: Provision aap-deployer ServiceAccount
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: ocp_service_account
|
||||||
|
vars:
|
||||||
|
ocp_service_account_name: aap-deployer
|
||||||
|
ocp_service_account_namespace: aap
|
||||||
|
ocp_service_account_cluster_role_rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["get", "list", "create", "patch"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "list", "watch", "create", "patch"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["serviceaccounts"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
resources: ["deployments"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
- apiGroups: ["operators.coreos.com"]
|
||||||
|
resources: ["operatorgroups", "subscriptions", "clusterserviceversions"]
|
||||||
|
verbs: ["get", "list", "create", "patch", "watch"]
|
||||||
|
- apiGroups: ["apiextensions.k8s.io"]
|
||||||
|
resources: ["customresourcedefinitions"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
- apiGroups: ["aap.ansible.com"]
|
||||||
|
resources: ["ansibleautomationplatforms"]
|
||||||
|
verbs: ["get", "list", "create", "patch", "watch"]
|
||||||
|
|
||||||
|
- name: Store aap-deployer token in Vault
|
||||||
|
community.hashi_vault.vault_kv2_write:
|
||||||
|
path: toallab/aap-deployer
|
||||||
|
engine_mount_point: kv
|
||||||
|
data:
|
||||||
|
token: "{{ __ocp_service_account_token }}"
|
||||||
|
no_log: true
|
||||||
|
|||||||
44
playbooks/deploy_supabase.yml
Normal file
44
playbooks/deploy_supabase.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
# Deploy Supabase on OpenShift (SNO) via the supabase-community Helm chart.
|
||||||
|
#
|
||||||
|
# Chart: https://github.com/supabase-community/supabase-kubernetes
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - cert-manager installed (deploy_openshift.yml --tags sno_deploy_certmanager)
|
||||||
|
# - NFS StorageClass available (deploy_openshift.yml --tags sno_deploy_nfs)
|
||||||
|
# - supabase-deployer ServiceAccount provisioned and token saved to 1Password:
|
||||||
|
# ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_service_accounts
|
||||||
|
# - vault_supabase_deployer_token set in host_vars (see hashivault_secrets.yml)
|
||||||
|
#
|
||||||
|
# All Supabase secrets (JWT, DB password, etc.) are auto-generated on first run
|
||||||
|
# and stored in Vault at supabase_vault_path. No manual secret setup required.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ansible-navigator run playbooks/deploy_supabase.yml --mode stdout
|
||||||
|
# ansible-navigator run playbooks/deploy_supabase.yml --mode stdout -e supabase_chart_version=0.0.9
|
||||||
|
# ansible-navigator run playbooks/deploy_supabase.yml --mode stdout -e supabase_db_storage_size=20Gi
|
||||||
|
|
||||||
|
- name: Deploy Supabase on OpenShift
|
||||||
|
hosts: sno.openshift.toal.ca
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
|
||||||
|
environment:
|
||||||
|
K8S_AUTH_HOST: "{{ supabase_k8s_api_url }}"
|
||||||
|
K8S_AUTH_API_KEY: "{{ vault_supabase_deployer_token }}"
|
||||||
|
K8S_AUTH_VERIFY_SSL: "false"
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Verify supabase-deployer token is available
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- vault_supabase_deployer_token is defined
|
||||||
|
- vault_supabase_deployer_token | length > 0
|
||||||
|
fail_msg: >-
|
||||||
|
vault_supabase_deployer_token is not set.
|
||||||
|
Provision the ServiceAccount with:
|
||||||
|
ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_service_accounts
|
||||||
|
Then save the displayed token to 1Password and reference it as vault_supabase_deployer_token.
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: supabase
|
||||||
65
roles/supabase/defaults/main.yml
Normal file
65
roles/supabase/defaults/main.yml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
# --- Debugging ---
|
||||||
|
# Set to false to disable no_log and expose secret values in task output.
|
||||||
|
# Never set false in production or shared environments.
|
||||||
|
supabase_no_log: true
|
||||||
|
|
||||||
|
# --- Vault ---
|
||||||
|
# KV2 path where Supabase secrets are stored and retrieved.
|
||||||
|
# Run with: op run --env-file=~/.ansible.zshenv -- ansible-navigator ...
|
||||||
|
# so that VAULT_ADDR / VAULT_TOKEN are injected from 1Password.
|
||||||
|
supabase_vault_mount: kv
|
||||||
|
supabase_vault_path: supabase
|
||||||
|
|
||||||
|
# --- Helm release ---
|
||||||
|
supabase_release_name: supabase
|
||||||
|
supabase_namespace: supabase
|
||||||
|
supabase_chart_repo_url: https://supabase-community.github.io/supabase-kubernetes
|
||||||
|
supabase_chart_version: "" # blank = latest
|
||||||
|
|
||||||
|
# --- External access ---
|
||||||
|
# Studio and API gateway are both served through Kong on this hostname.
|
||||||
|
# No generic default — must be set in host_vars or group_vars.
|
||||||
|
supabase_studio_host: ""
|
||||||
|
supabase_tls_cluster_issuer: ""
|
||||||
|
|
||||||
|
# --- Storage ---
|
||||||
|
# Empty string uses the cluster's default StorageClass.
|
||||||
|
supabase_storage_class: ""
|
||||||
|
supabase_db_storage_size: 10Gi
|
||||||
|
supabase_functions_storage_size: 1Gi
|
||||||
|
supabase_imgproxy_storage_size: 1Gi
|
||||||
|
supabase_minio_storage_size: 20Gi
|
||||||
|
|
||||||
|
# --- Database ---
|
||||||
|
supabase_db_name: postgres
|
||||||
|
|
||||||
|
# --- Studio branding ---
|
||||||
|
supabase_org_name: ""
|
||||||
|
supabase_project_name: ""
|
||||||
|
|
||||||
|
# --- SMTP (set supabase_smtp_enabled: true to configure email auth) ---
|
||||||
|
supabase_smtp_enabled: false
|
||||||
|
supabase_smtp_host: ""
|
||||||
|
supabase_smtp_port: 587
|
||||||
|
supabase_smtp_admin_email: ""
|
||||||
|
supabase_smtp_sender_name: Supabase
|
||||||
|
|
||||||
|
# --- Internal component name overrides ---
|
||||||
|
# These become predictable Kubernetes resource names (service, SA, etc.)
|
||||||
|
# Must match deployment.*.fullnameOverride values passed to the chart
|
||||||
|
supabase_db_fullname: supabase-db
|
||||||
|
supabase_kong_fullname: supabase-kong
|
||||||
|
supabase_functions_fullname: supabase-functions
|
||||||
|
supabase_vector_fullname: supabase-vector
|
||||||
|
|
||||||
|
# --- Docker Hub pull secret (optional) ---
|
||||||
|
# Set to avoid anonymous rate limiting on docker.io images (kong, timberio/vector, etc.)
|
||||||
|
# vault_supabase_dockerhub_token must be set in host_vars when enabled.
|
||||||
|
supabase_dockerhub_enabled: false
|
||||||
|
supabase_dockerhub_username: ""
|
||||||
|
|
||||||
|
# --- Wait ---
|
||||||
|
# helm wait is disabled — image pulls from docker.io can take longer than any
|
||||||
|
# reasonable helm timeout. Pod readiness is checked separately below.
|
||||||
|
supabase_wait_timeout: 600
|
||||||
120
roles/supabase/meta/argument_specs.yml
Normal file
120
roles/supabase/meta/argument_specs.yml
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
argument_specs:
|
||||||
|
main:
|
||||||
|
short_description: Deploy Supabase on OpenShift via the supabase-community Helm chart
|
||||||
|
description:
|
||||||
|
- Deploys the full Supabase stack (Studio, Kong, Auth, REST, Realtime, Storage,
|
||||||
|
Meta, Analytics, Functions, ImgProxy, Vector) to an OpenShift cluster.
|
||||||
|
- All secrets are created as Kubernetes Secrets and referenced via secretRef
|
||||||
|
so secret material never appears in Helm values.
|
||||||
|
- OpenShift SCCs are granted before the Helm install so pods start with
|
||||||
|
correct permissions (anyuid for postgres, privileged for vector).
|
||||||
|
- An OCP Route with edge TLS termination is created, backed by a cert-manager
|
||||||
|
Certificate against the configured ClusterIssuer.
|
||||||
|
- >-
|
||||||
|
Vault variables (vault_supabase_*) must be defined in host_vars before running.
|
||||||
|
Generate JWT values at https://supabase.com/docs/guides/self-hosting/docker
|
||||||
|
Generate secrets with openssl rand -base64 64
|
||||||
|
options:
|
||||||
|
supabase_release_name:
|
||||||
|
description: Helm release name.
|
||||||
|
type: str
|
||||||
|
default: supabase
|
||||||
|
supabase_namespace:
|
||||||
|
description: Kubernetes namespace for all Supabase resources.
|
||||||
|
type: str
|
||||||
|
default: supabase
|
||||||
|
supabase_chart_repo_url:
|
||||||
|
description: URL of the supabase-community Helm chart repository.
|
||||||
|
type: str
|
||||||
|
default: https://supabase-community.github.io/supabase-kubernetes
|
||||||
|
supabase_chart_version:
|
||||||
|
description: Chart version to install. Empty string means latest.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
supabase_studio_host:
|
||||||
|
description: >-
|
||||||
|
FQDN for the Studio dashboard and Kong API gateway Route.
|
||||||
|
Defaults to supabase.apps.<cluster>.<domain>.
|
||||||
|
type: str
|
||||||
|
supabase_tls_cluster_issuer:
|
||||||
|
description: cert-manager ClusterIssuer name for TLS certificate provisioning.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
supabase_storage_class:
|
||||||
|
description: StorageClass name for all PersistentVolumeClaims (must support ReadWriteOnce).
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
supabase_db_storage_size:
|
||||||
|
description: Size of the PostgreSQL PVC.
|
||||||
|
type: str
|
||||||
|
default: 10Gi
|
||||||
|
supabase_functions_storage_size:
|
||||||
|
description: Size of the Edge Functions PVC.
|
||||||
|
type: str
|
||||||
|
default: 1Gi
|
||||||
|
supabase_imgproxy_storage_size:
|
||||||
|
description: Size of the ImgProxy PVC.
|
||||||
|
type: str
|
||||||
|
default: 1Gi
|
||||||
|
supabase_minio_storage_size:
|
||||||
|
description: Size of the MinIO (object storage) PVC.
|
||||||
|
type: str
|
||||||
|
default: 20Gi
|
||||||
|
supabase_db_name:
|
||||||
|
description: PostgreSQL database name.
|
||||||
|
type: str
|
||||||
|
default: postgres
|
||||||
|
supabase_org_name:
|
||||||
|
description: Default organisation name shown in Supabase Studio.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
supabase_project_name:
|
||||||
|
description: Default project name shown in Supabase Studio.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
supabase_smtp_enabled:
|
||||||
|
description: >-
|
||||||
|
Enable SMTP for auth email. When false, auto-confirm is enabled so
|
||||||
|
sign-ups do not require email verification.
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
supabase_smtp_host:
|
||||||
|
description: SMTP server hostname.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
supabase_smtp_port:
|
||||||
|
description: SMTP server port.
|
||||||
|
type: int
|
||||||
|
default: 587
|
||||||
|
supabase_smtp_admin_email:
|
||||||
|
description: Admin email address used as From header.
|
||||||
|
type: str
|
||||||
|
default: ""
|
||||||
|
supabase_smtp_sender_name:
|
||||||
|
description: Display name used in outgoing email.
|
||||||
|
type: str
|
||||||
|
default: Supabase
|
||||||
|
supabase_db_fullname:
|
||||||
|
description: >-
|
||||||
|
fullnameOverride for the db component. Determines the ServiceAccount
|
||||||
|
name that receives the anyuid SCC — must match deployment.db.fullnameOverride
|
||||||
|
passed to the chart.
|
||||||
|
type: str
|
||||||
|
default: supabase-db
|
||||||
|
supabase_kong_fullname:
|
||||||
|
description: >-
|
||||||
|
fullnameOverride for the kong component. Determines the Service name
|
||||||
|
that the OCP Route targets.
|
||||||
|
type: str
|
||||||
|
default: supabase-kong
|
||||||
|
supabase_vector_fullname:
|
||||||
|
description: >-
|
||||||
|
fullnameOverride for the vector component. Determines the ServiceAccount
|
||||||
|
name that receives the privileged SCC.
|
||||||
|
type: str
|
||||||
|
default: supabase-vector
|
||||||
|
supabase_wait_timeout:
|
||||||
|
description: Seconds to wait for deployments to become ready.
|
||||||
|
type: int
|
||||||
|
default: 600
|
||||||
13
roles/supabase/meta/main.yml
Normal file
13
roles/supabase/meta/main.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
role_name: supabase
|
||||||
|
author: ptoal
|
||||||
|
description: Deploy Supabase on OpenShift via the supabase-community Helm chart
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.14"
|
||||||
|
platforms:
|
||||||
|
- name: EL
|
||||||
|
versions:
|
||||||
|
- "9"
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
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' }}
|
||||||
13
roles/supabase/vars/main.yml
Normal file
13
roles/supabase/vars/main.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
# Computed internal vars — not intended for override
|
||||||
|
|
||||||
|
__supabase_wait_retries: "{{ (supabase_wait_timeout | int / 10) | int }}"
|
||||||
|
|
||||||
|
# Kubernetes secret names created before helm install; referenced via secretRef
|
||||||
|
__supabase_secret_jwt: supabase-jwt
|
||||||
|
__supabase_secret_db: supabase-db
|
||||||
|
__supabase_secret_dashboard: supabase-dashboard
|
||||||
|
__supabase_secret_analytics: supabase-analytics
|
||||||
|
__supabase_secret_realtime: supabase-realtime
|
||||||
|
__supabase_secret_meta: supabase-meta
|
||||||
|
__supabase_secret_smtp: supabase-smtp
|
||||||
Reference in New Issue
Block a user