# LibrePortal — Upgrade Notes Version-specific notes for **existing installs**. `libreportal update apply` replaces the install tree only — it never rewrites an app's deployed `//docker-compose.yml`. So a fix to an app template reaches a running app on its next `libreportal app install ` (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||`, where `` 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 (`/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 /mastodon /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)=' /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 ""; CREATE ROLE "" LOGIN SUPERUSER PASSWORD ''; \c "" REASSIGN OWNED BY "RANDOM_TAG_1_DATA" TO ""; \c postgres "" 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 --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 `.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_`, `RANDOM_TAG_`, `HEX_TAG_`, `VAPID_TAG_`). 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 `.config` as `RANDOMIZED*` placeholders and reach the compose through the same `#LIBREPORTAL|__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 `, 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__DB_PASSWORD` became `CFG__DB_PASSWORD_1`, `CFG_GITEA_ADMIN_PASSWORD` became `CFG_GITEA_ADMIN_PASSWORD_1`, and so on — 40 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 `..config.bak` sibling, so the old value is still there: ```bash grep -E '=RANDOMIZED|PASSWORD|SECRET|TOKEN|KEY' //..config.bak ``` Paste each value into the matching `_1` key in `//.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' //docker-compose.yml ``` then paste each value into the matching `CFG__` in `//.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.