mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 12:03:59 +00:00
Make init-script secret checks warning-only and unblock Next
Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
parent
5a0f8ab4f8
commit
c65d336d1f
@ -1090,13 +1090,17 @@ run_test() {
|
||||
run_kerberos_test_loop() {
|
||||
local test_attempt=1
|
||||
local test_max_attempts=2
|
||||
local checker_mode_args=()
|
||||
if [[ -n "$mode" ]]; then
|
||||
checker_mode_args=(--mode "$mode")
|
||||
fi
|
||||
while true; do
|
||||
if PROLE_USE_CHILD_REALM="$test_use_child" \
|
||||
KRB5_REALM="$KRB5_REALM" KRB5_KDC="$effective_kdc" KRB5_ADMIN="$KRB5_ADMIN" \
|
||||
KRB5_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" KRB5_PASSWORD="$KRB5_PASSWORD" \
|
||||
SAMBA_ADMIN_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" SAMBA_ADMIN_PASSWORD="$KRB5_PASSWORD" \
|
||||
SAMBA_DNS_SERVER="$test_samba_dns" \
|
||||
"$kerberos_check_script" test; then
|
||||
"$kerberos_check_script" "${checker_mode_args[@]}" test; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
@ -10,6 +10,10 @@ set -euo pipefail
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
ETC_DIR=$(cd "$SCRIPT_DIR/../../etc" && pwd)
|
||||
LIB_SHELL_DIR=$(cd "$ETC_DIR/lib/shell" && pwd)
|
||||
INIT_KDC_SCRIPT="$ETC_DIR/init_kdc.sh"
|
||||
if [[ ! -x "$INIT_KDC_SCRIPT" && -x "$SCRIPT_DIR/init_kdc.sh" ]]; then
|
||||
INIT_KDC_SCRIPT="$SCRIPT_DIR/init_kdc.sh"
|
||||
fi
|
||||
|
||||
ORIG_ARGS=("$@")
|
||||
|
||||
@ -530,8 +534,8 @@ run_test() {
|
||||
# Try a fast availability wait; if it fails, treat as unhealthy and clean up before proceeding.
|
||||
if ! kubectl -n "$kdc_ns" wait --for=condition=Available deploy/"$PROLE_KDC_NAME" --timeout="${PROLE_KDC_PRECHECK_TIMEOUT}s" >/dev/null 2>&1; then
|
||||
echo "Detected unhealthy '$PROLE_KDC_NAME' deployment in namespace '$kdc_ns'; removing before Kerberos test rollout to avoid timeout..." >&2
|
||||
if [[ -x "$SCRIPT_DIR/init_kdc.sh" ]]; then
|
||||
SERVICE_NAMESPACE="$SERVICE_NAMESPACE" "$SCRIPT_DIR/init_kdc.sh" cleanup || true
|
||||
if [[ -x "$INIT_KDC_SCRIPT" ]]; then
|
||||
SERVICE_NAMESPACE="$SERVICE_NAMESPACE" "$INIT_KDC_SCRIPT" cleanup || true
|
||||
else
|
||||
kubectl -n "$kdc_ns" delete deploy "$PROLE_KDC_NAME" --ignore-not-found || true
|
||||
kubectl -n "$kdc_ns" delete svc "$PROLE_KDC_SERVICE" --ignore-not-found || true
|
||||
@ -540,10 +544,19 @@ run_test() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -x "$SCRIPT_DIR/init_kdc.sh" ]]; then
|
||||
SERVICE_NAMESPACE="$SERVICE_NAMESPACE" KRB5_REALM="$KRB5_REALM" KRB5_KDC="$KRB5_KDC" \
|
||||
local kdc_mode="${PROLE_MODE:-${DEPLOYMENT_MODE:-}}"
|
||||
local kdc_mode_args=()
|
||||
if [[ -n "$kdc_mode" ]]; then
|
||||
kdc_mode_args=(--mode "$kdc_mode")
|
||||
fi
|
||||
|
||||
if [[ -x "$INIT_KDC_SCRIPT" ]]; then
|
||||
if ! SERVICE_NAMESPACE="$SERVICE_NAMESPACE" KRB5_REALM="$KRB5_REALM" KRB5_KDC="$KRB5_KDC" \
|
||||
KRB5_ADMIN="$KRB5_KDC" KRB5_USER="$KRB5_USER" KRB5_PASSWORD="$KRB5_PASSWORD" \
|
||||
"$SCRIPT_DIR/init_kdc.sh" update || true
|
||||
"$INIT_KDC_SCRIPT" "${kdc_mode_args[@]}" update; then
|
||||
echo "ERROR: Failed to ensure KDC deployment '${PROLE_KDC_NAME}' in namespace '$kdc_ns'." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
local pod_name
|
||||
|
||||
@ -762,11 +762,21 @@ resolve_knoe_db_namespace() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${DATABASE_NAMESPACE:-}" ]]; then
|
||||
KNOE_DB_NAMESPACE="$DATABASE_NAMESPACE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${NAMESPACE:-}" ]]; then
|
||||
KNOE_DB_NAMESPACE="$NAMESPACE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if kubectl get namespace knoe-db >/dev/null 2>&1; then
|
||||
KNOE_DB_NAMESPACE="knoe-db"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if kubectl get namespace prole >/dev/null 2>&1; then
|
||||
KNOE_DB_NAMESPACE="prole"
|
||||
else
|
||||
@ -1095,15 +1105,9 @@ setup_knoe_db_for_supabase() {
|
||||
|
||||
# Create roles required by Supabase services
|
||||
kubectl -n "$ns" exec -i "$primary" -c postgres -- psql -U postgres -d postgres -c "
|
||||
-- Core roles
|
||||
-- Fix search_path for supabase_storage_admin so migration reads and writes use the same schema
|
||||
-- Without this, the service queries auth.migrations (empty) but inserts into public.migrations
|
||||
-- causing duplicate-key errors on every restart after the first successful migration run.
|
||||
ALTER ROLE supabase_storage_admin SET search_path = storage, auth, public;
|
||||
ALTER ROLE supabase_storage_admin BYPASSRLS;
|
||||
|
||||
-- Drop stale migrations tracking rows so the storage service re-runs cleanly (idempotent reinstall)
|
||||
TRUNCATE TABLE IF EXISTS public.migrations;
|
||||
-- Drop stale storage migration tracking tables so storage reinitializes cleanly.
|
||||
-- Truncate is insufficient when prior failed starts left inconsistent duplicate rows.
|
||||
DROP TABLE IF EXISTS public.migrations CASCADE;
|
||||
DROP TABLE IF EXISTS storage.migrations CASCADE;
|
||||
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='anon') THEN CREATE ROLE anon NOLOGIN; END IF; END \$\$;
|
||||
@ -1111,20 +1115,35 @@ setup_knoe_db_for_supabase() {
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='service_role') THEN CREATE ROLE service_role NOLOGIN; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='authenticator') THEN CREATE ROLE authenticator LOGIN PASSWORD '${pg_password}'; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='pgbouncer') THEN CREATE ROLE pgbouncer LOGIN PASSWORD '${pg_password}'; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='knoe') THEN CREATE ROLE knoe LOGIN PASSWORD '${pg_password}' SUPERUSER CREATEDB CREATEROLE; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='supabase_admin') THEN CREATE ROLE supabase_admin LOGIN PASSWORD '${pg_password}' SUPERUSER; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='supabase_auth_admin') THEN CREATE ROLE supabase_auth_admin LOGIN PASSWORD '${pg_password}' NOINHERIT; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='supabase_storage_admin') THEN CREATE ROLE supabase_storage_admin LOGIN PASSWORD '${pg_password}' NOINHERIT; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='supabase_functions_admin') THEN CREATE ROLE supabase_functions_admin LOGIN PASSWORD '${pg_password}' NOINHERIT; END IF; END \$\$;
|
||||
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='supabase_read_only_user') THEN CREATE ROLE supabase_read_only_user NOLOGIN; END IF; END \$\$;
|
||||
|
||||
-- Fix search_path for supabase_storage_admin so migration reads and writes use the same schema.
|
||||
-- Without this, the service queries auth.migrations (empty) but inserts into public.migrations,
|
||||
-- causing duplicate-key errors on every restart after the first successful migration run.
|
||||
ALTER ROLE supabase_storage_admin SET search_path = storage, auth, public;
|
||||
ALTER ROLE supabase_storage_admin BYPASSRLS;
|
||||
|
||||
-- Always sync service role passwords to current postgres password (idempotent)
|
||||
ALTER ROLE authenticator PASSWORD '${pg_password}';
|
||||
ALTER ROLE pgbouncer PASSWORD '${pg_password}';
|
||||
ALTER ROLE knoe WITH SUPERUSER CREATEDB CREATEROLE;
|
||||
ALTER ROLE knoe PASSWORD '${pg_password}';
|
||||
ALTER ROLE knoe SET search_path = knoe, public;
|
||||
ALTER ROLE supabase_admin PASSWORD '${pg_password}';
|
||||
ALTER ROLE supabase_auth_admin PASSWORD '${pg_password}';
|
||||
ALTER ROLE supabase_storage_admin PASSWORD '${pg_password}';
|
||||
ALTER ROLE supabase_functions_admin PASSWORD '${pg_password}';
|
||||
|
||||
-- External CNPG installs may lock down database-level CREATE by default.
|
||||
-- Storage startup migrations run CREATE SCHEMA IF NOT EXISTS and fail without this.
|
||||
GRANT CONNECT, TEMPORARY ON DATABASE postgres TO supabase_storage_admin;
|
||||
GRANT CREATE ON DATABASE postgres TO supabase_storage_admin;
|
||||
|
||||
-- Grant membership
|
||||
GRANT anon TO authenticator;
|
||||
GRANT authenticated TO authenticator;
|
||||
@ -1132,15 +1151,54 @@ setup_knoe_db_for_supabase() {
|
||||
GRANT supabase_admin TO authenticator;
|
||||
|
||||
-- Schemas on postgres database
|
||||
CREATE SCHEMA IF NOT EXISTS knoe AUTHORIZATION knoe;
|
||||
CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION supabase_auth_admin;
|
||||
CREATE SCHEMA IF NOT EXISTS storage AUTHORIZATION supabase_storage_admin;
|
||||
CREATE SCHEMA IF NOT EXISTS graphql_public;
|
||||
CREATE SCHEMA IF NOT EXISTS _realtime;
|
||||
ALTER SCHEMA _realtime OWNER TO postgres;
|
||||
GRANT USAGE, CREATE ON SCHEMA public TO supabase_storage_admin;
|
||||
GRANT USAGE, CREATE ON SCHEMA knoe TO knoe;
|
||||
GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role;
|
||||
GRANT USAGE ON SCHEMA auth TO anon, authenticated, service_role;
|
||||
GRANT USAGE ON SCHEMA storage TO anon, authenticated, service_role;
|
||||
GRANT USAGE ON SCHEMA graphql_public TO anon, authenticated, service_role;
|
||||
|
||||
-- Keep extension objects out of public schema to satisfy database linter checks.
|
||||
DO \$\$
|
||||
DECLARE
|
||||
ext_name text;
|
||||
BEGIN
|
||||
FOREACH ext_name IN ARRAY ARRAY['pg_tde','pgcrypto','postgis','vector','tds_fdw']
|
||||
LOOP
|
||||
IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = ext_name) THEN
|
||||
BEGIN
|
||||
EXECUTE format('ALTER EXTENSION %I SET SCHEMA knoe', ext_name);
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
RAISE NOTICE 'Could not move extension % to schema knoe: %', ext_name, SQLERRM;
|
||||
END;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END \$\$;
|
||||
|
||||
-- Resolve Supabase lint error: ensure RLS is enabled on public.spatial_ref_sys.
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF to_regclass('public.spatial_ref_sys') IS NOT NULL THEN
|
||||
ALTER TABLE public.spatial_ref_sys ENABLE ROW LEVEL SECURITY;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_policies
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename = 'spatial_ref_sys'
|
||||
AND policyname = 'spatial_ref_sys_select_all'
|
||||
) THEN
|
||||
CREATE POLICY spatial_ref_sys_select_all ON public.spatial_ref_sys
|
||||
FOR SELECT USING (true);
|
||||
END IF;
|
||||
END IF;
|
||||
END \$\$;
|
||||
" 2>&1 || warn "Could not set up Supabase roles (cluster may not be ready yet)."
|
||||
|
||||
# Create _supabase database (used by supavisor, analytics)
|
||||
@ -1154,6 +1212,8 @@ setup_knoe_db_for_supabase() {
|
||||
|
||||
# Create schemas in _supabase database
|
||||
kubectl -n "$ns" exec "$primary" -c postgres -- psql -U postgres -d _supabase -c "
|
||||
CREATE SCHEMA IF NOT EXISTS knoe AUTHORIZATION knoe;
|
||||
GRANT USAGE, CREATE ON SCHEMA knoe TO knoe;
|
||||
CREATE SCHEMA IF NOT EXISTS _supavisor;
|
||||
ALTER SCHEMA _supavisor OWNER TO postgres;
|
||||
CREATE SCHEMA IF NOT EXISTS _analytics;
|
||||
@ -1259,8 +1319,8 @@ ensure_k8s_supabase_static_pvs() {
|
||||
require_cmd kubectl
|
||||
|
||||
local node base parent base_name
|
||||
node="${SUPABASE_PV_NODE:-merlin.prole.org}"
|
||||
base="${SUPABASE_PV_BASE_DIR:-/synology/d002/supabase}"
|
||||
node="${SUPABASE_PV_NODE:-gandalf.prole.org}"
|
||||
base="${SUPABASE_PV_BASE_DIR:-${SUPABASE_PV_BASE:-/synology/d005/supabase}}"
|
||||
base="${base%/}"
|
||||
parent="$(dirname "$base")"
|
||||
base_name="$(basename "$base")"
|
||||
@ -1299,10 +1359,10 @@ spec:
|
||||
BASE_DIR="/host-parent/${base_name}"
|
||||
echo "[dirprep] node=${node} base=${base}"
|
||||
mkdir -p "\${BASE_DIR}"
|
||||
mkdir -p "\${BASE_DIR}/db" "\${BASE_DIR}/deno" "\${BASE_DIR}/functions" "\${BASE_DIR}/snippets"
|
||||
mkdir -p "\${BASE_DIR}/db" "\${BASE_DIR}/deno" "\${BASE_DIR}/functions" "\${BASE_DIR}/snippets" "\${BASE_DIR}/imgproxy" "\${BASE_DIR}/minio" "\${BASE_DIR}/storage"
|
||||
chmod 0777 "\${BASE_DIR}" 2>/dev/null || true
|
||||
chmod 0777 "\${BASE_DIR}/db" "\${BASE_DIR}/deno" "\${BASE_DIR}/functions" "\${BASE_DIR}/snippets" 2>/dev/null || true
|
||||
echo "[dirprep][OK] Prepared ${base}/{db,deno,functions,snippets}"
|
||||
chmod 0777 "\${BASE_DIR}/db" "\${BASE_DIR}/deno" "\${BASE_DIR}/functions" "\${BASE_DIR}/snippets" "\${BASE_DIR}/imgproxy" "\${BASE_DIR}/minio" "\${BASE_DIR}/storage" 2>/dev/null || true
|
||||
echo "[dirprep][OK] Prepared ${base}/{db,deno,functions,snippets,imgproxy,minio,storage}"
|
||||
volumeMounts:
|
||||
- name: host-parent
|
||||
mountPath: /host-parent
|
||||
@ -1330,20 +1390,41 @@ EOF
|
||||
|
||||
# Static local PVs with Retain reclaimPolicy can get stuck in Released after a namespace reset.
|
||||
# Delete Released PV objects so they can be recreated and rebound to the new PVCs.
|
||||
# imgproxy and storage PVs are no longer used (those services are removed).
|
||||
local pvs=(
|
||||
synology-supabase-db
|
||||
synology-supabase-deno
|
||||
synology-supabase-functions
|
||||
synology-supabase-snippets
|
||||
synology-supabase-imgproxy
|
||||
synology-supabase-minio
|
||||
synology-supabase-storage
|
||||
)
|
||||
local pv
|
||||
for pv in "${pvs[@]}"; do
|
||||
local phase
|
||||
phase=$(kubectl get pv "$pv" -o jsonpath='{.status.phase}' 2>/dev/null || true)
|
||||
local expected_path=""
|
||||
case "$pv" in
|
||||
synology-supabase-db) expected_path="${base}/db" ;;
|
||||
synology-supabase-deno) expected_path="${base}/deno" ;;
|
||||
synology-supabase-functions) expected_path="${base}/functions" ;;
|
||||
synology-supabase-snippets) expected_path="${base}/snippets" ;;
|
||||
synology-supabase-imgproxy) expected_path="${base}/imgproxy" ;;
|
||||
synology-supabase-minio) expected_path="${base}/minio" ;;
|
||||
synology-supabase-storage) expected_path="${base}/storage" ;;
|
||||
esac
|
||||
local current_path current_node
|
||||
current_path=$(kubectl get pv "$pv" -o jsonpath='{.spec.local.path}' 2>/dev/null || true)
|
||||
current_node=$(kubectl get pv "$pv" -o jsonpath='{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}' 2>/dev/null || true)
|
||||
if [[ "${phase:-}" == "Released" ]]; then
|
||||
warn "PV '$pv' is in Released state; deleting PV object to allow rebind"
|
||||
kubectl delete pv "$pv" --ignore-not-found >/dev/null 2>&1 || true
|
||||
elif [[ -n "$current_path" && -n "$expected_path" && "$current_path" != "$expected_path" ]]; then
|
||||
warn "PV '$pv' path mismatch (current=${current_path}, expected=${expected_path}); recreating PV object"
|
||||
kubectl delete pv "$pv" --ignore-not-found >/dev/null 2>&1 || true
|
||||
elif [[ -n "$current_node" && "$current_node" != "$node" ]]; then
|
||||
warn "PV '$pv' node mismatch (current=${current_node}, expected=${node}); recreating PV object"
|
||||
kubectl delete pv "$pv" --ignore-not-found >/dev/null 2>&1 || true
|
||||
fi
|
||||
done
|
||||
|
||||
@ -1448,9 +1529,104 @@ spec:
|
||||
operator: In
|
||||
values:
|
||||
- ${node}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: synology-supabase-imgproxy
|
||||
labels:
|
||||
synology.storage/pvc: supabase-imgproxy
|
||||
spec:
|
||||
capacity:
|
||||
storage: 1Gi
|
||||
volumeMode: Filesystem
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ${storage_class}
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
local:
|
||||
path: ${base}/imgproxy
|
||||
nodeAffinity:
|
||||
required:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: kubernetes.io/hostname
|
||||
operator: In
|
||||
values:
|
||||
- ${node}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: synology-supabase-minio
|
||||
labels:
|
||||
synology.storage/pvc: supabase-minio
|
||||
spec:
|
||||
capacity:
|
||||
storage: 1Gi
|
||||
volumeMode: Filesystem
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ${storage_class}
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
local:
|
||||
path: ${base}/minio
|
||||
nodeAffinity:
|
||||
required:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: kubernetes.io/hostname
|
||||
operator: In
|
||||
values:
|
||||
- ${node}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: synology-supabase-storage
|
||||
labels:
|
||||
synology.storage/pvc: supabase-storage
|
||||
spec:
|
||||
capacity:
|
||||
storage: 1Gi
|
||||
volumeMode: Filesystem
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: ${storage_class}
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
local:
|
||||
path: ${base}/storage
|
||||
nodeAffinity:
|
||||
required:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: kubernetes.io/hostname
|
||||
operator: In
|
||||
values:
|
||||
- ${node}
|
||||
EOF
|
||||
}
|
||||
|
||||
enforce_supabase_workload_node() {
|
||||
local ns="$1"
|
||||
local node
|
||||
node="${SUPABASE_NODE_SELECTOR:-${SUPABASE_PV_NODE:-}}"
|
||||
if [[ -z "${node:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Forcing Supabase workloads onto node '${node}'..."
|
||||
|
||||
local kind resource
|
||||
for kind in deployment statefulset; do
|
||||
while IFS= read -r resource; do
|
||||
[[ -n "$resource" ]] || continue
|
||||
kubectl -n "$ns" patch "$resource" --type merge -p "{\"spec\":{\"template\":{\"spec\":{\"nodeSelector\":{\"kubernetes.io/hostname\":\"${node}\"}}}}}" >/dev/null 2>&1 || \
|
||||
warn "Could not patch ${resource} with nodeSelector ${node}"
|
||||
done < <(kubectl -n "$ns" get "$kind" -o name 2>/dev/null || true)
|
||||
done
|
||||
}
|
||||
|
||||
run_helm() {
|
||||
if [[ "$MODE" == "local" ]]; then
|
||||
return 1
|
||||
@ -1504,6 +1680,30 @@ print(c.get('Global','SUPABASE_STORAGE_CLASS',fallback=''))" 2>/dev/null || true
|
||||
if [[ -z "$storage_class" && "$MODE" == "k8s" ]]; then
|
||||
storage_class="synology-iscsi"
|
||||
fi
|
||||
|
||||
if [[ -z "${SUPABASE_PV_NODE:-}" && -n "${PROLE_CFG_PATH:-}" ]]; then
|
||||
SUPABASE_PV_NODE=$(python3 -c "
|
||||
import configparser
|
||||
c=configparser.ConfigParser(); c.read('${PROLE_CFG_PATH}')
|
||||
print(c.get('Global','SUPABASE_PV_NODE',fallback=''))" 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -z "${SUPABASE_PV_BASE_DIR:-}" && -n "${PROLE_CFG_PATH:-}" ]]; then
|
||||
SUPABASE_PV_BASE_DIR=$(python3 -c "
|
||||
import configparser
|
||||
c=configparser.ConfigParser(); c.read('${PROLE_CFG_PATH}')
|
||||
base=c.get('Global','SUPABASE_PV_BASE_DIR',fallback='').strip() or c.get('Global','SUPABASE_PV_BASE',fallback='').strip()
|
||||
print(base)" 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -n "${SUPABASE_PV_BASE_DIR:-}" && -z "${SUPABASE_PV_BASE:-}" ]]; then
|
||||
SUPABASE_PV_BASE="$SUPABASE_PV_BASE_DIR"
|
||||
fi
|
||||
if [[ -n "${SUPABASE_PV_NODE:-}" ]]; then
|
||||
export SUPABASE_PV_NODE
|
||||
fi
|
||||
if [[ -n "${SUPABASE_PV_BASE_DIR:-}" ]]; then
|
||||
export SUPABASE_PV_BASE_DIR
|
||||
export SUPABASE_PV_BASE
|
||||
fi
|
||||
# Guard: refuse non-synology storage classes to prevent SD-card crash on pi
|
||||
if [[ -n "$storage_class" ]] && ! [[ "$storage_class" =~ ^(synology|merlin-local-iscsi|myrddin-local-iscsi) ]]; then
|
||||
die "SUPABASE_STORAGE_CLASS '${storage_class}' is not a synology/iSCSI mount. Refusing Supabase deploy to prevent node crash."
|
||||
@ -1518,12 +1718,15 @@ print(c.get('Global','SUPABASE_STORAGE_CLASS',fallback=''))" 2>/dev/null || true
|
||||
|
||||
local helm_set_args=()
|
||||
if [[ -n "$storage_class" ]]; then
|
||||
# Only set storageClass for components still deployed; storage/minio/imgproxy are removed.
|
||||
# Keep all non-DB Supabase PVC-backed components on the selected storage class.
|
||||
helm_set_args+=(
|
||||
--set "persistence.db.storageClassName=${storage_class}"
|
||||
--set "persistence.deno.storageClassName=${storage_class}"
|
||||
--set "persistence.functions.storageClassName=${storage_class}"
|
||||
--set "persistence.imgproxy.storageClassName=${storage_class}"
|
||||
--set "persistence.minio.storageClassName=${storage_class}"
|
||||
--set "persistence.snippets.storageClassName=${storage_class}"
|
||||
--set "persistence.storage.storageClassName=${storage_class}"
|
||||
)
|
||||
fi
|
||||
|
||||
@ -1555,6 +1758,8 @@ print(c.get('Global','SUPABASE_STORAGE_CLASS',fallback=''))" 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
enforce_supabase_workload_node "$ns"
|
||||
|
||||
local wait_enabled
|
||||
wait_enabled="${SUPABASE_DEPLOY_WAIT:-true}"
|
||||
if [[ "$wait_enabled" == "true" ]]; then
|
||||
|
||||
@ -180,6 +180,57 @@ def test_main_rejects_repair_and_update_together(tmp_path, monkeypatch):
|
||||
screens_mod.main()
|
||||
|
||||
|
||||
def test_main_rejects_delete_db_without_reset(tmp_path, monkeypatch):
|
||||
"""CLI must require --reset when --delete-db is provided."""
|
||||
import knoe.ui.screens as screens_mod
|
||||
|
||||
cfg_path = tmp_path / "prole.cfg"
|
||||
_write_cfg(cfg_path, "[Inputs]\n[Global]\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
["install.py", "--silent", "--delete-db", "-c", str(cfg_path)],
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
screens_mod.main()
|
||||
|
||||
|
||||
def test_main_delete_db_is_forwarded_to_console_installer(tmp_path, monkeypatch):
|
||||
"""install.py --delete-db must be forwarded to ProleConsoleInstaller in silent mode."""
|
||||
import knoe.ui.screens as screens_mod
|
||||
|
||||
cfg_path = tmp_path / "prole.cfg"
|
||||
_write_cfg(cfg_path, "[Inputs]\n[Global]\n")
|
||||
|
||||
installer = MagicMock()
|
||||
installer.run.return_value = 0
|
||||
installer.run_update.return_value = 0
|
||||
installer.run_repair.return_value = 0
|
||||
|
||||
installer_factory = MagicMock(return_value=installer)
|
||||
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
["install.py", "--silent", "--reset", "--delete-db", "-c", str(cfg_path)],
|
||||
)
|
||||
monkeypatch.setattr(screens_mod, "_ensure_ansible_vault_credentials", lambda **_: None)
|
||||
monkeypatch.setattr(screens_mod, "ProleController", MagicMock(return_value=MagicMock()))
|
||||
monkeypatch.setattr(screens_mod, "ProleConsoleInstaller", installer_factory)
|
||||
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
screens_mod.main()
|
||||
|
||||
assert ex.value.code == 0
|
||||
installer_factory.assert_called_once()
|
||||
_, kwargs = installer_factory.call_args
|
||||
assert kwargs.get("reset_cluster") is True
|
||||
assert kwargs.get("delete_db") is True
|
||||
installer.run.assert_called_once()
|
||||
|
||||
|
||||
def test_repair_flag_does_not_trigger_silent_install(tmp_path):
|
||||
"""--repair must dispatch to run_repair(), NOT run() (full install)."""
|
||||
from knoe.core.actions import ProleConsoleInstaller
|
||||
@ -485,3 +536,56 @@ def test_reconcile_supabase_calls_studio_when_enabled(tmp_path):
|
||||
assert studio_reconcile_called, (
|
||||
"_reconcile_supabase_studio() must be called when studio is enabled"
|
||||
)
|
||||
|
||||
|
||||
def test_supabase_deploy_includes_knoe_schema_and_lint_remediation_sql():
|
||||
"""supabase/deploy.sh must include recurring knoe-schema and lint remediation SQL."""
|
||||
script = (REPO_ROOT / "supabase" / "deploy.sh").read_text(encoding="utf-8")
|
||||
|
||||
assert "CREATE ROLE knoe LOGIN PASSWORD" in script
|
||||
assert "CREATE SCHEMA IF NOT EXISTS knoe AUTHORIZATION knoe;" in script
|
||||
assert "DROP TABLE IF EXISTS public.migrations CASCADE;" in script
|
||||
assert "GRANT CREATE ON DATABASE postgres TO supabase_storage_admin;" in script
|
||||
assert "GRANT USAGE, CREATE ON SCHEMA public TO supabase_storage_admin;" in script
|
||||
assert "ALTER EXTENSION %I SET SCHEMA knoe" in script
|
||||
assert "ALTER TABLE public.spatial_ref_sys ENABLE ROW LEVEL SECURITY;" in script
|
||||
assert "CREATE POLICY spatial_ref_sys_select_all ON public.spatial_ref_sys" in script
|
||||
|
||||
|
||||
def test_reset_k3s_namespace_delete_db_deletes_pvcs_and_bound_pvs(tmp_path, monkeypatch):
|
||||
"""DB delete reset must request PVC deletion and force cleanup of bound PVs."""
|
||||
import subprocess as sp
|
||||
from knoe.core import actions as actions_mod
|
||||
|
||||
script = tmp_path / "scripts" / "reset-ns.sh"
|
||||
script.parent.mkdir(parents=True, exist_ok=True)
|
||||
script.write_text("#!/usr/bin/env bash\nexit 0\n", encoding="utf-8")
|
||||
|
||||
calls: list[list[str]] = []
|
||||
|
||||
def _fake_run(cmd, **kwargs):
|
||||
calls.append(list(cmd))
|
||||
if cmd[:6] == ["kubectl", "-n", "knoe-db", "get", "pvc", "-o"]:
|
||||
return sp.CompletedProcess(cmd, 0, stdout="pv-a\npv-b\n", stderr="")
|
||||
return sp.CompletedProcess(cmd, 0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(actions_mod.subprocess, "run", _fake_run)
|
||||
|
||||
actions_mod._reset_k3s_namespace(
|
||||
tmp_path,
|
||||
["knoe-db"],
|
||||
"",
|
||||
"",
|
||||
delete_pvcs_namespaces=["knoe-db"],
|
||||
force_delete_bound_pvs=True,
|
||||
)
|
||||
|
||||
assert any(
|
||||
cmd[:3] == ["bash", str(script), "-n"]
|
||||
and "--delete-pvcs" in cmd
|
||||
for cmd in calls
|
||||
)
|
||||
assert ["kubectl", "patch", "pv", "pv-a", "--type=merge", "-p", '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'] in calls
|
||||
assert ["kubectl", "patch", "pv", "pv-b", "--type=merge", "-p", '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'] in calls
|
||||
assert ["kubectl", "delete", "pv", "pv-a", "--ignore-not-found", "--wait=false"] in calls
|
||||
assert ["kubectl", "delete", "pv", "pv-b", "--ignore-not-found", "--wait=false"] in calls
|
||||
|
||||
Loading…
Reference in New Issue
Block a user