Migration-focused

Breaking Changes

Each entry explains what changed, who is affected, and what to migrate.

v1.2.4

September 2026
  • The public chat API now allows only the letschat WebSocket and token exchange. Anonymous database publishing, SQL and all other API routes return 404. Existing tunnel routes and host proxies on port 44300 keep working; the administrative API moves to loopback port 44302.
  • core-api reads the publisher and archive-worker credentials from read-only volume mounts and completes setup automatically, retrying transient failures. New registrations fail closed until the issuer is pinned. No token copying or manual archive registration is needed.
  • Fresh installs generate an OIDC signing key in core_api_data. Existing explicit signing keys remain authoritative. Bootstrap-admin creation is skipped whenever any administrator already exists.

Migration note: Download the base Compose file and your overlay together; on the Caddy track also update deploy/caddy/Caddyfile. Use Compose 2.23.1+ and the matching 1.2.4 images. Preserve existing volumes and any SPACETIME_OIDC_PRIVATE_KEY override, then pull and up -d. Include core_api_data in backups. Tunnel ingress names are unchanged; never expose administrative port 44302.

v1.2.3

September 2026
  • MinIO's own images (minio/minio, minio/mc) were deleted from Docker Hub, so a fresh docker compose pull failed. The stack now runs pgsty/silo and pgsty/mc — Pigsty's maintained fork of the same server, with the same data format and MINIO_* settings. Your existing minio_data volume is used as-is.
  • core-api refuses to start while any secret still holds the example file's change-me… placeholder (AUTH_JWT_SECRET, LIVEKIT_API_SECRET, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, POSTGRES_PASSWORD, ADMIN_BOOTSTRAP_PASSWORD, SPACETIME_OIDC_PRIVATE_KEY). Those values are public; a forgotten one let anyone forge sign-ins.
  • LiveKit's API key now comes from LIVEKIT_API_KEY / LIVEKIT_API_SECRET in .env (passed as LIVEKIT_KEYS), the same values core-api uses. livekit/config.prod.yaml no longer holds keys, and LiveKit is pinned to v1.13.5 instead of :latest.
  • The module publisher image uses the SpacetimeDB 2.10.1 CLI, matching the server; it had been left on 2.5.0.
  • APP_DOMAIN now supplies the hosted browser address advertised in discovery; the desktop app builds invite links on it. They used to point at http://localhost:1420 and never worked.
  • Public hostnames are entered once on both tracks: AUTH_DOMAIN, CHAT_DOMAIN, FILES_DOMAIN, LIVEKIT_DOMAIN and optional APP_DOMAIN. Compose builds the service URLs; native Caddy configuration handles routing, CSP and /config.js without entrypoint scripts. Remove DISCOVERY_* URL settings, MINIO_PUBLIC_ENDPOINT and VITE_WEB_CONNECT_URL from the deployment .env. HTTPS is the default; PUBLIC_SCHEME=http enables LAN HTTP. The client SDKs open WebSockets from the generated HTTP(S) service URLs. Empty APP_DOMAIN omits the Caddy browser site.
  • SPACETIMEDB_MODULE_NAME and DISCOVERY_DATABASE are fixed to letschat in the compose file, like module-init's publish target — changing only one of them used to point core-api or clients at a database that does not exist.

Migration note: Download the new docker-compose.prod.base.yml (and your track's overlay) and livekit/config.prod.yaml. On the Caddy track also download deploy/caddy/Caddyfile. Configure the hostnames once using the updated .env example; no separate public URLs or shell scripts are needed. Then pull and up -d. Replace any change-me… value still in .env before pulling, or core-api will not start and names the variable in docker compose logs core-api. Set APP_DOMAIN=app.<domain> if you host the browser client.

v1.2.2

September 2026
  • Nothing is entered twice any more: the web container derives the browser client's connect address from DISCOVERY_AUTH_URL and its Content-Security-Policy hosts from DISCOVERY_AUTH_URL, DISCOVERY_SPACETIMEDB_URI, DISCOVERY_LIVEKIT_URL and MINIO_PUBLIC_ENDPOINT. On the tunnel track, VITE_WEB_CONNECT_URL and AUTH_DOMAIN / CHAT_DOMAIN / FILES_DOMAIN / LIVEKIT_DOMAIN are no longer needed. The Caddy track still sets the *_DOMAIN values, because they are Caddy's virtual hosts.

Migration note: Nothing required: existing values keep working as overrides. Download the new docker-compose.prod.base.yml (it passes the URLs to the web container), then you may delete the duplicated lines from .env.

v1.2.1

September 2026
  • The browser client is now a pre-built image (ghcr.io/da-stoaz/letschat-web) published by GitHub Actions with every release, like core-api, the module and the archive worker. Nothing is compiled on the server any more.
  • VITE_WEB_CONNECT_URL and VITE_WEB_WS_COMPRESSION are read when the web container starts instead of being baked into the bundle, so changing them needs docker compose … up -d web, not a rebuild. The container refuses to start if the URL is not an http(s):// address or the compression is not gzip or none.
  • Invite links created in the browser client now point at the instance's own web address instead of a desktop development URL.

Migration note: Download the new docker-compose.prod.base.yml: its web service now pulls an image instead of building from source. Your .env keeps working unchanged. If you kept a customised compose file, replace the web service's build: block with image: ghcr.io/da-stoaz/letschat-web:${LETSCHAT_VERSION:-latest} and pass VITE_WEB_CONNECT_URL and VITE_WEB_WS_COMPRESSION as environment variables.

v1.2.0

September 2026
  • The browser client's Content-Security-Policy is now enforced instead of report-only. The web container builds it from AUTH_DOMAIN, CHAT_DOMAIN, FILES_DOMAIN and LIVEKIT_DOMAIN, so all four must be set on both tracks, as hostnames only (auth.example.com, no https://) matching the hosts in your DISCOVERY_* URLs and MINIO_PUBLIC_ENDPOINT. The web container now refuses to start if one is missing or contains a scheme. The desktop app is not affected.
  • core-api refuses to start when a boolean or number setting has an unrecognised value (for example REQUIRE_EMAIL_CONFIRMATION=enabled). Such values used to be read silently as false or as the default — which could switch email confirmation off.
  • Auth rate limits are counted per purpose: registration, email actions and password actions each get RATE_LIMIT_PERMIT per window, and sign-in gets ten times that. Confirmation and reset emails are additionally capped at three per account per hour.
  • core-api now removes people from LiveKit calls once they lose voice presence (kick, ban, timeout, leave), within about 40 seconds. It reaches LiveKit's room API at http://livekit:44380, which the shipped compose file sets.

Migration note: Check that AUTH_DOMAIN, CHAT_DOMAIN, FILES_DOMAIN and LIVEKIT_DOMAIN are set in .env as bare hostnames before pulling (the example files have them); if the web container exits, docker compose logs web names the variable. If core-api does not come up, docker compose logs core-api names the setting with the bad value. Pull the new compose file along with the images. The module update adds one private table; no data is deleted.

v1.0.5

August 2026
  • Fixed: the production compose never passed SPACETIMEDB_HTTP_URL to core-api, so it fell back to a development default pointing at localhost — which, inside a container, is core-api itself. Every core-api call into the chat module failed in transport. This affected every deployment made from the shipped compose file.
  • Symptoms it caused: voice always refused with 'You are not a participant in this voice room', the trusted-issuer pin never landed, instance-admin changes never reached the module, and the OIDC identity migration deferred indefinitely.
  • Voice authorization now distinguishes a refusal from an outage: 403 still means the module says you are not in the room, while an unreachable module returns 503 and says so. The gate still fails closed either way — no token is minted without a presence row.
  • core-api probes the database once at startup and logs an error naming the configured URL if it cannot reach it, instead of failing silently across four separate best-effort paths.
  • The service domain variables (AUTH_DOMAIN, CHAT_DOMAIN, FILES_DOMAIN, LIVEKIT_DOMAIN) are documented for the tunnel track too — the web container builds the browser client's Content-Security-Policy from them on both topologies, not just on Caddy.

Migration note: Pull the new compose file along with the images; the fix is in docker-compose.prod.base.yml, not only in the container. If you keep a customised compose file, add SPACETIMEDB_HTTP_URL, SPACETIMEDB_MODULE_NAME and SPACETIMEDB_SERVICE_TOKEN to the core-api service yourself. After restarting, confirm with: docker compose logs core-api | grep -i 'SpacetimeDB reachable'. Nothing else to set, and no data is touched.

v1.0.3

August 2026
  • Disabling an account in the control panel now ends its chat access immediately. Previously it only blocked the next sign-in — the account kept reading and posting until its SpacetimeDB token expired, up to 30 days later.
  • Resetting or changing a password now signs out every other device. Previously a stolen session survived the reset for its full lifetime, and could be traded for fresh ones indefinitely.
  • A session's refresh token is no longer accepted in place of its access token, which had been silently buying a 7-day session where the design allows one hour.
  • Publishing the updated module adds two columns to the user table and disconnects connected clients, which reconnect on their own. No data is deleted.

Migration note: Nothing to set — core-api pushes each account's state to the module on its own, and the database migration applies on startup. The push fails open: if SpacetimeDB is unreachable when an account is disabled, the change is still saved and only the revocation is delayed, with 'Could not push access state' in the core-api log. Any later status or credential change re-pushes it.

v1.0.2

August 2026
  • The SpacetimeDB module now requires a registered account for every action, and only accepts registrations signed by this instance's own OIDC issuer. Before this, anyone could mint an anonymous SpacetimeDB identity and call reducers directly over the public chat WebSocket, bypassing registration, email confirmation and admin approval.
  • core-api pins the issuer into the module itself, at startup and on every admin sign-in — there is no new setting to configure.
  • Publishing the updated module adds one column to system_settings and disconnects connected clients, which reconnect on their own. No data is deleted.

Migration note: Nothing to set. A brand-new instance still has no admin until its first account registers, so that first registration stays ungated and the pin lands right after it — sign in once yourself before exposing a new instance publicly. Until the pin lands the check is off rather than on, so upgrading a running deployment cannot lock its users out. Verify with: SELECT trusted_issuer FROM system_settings.

v1.0.0

August 2026
  • Production images are now pinned by tag: set LETSCHAT_VERSION in .env (e.g. LETSCHAT_VERSION=1.0.0). Unset, compose still resolves to latest, but a pinned deployment is what makes a rollback one line.
  • module-init now keeps its SpacetimeDB publisher identity in a module_init_home volume. Only the identity that created a database may update it, so this volume is what lets an upgrade republish the module at all.
  • The first account to register on an instance with no admin becomes the instance admin. Sign in once yourself before exposing a new instance publicly.
  • Images are published for linux/amd64 and linux/arm64, so ARM hosts no longer need a local build.

Migration note: Deployments created BEFORE v1.0.0 have no module_init_home volume, so their publisher identity was already ephemeral and cannot be recovered. Treat the first 1.0.0 upgrade of such a deployment as a fresh install, or publish under a new database name, repoint DISCOVERY_DATABASE / SPACETIMEDB_MODULE_NAME, and rebuild from the cold archive. Confirm the archive worker is actually replicating first. Never delete module_init_home afterwards.

v0.9.x

August 2026
  • core-api became the OIDC issuer for SpacetimeDB; the database verifies access tokens against a published JWKS.
  • Identities are now derived deterministically from issuer plus account, so they survive a database wipe and never need relinking.
  • The cold-archive replication worker was wired into the production stack and needs its service identity registered once.

Migration note: Breaking deploy: set SPACETIME_OIDC_PRIVATE_KEY before pulling, or core-api refuses to start. The issuer URL is a permanent constant — changing it later rederives every identity. After first start, register the archive worker with set_archive_service_identity using the 0x-prefixed identity from its logs, called by an instance admin.

v0.7.x

June 2026
  • SpacetimeDB moved 2.2 to 2.5 across the CLI, the npm SDK, the Rust crate, and the server image.
  • A hosted browser client was added at app.<domain>, with MinIO browser CORS rules.

Migration note: Upgrade all four SpacetimeDB components together — a minor-version skew breaks module load and the client connection. Update the server image and republish the module in the same window.

v0.5.x

June 2026
  • The legacy Rust auth-service was removed. core-api (.NET, PostgreSQL) is the sole auth backend.
  • Auth data moved from SQLite to the PostgreSQL auth database, with EF Core migrations applied on startup.

Migration note: Drop the auth-service container and its env vars, and point clients at core-api. If an old auth.db still exists, import it once with the CoreApi.Migrator tool before decommissioning.

v0.3.x

May 2026
  • Service discovery moved into the auth backend (replace legacy external discovery setup).
  • Production compose split into base plus topology overlays.
  • Environment examples for tunnel and caddy tracks updated.

Migration note: Review deployment env files and align domain/discovery variables before upgrading.

v0.2.x

April 2026
  • Runtime and dependency baseline updates.
  • Desktop packaging and release workflow adjustments.

Migration note: Pull the latest deployment files and revalidate service startup order.