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
Replace the implicit runtime-scripts for-loop with two explicit arrays
(RUNTIME_SCRIPTS + DEV_ONLY_SCRIPTS) and a pre-build assertion that
aborts if any scripts/*.sh isn't classified.
Motivation: during the v2.9.8 sprint we added three new scripts
(pangolin-teardown.sh, ccp-deregister.sh, validate-env.sh) and had to
manually remember to include each in the whitelist. Missing one is
silent — the script just doesn't ship, and the release tarball is
subtly broken in a way you only notice when the docs reference the
missing script.
The parity check surfaces the decision: every new scripts/*.sh must
be deliberately classified as ship (RUNTIME_SCRIPTS) or don't-ship
(DEV_ONLY_SCRIPTS). Adding a script and forgetting aborts the build
with a clear message naming the unclassified file.
Side-effect fixes from this audit:
- register-with-ccp.sh is now shipped (was only in source; needed
for retrofitting CCP onto existing installs)
- update-env.sh is now shipped (safe .env updater used by upgrade
flows)
Bunker Admin
scripts/install.sh cleanup trap previously removed $INSTALL_DIR on
any non-zero exit if .env wasn't written yet. That made sense for
half-extracted state, but also bit us when config.sh failed at
/dev/tty (the common "curl | ssh bash" non-interactive case) — the
15MB tarball had already extracted cleanly and the user was forced
to re-download to retry on a console.
New EXTRACT_COMPLETE state flag:
- Set to true after the tar xzf step verifies docker-compose.yml.
- cleanup() distinguishes "extract OK, config wizard didn't run"
from "extraction never completed":
* First case: preserve the dir, print a resumption hint
(cd $INSTALL_DIR && bash config.sh on an interactive console).
* Second case: unchanged behaviour — remove the partial dir.
Typical SSH-without-tty recovery path now costs zero re-download.
Bunker Admin
Pairs with pangolin-teardown.sh. For instances that were phone-home
registered with a CCP, this script removes the CCP-side Instance row
during teardown so the slug is freed for re-registration.
Without it, tearing down a CCP-registered instance leaves a stale
Instance row in CCP's DB. The next phone-home-registration with the
same slug hits the unique-constraint violation we saw in marcelle
testing.
Matching: by agentUrl (default from .env CCP_AGENT_URL), --slug, or
--instance-id. Dry-run by default; --yes to execute. CCP admin token
via --token or CCP_ADMIN_TOKEN env var.
Added to build-release.sh whitelist so release tarballs include it
alongside pangolin-teardown.sh and validate-env.sh.
Bunker Admin
scripts/build-release.sh --upload now checks for an existing release
at the given tag before POSTing a new one. If found and --replace is
not set, errors out with a clear message.
This prevents the silent-overwrite problem: a user on v2.9.7 running
./scripts/upgrade.sh sees "no update available" when the v2.9.7
release's tarball contents have silently changed. Version tags should
be immutable once published.
--replace is still available for deliberate test-bench iteration
(DELETEs the existing release, then POSTs). Documented as destructive
in the --help output and DEV_WORKFLOW.md.
Bunker Admin
scripts/install.sh: inline ss -Htln check before tarball download so
cockpit-on-9090 (and friends) fail early instead of breaking the stack
mid-compose-up. Culprit-specific hints for :9090 (cockpit.socket) and
:80/:443. Gracefully skipped if iproute2 not installed.
config.sh: Next Steps in release mode now surfaces
- test-deployment.sh --wait 60 (verify step)
- validate-env.sh (re-check ports/.env)
- pangolin-teardown.sh (clean reset before reinstall)
Also documents the ~3min first-pull + ~90s stabilization window so
brief "unhealthy" statuses don't panic new users.
Bunker Admin
Fixes surfaced by three rounds of fresh-install testing on marcelle:
- config.sh: add host-port preflight check (ss -tln) to catch
cockpit-on-9090 style collisions before compose up; add
--skip-port-check escape hatch; add --install-watcher /
--no-install-watcher / --install-backup-timer /
--no-install-backup-timer flags; -y --enable-all now installs both
systemd units by default (previously silently skipped); print
resolved admin email in Configuration Complete block.
- scripts/validate-env.sh: new section 5b "Host Port Availability"
using ss-based detection, with process-name surfacing when run as
root.
- scripts/pangolin-teardown.sh: new wrapper. Reads credentials from
.env or takes --api-url/--api-key/--org-id flags. Dry-run by
default; --yes to execute. Deletes resources before sites (avoids
orphans). --keep-site-ids for safety.
- scripts/build-release.sh: include validate-env.sh and
pangolin-teardown.sh in release tarball whitelist.
- CCP instances.service.ts: deleteInstance() now calls
teardownTunnel() before composeDown when pangolinSiteId is set.
Previously an admin clicking "Delete Instance" orphaned the
Pangolin site + all its resources. Best-effort with try/catch
matching the existing Docker-cleanup tolerance pattern.
- CLAUDE.md: sync drift — 44 → 50 migrations, 186 → 192 models,
40 → 44 modules.
Bunker Admin
Prior commit (ac901c9e, Fix B) gated VERSION.pending promotion behind
VERIFY_FAILED=false, but VERIFY_FAILED is a soft warning signal — it
also fires when the admin container's 30s verify budget is tight
(which was the cry-wolf case Fix 3 addressed in the same commit).
Observed on marcelle during v2.9.5 → v2.9.6: the upgrade completed
successfully (tarball extracted, containers pulled and running new
image), but because the admin healthcheck warned at 30s (still using
v2.9.5's upgrade.sh with its 30s budget), VERIFY_FAILED=true pinned
VERSION back to v2.9.5 despite everything else having advanced. result.json showed success=true but newCommit=v2.9.5.
Hard failures still prevent promotion via on_failure's rm -f of
VERSION.pending before the promotion site is reached. Reaching the
promotion site means Phase 7 completed without exit-code or trap —
that's the correct gate.
Bunker Admin
Four fixes building on the prior upgrade-path work. All observed on
marcelle across today's v2.9.2 → v2.9.5 cycles and addressed here.
- Fix 1 (breaking-release gate). upgrade-check.sh now parses the first
line of each Gitea release body for `BREAKING: <reason>` and threads
`breaking`/`breakingReason` through status.json into the API status
response. Admin UI renders a red Alert with a typed-tag confirmation
input and gates the Start Upgrade button. auto-upgrade.service.ts
refuses to apply breaking releases, logging a skip and holding off
until the operator confirms manually.
- Fix 2 (release-mode rollback). print_rollback_help and the --rollback
flow both used `git checkout`, which silently fails in release
installs (no .git). Added INSTALL_MODE branches: release mode
downloads the prior tarball from Gitea using a new VERSION.rollback
marker seeded at Phase 3 start. Source mode retains the existing
git-based flow.
- Fix 3 (Phase 7 health budgets). admin verify_service_health budget
30s → 90s (matches the admin container's start_period from commit
47704667). Gancio + MkDocs switched from one-shot to the existing
verify_service_health retry wrapper. Cuts the cry-wolf
"services may still be starting" warning from every upgrade result.
- Fix 4 (symmetric success archival). Bash archive_failure_to_history
already logs failures on exit; added a matching archive_success_to_
history called after write_result on the success path. API-side
archiveResult now dedupes on completedAt so double-recording (bash
+ post-restart handler) can't land twice in history.json.
Release the bundle as v2.9.6.
Bunker Admin
Three fixes to harden the admin-UI upgrade path, all in scripts/upgrade.sh.
Root-caused by yesterday's v2.9.2 → v2.9.3 on marcelle which was killed by
systemd mid-Phase-4 and left the system in a misleading half-upgraded state
(VERSION bumped, container pre-upgrade, result.json stale from 24h prior).
- Fix A (failure visibility): stop silencing stderr on the five docker
compose pull sites so timeouts / auth failures / network errors flow
into upgrade-watcher.log. Add explicit SIGTERM/SIGINT traps alongside
the existing EXIT trap. Track CURRENT_PHASE_NAME globally so the
failure message reports "during Phase 4: Container Rebuild" rather
than just an exit code. Introduce write_result_force (bypasses
API_MODE guard) + archive_failure_to_history so a killed upgrade
always leaves a truthful result.json + history.json entry, and the
progress.json is cleared so the admin UI stops showing a phantom
in-progress phase.
- Fix B (atomic VERSION): Phase 3 rsync now --excludes VERSION and
stashes the new one at data/upgrade/VERSION.pending. Phase 7 promotes
it to VERSION only after VERIFY_FAILED stays false. on_failure deletes
the pending file. upgrade-check.sh needs no changes — its head -1
VERSION read sees actual state instead of a mid-upgrade promise.
- Fix C (external smoke): after Phase 7 localhost checks, curl
https://api.${DOMAIN}/api/health with --max-time 10 and warn (not
fail) on non-200. Catches Pangolin resource misassignments that the
localhost-only checks miss. Appends to UPGRADE_WARNINGS so the admin
UI surfaces it in result.json.
Bunker Admin
Admin-UI-triggered v2.9.2 → v2.9.3 upgrade on marcelle was killed by
systemd at 15min mid `docker compose pull` because Gitea registry
pulls over the Pangolin tunnel routinely exceed that budget on a
cold cache. 1hr gives enough headroom for slow pulls without
hiding genuinely hung upgrades.
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
- Use --must-change-password=false (equals syntax) in gitea CLI
The space-separated form was parsed as boolean flag + extra arg
- Fix auto-setup readiness check: use Basic Auth instead of
unauthenticated /version endpoint (blocked by REQUIRE_SIGNIN_VIEW)
- Increase retries to 8 to accommodate gitea-init container startup
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
The Gitea Docker entrypoint sets up directories as root then exec's
the CMD still as root. Gitea refuses to run as root, so our init
script must re-exec itself as the 'git' user via su-exec before
running any gitea commands.
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
- 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
Interactive script that configures .env, adds ccp-agent to COMPOSE_PROFILES,
and starts the agent container. Supports --ccp-url, --invite-code, --agent-url
flags for non-interactive use, and --unregister to remove registration.
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_API_TOKEN is for the local platform Gitea (docs comments, user
provisioning, SSO). New GITEA_REGISTRY_API_TOKEN is for the remote
registry at gitea.bnkops.com (release uploads via build-release.sh).
Previously both contexts shared one variable, causing auth failures
when the token for one instance was used against the other.
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
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
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
- 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
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>
Follows the listmonk-init pattern: an alpine:3 container that runs once
after NocoDB is healthy, calls the REST API to register changemaker_v2
as a browsable data source, and exits. Idempotent — exits immediately
if the base already has tables, and guards against duplicate sources
during async table discovery.
Bunker Admin
API container writes trigger files to a shared volume (data/upgrade/),
and a systemd path watcher on the host detects them and runs the
upgrade scripts. This avoids giving the container Docker socket access.
- Add upgrade-check.sh (git fetch + compare + write status.json)
- Add upgrade-watcher.sh (systemd bridge, dispatches check/upgrade)
- Add systemd path/service units with placeholder substitution
- Modify upgrade.sh with --api-mode flag (progress.json + result.json)
- Add API upgrade module (service + routes, SUPER_ADMIN only)
- Add System tab to Settings page with version info, changelog,
progress steps, and upgrade confirmation modal
- Add upgrade watcher installation to config.sh wizard
- Add data/upgrade/ shared volume to api service in docker-compose
Bunker Admin
Two issues occurred during upgrades:
1. Gancio config.json lost when Docker volume name prefix changes
(e.g., changemakerlite_ vs changemaker-lite_). Gancio finds existing
DB but no config and enters restart loop. Fix: verify_gancio_config()
checks the volume and regenerates config.json from .env if missing.
2. mkdocs-site-server (LSIO nginx) returns 403 after upgrade because
the anonymous /config volume shadows the ./mkdocs/site bind mount.
Fix: docker compose rm -sf the LSIO container before up -d so the
anonymous volume is recreated fresh.
Also adds Gancio and MkDocs site health checks to Phase 6 verification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>