Full production-ready docker-compose stack (db, kong, auth, rest, realtime, storage, imgproxy, meta, functions, studio) targeting api.caterium.ru, plus bootstrap script for a fresh Cloud Server, Caddy reverse-proxy config (HTTPS, WebSocket, upload limits), and dump/restore/verify/storage-sync scripts for moving off the managed Supabase project (cksuehzcimitsxmeloes). Does not touch public/ or any live runtime behavior — frontend cutover is documented separately in frontend-cutover.md and only applied after Etap 8 verification. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.4 KiB
Frontend cutover (Etap 9-11) — apply only after Etap 8 counts match
Two changes to public/app-runtime.js, both required together. Do not commit either until
scripts/03-verify-counts.sql shows matching counts on old vs new (14/11/53/52/460/18) and
the Etap 10 smoke test has passed against https://api.caterium.ru directly (before touching
the frontend).
1. Point the default at the new backend
// public/app-runtime.js, line ~1989-1990
const DEFAULT_SUPABASE_URL = 'https://api.caterium.ru';
const DEFAULT_SUPABASE_KEY = '<NEW_ANON_KEY_FROM_.env>';
2. Auto-migrate devices that already have the OLD url cached in localStorage
Every Caterium device has sunCloudV2Config (and possibly the legacy sunEnterpriseSettingsV1)
in localStorage, which loadConfig() reads before falling back to DEFAULT_SUPABASE_URL.
Just changing the default does nothing for those devices — they keep talking to
cksuehzcimitsxmeloes.supabase.co forever unless we rewrite the stored value once. Insert this
right after the existing DEFAULT_SUPABASE_KEY line, and call it from inside loadConfig()
before the return { ... }:
const DEFAULT_SUPABASE_URL = 'https://api.caterium.ru';
const DEFAULT_SUPABASE_KEY = '<NEW_ANON_KEY_FROM_.env>';
const LEGACY_SUPABASE_URL = 'https://cksuehzcimitsxmeloes.supabase.co';
function migrateLegacySupabaseUrl(value) {
// One-time, silent migration: any device still pointed at the retired managed
// Supabase project is switched to the self-hosted backend automatically. Users
// are never asked to clear localStorage or reconfigure anything.
if (String(value.url || '').trim().replace(/\/$/, '') === LEGACY_SUPABASE_URL) {
value.url = DEFAULT_SUPABASE_URL;
value.key = DEFAULT_SUPABASE_KEY;
}
return value;
}
Then in loadConfig(), wrap the legacy-settings merge:
function loadConfig() {
let value = {};
try { value = JSON.parse(localStorage.getItem(CONFIG_KEY) || '{}') || {}; } catch (_) {}
if (!value.url || !value.key) {
try {
const legacy = JSON.parse(localStorage.getItem('sunEnterpriseSettingsV1') || '{}') || {};
const cloud = legacy.cloud || {};
value.url ||= String(cloud.supabaseUrl || '').trim();
value.key ||= String(cloud.anonKey || '').trim();
} catch (_) {}
}
value = migrateLegacySupabaseUrl(value); // <-- add this line
return {
url: String(value.url || DEFAULT_SUPABASE_URL || '').trim().replace(/\/$/, ''),
key: String(value.key || DEFAULT_SUPABASE_KEY || '').trim(),
...
saveConfig() (a few lines below) persists config back to localStorage on every config
change already, so the corrected URL/key get written back to sunCloudV2Config the first time
loadConfig() runs on each device — no explicit persistence call needed here, but if a save
is not triggered naturally during your smoke test, call saveConfig() once right after
config = loadConfig(); at module init (line ~1992) to force-persist the migration immediately
rather than waiting for the next unrelated write.
3. Session invalidation is expected
Because the new stack uses a freshly generated JWT_SECRET (README decision, since the old
one can't be safely retrieved), any cached Supabase auth session (JWT) in localStorage
becomes invalid the moment the client points at the new URL — supabase-js will see a 401 on
its first call, clear the stale session automatically, and the user lands back on the Caterium
login screen. That matches the task's accepted trade-off: users log in once more with their
existing email + password (unchanged, since auth.users.encrypted_password is copied
byte-for-byte). No code change needed for this part — it's supabase-js's default behavior.
Checklist before running this cutover
scripts/03-verify-counts.sqlmatches old vs new (14/11/53/52/460/18)curl https://api.caterium.ru/rest/v1/,/auth/v1/,/storage/v1/,/functions/v1/all respond (not connection errors)- Realtime WebSocket connects:
wscat -c wss://api.caterium.ru/realtime/v1/websocket?apikey=<ANON_KEY> scripts/05-storage-sync.shfinished, spot-checked photo URLs loadscripts/04-deploy-edge-function.shshowscaterium-create-employeerejecting an unauthenticated call with 401/400 (not 5xx)- Manual login test against
https://api.caterium.rudirectly (temporarily point a local dev copy ofapp-runtime.jsat it) with one real existing account, confirm orders/ clients/catalog load
Only once every box above is checked: apply the two edits above, commit to main with a
message like Cutover: point Caterium at self-hosted Supabase (api.caterium.ru), push. CI
(Caterium QA -> Caterium Promote Production) auto-promotes main to production on green,
and the existing Timeweb cron (ops/timeweb/caterium-production-sync.sh) deploys it to
app.caterium.ru within 5 minutes. Do NOT skip CI (--no-verify or manual production-branch
pushes) for this change — the QA workflow's Playwright e2e run is exactly the safety net you
want on a backend cutover.
After confirming https://app.caterium.ru works end-to-end with no supabase.co requests in
DevTools Network, leave the old managed Supabase project (cksuehzcimitsxmeloes) running
untouched as a cold backup, per the task's explicit instruction — do not pause or delete it
without separate owner confirmation.