2 Commits
Author SHA1 Message Date
Linus Gasser fd8a5d1541 chore: publish 0.1.1+3.2.0 release 2026-11-09 16:09:49 +01:00
Linus GasserandClaude Opus 5.5 5295915dff feat: bootstrap admin account from ADMIN_EMAIL
Create/promote the ADMIN_EMAIL account on every start, before the seeds,
so the public admin@claper.co/claper default is never created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:58:31 +01:00
6 changed files with 128 additions and 19 deletions
+8 -3
View File
@@ -16,13 +16,18 @@ COMPOSE_FILE="compose.yml"
SECRET_DB_PASSWORD_VERSION=v1 # charset=default
# SECRET_KEY_BASE, must be at least 32 bytes
SECRET_KEY_BASE_VERSION=v1 # length=64
# Password for the seeded admin@claper.co account, applied with
# `abra app cmd <app-name> app set_admin_password`
SECRET_ADMIN_PASS_VERSION=v1
# Initial password of the ADMIN_EMAIL account, see ADMIN_EMAIL below
SECRET_ADMIN_PASS_VERSION=v1 # length=32
##############################################################################
# CLAPER
##############################################################################
# Account that is ensured to exist and be an admin, checked on every start.
# It is created with the admin_pass secret as password; later password changes
# are done in the UI or with `abra app cmd <app-name> app set_admin_password`.
# Changing it adds a new admin, the previous one is kept.
ADMIN_EMAIL=admin@claper.example.com
# See https://docs.claper.co/self-hosting/configuration.html
#ENABLE_ACCOUNT_CREATION=true
#EMAIL_CONFIRMATION=false
+40 -9
View File
@@ -18,16 +18,38 @@
## Quick start
* `abra app new claper --secrets`
* `abra app config <app-name>`
* `abra app config <app-name>`, set `ADMIN_EMAIL`
* `abra app deploy <app-name>`
* Log in as `ADMIN_EMAIL` with the `admin_pass` secret printed by
`abra app new --secrets`
## Admin account
On every start of the `app` container, the account `ADMIN_EMAIL` is ensured to
exist and to be an admin:
* if it doesn't exist, it is created with the `admin_pass` secret as password
* if it exists without admin role, it is promoted to admin
* if it already is an admin, nothing changes, the password is never overwritten
Changing `ADMIN_EMAIL` and redeploying adds a new admin; the previous admin is
kept and has to be demoted or deleted in the admin panel if it isn't needed
anymore.
To reset the admin password to the `admin_pass` secret, e.g. after rotating it:
* `abra app secret rm <app-name> admin_pass` and
`abra app secret insert <app-name> admin_pass v1 <password>`, or bump
`SECRET_ADMIN_PASS_VERSION` and `abra app secret generate <app-name> admin_pass`
* `abra app deploy <app-name> --force`
* `abra app cmd <app-name> app set_admin_password`
On first start Claper creates the admin account `admin@claper.co` with the
password `claper`. `set_admin_password` replaces it with the `admin_pass`
secret printed by `abra app new --secrets`. Do this right after the first
deploy, the default password is public.
Without `ADMIN_EMAIL`, Claper's own seeds create `admin@claper.co` with the
public password `claper` on an empty database. Change it right away, e.g. by
setting `ADMIN_EMAIL=admin@claper.co` and running `set_admin_password`.
Database migrations and seeds run on every start of the `app` container.
Database migrations, the admin bootstrap and seeds run on every start of the
`app` container.
## Email
@@ -59,9 +81,18 @@ SECRET_OIDC_SECRET_VERSION=v1
* `abra app secret insert <app-name> oidc_secret v1 <client-secret>`
* `abra app deploy <app-name> --force`
To allow only OIDC logins (`DISABLE_PASSWORD_LOGIN=true`), first log in once as
`admin@claper.co`, log in once via OIDC with the account that should be the
admin, promote it to admin in the admin panel, and only then enable the option.
To log in as admin via OIDC, set `ADMIN_EMAIL` to the email address of the admin
at the provider. The first OIDC login with that address is linked to the
existing admin account:
* directly, if `OIDC_TRUST_EMAIL_VERIFIED=true` and the provider marks the email
as verified
* otherwise after clicking a confirmation link that Claper mails to that
address, which needs [email](#email) to be set up
With this, `DISABLE_PASSWORD_LOGIN=true` (only OIDC logins) can be enabled from
the first deploy on. The `admin_pass` secret is still required, but not used for
logging in anymore.
## S3 storage
+7 -5
View File
@@ -2,18 +2,20 @@
# Docs: https://docs.coopcloud.tech/maintainers/handbook/#manage-configs
export ABRA_ENTRYPOINT_VERSION=v1
export PG_BACKUP_VERSION=v1
export BOOTSTRAP_ADMIN_VERSION=v1
# Replace the password of the seeded admin@claper.co account with the
# admin_pass secret.
# Reset the password of the ADMIN_EMAIL account to the admin_pass secret.
# Usage: abra app cmd <app-name> app set_admin_password
set_admin_password() {
/app/bin/claper rpc '
email = System.get_env("ADMIN_EMAIL", "") |> String.trim()
pw = File.read!("/run/secrets/admin_pass") |> String.trim()
case Claper.Accounts.get_user_by_email("admin@claper.co") do
nil -> IO.puts("admin@claper.co does not exist, nothing to do")
case email != "" && Claper.Accounts.get_user_by_email(email) do
false -> IO.puts("ADMIN_EMAIL is not set, nothing to do")
nil -> IO.puts("#{email} does not exist, nothing to do")
user ->
{:ok, _} = Claper.Accounts.reset_user_password(user, %{password: pw, password_confirmation: pw})
IO.puts("Password of admin@claper.co updated")
IO.puts("Password of #{email} updated")
end
'
}
+59
View File
@@ -0,0 +1,59 @@
# Ensures that the account ADMIN_EMAIL exists and is an admin. Runs on every
# start, between the migrations and the seeds:
# - missing account: created with the admin_pass secret as password
# - account without admin role: promoted to admin
# - existing admin: left untouched, the password is never overwritten
#
# Because the account exists before the seeds run, the seeds never create the
# default admin@claper.co/claper account.
# Without ADMIN_EMAIL nothing is done and the upstream seeds apply.
alias Claper.Accounts
alias Claper.Accounts.Role
alias Claper.Repo
case System.get_env("ADMIN_EMAIL", "") |> String.trim() do
"" ->
IO.puts("ADMIN_EMAIL not set, skipping admin bootstrap")
email ->
Application.load(:claper)
{:ok, _, _} =
Ecto.Migrator.with_repo(Repo, fn _repo ->
# The roles are normally created by the seeds, which run after this.
admin_role =
Repo.get_by(Role, name: "admin") ||
Repo.insert!(%Role{name: "admin", permissions: %{"all" => true}})
Repo.get_by(Role, name: "user") || Repo.insert!(%Role{name: "user", permissions: %{}})
case Accounts.get_user_by_email(email) do
nil ->
pw = File.read!("/run/secrets/admin_pass") |> String.trim()
case Accounts.register_user(%{
email: email,
first_name: "Claper",
last_name: "Admin",
password: pw,
role_id: admin_role.id,
confirmed_at: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
}) do
{:ok, _} ->
IO.puts("Created admin #{email} with the admin_pass secret as password")
{:error, changeset} ->
raise "Could not create admin #{email}: #{inspect(changeset.errors)}"
end
user ->
if user.role_id == admin_role.id do
IO.puts("Admin #{email} exists, nothing to do")
else
{:ok, _} = Accounts.assign_role(user, admin_role)
IO.puts("Promoted #{email} to admin")
end
end
end)
end
+8 -2
View File
@@ -18,11 +18,13 @@ services:
command:
- sh
- -c
- "/app/bin/claper eval 'Claper.Release.migrate(); Claper.Release.seeds()' && exec /app/bin/claper start"
- "/app/bin/claper eval 'Claper.Release.migrate(); Code.eval_file(\"/bootstrap_admin.exs\"); Claper.Release.seeds()' && exec /app/bin/claper start"
configs:
- source: abra_entrypoint
target: /abra-entrypoint.sh
mode: 0555
- source: bootstrap_admin
target: /bootstrap_admin.exs
secrets:
- db_password
- admin_pass
@@ -30,6 +32,7 @@ services:
target: SECRET_KEY_BASE
environment:
- STACK_NAME
- ADMIN_EMAIL
- BASE_URL=https://${DOMAIN}
- SECURE_COOKIE=true
- SAME_SITE_COOKIE
@@ -66,7 +69,7 @@ services:
- "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})"
- "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure"
- "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}"
- "coop-cloud.${STACK_NAME}.version=0.1.0+3.2.0"
- "coop-cloud.${STACK_NAME}.version=0.1.1+3.2.0"
- "coop-cloud.${STACK_NAME}.timeout=${TIMEOUT:-300}"
- "backupbot.backup=${ENABLE_BACKUPS:-true}"
healthcheck:
@@ -124,6 +127,9 @@ configs:
abra_entrypoint:
name: ${STACK_NAME}_entrypoint_${ABRA_ENTRYPOINT_VERSION}
file: abra-entrypoint.sh
bootstrap_admin:
name: ${STACK_NAME}_bootstrap_admin_${BOOTSTRAP_ADMIN_VERSION}
file: bootstrap_admin.exs
pg_backup:
name: ${STACK_NAME}_pg_backup_${PG_BACKUP_VERSION}
file: pg_backup.sh
+6
View File
@@ -0,0 +1,6 @@
The admin account is now created from the new ADMIN_EMAIL variable, with the
admin_pass secret as initial password. The public default admin@claper.co /
claper account is no longer created on new installs, and running
`abra app cmd <app-name> app set_admin_password` after deploying is not needed
anymore. Existing installs without ADMIN_EMAIL are unchanged; set it to make
sure that account exists and is an admin. See the README for details.