Fix seeds to run in production #462
5 changed files with 57 additions and 4 deletions
|
|
@ -2,24 +2,26 @@
|
||||||
|
|
||||||
## Overview
|
## 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.
|
- **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)
|
## Admin Bootstrap (Part A)
|
||||||
|
|
||||||
### Environment Variables
|
### 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_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` – 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).
|
- `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.
|
- `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
|
### 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)
|
### Seeds (Dev/Test)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ defmodule Mv.Release do
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
- `migrate/0` - Runs all pending Ecto migrations.
|
- `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
|
- `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
|
or ADMIN_PASSWORD_FILE). Idempotent; can be run on every deployment or via shell
|
||||||
to update the admin password without redeploying.
|
to update the admin password without redeploying.
|
||||||
|
|
@ -26,6 +28,40 @@ defmodule Mv.Release do
|
||||||
end
|
end
|
||||||
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
|
def rollback(repo, version) do
|
||||||
load_app()
|
load_app()
|
||||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
# Bootstrap runs in all environments. Dev seeds (members, groups, sample data)
|
# Bootstrap runs in all environments. Dev seeds (members, groups, sample data)
|
||||||
# run only in dev and test.
|
# 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
|
# Compiler option ignore_module_conflict is set only during seed evaluation
|
||||||
# so that eval_file of bootstrap/dev does not emit "redefining module" warnings;
|
# 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.
|
# it is always restored in `after` to avoid hiding real conflicts elsewhere.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
# Bootstrap seeds: run in all environments (dev, test, prod).
|
# Bootstrap seeds: run in all environments (dev, test, prod).
|
||||||
# Creates only data required for system startup: fee types, custom fields,
|
# Creates only data required for system startup: fee types, custom fields,
|
||||||
# roles, admin user, system user, global settings. No members, no groups.
|
# 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.Accounts
|
||||||
alias Mv.Membership
|
alias Mv.Membership
|
||||||
|
|
@ -121,7 +130,7 @@ end
|
||||||
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
|
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
|
||||||
System.put_env("ADMIN_EMAIL", admin_email)
|
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
|
is_nil(System.get_env("ADMIN_PASSWORD_FILE")) do
|
||||||
System.put_env("ADMIN_PASSWORD", "testpassword")
|
System.put_env("ADMIN_PASSWORD", "testpassword")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ set -e
|
||||||
echo "==> Running database migrations..."
|
echo "==> Running database migrations..."
|
||||||
/app/bin/migrate
|
/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)..."
|
echo "==> Seeding admin user from ENV (ADMIN_EMAIL, ADMIN_PASSWORD)..."
|
||||||
/app/bin/mv eval "Mv.Release.seed_admin()"
|
/app/bin/mv eval "Mv.Release.seed_admin()"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue