The generated api.conf and services.conf we edited earlier were overwritten
at container startup by envsubst from *.template files. Fix the actual
templates:
- api.conf.template: X-Forwarded-For → $remote_addr, add limit_req
- services.conf.template: add frame-ancestors CSP after proxy_hide_header
- Add Prisma migration file for ticket_tiers.reserved_count
Bunker Admin
Deferred findings from the March 27 security audit, plus a bug fix:
MongoDB keyfile (bug fix):
- Generate replica.key on first boot via entrypoint script
- Fixes crash from --auth + --keyFile without an existing keyfile
- Applied to docker-compose.yml, docker-compose.prod.yml, CCP template
I7 — Ticket overselling prevention (reservation pattern):
- Add reservedCount field to TicketTier schema
- Atomically increment reservedCount inside transaction on checkout
- Release reservation on checkout.session.completed (webhook)
- Release reservation on checkout.session.expired (webhook)
- Include reservedCount in availability calculations
I17 — Move refresh token to httpOnly cookie:
- Server sets httpOnly SameSite=Strict cookie on login/register/refresh
- Cookie scoped to /api/auth path, secure in production
- Refresh/logout endpoints read from cookie (with body fallback for compat)
- Frontend no longer stores refreshToken in localStorage
- Auth store simplified: removed refreshToken from state + persistence
- API interceptor uses withCredentials:true for automatic cookie sending
- Updated media-api, media-public-api, QuickJoinPage, volunteer-invite
- Renamed getTokens → getAccessToken across all media components
- Install cookie-parser middleware
L2 — FeatureGate loading state:
- Show Skeleton instead of children while settings are loading
- Prevents briefly exposing disabled feature pages
Bunker Admin
Major additions: onboarding tour system, correlation-id middleware, media
error handler, restore script, env validation script, Dockerignore files.
Updates across 70+ admin components for improved UX and error handling.
Bunker Admin
Drop the custom Dockerfile.code-server that bundled Claude Code CLI,
Python/MkDocs tooling, and build-essential on top of codercom base.
Switch to the already-mirrored linuxserver/code-server image instead.
- Both compose files: use code-server:latest, LinuxServer env vars
(PUID/PGID/DEFAULT_WORKSPACE), port 8443, /config mount layout
- Nginx configs + templates: proxy to :8443 instead of :8080
- API env default: CODE_SERVER_URL updated to :8443
- build-and-push.sh: remove --include-code-server flag
- upgrade.sh: remove code-server conditional rebuild + registry fallback
- install.sh: add --ignore-pull-failures for optional missing images
- .env.example, CCP templates, bunker-ops template: updated
Bunker Admin
When piped (curl | bash), stdin is the curl output, not the terminal.
All read prompts in config.sh were reading leftover pipe data or EOF,
causing infinite password validation loops and garbage domain values.
Bunker Admin
- Dashboard: auto-discovers containers from Docker network via socket
proxy API instead of hardcoded 30-name list. Labels derived from
docker compose service metadata.
- Email/Settings: mailhog host read from env.SMTP_HOST instead of
hardcoded 'mailhog-changemaker' string
- Pangolin: grafana container derived from env.GRAFANA_URL hostname;
newt container/service names from NEWT_CONTAINER_NAME/NEWT_COMPOSE_SERVICE
- SSRF blocklist: built dynamically from all service URL env vars
instead of hardcoded hostname list
- New env vars: DOCKER_NETWORK_NAME, DOCKER_PROXY_URL,
NEWT_CONTAINER_NAME, NEWT_COMPOSE_SERVICE
Bunker Admin
Rocket.Chat and Jitsi embed ports were hardcoded in the Homepage
services.yaml generation. Now reads from .env so multi-instance
deployments with custom ports get correct Homepage dashboard links.
Bunker Admin
All 13 nginx embed proxy ports (8881-8895) are now driven by environment
variables instead of being hardcoded. This prevents port conflicts when
running multiple Changemaker instances on the same host.
Chain: .env → docker-compose port mappings → nginx container env →
entrypoint.sh envsubst → services.conf.template listen directives →
API /services/config endpoint → frontend buildServiceUrl().
Existing deployments are unaffected (all vars default to current values).
Bunker Admin
- Omit subdomain field for root domain resources (Pangolin rejects empty
string but accepts absent field)
- Set sso:false + blockAccess:false after resource creation so resources
are publicly accessible without Pangolin auth redirects
- Make subdomain optional in CreateHttpResourcePayload type
- Applied to both /setup and /sync endpoints
Bunker Admin
When --use-registry is set, the upgrade script tries to pull images
tagged with the current HEAD SHA. If images were built at an earlier
commit, that SHA tag won't exist. Now tries :latest before falling
back to a full source build. Also applies to nginx and code-server.
Bunker Admin
GITEA_URL points to the internal Docker hostname (gitea-changemaker:3000),
which is unreachable from the host. Derive external URL from GITEA_REGISTRY
instead, which already contains the external hostname.
Bunker Admin
New install method: curl one-liner downloads a lightweight release
tarball (~9 MB) and runs the config wizard. No git clone needed,
no TypeScript compilation — pulls pre-built images from Gitea registry.
- docker-compose.prod.yml: production compose without build blocks or
source code volume mounts; IMAGE_TAG defaults to latest
- scripts/install.sh: curl-friendly installer (downloads tarball,
extracts, runs config.sh)
- scripts/build-release.sh: creates release tarball from dev repo
with only runtime files (configs, scripts, docs, empty data dirs)
- config.sh: release-mode detection (VERSION file + no .git dir),
auto-sets IMAGE_TAG=latest and NODE_ENV=production
- upgrade.sh: release-mode upgrade path (downloads new tarball from
Gitea Releases API instead of git pull, always uses registry mode)
- upgrade-check.sh: release-mode version check via Gitea API
- .gitignore: exclude releases/ and api/dist/
- Docs: updated getting-started with pre-built install instructions
Bunker Admin
- Create api/src/modules/registry/ (service + routes) so server.ts
import resolves and TypeScript compiles all 38 modules cleanly
- Add api/.dockerignore to exclude stale local dist/ from Docker build
context, preventing old compiled output from persisting in images
- Registry routes: GET /status (Gitea packages API), POST /build-push
and POST /mirror (write trigger files for host watcher, SUPER_ADMIN only)
Bunker Admin
- Fix @/utils/logger path alias (tsc doesn't transform @/ in output)
- Add JWT_INVITE_SECRET to media-api compose environment block
- Fix redis-exporter depends_on to use service name not container name
- Fix upgrade.sh: restore tracked files deleted by restore_user_paths
- Add scripts/build-and-push.sh for building + pushing production images
- Add scripts/mirror-images.sh for mirroring third-party images
Bunker Admin
docker-compose.yml explicitly enumerates each env var passed to
containers, so the new JWT_INVITE_SECRET needed to be wired through
the environment block or the API would fail Zod env validation at
startup.
Bunker Admin
- Pin HS256 algorithm on all jwt.verify() calls (9 sites) and jwt.sign()
calls (3 sites) — prevents algorithm confusion attacks
- Add JWT_INVITE_SECRET env var; volunteer invite tokens now use a
dedicated key separate from access/refresh secrets
- Remove req.query.secret fallback from Listmonk webhook route — secrets
must not appear in nginx access logs
- Replace child_process.spawn in email template seed endpoint with direct
function import; add require.main guard to seed script
- Add sanitizeCsvField() to location CSV export to prevent formula
injection in Excel/Sheets (=, +, -, @ prefix → apostrophe prefix)
- Cap QR endpoint text input at 2000 chars to prevent DoS via large payloads
- Fix pre-existing TS errors: type participantNeeds as UpsertNeedsInput
in meeting-planner service; add sso field to UpdateResourcePayload
Bunker Admin
Use inline JS styles (applySearchLayout) instead of CSS-only approach
for search panel layout - fixes Firefox compatibility where cross-origin
Material stylesheets override !important rules. Adds explicit height,
flex layout, z-index, and background on search elements. Also fixes
click-to-exit by deferring DOM queries to DOMContentLoaded. Syncs
header-builder.service.ts with main.html changes.
Bunker Admin
Vaultwarden sends a restrictive Content-Security-Policy with frame-ancestors
that blocks iframe embedding. The embed proxy (port 8890) already stripped
this header, but the subdomain server block did not.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The /api/ location blocks in both default.conf and services.conf templates
were missing Upgrade/Connection headers, preventing the Hocuspocus WebSocket
connection from establishing through nginx.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kiliandeca/excalidraw:sha-e42a510 tag doesn't exist. The kiliandeca fork
hasn't been updated since 2021. Official image is current (Jan 2026).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The collapsed Material header (height: 0, overflow: visible) left the
search input reachable but the __search checkbox was never toggled when
users typed directly into it. This prevented both Material's native CSS
and our custom CSS from revealing the results panel (opacity stayed 0,
scrollwrap max-height stayed 0).
- Add focusin/input event delegation to check __search on direct input
- Add search icon, dark mode toggle, and docs sidebar toggle to header
- Add CSS for hidden Material header, search positioning, palette, tabs
- Avoid Jinja2 block syntax inside JS comments (parsed as directives)
Bunker Admin
Inserts Phase 5 (Database Migration) between container rebuild and service
restart. Detects failed/incomplete Prisma migrations via _prisma_migrations
query and auto-resolves them before running migrate deploy in a one-off
container — catching errors in the script rather than letting the API enter
a restart loop. Also detects when package.json/package-lock.json changed
and removes old API/admin containers to prevent stale anonymous volumes
from shadowing updated node_modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add scripts/systemd/install.sh to handle placeholder substitution
(__PROJECT_DIR__, __USER__) and systemd unit installation in one command
- Simplify manual install instructions in SettingsPage and config.sh to
reference the new install script
- Preserve existing home.html and home.css in reset-site.sh instead of
overwriting with templates
- Add comments/ and partials/ to preserved directories list
- Fix nav removal in mkdocs.yml using Python regex instead of fragile sed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Resolve Pangolin site slug to numeric ID in sync route (fixes target creation 400 errors)
- Disable SSO on newly created Pangolin resources for public access
- Fix nginx media API proxy: use rewrite + set ordering for proper URI rewriting
- Upgrade script: clear skip-worktree flags, fix Docker-owned dir permissions, stash untracked files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add editorReady state flag to resolve race condition where Y.js
sync completes before Monaco editor mounts, causing the
MonacoBinding effect to skip binding creation on the initial
file selection.
Bunker Admin
- Replace postMessage wildcard ('*') with explicit parent origin passed
via ?origin= parameter to prevent auth state disclosure to arbitrary
embedders
- Tighten frame-ancestors CSP: production restricts to self + DOMAIN,
dev restricts to localhost origins (was frame-ancestors *)
- Remove deprecated X-Frame-Options ALLOW-FROM header (CSP
frame-ancestors is the modern replacement)
- Validate targetOrigin with URL constructor before use
Bunker Admin