Add https://isitup.lotusguild.org to connect-src so the client can fetch
the public status JSON (GET /api/status-page/matrix and
/api/status-page/heartbeat/matrix) for the homeserver status banner.
Read-only public JSON, no credentials; nothing else changes.
The live snippet (/etc/nginx/snippets/cinny-security-headers.conf on
LXC 106) was identical to this file before the change; install it by hand
with a backup and `nginx -t` (the deploy script only handles
cinny/nginx.conf).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Sets elementCallUrl so the web app frames the call page on its own origin
(desktop keeps its bundled copy). Applied on LXC 106 in place (the live file
also carries the injected gifApiKey; backup in /root/config-backups/), since
the matrix-deploy webhook there has not fired since May.
Rollback: remove this key (live: restore the backup) and new calls use the
bundled same-origin page again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
- cinny/nginx.conf: a call.chat.lotusguild.org server block that serves ONLY
/public/element-call/ (the same files chat.lotusguild.org already serves
there) and 404s everything else, including source maps and dotfiles.
- cinny/nginx-security-headers-call.conf (new snippet): frame-ancestors
https://chat.lotusguild.org instead of X-Frame-Options SAMEORIGIN, which
would block the now cross-origin parent.
- cinny/nginx-security-headers.conf: the app's Permissions-Policy delegates
autoplay/camera/display-capture/microphone to the call origin (without it
the cross-origin frame's getUserMedia is refused). Outer quotes switched to
single: the inner "origin" quotes broke nginx parsing.
Nothing changes for users until config.json sets elementCallUrl (separate
step). Snippets are installed by hand on LXC 106; nginx.conf deploys on merge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Web calls broke ("nobody can join calls from the web version"): 23ad133
(cinny #210) put the security-headers snippet, including the app CSP, on
every .html response. /public/element-call/index.html then carried
frame-ancestors 'none', so the browser refused to load it inside the app
("Content-Security-Policy ... frame-ancestors 'none'"). Desktop was fine (it
loads Element Call from its own bundle). Before #210 that page had no CSP.
A `location ^~ /public/element-call/` now serves it with
cinny-security-headers-framed.conf: the same headers minus the CSP
(X-Frame-Options SAMEORIGIN still limits framing to chat.lotusguild.org),
and the same caching (HTML no-cache, hashed assets 1 year). Applied live on
LXC 106 (backup sites-available/cinny.bak-ecframe-*), nginx -t ok; verified
from outside: no CSP on the call page, and the live site frames it and it
loads ("Element Call").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Applied on LXC 106 (backed up, `nginx -t`, reloaded) and recorded here:
- Security headers (incl. CSP) moved to snippets/cinny-security-headers.conf
and included at server level plus in the /sw.js, static-asset and
json/html locations. nginx drops inherited add_header in any block that
sets its own, so static assets were served without nosniff and /sw.js
without a CSP (a service worker takes its CSP from its own script).
Now every path carries all seven headers. Verified: the SW installs and
controls the page under the CSP with no violations.
- #214: CSP no longer allows fonts.googleapis.com / fonts.gstatic.com
(VT323 is self-hosted since cinny 6f250353).
- #155: the /share-target → /share 303 is now live (it was only in the
repo; the 106 matrix-deploy hook has been dead since May, so repo edits
never reached it). absolute_redirect off makes it relative.
- README: the snippet, and that 106 needs these applied by hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Lotus Chat 6f250353 self-hosts VT323, so nothing legitimate loads from Google
any more; keeping the hosts allowed would let a regression pass silently.
NOTE: the live config is hand-maintained at /etc/nginx/sites-available/cinny
on LXC 106 — apply the same two edits there after the cinny deploy lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
lotus_deploy.sh took an flock and exited if a deploy was already running.
The poll loop only retargets origin/lotus while it is still waiting on
CI; a push that lands during npm ci / npm run build was silently never
deployed until the next unrelated push (cinny 81a6d9c9 — the MSC4515
call fix — passed CI but never reached the web root). A concurrent
trigger now leaves a .pending marker and the running deploy re-execs
itself once at the end, which re-fetches and re-gates on CI as usual.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
lotus-build.sh merged the latest upstream tag, then built, copied to the
web root, reloaded nginx and only then pushed — the one path that could
change production without any CI gate (cinny#98). It now merges, runs the
same local gates CI runs (npm ci, typecheck, eslint, prettier, tests),
and pushes; the push triggers Gitea CI and lotus_deploy.sh deploys once
"Build & Quality Checks" is green, like every other lotus commit. A
failed gate leaves the merge local for inspection and notifies the room.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
Companion to cinny's `concurrency: cancel-in-progress`. The poll loop latched
origin/lotus once at startup; with CI cancel-in-progress a superseded run's
status flips to error/failure, so polling the latched (now-cancelled) SHA would
abort and — with the flock skipping the newer push's deploy — strand the newest
commit undeployed (a deploy freeze, the exact class this script fought before).
- Re-resolve origin/lotus each poll iteration; retarget the CI gate to HEAD if
it advanced (fresh MAX_WAIT window).
- On a failure/error status, re-check HEAD before aborting — only a genuine
failure of the CURRENT HEAD aborts; a superseded/blip SHA is followed instead.
- Reset to the gated $COMMIT_SHA (not a bare origin/lotus that may have advanced
past the gate after the loop) so we build exactly what passed CI.
shellcheck clean; reviewed (SHIP).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The production config.json (cp'd from cinny/config.json by deploy/lxc106-cinny.sh)
lost its "gifApiKey": "" placeholder, so lotus_deploy.sh's sed injection had no
field to fill -> the GIF composer button disappeared (it renders only when
gifApiKey is non-empty). Restore the placeholder so future deploys inject the key.
Also redact the real Giphy key that was committed in the README (it belongs only in
/etc/lotus-deploy.env on LXC 106). NOTE: the key remains in git history (commit
f3a7bcd) — it should be rotated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds Strict-Transport-Security (2y, includeSubDomains, preload) and a
Permissions-Policy that allows only the features the app uses (camera/mic/
display-capture for calls, geolocation for location share, autoplay/fullscreen/
encrypted-media) and denies the rest. Complements the existing X-Frame/CSP/
Referrer headers.
Apply: reload nginx on the LXC. TLS terminates upstream (listen 80), so verify
the header reaches the browser (front proxy must pass it through) — else set
HSTS at the TLS terminator. Verify a call + location share still work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shellcheck directives bind to the NEXT command; on the compound line
`set -a; source /etc/lotus-deploy.env; set +a` the existing
`# shellcheck disable=SC1091` bound to `set -a`, so the info-level SC1091
finding on the runtime-only env file still failed the lint workflow
(find -exec shellcheck exits non-zero on any finding). Split the line so the
directive sits directly above `source` (as `source=/dev/null`, the standard
idiom for host-only env files). Verified with CI's exact invocation:
`find . -name "*.sh" -exec shellcheck {} +` now exits 0 (shellcheck 0.9.0).
No runtime behavior change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The live /usr/local/bin/lotus_deploy.sh (the `lotus-deploy` webhook target) was
never under version control and had rotted into two deploy-killing bugs that
froze chat.lotusguild.org on an old build:
1. CI gate: it waited on the WHOLE workflow run with a 15-min cap. Web CI shares
the single act_runner with the slow Tauri desktop builds, so a web run could
sit queued >15 min -> "result: timeout" -> deploy aborted. Now it gates only
on the "Build & Quality Checks" commit-status context (build + unit tests),
decoupled from "Trigger Desktop Build", and waits up to 45 min.
2. Dead element-call copy: `cp node_modules/@element-hq/element-call-embedded/...`
under `set -e` aborted every deploy after the widget was forked to
@lotusguild/element-call-embedded. The build already emits dist/public/
element-call; replaced the copy with a presence check.
Also: rsync now excludes config.json so the app deploy stops clobbering the
production runtime config (homeserver list / allowCustomHomeservers) that the
matrix repo owns. lxc106-cinny.sh now installs this script (syntax-checked).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the client supports MSC3861 OIDC login, add mozilla.org to the
homeserverList and its origins to the CSP. mozilla delegates: homeserver ->
mozilla.modular.im, OIDC issuer -> chat.mozilla.org, identity -> vector.im.
- connect-src += mozilla.org mozilla.modular.im chat.mozilla.org vector.im
- img-src += mozilla.org mozilla.modular.im
Applied live to LXC 106 and synced here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Federated matrix.org users load avatars/images from their own media endpoint
(matrix-client.matrix.org), which img-src still blocked — so every avatar
tripped a CSP violation. Add https://matrix.org + https://*.matrix.org to
img-src to match connect-src. (media-src already allows https: so video/audio
were fine.) Applied live to LXC 106 and synced here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chat.lotusguild.org nginx config (LXC 106) was edited directly on the box
and never tracked — which is how its CSP drifted (kept a dead Sentry URL and
blocked matrix.org logins). Snapshot it as cinny/nginx.conf (verbatim from prod,
incl. the corrected connect-src that now allows matrix.org/*.matrix.org) and
deploy it via lxc106-cinny.sh: back up the live file, swap, `nginx -t`, and
reload only on success (auto-restore the backup if validation fails, so a bad
config can't take the site down). TLS terminates at the NPM proxy, so this is a
plain HTTP server block with no secrets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add matrix.org to homeserverList so federated friends with matrix.org accounts
can sign into chat.lotusguild.org. defaultHomeserver stays 0 (lotusguild), and
allowCustomHomeservers stays false — only the two listed servers are selectable,
so the client isn't opened up to arbitrary homeservers.
Deploys via lxc106-cinny.sh (cp -> /var/www/html/config.json); lotus-build.sh
preserves the live config across app rebuilds, so this is the authoritative copy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OOM observed during rendering-chunks phase at 896MB and 3072MB.
6144MB heap with 8GB LXC memory is confirmed working.
Also update README rebuild command to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>