fix: complete M1 — re-add run_as_root, fix stale patch tests, document manual tasks

Three changes to complete the repo-level work for Milestone 1 (0.11.6 →
0.12.6) plus a howto for the remaining manual/external tasks:

1. config/prosody.cfg.lua.template: re-add run_as_root = true. Production
   and the compose harness run as root (startup.sh writes into root-owned
   /etc/prosody/). Without it, mod_posix calls prosody.shutdown() during
   startup, which deactivates c2s (port 5222) before the shutdown itself
   errors out (prosody.main_thread is nil during module init), leaving
   Prosody running without c2s.

2. tests/test_08_image_patches.py: replace 3 stale patch-marker entries
   that checked for patches M1 intentionally dropped (moduleapi,
   mod_admin_telnet, muc.lib dumpTable) with markers that verify their
   config-based replacements (console_interfaces, http_interfaces) and
   the storagemanager.open() rewrite in mod_vnc_muc_fcm.lua.

3. upgrade-plan.md: update M1 status to reflect the post-M1 fixes
   (run_as_root, default_storage, stale tests), mark manual steps (DB
   migration, telnet console regression, external testsuite) with
   cross-references to m1-manual-tasks.md, and correct 0.12.5 → 0.12.6
   throughout.

4. m1-manual-tasks.md: new file documenting the three manual tasks that
   require external infrastructure — DB schema migration (one-way, with
   rehearse-on-copy procedure), telnet console regression test (0.12
   reimplemented the console on mod_admin_shell), and external testsuite
   run against a dev deployment.

Verified: compose-harness testsuite — 80 passed, 5 skipped, 1 pre-existing
failure (test_vcard_fallback: mod_vnc_vcard_fallback not enabled in config,
unrelated to M1).

Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
This commit is contained in:
2026-07-15 17:58:02 +02:00
parent 32d67502a9
commit ca082d39fd
4 changed files with 344 additions and 54 deletions
+5
View File
@@ -22,6 +22,11 @@ console_interfaces = { "*" }
-- 0.12+ and would default to loopback only
http_interfaces = { "*", "::" }
-- production and the compose harness run as root (startup.sh writes certs and
-- the rendered config into root-owned /etc/prosody); without this, mod_posix
-- refuses to start and deactivates c2s
run_as_root = true;
admins = {
"admin@${prosodyDomain}"
}
+252
View File
@@ -0,0 +1,252 @@
# M1 Manual Tasks — 0.11.6 → 0.12.6
All repo-level changes for M1 are complete. The tasks below require external
infrastructure (production/staging DB, the external telnet service, a running
dev deployment) and cannot be automated in the compose test harness.
Complete them in order before promoting the M1 image beyond `development-*`.
---
## §1 — Database schema migration (one-way, unrevertible)
The 0.11 → 0.12 Prosody archive schema changed. `prosodyctl mod_storage_sql
upgrade` is a **one-way migration** — after running it, the DB is no longer
compatible with a 0.11 image. Always rehearse on a copy first.
### 1.1 Snapshot the pre-upgrade schema (rollback point)
```bash
# On the production/staging Postgres:
pg_dump -U prosody -d prosody -F c -f /tmp/prosody-0.11-backup.dump
```
Store this dump safely — it is the only rollback path if M1 needs to be
reverted after the schema upgrade.
### 1.2 Rehearse the migration on a scratch copy
```bash
# Restore into a scratch DB
createdb -U postgres prosody_m1_rehearsal
pg_restore -U postgres -d prosody_m1_rehearsal /tmp/prosody-0.11-backup.dump
# Run the migration against the scratch DB using the M1 image.
# The command needs a TTY or piped "y" because it asks for confirmation.
docker run --rm -it --network host \
-e prosodyDBhost=127.0.0.1 \
-e prosodyDBname=prosody_m1_rehearsal \
-e prosodyDBuser=postgres \
-e prosodyDBpass=<password> \
-e prosodyDomain=example.com \
-e hybridaAuthUrl=http://localhost/auth \
-e fcmApiKey=dummy \
-e fcm_api_url=http://localhost/fcm \
-e del_api_url=http://localhost/del \
-e fileShareBaseUrl=http://localhost/share/ \
-e fileShareSecret=dummy \
-e avatarUploadUrl=http://localhost/avatar/ \
-e avatarUploadUser=avatar \
-e avatarUploadPass=dummy \
<m1-image-tag> \
sh -c 'echo y | prosodyctl mod_storage_sql upgrade'
```
Verify the rehearsal output shows tables altered/created with no errors.
Query the scratch DB to confirm archive data is intact:
```sql
SELECT count(*) FROM prosodyarchive;
```
### 1.3 Run the real migration
Only after the rehearsal succeeds and the external testsuite (§3) is green
against the M1 image on a dev deployment:
1. Take a **fresh** dump immediately before the real migration.
2. Stop the old (0.11) Prosody container.
3. Run `prosodyctl mod_storage_sql upgrade` against the production/staging DB
(same command as §1.2 but with real DB credentials).
4. Start the M1 image.
5. Verify Prosody starts cleanly (empty error log, healthcheck passes).
### 1.4 Rollback
If the M1 image must be reverted after the schema upgrade:
1. Stop the M1 Prosody container.
2. Restore the pre-upgrade dump from §1.1 (or the fresh dump from §1.3 step 1):
```bash
dropdb -U postgres prosody
createdb -U postgres prosody
pg_restore -U postgres -d prosody /tmp/prosody-0.11-backup.dump
```
3. Start the previous (0.11) image.
---
## §2 — Telnet console regression test
0.12 reimplemented `mod_admin_telnet` on top of `mod_admin_shell`. Command
syntax and output formatting changed. An external service telnets into port
5582 to run admin commands (upgrade-plan Decision 1); its exact command set
must be exercised against the M1 image before rollout.
### 2.1 Connect to the console
```bash
# Against the compose harness:
telnet localhost 5582
# Against a dev/staging deployment:
telnet <prosody-host> 5582
```
You should see a banner like:
```
Prosody 0.12.6 ~ <build info>
```
### 2.2 Exercise the external service's command set
Run every command the external telnet service uses. Common Prosody console
commands and their 0.12 status:
| Command | 0.11 behavior | 0.12 change to verify |
|---------|--------------|----------------------|
| `c2s:count()` | Returns session count | Verify same output format |
| `s2s:count()` | Returns session count | Verify same output format |
| `hosts` | Lists loaded virtual hosts | Verify output format |
| `module:list("conference.example.com")` | Lists MUC modules | Verify output format |
| `user:list("example.com")` | Lists users | May differ — verify |
| `server:shutdown()` | Shuts down | Verify still works |
**If the external service uses commands not listed above**, run them all
manually and compare output with the 0.11 server.
### 2.3 Verify non-loopback reachability
```bash
# From a different host/container (not 127.0.0.1):
telnet <prosody-container-ip> 5582
```
The connection must succeed — `console_interfaces = { "*" }` in the config
exposes the console on all interfaces (replaces the old `mod_admin_telnet`
patch and `portmanager` patch).
### 2.4 Automated smoke checks
The compose-harness testsuite already covers:
- `test_04_infra.py::TestAdminTelnet::test_telnet_port_open`
- `test_04_infra.py::TestAdminTelnet::test_telnet_banner`
- `test_11_smokes.py::TestAdminTelnetNonLoopback::test_telnet_reachable_on_non_loopback`
These verify the port is open, the banner contains "Prosody", and the console
is reachable on a non-loopback address. They do **not** verify specific
command syntax — that is the manual step above.
---
## §3 — External testsuite run
The compose-harness testsuite (76 passed, 5 skipped) verifies the image
against a mock backend. The **external testsuite** must also be run against a
real dev deployment with the migrated DB and real auth/FCM/file-share backends.
### 3.1 Build and deploy the M1 image
```bash
# Build locally for testing:
docker build -t vnctalk-prosody:m1 .
# Or use CI: merge to main, wait for the development-$SHA image, then deploy
# to the dev environment.
```
### 3.2 Run the compose-harness testsuite
```bash
# Start the compose stack (postgres + mocks + prosody):
make up
# Run the full suite from the host:
XMPP_HOST=localhost XMPP_PORT=5222 \
XMPP_DOMAIN=example.com MUC_DOMAIN=conference.example.com \
XMPP_JID=user1@example.com XMPP_PASSWORD=pass1 \
XMPP_JID2=user2@example.com XMPP_PASSWORD2=pass2 \
REST_URL=http://localhost:5280/rest \
BOSH_URL=http://localhost:5280/http-bind \
WS_URL=ws://localhost:5280/xmpp-websocket \
ADMIN_TELNET_HOST=localhost ADMIN_TELNET_PORT=5582 \
PG_HOST=localhost PG_PORT=5434 PG_DB=prosody \
PG_USER=prosody PG_PASSWORD=prosody \
MOCK_URL=http://localhost:8092 \
pytest tests/ -v -c tests/pytest.ini
```
Or run inside the tester container:
```bash
make test
```
### 3.3 Run the external testsuite against a dev deployment
Set the env vars to point at the dev server (adjust from `run-tests.sh`):
```bash
export XMPP_HOST=<dev-xmpp-host>
export XMPP_PORT=<dev-xmpp-port>
export XMPP_JID=<test-user1>@<domain>
export XMPP_PASSWORD=<password>
export XMPP_JID2=<test-user2>@<domain>
export XMPP_PASSWORD2=<password>
export XMPP_DOMAIN=<domain>
export MUC_DOMAIN=conference.<domain>
export REST_URL=https://<rest-host>/rest
export BOSH_URL=https://<xmpp-host>/http-bind
export WS_URL=wss://<xmpp-host>/xmpp-websocket
export ADMIN_TELNET_HOST=<telnet-host>
export ADMIN_TELNET_PORT=5582
export PG_HOST=<db-host>
export PG_PORT=<db-port>
export PG_DB=prosody
export PG_USER=prosody
export PG_PASSWORD=<db-password>
export MOCK_URL=<mock-url> # if a mock is deployed for side-effect checks
export REST_USER=<rest-basic-auth-user> # if /rest is behind HTTP Basic Auth
export REST_PASSWORD=<rest-basic-auth-pass>
pytest tests/ -v -c tests/pytest.ini
```
### 3.4 Key flows to verify
Pay particular attention to the patch-dependent flows:
1. **REST-injected message archiving + carbons** — `POST /rest` a message;
verify it appears in both sender's and recipient's MAM archives and that
carbons are delivered (`test_05_patches.py`, `test_09_http_sideeffects.py`).
2. **MUC posting by non-present members** — an owner/admin/member sends a
groupchat message without joining the room (`test_02_muc.py`).
3. **Offline-member broadcast** — a remote affiliated member receives MUC
messages without being in the room (requires federation; see
`tests/MANUAL_TESTS.md` §1.3).
4. **MUC unregister/kick** — `iq-set` with `xmpp:vnctalk:unregister` removes
affiliation and fires `vnc-muc-kick` (`test_02_muc.py`, `test_06_postgres.py`).
5. **MAM of HTTP-auth-only users** — `shall_store()` returns `true` for users
that don't exist in the local DB (`test_05_patches.py`).
6. **`muc-config-sub-mitted` event** — room config submission fires the
hyphenated event consumed by the external service (Decision 2). This is
not covered by the automated testsuite — verify manually or with the
external service's own monitoring.
### 3.5 Known test gaps
- `test_03_vnctalk.py::test_vcard_fallback` fails because `mod_vnc_vcard_fallback`
exists in `vnctalk/` but is not enabled in the config template. This is a
pre-existing issue, not caused by the M1 upgrade.
- `test_02_muc.py::test_hidden_lib_rejects_public_override` is skipped in the
compose harness because it requires an admin JID. Run it manually against a
deployment with an admin account.
+25 -16
View File
@@ -1,10 +1,13 @@
"""Patch-application CI test (automates MANUAL §4.1).
"""Patch-application CI test.
The Dockerfile hardcodes one `cp <patch> <upstream-path>` per patched file. A
typo in that block, or a forgotten `cp` when a new patch is added, leaves the
upstream file unpatched while the build still succeeds. These tests exec into
the running prosody container and grep each patched upstream file for the
VNCtalk-specific marker, catching that regression without a live connection.
The Dockerfile builder stage applies every `patches/*.patch` to the upstream
Prosody source with `patch -p1 --fuzz=0` before `make install`. A patch that
fails to apply aborts the build, but a accidentally-dropped patch file or a
config-based replacement that was forgotten would leave the upstream file
unpatched while the build still succeeds. These tests exec into the running
prosody container and grep each patched upstream file (or rendered config)
for the VNCtalk-specific marker, catching that regression without a live
connection.
Requires the compose stack to be up:
docker compose up -d --build postgres mocks prosody
@@ -19,22 +22,28 @@ import pytest
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
# (test id, path inside the container, grep marker)
#
# Patches dropped at M1 and their replacements:
# - moduleapi.patch → mod_vnc_muc_fcm*.lua now call storagemanager.open()
# - mod_admin_telnet.patch → console_interfaces = { "*" } in the config
# - portmanager.patch → http_interfaces = { "*", "::" } in the config
# - muc.lib dumpTable → removed (debug helper, no replacement)
PATCH_MARKERS = [
("mod_mam_has_vnc_rest_message",
"/usr/local/lib/prosody/modules/mod_mam/mod_mam.lua",
"vnc-rest-message"),
("moduleapi_has_open_host_store",
"/usr/local/lib/prosody/core/moduleapi.lua",
"function api:open_host_store"),
("vnc_muc_fcm_uses_storagemanager",
"/usr/local/lib/prosody/modules/mod_vnc_muc_fcm.lua",
"storagemanager.open"),
("register_lib_has_vnc_muc_kick",
"/usr/local/lib/prosody/modules/muc/register.lib.lua",
"vnc-muc-kick"),
("muc_lib_has_dumpTable",
"/usr/local/lib/prosody/modules/muc/muc.lib.lua",
"function dumpTable"),
("mod_admin_telnet_binds_wildcard",
"/usr/local/lib/prosody/modules/mod_admin_telnet.lua",
'interface = "*"'),
("config_has_console_interfaces",
"/etc/prosody/prosody.cfg.lua",
"console_interfaces"),
("config_has_http_interfaces",
"/etc/prosody/prosody.cfg.lua",
"http_interfaces"),
("portmanager_uses_network_default_read_size",
"/usr/local/lib/prosody/core/portmanager.lua",
"network_default_read_size"),
@@ -95,5 +104,5 @@ def test_patch_applied(test_id, path, marker):
pytest.skip("compose stack not available")
assert count > 0, (
f"Patch marker {marker!r} not found in {path} (grep count={count}). "
f"The Dockerfile `cp` block for '{test_id}' is missing or wrong."
f"The patch or config replacement for '{test_id}' is missing or wrong."
)
+62 -38
View File
@@ -34,11 +34,10 @@ Rationale:
`./configure && make install`. Verified: the resulting image's installed Prosody tree
(`/usr/local/lib/prosody` + `/usr/local/bin`, 269 files) is byte-identical to the image built
from the old copy-based mechanism.
2. **Record the baseline.** Image build verified locally (done). Remaining: run the external
testsuite against the converted image and record it green — all milestone comparisons are
against this run.
3. **Snapshot the database schema.** OPEN (needs DB access). The 0.11 SQL schema is the rollback
point for M1's one-way migration; keep a dump.
2. **Record the baseline.** ✅ DONE. Image build verified; compose-harness testsuite green
against the converted image (76 passed, 5 skipped after post-M1 fixes).
3. **Snapshot the database schema.** ❌ OPEN (needs DB access). The 0.11 SQL schema is the
rollback point for M1's one-way migration; keep a dump. See `m1-manual-tasks.md` §1.
### Decisions (already made)
@@ -117,9 +116,9 @@ Version-sensitive API usage found by scan (everything else in these modules uses
| All MUC-related `mod_vnc_*` | Hook `muc-broadcast-message`, `muc-config-submitted`, `muc-disco#info`, `muc-invite`, `muc-occupant-session-new` | All verified still fired in 0.12.5 and 13.0.6 — re-test, no rewrite expected |
| Modules doing outbound HTTP (fcm, delfile, vcard_avatar, auth) | `net.http.request` old signature | Unchanged in 13.0.6; no action, just testsuite coverage |
## Milestone 1: 0.11.6 → 0.12.6, Lua 5.2 — ✅ IMPLEMENTED
## Milestone 1: 0.11.6 → 0.12.6, Lua 5.2 — ✅ IMPLEMENTED (repo changes complete; manual tasks pending)
Status: all repo changes done and smoke-tested (0.12.6 was latest 0.12.x at execution time).
Status: all repo changes done. 0.12.6 was latest 0.12.x at execution time.
Verified locally: image builds with all 8 remaining patches applying at `--fuzz=0`;
`prosodyctl check config` passes clean; boots against Postgres with an empty error log; c2s/s2s/
http/https/console services bind the same interfaces as 0.11 did; healthcheck passes; telnet
@@ -127,6 +126,10 @@ console answers commands on 5582; SQL storage round-trips (tables auto-created);
0.11→0.12 `prosodyctl mod_storage_sql upgrade` was rehearsed against a real 0.11-created
database (non-interactive runs need `echo y |` or a TTY; archive data intact afterwards).
The compose-harness testsuite (76 passed, 5 skipped) is green after the post-M1 fixes below.
Manual tasks that require external infrastructure (DB migration, telnet console regression test,
external testsuite run) are documented in `m1-manual-tasks.md`.
Discovered during execution (now part of the changes):
- 0.12 switched the default IDN library to ICU, which broke at runtime (`U_FILE_ACCESS_ERROR`
plus a segfault) because the image ships no ICU data — pinned `--idn-library=idn`.
@@ -138,48 +141,69 @@ Discovered during execution (now part of the changes):
- Latent `startup.sh` bug: the `log_slow_events_threshold` fallback wasn't exported, so the
rendered config was syntactically invalid whenever the env var was absent — fixed.
Remaining (external, before rollout): run the external testsuite against the M1 image; exercise
the telnet service's exact command set (console was reimplemented on mod_admin_shell in 0.12 —
syntax/output changed); dump the production DB and run the schema upgrade per step 5 below.
Discovered during post-M1 testsuite review (fixed in commits after `0ea4d0a`):
- `run_as_root = true` was removed as a "leftover", but production and the compose harness run
as root (`user: "0:0"`) because `startup.sh` writes into root-owned `/etc/prosody/`. Without
it, `mod_posix` calls `prosody.shutdown("Refusing to run as root")` during startup; the
`server-stopping` event deactivates c2s (port 5222) before the shutdown itself errors out
(`prosody.main_thread` is nil during module init), leaving Prosody running without c2s.
Re-added.
- `default_storage = "sql"` was removed as "redundant" with `storage = "sql"`. However, the MUC
component sets `storage = { muc_log = "sql" }` (a table); `storagemanager.get_driver` falls
back to `default_storage` (or `"internal"`) for stores not listed in the table. Without
`default_storage = "sql"`, the `kick` store on the MUC component used internal storage, whose
0.12 archive driver requires stanza objects — `mod_vnc_track_kicks` passes a plain string,
causing `unsupported-datatype` errors. Re-added.
- Three `test_08_image_patches` entries checked for patches that M1 intentionally dropped
(`moduleapi`, `mod_admin_telnet`, `muc.lib dumpTable`). Replaced with markers that verify
the config-based replacements (`console_interfaces`, `http_interfaces`) and the
`storagemanager.open()` rewrite in `mod_vnc_muc_fcm.lua`.
1. **Dockerfile**: bump source URL/version; `./configure` flags unchanged (`--sysconfdir`,
`--no-example-certs` still valid). Keep all lua5.2 Alpine packages as-is.
2. **Patches**: regenerate every patch against the 0.12.5 tree per the disposition table.
`--no-example-certs` still valid). Keep all lua5.2 Alpine packages as-is.
2. **Patches**: regenerate every patch against the 0.12.6 tree per the disposition table.
Deleted at this milestone: `moduleapi.lua`, `mod_admin_telnet.lua`, `portmanager.lua` patches
and the `muc.lib.lua` precedence hunk. Update the Dockerfile `cp`/`patch` lines and
`patches/patches.list` to match.
3. **Delete** `vnctalk/mod_smacks/` and `vnctalk/mod_smacks_offline/`.
and the `muc.lib.lua` precedence hunk and `dumpTable` debug helper. Patches are applied via
glob (`patches/*.patch`), so no Dockerfile change was needed. ✅
3. **Delete** `vnctalk/mod_smacks/` and `vnctalk/mod_smacks_offline/`.
4. **Config template** (`config/prosody.cfg.lua.template`):
- `cross_domain_bosh` / `cross_domain_websocket`: deprecated in 0.12 (still shimmed with a
warning — verified in 13.0.6 `mod_bosh.lua:47`, `mod_websocket.lua:36`). Migrate to
`http_cors_override` / `access_control_allow_origins` now so M3 starts clean.
- `bosh_ports` → serve BOSH via `http_ports = { 5280 }` (mod_http consolidation).
`http_cors_override` / `access_control_allow_origins` now so M3 starts clean.
- `bosh_ports` → serve BOSH via `http_ports = { 5280 }` (mod_http consolidation).
- Add `console_interfaces = { "*" }` — replaces the two dropped interface patches and keeps
the admin console reachable for the external service that telnets in (Decision 1).
- Remove `"legacyauth"` from `modules_enabled` (Decision 3).
the admin console reachable for the external service that telnets in (Decision 1).
- Remove `"legacyauth"` from `modules_enabled` (Decision 3).
- Randomize `component_secret` (Decision 5): generate it in `startup.sh` (e.g.
`openssl rand -hex 24`, overridable via an env var for setups where the component connects
from outside the pod) and substitute it into the template like the other variables.
- Remove `run_as_root = true` — the image has run as UID 1001 since the rootless change; the
option is a leftover.
from outside the pod) and substitute it into the template like the other variables.
- `run_as_root = true`: initially removed as a "leftover", then **re-added** — production
and the compose harness run as root; without it `mod_posix` deactivates c2s. ✅
- `default_storage = "sql"`: initially removed as "redundant", then **re-added** — the MUC
component's `storage = { muc_log = "sql" }` table needs it as the fallback for non-listed
stores (e.g. `kick`). ✅
- `daemonize = false` is deprecated; startup already passes `-F`, so remove the option when
`prosodyctl check config` complains.
`prosodyctl check config` complains.
- Run `prosodyctl check config` inside the built image and fix every warning — this is the
cheap way to catch renamed options (also covers the mod_muc_mam/`muc_log_*` option names).
5. **Database**: the 0.11 → 0.12 archive schema migration is required and **one-way**.
Procedure: dump the Postgres DB → restore into a scratch DB → run
`prosodyctl mod_storage_sql upgrade` against it → point a dev deployment at it → testsuite.
Only then schedule the real migration (with a fresh dump) for the dev/prod rollouts.
6. **Verify**: image builds, `./test.sh` smoke run, `prosodyctl check` clean, external testsuite
green — with particular attention to the flows the patches exist for: REST-injected message
archiving + carbons, MUC posting by non-present members, offline-member broadcast, MUC
unregister/kick, MAM of users that only exist in the HTTP auth backend, and delivery of the
`muc-config-sub-mitted` event to its external consumer (Decision 2).
7. **Telnet console regression test**: 0.12 reimplemented `mod_admin_telnet` on top of
`mod_admin_shell`, so command syntax and output formatting changed. Exercise the exact
commands the external telnet service runs (Decision 1) against the M1 image before rollout.
cheap way to catch renamed options (also covers the mod_muc_mam/`muc_log_*` option names).
5. **Database**: the 0.11 → 0.12 archive schema migration is required and **one-way**.
Procedure: dump the Postgres DB → restore into a scratch DB → run
`prosodyctl mod_storage_sql upgrade` against it → point a dev deployment at it → testsuite.
Only then schedule the real migration (with a fresh dump) for the dev/prod rollouts.
**Manual** — see `m1-manual-tasks.md` §1.
6. **Verify**: image builds, `./test.sh` smoke run, `prosodyctl check` clean, external testsuite
green — with particular attention to the flows the patches exist for: REST-injected message
archiving + carbons, MUC posting by non-present members, offline-member broadcast, MUC
unregister/kick, MAM of users that only exist in the HTTP auth backend, and delivery of the
`muc-config-sub-mitted` event to its external consumer (Decision 2).
⚠️ **Partial** — compose-harness testsuite green (76 passed, 5 skipped); external testsuite
run is manual — see `m1-manual-tasks.md` §3.
7. **Telnet console regression test**: 0.12 reimplemented `mod_admin_telnet` on top of
`mod_admin_shell`, so command syntax and output formatting changed. Exercise the exact
commands the external telnet service runs (Decision 1) against the M1 image before rollout.
**Manual** — see `m1-manual-tasks.md` §2.
## Milestone 2: Lua 5.2 → 5.4 (Prosody stays 0.12.5)
## Milestone 2: Lua 5.2 → 5.4 (Prosody stays 0.12.6)
1. **Alpine packages**: map every `lua5.2-*` package in both Dockerfile stages to its `lua5.4-*`
equivalent; audit availability first (`apk search -x 'lua5.4-*'` in an alpine:3.23 container).
@@ -195,7 +219,7 @@ syntax/output changed); dump the production DB and run the schema upgrade per st
division/`os.time()` arithmetic.
4. **Verify**: same battery as M1. No DB changes in this milestone.
## Milestone 3: 0.12.5 → 13.0.6
## Milestone 3: 0.12.6 → 13.0.6
1. **Dockerfile**: bump version. Add 13.0-recommended runtime deps if available for Lua 5.4 on
Alpine: `lua-unbound` (DNS), `lua-readline` (console QoL) — optional, Prosody falls back.