Provision the native MCP server surface: create the endpoint, its owner
and its workspace, probe it on the internal origin, and refuse an
endpoint that never serves the stream. Baserow addresses a session
through the URL path, so meta/services.yml declares the endpoint key and
suffix the discovery layer composes the client URL from.
Split the role into a run-once guarded core. Registering the MCP surface
makes baserow a shared provider, and the variant-aware loader checks the
run-once flag before loading; without the flag as the first task a second
load request can include the role again.
Address the container by its hosting node. Migration and bootstrap ran
`container exec` on the manager while the replica sits on a worker, so
the resolved id existed nowhere the command could reach it. Re-resolve
after the post-migration restart as well: `service update --force`
replaces the container, and the bootstrap was still holding the id of the
one it replaced.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New roles/sys-svc-container/templates/env.j2 sets TZ={{ HOST_TIMEZONE }} and LANG=C.UTF-8. Every role env.j2 includes it at the top; it renders via stack_host_template, the same engine that resolves the includes in compose.yml.j2, so it works in both compose and swarm. TZ/LANG are removed from the individual role env.j2 so each value stays single-source.
New lint tests/lint/ansible/test_env_base_include.py verifies every role env.j2 includes the base and never re-sets a base-owned key (keys derived from the base file).
mattermost and gitea meta/volumes.yml drop the /etc/timezone host-path bind: swarm hard-rejects a task whose bind source is missing on the node (Debian-trixie nodes have no /etc/timezone). TZ now comes from the base env. /etc/localtime bind also dropped; the container timezone follows TZ.
Non-directive comments were removed from the touched env.j2 (banners, restatements, reference URLs, narration) so the changed files pass the comment-lint; only tool directives remain.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The all-in-one image runs supervisord with the startup migrate and celery-beat concurrently. With MIGRATE_ON_STARTUP defaulting true, the image's startup migrate races celery-beat (which crash-loops on the un-migrated schema) and deadlocks on the locked_migrate advisory lock; the schema never completes and the external locked_migrate task then blocks forever on that same lock. Set MIGRATE_ON_STARTUP=false so the external task is the sole migrator, and add timeout: 600 to the migrate and bootstrap exec tasks so a stuck exec fails cleanly instead of hanging (retries stay 60; timeout bounds the whole task).
Verified in dev container (deploy-4): locked_migrate now completes in ~6s (was a 30min+ hang); deploy reached ok=1879 end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Baserow's native SSO is Enterprise-only, so gate the community image with the shared Keycloak oauth2-proxy and patch urls.py (via a custom Dockerfile) with a trusted-header bridge. The bridge provisions/repairs a local Baserow user from the oauth2-proxy identity headers and mints native refresh/access/user_session tokens, mirroring Baserow's own SSO redirect contract (?token=&user_session=).
proxy.conf.j2 performs the nginx handoff: an authenticated browser without a jwt_token cookie is 302'd to /api/infinito/sso/login/, which redirects back to /?token=&user_session= for the frontend middleware to consume. bootstrap_admin marks the superuser email_verified + completed_onboarding to skip onboarding.
Replace the LDAP service/variant with proxy-gating and rewrite the seaweedfs + persona Playwright tests to drive Baserow through its API using the SSO-minted JWT.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch penpot, nextcloud, gitea, mattermost, openwebui, funkwhale, baserow, openproject, pixelfed, mastodon, bookwyrm, listmonk, peertube and opentalk to the engine-agnostic objstore lookup: the minio service entries are pinned off, a seaweedfs service (gated on the provider being in group_names, public for the media apps) replaces them, and the matrix variants pin the new flag. OpenTalk keeps object storage mandatory through an embedded fallback and provisions its bucket via weed shell.
Add objstore lookup SPOT keys consumed by these roles: endpoint (host:port), public_domain, public_bucket (public S3 host plus bucket) and public_url (scheme plus public_bucket, scheme derived from the provider TLS state). This removes the repeated lookup('config', lookup('objstore', ...), 'server.domains.canonical.api') / host:port concatenations from the consumer templates. Listmonk settings render their JSON values via the to_json filter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In CI run 26005522256, baserow Round 2 (variant 1, fresh sidecar
postgres) responded with status 500 + "connect ECONNREFUSED
127.0.0.1:8000" from baserow's internal Caddy. Container was up,
sidecar postgres + redis were Healthy, migrations succeeded (auth_user
existed, bootstrap admin worked), but gunicorn never came online on
:8000.
Cross-referenced baserow's official docs and the all-in-one image's
startup scripts:
- deploy/all-in-one/baserow.sh + supervisor/start.sh: embedded-postgres
trigger is DATABASE_HOST==localhost AND DATABASE_URL empty. Our
DATABASE_HOST=database avoids that, but DISABLE_EMBEDDED_PSQL/REDIS
removes the path-of-least-resistance fallback inside supervisor.
- backend/docker/docker-entrypoint.sh: wait_for_postgres tries
BASEROW_POSTGRES_STARTUP_CHECK_ATTEMPTS×2s before giving up (default
5×2s = 10s, too tight for a fresh sidecar's first migration round).
- supervisor.conf: [program:backend] has startsecs=30, startretries=3.
A long template-import pass (SYNC_TEMPLATES_ON_STARTUP=true is the
default) after migrations can starve the gunicorn bind on :8000 and
leave Caddy returning ECONNREFUSED.
Changes:
- vars/main.yml: introduce BASEROW_DATABASE_USER/NAME/HOST/PORT/PASSWORD
+ BASEROW_DATABASE_URL and BASEROW_REDIS_HOST/PORT/URL as single
SPOTs derived from lookup('database', ...). env.j2 renders all of
them from these vars instead of duplicating the lookup calls.
- templates/env.j2: emit discrete DATABASE_* AND DATABASE_URL together
(docs note URL completely overrides discrete, but the all-in-one
startup script also reads DATABASE_HOST directly to decide on
embedded mode, so both must be present). Same shape for REDIS_*.
- templates/env.j2: add DISABLE_EMBEDDED_PSQL=yes, DISABLE_EMBEDDED_
REDIS=yes, SYNC_TEMPLATES_ON_STARTUP=false, BASEROW_TRIGGER_SYNC_
TEMPLATES_AFTER_MIGRATION=false, BASEROW_DISABLE_PLUGIN_INSTALL_ON_
STARTUP=yes, BASEROW_POSTGRES_STARTUP_CHECK_ATTEMPTS=30.
Verified each name and semantics against
baserow/docs/installation/configuration.md and the actual source code
in baserow/deploy/all-in-one/. Notable: the docs name the variable
POSTGRES_STARTUP_CHECK_ATTEMPTS at line 108 without the BASEROW_
prefix, but the source uses BASEROW_POSTGRES_STARTUP_CHECK_ATTEMPTS —
source wins.
Caveat: EMAIL_SMTP is intentionally NOT emitted in the disabled
branch. The docs (line 335) state any non-empty value enables SMTP,
so an "EMAIL_SMTP=FALSE" defensive default would have inverted the
intent. Leaving the variable unset is the documented way to fall
back to console-only email logging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves the SYSTEM_EMAIL_* resolution logic out of group_vars/all/03_email.yml
into a dedicated Ansible lookup plugin, so every role template reads outgoing
mail settings via lookup('email') (global) or lookup('email', application_id)
(per-app). Per-app compose.services.email is merged on top of the resolved
defaults; SYSTEM_EMAIL_* variables remain usable as an inventory escape hatch.
Also splits the email docs: administrator-facing configuration stays under
docs/administration/configuration/, plugin contract and extension rules move
to docs/contributing/design/services/email.md. services.md is relocated to
services/base.md so the services/ subtree can host per-service design pages.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces inline `applications`/`users` merging with dedicated lookup
plugins and runtime hydration. Introduces `add_csp_hash` filter,
integrates sort into `find_dock_val_by_bkp_entr`, splits the Keycloak
one-shot realm import into `05a_realm_import_run.yml`, and fixes the
Mastodon administrator task YAML. Removes obsolete `csp_hashes` filter,
`sys-token-store` Jinja filter, and empty CLI setup modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add optional idempotent bootstrap of Django superuser via script module
- execute admin bootstrap through docker compose exec with env injection
- avoid shell/quoting issues by passing credentials via environment
- introduce BASEROW_SECRET_KEY and wire it into container env
- ensure container recreation when DB credentials change
- document admin bootstrap and SECRET_KEY requirements in README
https://chatgpt.com/share/6975538c-a6d0-800f-b2ea-fbab963ef142
- Replace DEFAULT_SYSTEM_EMAIL merge with flat SYSTEM_EMAIL_* variables
- Update templates across apps to new variable names
- Fix msmtp health check to use -t and local username fallback
- Improve postfix localhost config (localdomain, mydestination, timeouts)
- Extend sys-hostname to ensure /etc/hosts mapping once
https://chatgpt.com/share/695e8f39-0694-800f-b91c-a74f4b78d7c8
- Migrate Mailu credentials from legacy mailu_token to tokens['web-app-mailu']
- Update all affected application templates to use the new token structure
- Adjust inventory validation to no longer special-case mailu_token
- Minor typing and formatting cleanups in inventory creation and tests
https://chatgpt.com/share/69554915-87c8-800f-afd5-12118dbb1107