Repository navigation
fix(deploy): make the self-host install work on a clean server - #154
Merged
Merged
Conversation
The first install on a clean server (Hetzner cx23, Ubuntu 24.04, the public one-liner) failed three ways, each fatal on every fresh host: 1. versions.env pins postgres:18-alpine, but both database volumes are mounted at /var/lib/postgresql/data. From 18 on, the image keeps its cluster in /var/lib/postgresql/18/docker and refuses to start while a volume sits at the old path (docker-library/postgres#1259), so both databases restart-looped. Set PGDATA to the mount. Moving the mount to /var/lib/postgresql instead would make an existing volume look empty and initdb a second, blank cluster beside the old data. 2. The rendered mm-postgres-init.sql embeds both db passwords and is root-only (0600), but postgres runs /docker-entrypoint-initdb.d/* after dropping to the postgres user: "init.sql: Permission denied", mm_admin was never created, and mm-core failed with "password authentication failed for user mm_admin". The cluster still counted as initialised, so a re-run never retried it. Mount the file at /run/mm-init and copy it in, owned by postgres, while the entrypoint is still root. The host file stays 0600. 3. lk-jwt-service was pinned to latest-ci, upstream's rolling build of main, which now exits unless LIVEKIT_FULL_ACCESS_HOMESERVERS is set. Pin the 0.9.0 release (upstream tags releases now) and set the variable to this server's name. LIVEKIT_JWT_SERVICE_HOMESERVERS is not an upstream setting; it is replaced. test/fresh_install.bats covers all three, using `[[ ]] || false` so the checks also fail on macOS /bin/bash 3.2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The compose template defaults MM_MONETIZATION_ENABLED to true, and mm-core refuses to start with payments on and MM_STRIPE_SECRET_KEY or MM_STRIPE_WEBHOOK_SECRET empty. A plain install (no --demo, no Stripe account yet) therefore never started mm-core, and the Operator Console where an operator enters the keys and turns payments on is served by that same mm-core. --demo had the same hole: nothing generated keys for mm-fakestripe. install.sh now seeds MM_MONETIZATION_ENABLED in .env from payments_seed: true only when both Stripe secrets are set. An explicit MM_MONETIZATION_ENABLED in the environment still wins. --demo generates a throwaway sk_test_demo_ key and whsec_demo_ secret (mm-fakestripe signs its webhooks with the same secret mm-core verifies), and a re-run keeps them. When payments are left off, the installer says where to turn them on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e failed Traefik's docker provider was told --providers.docker.network=mm_network, but compose names the network matrixmedia_mm_network (install.sh always runs the stack as project `matrixmedia`). A name that matches nothing makes Traefik pick a network per container at random. Synapse, mm-core, mm-web, mm-switch and LiveKit are also on `internal`, which Traefik is not on, so their routes answered 504 after 30 s or worked, depending on the pick. On a clean server the owner login (/_matrix/client/v3/login) hit the 504 and the install stopped. Changing only this flag on that server took the Synapse routes from 504 to 200. That stop was also silent. Under install.sh's `set -euo pipefail`, an empty token extraction failed the pipeline and exited before capture_admin_token's die. Both the curl and the extraction now tolerate failure, so the die runs and prints what the login answered (never the password). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a clean server the self-smoke failed on a stack that was fine: - mm-core paths answered 404. install.sh recreates mm-core to hand it the captured admin token and probed straight after; Traefik routes a container only once it reports healthy. It now waits for the project to be healthy (wait_healthy) and for one mm-core route to answer through Traefik (new wait_http) before the smoke, which is unchanged and still reports real failures. - https://<domain>/.well-known/matrix/client answered 404. mm-web serves both Matrix well-known files, but Traefik forwarded only /server on the apex. Clients use /client to find the homeserver from "@alice:<domain>". Verified on the server: 404 before, the m.homeserver JSON after. Also link mmctl before the stack starts: every failure message from there on says "run: mmctl …", and the command did not exist until the smoke had passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run on a clean, healthy install: - doctor, check and upgrade's post-roll smoke passed MM_SYNAPSE_ADMIN_TOKEN first, and mm-core's /_mm/admin/v1/health answers 401 to it. doctor and check failed on every healthy install, and every upgrade would have "failed" its smoke and printed the restore command. All callers now pass MM_ADMIN_TOKEN, as install.sh does; one test covers every caller. - check died with "check_min: command not found". It lives in lib/preflight.sh, which mmctl did not load. - version printed "mmctl dev" on every piped install (no .git). The piped bootstrap now records the ref it downloaded in src/.deploy-ref, and version falls back to it. Verified on the server: doctor "self-smoke passed", check "check OK", version prints the deployed commit, backup writes config and both database dumps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The closing line said "Sign in at https://matrix.<domain>". On a self-host that URL answers 403: only the dashboard and viewer are baked into mm-web, and the root's landing page is an optional mount. The line now gives the Operator Console (/_mm/dashboard/, where the owner signs in with their Matrix ID), the homeserver address for Matrix apps, and where the password is. admin.credentials gains a console= line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The compose template set SUPER_USER: admin. lnbits validates it as a
128-bit (32 hex char) user id and aborts startup ("User ID must have at
least 128 bits"), so the demo profile's lnbits stayed unhealthy and every
--demo install failed its health wait. The same line took lnbits down on
the dev server in August. The comment beside it already describes the
intended bootstrap: the first account created in the web UI becomes the
superuser. The line is removed.
The compose fallback image is now the versions.env pin
(lnbits/lnbits:v1.6.2), not the abandoned lnbitsdocker/lnbits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
This is the first real run of the public one-liner on a clean server: Hetzner cx23 (2 vCPU, 4 GB), Ubuntu 24.04,
--no-domain --non-interactive. Every fresh install failed, one step after another. Each fix below was found on that server, covered by a test that failed first, and then checked on a brand-new server again.The finished install now completes in about 4 minutes with
self-smoke passed. It uses about 1.8 GB of 3.8 GB RAM and 8.9 GB of disk, and runs 14 containers.What was broken (in the order the server hit it)
stack_upversions.envpinspostgres:18-alpine. Postgres 18 refuses a volume at/var/lib/postgresql/data(docker-library/postgres#1259)PGDATA: /var/lib/postgresql/dataon both services. The mount is not moved: moving it would make existing volumes look empty and initdb a blank cluster beside thempassword authentication failed for user "mm_admin"mm-postgres-init.sqlisroot:root 0600, and Postgres runs init scripts after dropping to thepostgresuser:init.sql: Permission denied. The cluster still counts as initialized, so a re-run never recovers/run/mm-init/and copy it in (owned bypostgres) while the entrypoint is still root. The host file stays 0600lk-jwt-servicerestart-loopslatest-ci(upstream's rollingmain), which now requiresLIVEKIT_FULL_ACCESS_HOMESERVERS0.9.0(upstream tags releases now) and set the variable to${MM_DOMAIN}MM_STRIPE_SECRET_KEY required when monetization enabledinstall.shseedsMM_MONETIZATION_ENABLEDfrom the keys (payments_seed) and says where to turn payments on.--demogenerates throwawaysk_test_demo_/whsec_demo_values for mm-fakestripe--providers.docker.network=mm_network, but the real name ismatrixmedia_mm_network. Traefik then picks a network per container at random, so routes to Synapse, mm-core, mm-web, mm-switch and LiveKit could land oninternal. Underpipefail,capture_admin_tokenalso died before itsdiehttps://<domain>/.well-known/matrix/clientwas never routed on the bare domain (clients use it to find the homeserver)wait_healthyplus newwait_httpbefore the smoke. The apex rule now forwards both well-known files to mm-webmmctl doctor/checkfail on a healthy install; everyupgradewould "fail" its smoke and print the restore commandMM_SYNAPSE_ADMIN_TOKENto mm-core's admin health check (always 401)MM_ADMIN_TOKENeverywhere, matchinginstall.sh. One test covers every callermmctl check:check_min: command not foundlib/preflight.shmmctl versionprintsdevon every piped install.gitin a downloaded treesrc/.deploy-refhttps://matrix.<domain>/is an optional landing mount; only the dashboard and viewer are baked into mm-web/_mm/dashboard/) and the homeserver address.admin.credentialsgainsconsole=--demo: lnbits unhealthy, install failsSUPER_USER: admin(lnbits needs a 128-bit hex id). This is the same line that broke the dev server in Augustmmctlis also linked before the stack starts. Every failure message says "run: mmctl …", and until now the command didn't exist yet at that point.Verification
deploy/test/fresh_install.bats(new) and additions tobootstrap,smoke,mmctlandpipe_bootstrap. Each test was watched failing before its fix. Mid-test[[ ]]checks use|| false, because macOS/bin/bash3.2 doesn't fail a test on them otherwise.self-smoke passed.mmctl doctor: passed.mmctl check: OK.mmctl backupwrites config plus both database dumps.mmctl upgradebacks up, rolls, and passes the smoke.mmctl uninstall --purgeremoves every volume./versions,/login, federation version, the dashboard and viewer SPAs, and Element Call all answer 200. mm-switch metrics and health stay hidden.--demo, afteruninstall --purgeon the same server: exit 0,self-smoke passed, lnbits healthy, mm-fakestripe up, payments on with generated test keys, 16 containers.--domainpath with real Let's Encrypt certificates, and federation, were not tested either.Not changed here (owner decisions / follow-ups)
https://matrix.<domain>/should show on a self-host (it's a 403 until a landing page is mounted; the nginx root also aliases a dev-serversteegler-landingpath).livekit.yamlhasroom.auto_create: true. Upstream lk-jwt-service says it must be false to actually restrict room creation, but mm-core may rely on it.mm-postgressetslog_statement = 'ddl', soCREATE ROLE … PASSWORD '<plaintext>'lands indocker logson the first start.0644(inside the0700/opt/mm).deploy-e2eharness can't pass as written: it uses the retiredcx22type and amatrixmedia.appsubdomain that doesn't resolve, and it never tests the one-liner.postgres:18pin need apg_upgrade. WithPGDATApinned they fail loudly instead of silently.Stage deploys use the host's own
/opt/mm/docker-compose.ymland don't re-render these templates, so the dev server is unaffected.🤖 Generated with Claude Code