e958bb5c6bd8ffff0cdbf05a203e035d04600742
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 77d9a95f7f |
Run the database in a container of our own
Supabase was only ever the host: the application has talked to PostgreSQL
directly through pg/Kysely for a while. So the move is mostly about
supplying what the platform used to supply.
Proved before building anything. All 65 migrations replay onto an empty
database, and the result matches production exactly — 183 columns, 25
policies, 68 indexes, 84 constraints, identical sets, no diff. The only
function missing from the rebuild turned out to matter, see below.
What the platform supplied, deploy/db-init now does:
- alpenwerk_app, explicitly NOBYPASSRLS. The whole access model is 21
RLS policies; a role that bypasses them would leave everything
working while showing too much, and nobody would notice.
- pgcrypto and pg_trgm. uuid-ossp was available on Supabase but is
used nowhere — no column default, no function calls uuid_generate_*.
- anon, authenticated and service_role as NOLOGIN placeholders. No
policy names them; they only carry grants the platform handed out,
and a data dump referencing them would fail to restore without them.
- A stub `auth` schema. The end state needs none of it — checked: no
foreign key, no policy, no column default refers to it. The June
2026 migrations do, and rewriting those would be falsifying history;
they describe what was true then.
The gap the comparison found: rls_auto_enable() and the ensure_rls event
trigger existed only in the running database, created by hand, in no
migration. That is the net which forces RLS on every newly created
table — the reason a forgotten policy yields an empty table instead of
an open one. A rebuild from migrations would silently not have had it:
everything works, and the next new table is unprotected. Now a migration
(20260819100000), verified by creating a table on the rebuild and
confirming RLS came on by itself.
Data moves separately, via scripts/umzug-von-supabase.sh: schema from
the migrations, then pg_dump --data-only --disable-triggers for the rows.
Without --disable-triggers every foreign key trips over load order. RLS
does not interfere — none of the 19 tables uses FORCE ROW LEVEL
SECURITY, so the owner writes through. The dump is deliberately left on
disk afterwards.
psql and node come from two `tools`-profile services rather than being
installed on the host, so the server needs nothing but Docker. The db
service publishes no port at all — reachable only inside the compose
network.
SUPABASE_DB_URL is renamed MIGRATE_DATABASE_URL, since after this it
describes something else entirely; the old name still works so existing
.env files keep running. Both were exercised, as was the error when
neither is set.
The deploy workflow is set to manual-only. Its preconditions were never
met — no secrets, and whether the job container can reach the host's
Docker daemon is untested — and failing on every push teaches people to
ignore red runs. It also needs updating for the new database service
before it could work at all.
Not verified: none of this has run in an actual container. There is no
Docker daemon on this machine. What is verified is the part that
decides whether it can work — the schema, on a real empty database.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|||
| d574d3c9d6 |
Aim the deploy at the runner that exists
The workflow asked for a `self-hosted` label. No runner on the instance offers one, so the run would have sat in "Waiting" forever — no error, no message, nothing to notice. The existing global runner elycon-runner-01 offers `docker` and `ubuntu-latest`, so both workflows now ask for `ubuntu-latest`, the same label ci.yml already used. That correction exposed a second thing the first version glossed over. act_runner starts a container per job; mounting the Docker socket into the *runner* does not put it in the *job*. Whether this job can reach the host's daemon depends on the runner's config.yaml, which is not visible from here — and the runner is global, so changing it affects every repository on the instance, not just this one. Rather than guess, the workflow now measures it in its first step and fails with the fix if it cannot: which config lines to add for the socket, or that SSH is the other way. Without that, the run would have died three steps later on a message nobody could act on. Both branches of the check were exercised: docker absent prints the first message, docker present with no reachable daemon the second. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| fc0989debb |
Deploy from a push, and start keeping track of migrations
A push to master now builds and restarts the application on the server: a Gitea Actions workflow on a self-hosted runner writes .env from the repository secrets, applies pending migrations, rebuilds the compose stack against the host's Docker daemon, and waits for the container's healthcheck before calling the run green. Without that last step a deploy counts as successful the moment the container *starts*, even if the app inside it dies immediately. Switching migrations on automatically turned up something that had to be fixed first: supabase_migrations.schema_migrations did not exist at all. Every one of the 65 migrations was unrecorded, because they have been applied by hand all along. An automatic `db push` would therefore have replayed all 65 against the live database — initial_schema and the OM cutover included. The database was checked against a spread of migrations first (it is at head), then baselined: all 65 recorded as applied without executing them. The runner is scripts/migrate.mjs rather than the Supabase CLI. It needs only `pg`, which the project already ships, instead of downloading a CLI whose version drifts independently of this repository; and it does one thing — the missing files, in order, each in its own transaction — where `db push` also diffs schemas and may do more than that. Bookkeeping goes in the same table in the same shape the CLI uses, so `supabase db push` from a workstation still works and still skips what already ran. The workflow lives in .github/workflows, not .gitea/. Gitea reads .gitea/workflows and falls back to .github/workflows only when the former is absent — creating .gitea/ would have silently switched off ci.yml, with the run simply never appearing. Verified: both workflow files parse; the secret check names what is missing and refuses; values starting with "-" or containing "=" survive being written to .env; and the runner was exercised against the real database with a throwaway migration — applied once, skipped on a second run, and on a deliberate syntax error rolled back whole, recording nothing. Both probes were removed; the count is back to 65. Not verified: nothing has run on an actual Gitea runner — none is registered yet. DEPLOYMENT.md §5 covers registering one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |