feat: add supabase deployer

This commit is contained in:
2026-04-12 20:07:05 -04:00
parent df1dd39197
commit 5fe7b4edeb
8 changed files with 973 additions and 26 deletions

View 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' }}