fix(mastodon): make tag annotations substitutable
The tag manager reads `#LIBREPORTAL|<TAG>|<VALUE>` and takes <VALUE> as the current literal to search for on that line, so it must equal the string in the line body. Mastodon used `unconfigured` as the annotation value against bodies like `PASSWORD_TAG_1_DATA` — nothing matched, nothing was ever substituted, and the placeholder shipped as the live database password, SECRET_KEY_BASE, OTP secret and VAPID keypair. Nothing caught it either: `unconfigured` doesn't match `_DATA`, so tagsManagerGetTagState reported the tags as configured, and the stale-tag gate in dockerComposeUp (which tests the annotation value against `^[A-Z][A-Z0-9_]*_DATA(_[0-9]+)?$`) let the app start. Adopt the convention every other app already uses — body placeholder identical to the annotation value, `<KIND>_DATA_<n>`. All 11 tags now substitute, the app and postgres services agree on the same generated credentials, and an unfilled tag is visible to the pre-start gate. Existing 0.1.0 installs keep their literal credentials until re-installed, and their Postgres was initialised with them, so a plain re-install desynchronises the compose from the volume. Document both recovery paths in upgrade notes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9919eea138
commit
c11052b753
@ -15,18 +15,18 @@ services:
|
||||
- TZ=TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
|
||||
- LOCAL_DOMAIN=DOMAINSUBNAME_DATA #LIBREPORTAL|DOMAINSUBNAME_TAG|DOMAINSUBNAME_DATA
|
||||
- DB_HOST=mastodon-postgres
|
||||
- DB_USER=RANDOM_TAG_1_DATA #LIBREPORTAL|RANDOM_TAG_1|unconfigured
|
||||
- DB_PASS=PASSWORD_TAG_1_DATA #LIBREPORTAL|PASSWORD_TAG_1|unconfigured
|
||||
- DB_NAME=RANDOM_TAG_2_DATA #LIBREPORTAL|RANDOM_TAG_2|unconfigured
|
||||
- DB_USER=RANDOM_DATA_1 #LIBREPORTAL|RANDOM_TAG_1|RANDOM_DATA_1
|
||||
- DB_PASS=PASSWORD_DATA_1 #LIBREPORTAL|PASSWORD_TAG_1|PASSWORD_DATA_1
|
||||
- DB_NAME=RANDOM_DATA_2 #LIBREPORTAL|RANDOM_TAG_2|RANDOM_DATA_2
|
||||
- REDIS_HOST=mastodon-redis
|
||||
- SECRET_KEY_BASE=HEX_TAG_1_DATA #LIBREPORTAL|HEX_TAG_1|unconfigured
|
||||
- OTP_SECRET=HEX_TAG_2_DATA #LIBREPORTAL|HEX_TAG_2|unconfigured
|
||||
- VAPID_PRIVATE_KEY=VAPID_TAG_1_DATA #LIBREPORTAL|VAPID_TAG_1|unconfigured
|
||||
- VAPID_PUBLIC_KEY=VAPID_TAG_2_DATA #LIBREPORTAL|VAPID_TAG_2|unconfigured
|
||||
- SECRET_KEY_BASE=HEX_DATA_1 #LIBREPORTAL|HEX_TAG_1|HEX_DATA_1
|
||||
- OTP_SECRET=HEX_DATA_2 #LIBREPORTAL|HEX_TAG_2|HEX_DATA_2
|
||||
- VAPID_PRIVATE_KEY=VAPID_DATA_1 #LIBREPORTAL|VAPID_TAG_1|VAPID_DATA_1
|
||||
- VAPID_PUBLIC_KEY=VAPID_DATA_2 #LIBREPORTAL|VAPID_TAG_2|VAPID_DATA_2
|
||||
- SMTP_SERVER=
|
||||
- SMTP_PORT=587
|
||||
- SMTP_LOGIN=
|
||||
- SMTP_PASSWORD=PASSWORD_TAG_2_DATA #LIBREPORTAL|PASSWORD_TAG_2|unconfigured
|
||||
- SMTP_PASSWORD=PASSWORD_DATA_2 #LIBREPORTAL|PASSWORD_TAG_2|PASSWORD_DATA_2
|
||||
- SMTP_FROM_ADDRESS=
|
||||
- EMAIL_DELIVERY_METHOD=none
|
||||
- SMTP_AUTH_METHOD=none
|
||||
@ -59,9 +59,9 @@ services:
|
||||
image: postgres:15
|
||||
container_name: mastodon-postgres
|
||||
environment:
|
||||
- POSTGRES_DB=RANDOM_TAG_2_DATA #LIBREPORTAL|RANDOM_TAG_2|unconfigured
|
||||
- POSTGRES_USER=RANDOM_TAG_1_DATA #LIBREPORTAL|RANDOM_TAG_1|unconfigured
|
||||
- POSTGRES_PASSWORD=PASSWORD_TAG_1_DATA #LIBREPORTAL|PASSWORD_TAG_1|unconfigured
|
||||
- POSTGRES_DB=RANDOM_DATA_2 #LIBREPORTAL|RANDOM_TAG_2|RANDOM_DATA_2
|
||||
- POSTGRES_USER=RANDOM_DATA_1 #LIBREPORTAL|RANDOM_TAG_1|RANDOM_DATA_1
|
||||
- POSTGRES_PASSWORD=PASSWORD_DATA_1 #LIBREPORTAL|PASSWORD_TAG_1|PASSWORD_DATA_1
|
||||
volumes:
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
|
||||
@ -73,6 +73,10 @@ libreportal update check # just re-check the channel
|
||||
Updates download + verify the new release tarball and redeploy. Your data,
|
||||
configs, and backups are untouched (they live outside the replaced install tree).
|
||||
|
||||
Some releases carry a fix that an already-installed app only picks up on its next
|
||||
install — see [upgrade notes](upgrade-notes.md) for the versions that need a
|
||||
manual step.
|
||||
|
||||
## Backups on an external / removable drive
|
||||
|
||||
Point a backup location at the drive's mount path. For a removable disk, set
|
||||
|
||||
139
docs/guide/upgrade-notes.md
Normal file
139
docs/guide/upgrade-notes.md
Normal file
@ -0,0 +1,139 @@
|
||||
# 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.
|
||||
|
||||
### Known limitation, both paths
|
||||
|
||||
Re-templating an app regenerates every `PASSWORD_TAG_*` / `RANDOM_TAG_*` /
|
||||
`HEX_TAG_*` / `VAPID_TAG_*` value — the generators mint a fresh secret on each run
|
||||
and the tag manager writes it in. For any app whose database lives in a persistent
|
||||
volume, that means a re-template can desynchronise the compose file from the
|
||||
initialised database exactly as described above. This is not specific to Mastodon
|
||||
or to this fix; app credentials that must survive re-templating are the ones held
|
||||
in `<app>.config` as `RANDOMIZEDPASSWORD<n>` / `RANDOMIZEDUSERNAME<n>`, which are
|
||||
generated once and persisted.
|
||||
Loading…
x
Reference in New Issue
Block a user