diff --git a/docs/admin-bootstrap-and-oidc-role-sync.md b/docs/admin-bootstrap-and-oidc-role-sync.md index abbd03f..5e26c85 100644 --- a/docs/admin-bootstrap-and-oidc-role-sync.md +++ b/docs/admin-bootstrap-and-oidc-role-sync.md @@ -2,24 +2,26 @@ ## Overview -- **Admin bootstrap:** In production, no seeds run. The first admin user is created/updated from environment variables in the Docker entrypoint (after migrate, before server). Password can be changed without redeploy via `bin/mv eval "Mv.Release.seed_admin()"`. +- **Admin bootstrap:** In production, the Docker entrypoint runs migrate, then `Mv.Release.run_seeds/0` (bootstrap seeds; set `RUN_DEV_SEEDS=true` to also run dev seeds), then `seed_admin/0` from ENV, then the server. Password can be changed without redeploy via `bin/mv eval "Mv.Release.seed_admin()"`. - **OIDC role sync:** Optional mapping from OIDC groups (e.g. from Authentik profile scope) to the Admin role. Users in the configured admin group get the Admin role on registration and on each sign-in. ## Admin Bootstrap (Part A) ### Environment Variables +- `RUN_DEV_SEEDS` – If set to `"true"`, `run_seeds/0` also runs dev seeds (members, groups, sample data). Otherwise only bootstrap seeds run. - `ADMIN_EMAIL` – Email of the admin user to create/update. If unset, seed_admin/0 does nothing. - `ADMIN_PASSWORD` – Password for the admin user. If unset (and no file), no new user is created; if a user with ADMIN_EMAIL already exists (e.g. OIDC-only), their role is set to Admin (no password change). - `ADMIN_PASSWORD_FILE` – Path to a file containing the password (e.g. Docker secret). -### Release Task +### Release Tasks +- `Mv.Release.run_seeds/0` – Runs bootstrap seeds (fee types, custom fields, roles, settings). If `RUN_DEV_SEEDS` env is `"true"`, also runs dev seeds (members, groups, sample data). Idempotent. - `Mv.Release.seed_admin/0` – Reads ADMIN_EMAIL and password from ADMIN_PASSWORD or ADMIN_PASSWORD_FILE. If both email and password are set: creates or updates the user with the Admin role. If only ADMIN_EMAIL is set: sets the Admin role on an existing user with that email (for OIDC-only admins); does not create a user. Idempotent. ### Entrypoint -- rel/overlays/bin/docker-entrypoint.sh – After migrate, runs seed_admin(), then starts the server. +- rel/overlays/bin/docker-entrypoint.sh – After migrate, runs run_seeds(), then seed_admin(), then starts the server. ### Seeds (Dev/Test) diff --git a/lib/mv/release.ex b/lib/mv/release.ex index 54bc245..00dcadf 100644 --- a/lib/mv/release.ex +++ b/lib/mv/release.ex @@ -6,6 +6,8 @@ defmodule Mv.Release do ## Tasks - `migrate/0` - Runs all pending Ecto migrations. + - `run_seeds/0` - Runs bootstrap seeds (fee types, custom fields, roles, settings). + In production, set `RUN_DEV_SEEDS=true` to also run dev seeds (members, groups, sample data). - `seed_admin/0` - Ensures an admin user exists from ENV (ADMIN_EMAIL, ADMIN_PASSWORD or ADMIN_PASSWORD_FILE). Idempotent; can be run on every deployment or via shell to update the admin password without redeploying. @@ -26,6 +28,40 @@ defmodule Mv.Release do end end + @doc """ + Runs seed scripts so the database has required bootstrap data (and optionally dev data). + + - Always runs bootstrap seeds (fee types, custom fields, roles, system user, settings). + - If `RUN_DEV_SEEDS` env is set to `"true"`, also runs dev seeds (members, groups, sample data). + + Uses paths from the application's priv dir so it works in releases (no Mix). Idempotent. + """ + def run_seeds do + case Application.ensure_all_started(@app) do + {:ok, _} -> :ok + {:error, {app, reason}} -> raise "Failed to start #{inspect(app)}: #{inspect(reason)}" + end + + priv = :code.priv_dir(@app) + bootstrap_path = Path.join(priv, "repo/seeds_bootstrap.exs") + dev_path = Path.join(priv, "repo/seeds_dev.exs") + + prev = Code.compiler_options() + Code.compiler_options(ignore_module_conflict: true) + + try do + Code.eval_file(bootstrap_path) + IO.puts("✅ Bootstrap seeds completed.") + + if System.get_env("RUN_DEV_SEEDS") == "true" do + Code.eval_file(dev_path) + IO.puts("✅ Dev seeds completed.") + end + after + Code.compiler_options(prev) + end + end + def rollback(repo, version) do load_app() {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 44df447..7257f8b 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -5,6 +5,9 @@ # Bootstrap runs in all environments. Dev seeds (members, groups, sample data) # run only in dev and test. # +# In production (release): seeds are run via Mv.Release.run_seeds/0 from the +# container entrypoint. Set RUN_DEV_SEEDS=true to also run dev seeds there. +# # Compiler option ignore_module_conflict is set only during seed evaluation # so that eval_file of bootstrap/dev does not emit "redefining module" warnings; # it is always restored in `after` to avoid hiding real conflicts elsewhere. diff --git a/priv/repo/seeds_bootstrap.exs b/priv/repo/seeds_bootstrap.exs index 94b8cc0..7aafaac 100644 --- a/priv/repo/seeds_bootstrap.exs +++ b/priv/repo/seeds_bootstrap.exs @@ -1,6 +1,15 @@ # Bootstrap seeds: run in all environments (dev, test, prod). # Creates only data required for system startup: fee types, custom fields, # roles, admin user, system user, global settings. No members, no groups. +# +# Safe to run from release (no Mix): env is taken from MIX_ENV when Mix.env/0 is not available. + +mix_env = + try do + Mix.env() + rescue + UndefinedFunctionError -> (System.get_env("MIX_ENV") || "prod") |> String.to_atom() + end alias Mv.Accounts alias Mv.Membership @@ -121,7 +130,7 @@ end admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost" System.put_env("ADMIN_EMAIL", admin_email) -if Mix.env() in [:dev, :test] and is_nil(System.get_env("ADMIN_PASSWORD")) and +if mix_env in [:dev, :test] and is_nil(System.get_env("ADMIN_PASSWORD")) and is_nil(System.get_env("ADMIN_PASSWORD_FILE")) do System.put_env("ADMIN_PASSWORD", "testpassword") end diff --git a/rel/overlays/bin/docker-entrypoint.sh b/rel/overlays/bin/docker-entrypoint.sh index caa389a..fbe345d 100755 --- a/rel/overlays/bin/docker-entrypoint.sh +++ b/rel/overlays/bin/docker-entrypoint.sh @@ -4,6 +4,9 @@ set -e echo "==> Running database migrations..." /app/bin/migrate +echo "==> Running seeds (bootstrap; dev if RUN_DEV_SEEDS=true)..." +/app/bin/mv eval "Mv.Release.run_seeds()" + echo "==> Seeding admin user from ENV (ADMIN_EMAIL, ADMIN_PASSWORD)..." /app/bin/mv eval "Mv.Release.seed_admin()"