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>
219 lines
9.0 KiB
Markdown
219 lines
9.0 KiB
Markdown
# 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:
|
|
|
|
```yaml
|
|
- 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)
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
libreportal app install mastodon
|
|
```
|
|
|
|
Read the values it generated:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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:
|
|
|
|
```sql
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.
|