diff --git a/AGENTS.md b/AGENTS.md index 8704da7..3754907 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,7 +80,7 @@ _build/prod/rel/flick/bin/flick remote In that shell, run `:observer_cli.start()`. Press `q` and then Enter to quit observer_cli. Quitting leaves the node running. Never run `System.stop/0` or `:init.stop/0` in the remote shell, because they stop production. Leave the shell with Ctrl+C and then `a`, which closes only the remote shell. The release also includes `:recon` for one-off queries such as `:recon.proc_count(:memory, 10)`. -Phoenix LiveDashboard runs at `/admin/dashboard`, behind the same basic auth as `/admin/ballots`. In development, the credentials are `flick-admin` / `unsafe-password`. In production, they come from `BASIC_AUTH_ADMIN_USERNAME` and `BASIC_AUTH_ADMIN_PASSWORD`. Its Processes page shows each process's dictionary and stack trace, but not its state. It still exposes the node's internals, so never move it out of the `/admin` scope. Its Ecto Stats page reads table sizes, index usage, and locks through `ecto_psql_extras` over the private network. It needs no remote shell and starts no second VM, so try it before observer_cli. Use observer_cli when the web endpoint cannot serve a LiveView. +Phoenix LiveDashboard runs at `/admin/dashboard`, behind the same basic auth as `/admin/ballots`. In development, the credentials are `flick-admin` / `unsafe-password`. In production, they come from `BASIC_AUTH_ADMIN_USERNAME` and `BASIC_AUTH_ADMIN_PASSWORD`. Its Processes page shows each process's dictionary and stack trace, but not its state. It still exposes the node's internals, so never move it out of the `/admin` scope. Its Ecto Stats page reads table sizes, index usage, and locks through `ecto_psql_extras` over the private network. It needs no remote shell and starts no second VM, so try it before observer_cli. Its "outliers" and "calls" tabs need `pg_stat_statements`, so they work only on a server that preloads the library. Local and CI Postgres do not, and their migration skips the extension with a notice. Use observer_cli when the web endpoint cannot serve a LiveView. `rel/env.sh.eex` binds EPMD and Erlang distribution to loopback and names the node `flick@localhost`, so only a shell inside the container can reach it. Keep it that way. Never expose distribution on Render's network, and never set a custom release cookie. The default cookie never leaves the container. A custom cookie would sit in Render's environment variables, and its only use is connecting from outside, where a cookie grants full code execution on the node. diff --git a/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs new file mode 100644 index 0000000..9bb8bed --- /dev/null +++ b/priv/repo/migrations/20261020164842_enable_pg_stat_statements.exs @@ -0,0 +1,27 @@ +defmodule Flick.Repo.Migrations.EnablePgStatStatements do + use Ecto.Migration + + # LiveDashboard's Ecto Stats needs this extension for its "outliers" and + # "calls" tabs. The extension's view raises an error unless the server + # preloads the library. Local and CI Postgres do not preload it. + # + # Only a superuser can read `shared_preload_libraries`, so the block creates + # the extension and reads the view once. On an error, the block rolls back + # and leaves no extension. A diagnostics extension must never block a deploy. + def change do + # The raw SQL touches no table. The down step reverses it. + # excellent_migrations:safety-assured-for-next-line raw_sql_executed + execute """ + DO $$ + BEGIN + CREATE EXTENSION IF NOT EXISTS pg_stat_statements; + PERFORM 1 FROM pg_stat_statements LIMIT 1; + EXCEPTION + WHEN OTHERS THEN + RAISE NOTICE 'pg_stat_statements skipped: %', SQLERRM; + END + $$ + """, + "DROP EXTENSION IF EXISTS pg_stat_statements" + end +end