Running any tool jumped to the Tasks tab and left the user stranded there. That is right for an install — long, log-heavy, worth watching — and wrong for a tool, which is a short admin action whose answer is one line. Worse, half of these are only meaningful back on Tools: List Users opens a modal over that tab, and Create User Account returns a generated password that was being buried in a log the user then had to go read. Tools now stay put. On completion the tool's own outcome lines — the isSuccessful/isError/isNotice output, ANSI stripped and framework boilerplate filtered — are shown in a small result modal, with a View log button for anything needing the full detail. list_users is left alone because the existing account-list modal is already a better result view. Also stops generate_arrays.sh walking scripts/dev. That directory is `export-ignore`d, so it exists in a working clone but never in a shipped install; generating a files_dev.sh entry from it wrote a reference into files_source.sh that no install could satisfy, and the loader treats a missing array file as a broken installation — every libreportal command stopped with "files_dev.sh is missing from your LibrePortal Installation". Excluded alongside unused/, system/ and release/. Regenerating also picked up scripts/validation, which had never had an array file. And Matrix's account listing prints its aligned line from python rather than re-splitting the marker line in bash: TAB is IFS whitespace, so an empty display name collapsed into the previous delimiter and shifted every later column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.0 KiB
LibrePortal — Upgrade Notes
Version-specific notes for existing installs. libreportal update apply replaces
the install tree only — it never rewrites an app's deployed
<containers-dir>/<app>/docker-compose.yml. So a fix to an app template reaches a
running app on its next libreportal app install <app> (also the path taken by
restore, peer pull, and migrate), not on update. Anything below that needs a manual
step says so.
0.2.0 — Mastodon shipped with unsubstituted database credentials
Affects: anyone who installed Mastodon on 0.1.0. No other app is affected.
What went wrong
Compose templates carry tag annotations of the form
#LIBREPORTAL|<TAG>|<VALUE>, where <VALUE> is the current literal the tag
manager searches for on that line. Mastodon's annotations used unconfigured
while the line bodies said PASSWORD_TAG_1_DATA and friends, so the two never
matched and nothing was ever substituted:
- DB_PASS=PASSWORD_TAG_1_DATA #LIBREPORTAL|PASSWORD_TAG_1|unconfigured
The literal placeholder strings shipped as the real values. Because unconfigured
does not look like a placeholder, neither the tag state check nor the pre-start
stale-tag gate in dockerComposeUp flagged it, and restarts did not heal it.
An affected instance is running with these publicly known constants:
| Setting | Value in an affected install |
|---|---|
DB_USER / POSTGRES_USER |
RANDOM_TAG_1_DATA |
DB_PASS / POSTGRES_PASSWORD |
PASSWORD_TAG_1_DATA |
DB_NAME / POSTGRES_DB |
RANDOM_TAG_2_DATA |
SECRET_KEY_BASE |
HEX_TAG_1_DATA |
OTP_SECRET |
HEX_TAG_2_DATA |
VAPID_PRIVATE_KEY / VAPID_PUBLIC_KEY |
VAPID_TAG_1_DATA / VAPID_TAG_2_DATA |
mastodon-postgres publishes no host port, so the database is reachable only from
other containers on the LibrePortal network — but SECRET_KEY_BASE is what Rails
uses to sign and encrypt session cookies, and the web service is internet-facing
through Traefik. Treat a public instance as needing rotation, not just cleanup.
Why you cannot just re-install
libreportal app install mastodon copies the corrected template over the deployed
compose and generates real credentials. The Postgres volume
(<containers-dir>/mastodon/postgres) was initialised with the old literals and
keeps them — POSTGRES_* is only read by initdb on an empty data directory. The
new credentials will not match, and the app will fail to reach its database.
Pick one of the two paths below.
Path A — discard the instance (no data worth keeping)
libreportal app uninstall mastodon
libreportal app install mastodon
Everything is regenerated correctly. Federation identity is lost; the instance is new to the network.
Path B — keep the data, rotate the credentials
Take a cold copy first — with the app stopped, the app folder contains the whole database:
libreportal app stop mastodon
sudo cp -a <containers-dir>/mastodon <containers-dir>/mastodon.bak-0.1.0
Re-template to get real credentials into the compose file:
libreportal app install mastodon
Read the values it generated:
grep -E 'POSTGRES_(DB|USER|PASSWORD)=' <containers-dir>/mastodon/docker-compose.yml
Bring up only the database and connect as the old superuser (the container's unix socket trusts local connections, so no password is needed):
docker start mastodon-postgres
docker exec -it mastodon-postgres psql -U RANDOM_TAG_1_DATA -d postgres
Rename the database and create the new role, substituting the three values you just read:
ALTER DATABASE "RANDOM_TAG_2_DATA" RENAME TO "<new POSTGRES_DB>";
CREATE ROLE "<new POSTGRES_USER>" LOGIN SUPERUSER PASSWORD '<new POSTGRES_PASSWORD>';
\c "<new POSTGRES_DB>"
REASSIGN OWNED BY "RANDOM_TAG_1_DATA" TO "<new POSTGRES_USER>";
\c postgres "<new POSTGRES_USER>"
DROP ROLE "RANDOM_TAG_1_DATA";
The rename needs no active sessions on that database, which is why Mastodon is stopped. The role is created and the objects reassigned rather than renamed — PostgreSQL refuses to rename the session role you are connected as.
Start the app:
libreportal app start mastodon
Expect user-visible fallout. SECRET_KEY_BASE changed, so every session is
invalidated and everyone logs in again. OTP_SECRET changed, which affects stored
two-factor enrolments — be ready to clear 2FA for users who are locked out:
docker exec -it mastodon-service bin/tootctl accounts modify <username> --disable-2fa
The VAPID keypair changed too; browsers re-subscribe to push on next login.
Once the instance is healthy, remove the backup copy.
0.2.0 — App credentials moved into <app>.config
Affects: existing installs of mastodon, owncloud, mattermost, matrix and stoat. Nothing to do until you next reinstall one of them.
What changed
Those five apps took their generated secrets from the compose-side generator tags
(PASSWORD_TAG_<n>, RANDOM_TAG_<n>, HEX_TAG_<n>, VAPID_TAG_<n>). Those mint
a fresh secret on every templating run, so a reinstall handed the app a brand
new database password while its data volume kept the one initdb was given.
Their secrets now live in <app>.config as RANDOMIZED* placeholders and reach
the compose through the same #LIBREPORTAL|<APP>_<KEY>_TAG| mechanism every other
config value uses — generated once on first install, then preserved. Passwords are
still randomly generated; nothing here asks you to choose one.
What an existing install sees
Config reconciliation adds the new keys on update, still holding their placeholders. The deployed compose is untouched until you reinstall, so the app keeps running on its current credentials.
On the next libreportal app install <app>, the placeholders are filled with
freshly generated secrets — which will not match what the app's data volume was
initialised with. That is the same desync the old mechanism caused on every
reinstall; the difference is that it now happens at most once, because the values
are preserved from then on.
Every generated secret is now a numbered slot
The convention is now uniform: if a config key holds a generated value, its name
ends in a slot number. CFG_<APP>_DB_PASSWORD became
CFG_<APP>_DB_PASSWORD_1, CFG_STALWART_ADMIN_PASSWORD became
CFG_STALWART_ADMIN_PASSWORD_1, and so on — 39 keys across the catalog. An app
that needs a second credential of the same kind just adds _2; nothing has to be
registered, because the tag name is derived from the key.
Keys holding a value you chose rather than one the installer generated (say
CFG_NEXTCLOUD_ADMIN_USER=admin) are unchanged — the slot number is what marks a
value as generated.
This reaches apps that had nothing to do with the compose-side generator problem above, including adguard, authelia, bookstack, gitea, gluetun, headscale, invidious, moneyapp, nextcloud, pihole, speedtest, stalwart, traefik, vaultwarden and wireguard.
A rename is a delete plus an add as far as config reconciliation is concerned: the
old key is dropped and the new one arrives holding its RANDOMIZED* placeholder,
so the next install generates a fresh password rather than keeping the current
one. To carry the existing password over, copy it into the new key before
reinstalling — reconciliation leaves the previous file as a hidden
.<app>.config.bak sibling, so the old value is still there:
grep -E '=RANDOMIZED|PASSWORD|SECRET|TOKEN|KEY' <containers-dir>/<app>/.<app>.config.bak
Paste each value into the matching _1 key in <containers-dir>/<app>/<app>.config.
For a Nextcloud install that means CFG_NEXTCLOUD_ADMIN_PASSWORD_1,
CFG_NEXTCLOUD_DB_PASSWORD_1 and CFG_NEXTCLOUD_DB_ROOT_PASSWORD_1 — the two
database ones are the pair that matters, because MariaDB will keep rejecting a
regenerated password.
Mastodon web push
VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY were generated as two independent
random strings, which is not a keypair — web push could never have worked. They
are now generated together as a P-256 keypair by the install hook, in the format
Mastodon's webpush gem expects. Any install still carrying the old values
regenerates the pair once on its next install (the old public half is the wrong
length to be a valid point, which is what triggers it) and is correct from then
on. Existing push subscriptions were not working anyway, so nothing is lost;
clients re-subscribe on next login.
Carrying credentials across, generally
If you have one of these installed and want to avoid regeneration, capture the credentials before reinstalling. With the app stopped:
grep -E 'POSTGRES_|MYSQL_|SECRET_KEY_BASE|OTP_SECRET|VAPID_|RABBITMQ_DEFAULT_PASS' <containers-dir>/<app>/docker-compose.yml
then paste each value into the matching CFG_<APP>_<KEY> in
<containers-dir>/<app>/<app>.config, replacing the RANDOMIZED* placeholder.
The install will adopt what it finds rather than generating over it. If you skip
this, follow the database steps in the Mastodon section above — they apply to any
of the five, with that app's own database and role names.