Previously both compose files defaulted GITEA_ADMIN_PASSWORD to empty,
and scripts/gitea-init.sh silently skipped admin creation on blank input.
If config.sh failed to propagate the password (e.g. Docs Comments not
enabled, or --admin-password omitted), fresh installs ended up with a
Gitea container running but zero users — and the admin GUI's Gitea setup
wizard had no token to progress.
Changes:
- docker-compose.yml + docker-compose.prod.yml: GITEA_ADMIN_PASSWORD now
falls back to INITIAL_ADMIN_PASSWORD when unset
- .env.example: declare GITEA_ADMIN_PASSWORD= with explanatory comment so
users discover the override
- scripts/gitea-init.sh: silent skip becomes a loud WARN so a broken
config is visible in compose logs
Bunker Admin
Six independent fixes surfaced during the v2.9.1 → v2.9.2 admin-UI
upgrade validation today. Together they make a clean install on a new
box work end-to-end without in-session patching.
- Fix 1: scripts/validate-compose-parity.sh + build-release.sh hook —
fail release builds when api/admin/media-api/nginx healthcheck
blocks drift between docker-compose.yml and docker-compose.prod.yml.
Previous boot-race fix had to be applied to both files manually.
- Fix 2: scripts/systemd/install.sh chowns logs/ to the install user
(the API container creates subdirs there as root, locking the
host-side watcher out), pre-creates logs/upgrade-watcher.log, and
changemaker-upgrade.service adds StartLimitIntervalSec=0 so a
single transient failure can't wedge the .path unit permanently.
- Fix 3: /api/upgrade/status now returns a `watcher` sub-object that
flags the host systemd watcher as stalled when trigger.json has
been pending >30s. Admin SettingsPage SystemUpgradeTab renders a
warning Alert with the systemctl recovery command when unhealthy.
- Fix 4: scripts/upgrade.sh write_result() — prefer head -1 VERSION
over `git rev-parse HEAD` so release-mode upgrades report the new
tag in result.json instead of "unknown".
- Fix 5: admin container healthcheck start_period 20s → 60s in both
compose files, same class as the earlier api fix. Matches Gancio
convention.
- Fix 7: /api/pangolin/sync now detects resources bound to a stale
siteId (common after --pangolin-site new rotations), deletes and
recreates them against the current site, and reports them under
a new `reassigned` response field.
Bunker Admin
On a fresh install, the api container's docker-entrypoint.sh does
migrations + seed + MaxMind GeoLite2 download synchronously before
binding port 4000, which routinely exceeds the previous 75s tolerance
(start_period 30s + 3x15s retries). When the API flipped to unhealthy,
nginx and newt (which depend on api: condition: service_healthy)
aborted, leaving the tunnel offline and requiring a manual
'docker compose up -d nginx newt' second pass.
Bumping start_period to 120s gives first-boot enough slack, matches
convention (Rocket.Chat 90s, Gancio 60s), and leaves steady-state
restarts unaffected. Change applied to both dev (docker-compose.yml)
and prod (docker-compose.prod.yml) since the release tarball ships
the latter.
Bunker Admin
Mount ${MEDIA_ROOT}/local/documents:/media/local/documents:rw on the
media-api service so the document upload route can write PDFs to
disk. Without this mount, uploads fail on fresh deployments.
Also hardens several depends_on entries from service_started to
service_healthy (redis for media-api, api for admin, listmonk-app
for listmonk-init, gitea-db for gitea, nginx for newt) so
containers wait for actual readiness, not just process start.
Bunker Admin
NocoDB v2 stores auth tokens in-memory (Pinia store), not in cookies
accessible to external pages. The auth bridge approach can't inject
tokens into NocoDB's SPA state. Reverted to the original banner
approach ("sign in to NocoDB in a new tab").
Kept: CSP fix (frame-ancestors http://localhost:* instead of just
localhost, which only matched port 80).
Bunker Admin
The previous approach (custom CMD on gitea-app) failed because:
- Gitea's entrypoint generates app.ini as root, then drops to git user
- Overriding CMD ran our script before app.ini was generated
- su-exec to git user lost access to the entrypoint-generated config
Now uses the same pattern as nocodb-init: a separate container that
depends on gitea-app being healthy, shares the gitea-data volume
(which has app.ini), and runs gitea admin user create.
Bunker Admin
- Add scripts/gitea-init.sh: runs migrations + creates admin user on
first boot, replacing the manual installation wizard
- Set GITEA__security__INSTALL_LOCK=true in both compose files
- Add NocoDB auth bridge (nginx) + /api/services/nocodb-auth proxy
endpoint so the admin iframe auto-authenticates
- Update NocoDBPage.tsx to fetch token and use auth bridge flow
- Fix docker-compose.prod.yml missing Gitea env vars for API container
(GITEA_URL, GITEA_API_TOKEN, GITEA_ADMIN_PASSWORD, etc.)
- Pass NC_ADMIN_EMAIL/PASSWORD to API for NocoDB auth proxy
- Increase Gitea auto-setup retries from 3 to 6 with admin auth check
- Update config.sh non-interactive mode to set GITEA_ADMIN_USER
- Include gitea-init.sh in release tarball (build-release.sh)
Bunker Admin
- Fix Pangolin endpoint: ask separately from API URL (different hostnames)
- Add pangolin_create_resources() to create resources + targets during setup
- Set all resources as public (no SSO/blockAccess) automatically
- Fix API health check URL to use actual org endpoint
- Fix MongoDB entrypoint: delegate to docker-entrypoint.sh so INITDB user
creation works on fresh volumes (was bypassing Docker's init sequence)
Bunker Admin
These env vars were defined in .env but never mapped into the API
container's environment block, causing silent fallback to
JWT_ACCESS_SECRET and security warnings on startup.
Bunker Admin
- Added ccp-agent as 5th service in build-and-push.sh (builds from
changemaker-control-panel/agent/Dockerfile)
- Fixed prod compose image name to match registry convention:
changemaker-ccp-agent (consistent with changemaker-api, etc.)
Bunker Admin
Operators can now register with a Control Panel directly from the admin
GUI (Services → Control Panel) without SSH access. Uses the existing
updateEnvFile + dockerService pattern from the Pangolin setup.
New endpoint: /api/ccp-registration (status, register, unregister)
New page: ControlPanelPage with form for CCP URL, invite code, agent URL
Also passes CCP env vars through docker-compose to the API container.
Bunker Admin
- install.sh: Use tar --strip-components=1 instead of mv for robust
extraction when install dir partially exists (root-owned Docker
artifacts)
- config.sh: Add --non-interactive mode (--domain, --admin-password,
--enable-all flags) for CI/CD and automated deployments
- docker-entrypoint.sh: Validate critical env vars on startup, fail
early with clear messages instead of silent failures
- docker-compose.yml: Change Redis eviction policy from allkeys-lru
to noeviction (required by BullMQ job queues)
- Prisma: Add missing petitions.coverVideoId migration (schema had
the column but migration omitted it, causing 500 on public endpoint)
- Add scripts/uninstall.sh for clean removal including root-owned files
- Add scripts/test-deployment.sh for automated post-install verification
Bunker Admin
Gitea SSO: cookie-based single sign-on via nginx auth_request — sets
cml_session cookie on login/refresh, validates via /api/auth/gitea-sso-validate,
injects X-WEBAUTH-USER header for reverse proxy auth. Dedicated GITEA_SSO_SECRET
and SERVICE_PASSWORD_SALT env vars isolate secret rotation.
Security fixes from March 30 audit: IDOR on ticketed events (requireEventOwnership
middleware), IDOR on action items (admin/assignee/creator check), path traversal
on photos (resolve-based validation), CSV upload size limit (5MB), shared calendar
email exposure removed.
Gitea provisioner: auto-sync docs repo collaborator access based on role
(CONTENT_ROLES get write, SUPER_ADMIN gets admin). Gitea client extended
with collaborator management API methods.
Production hardening: NODE_ENV defaults to production in docker-compose.prod.yml,
Grafana anonymous auth disabled, install.sh branch ref updated to main.
Admin UI: moved docs reset from toolbar to MkDocs Settings danger zone,
improved collab Ctrl+S to explicitly save + cache-bust preview.
MkDocs site rebuild with updated repo data, upgrade screenshots, and content.
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
- 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
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
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