"""Local source patches, applied to /code at container start. Called from compose/start, compose/worker and compose/temporal-django-worker before the app boots, so every recreated container comes back patched. (The old approach patched running containers by hand and silently vanished on the next `docker compose up`.) Both patches exist because PostHog no longer sells self-hosted licenses, so the license branches below are unreachable here. Both are gated on MULTI_ORG_ENABLED (set in compose.override.yml), so unsetting it restores upstream behaviour without un-patching. Only posthog/ files are touched, which is MIT-licensed; ee/ is not involved. Idempotent. If upstream changes shape, this warns loudly and lets the app start unpatched rather than taking PostHog down. Check with: docker compose logs web worker | grep LOCAL-PATCH """ import sys PATCHES = [ ( # Shows the "New project" / "New organization" buttons. Upstream gates # them on preflight's can_create_org, which demands a license. "can_create_org", "/code/posthog/utils.py", """ if settings.MULTI_ORG_ENABLED: license = get_cached_instance_license() if license is not None and AvailableFeature.ZAPIER in license.available_features: return True else: logger.warning("You have configured MULTI_ORG_ENABLED, but not the required premium PostHog plan!") """, """ if settings.MULTI_ORG_ENABLED: # Local patch, see compose/local-patches.py return True """, ), ( # Lifts the 1-project-per-org limit in the project API. Without a # license, every org save and the periodic feature sync reset the org's # features to [], and an org without `organizations_projects` may only # have one project. With it and no `limit`, the cap is upstream's # MAX_ALLOWED_PROJECTS_PER_ORG (2000). "organizations_projects", "/code/posthog/models/organization.py", """ return self.available_product_features def get_available_feature(""", """ # Local patch, see compose/local-patches.py if settings.MULTI_ORG_ENABLED and not self.get_available_feature("organizations_projects"): self.available_product_features = [ *self.available_product_features, {"key": "organizations_projects", "name": "Organizations projects"}, ] return self.available_product_features def get_available_feature(""", ), ] for name, path, old, new in PATCHES: src = open(path).read() if new in src: status = "already applied" elif src.count(old) == 1: open(path, "w").write(src.replace(old, new)) status = "applied" else: status = "PATTERN NOT FOUND, upstream changed, running UNPATCHED" print(f"LOCAL-PATCH {name}: {status}", file=sys.stderr, flush=True)