-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender.yaml
More file actions
273 lines (265 loc) · 14.3 KB
/
Copy pathrender.yaml
File metadata and controls
273 lines (265 loc) · 14.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# render.yaml — Render Blueprint for the dash-leaflet2 documentation site.
# Deploys run.py as a Docker web service, fronted by https://leaflet.2plot.dev.
# Blueprint spec: https://render.com/docs/blueprint-spec
#
# How to deploy:
# 1. Push this repo (with Dockerfile + render.yaml) to GitHub.
# 2. Render dashboard → New → Blueprint → select this repo.
# 3. Render builds the Dockerfile and serves run:server.
# 4. Add the custom domain leaflet.2plot.dev and point its CNAME at the
# service. The Clerk satellite domain below must match it exactly.
#
# Local equivalent (no Render needed):
# docker build -t dash-leaflet2-docs .
# docker run --rm -p 8050:8050 -e PORT=8050 dash-leaflet2-docs
# open http://localhost:8050
services:
- type: web
name: dash-leaflet2-docs
runtime: docker
dockerfilePath: ./Dockerfile
dockerContext: .
# Starter (always-on): the free tier sleeps after ~15 min idle, cold-starts
# on the next request, and its evictions swallow unreported analytics. The
# dashboard plan was upgraded 2026-08-16; this file matching it is what
# keeps a future blueprint sync from silently downgrading the service.
plan: starter
# /healthz is the 2plot network convention (lib/health.py) and is excluded
# from traffic counts, so it makes a cleaner check than /.
healthCheckPath: /healthz
autoDeploy: true
envVars:
# --- Core --------------------------------------------------------
# Flask (WSGI) backend, served by gunicorn. Do NOT set fastapi/quart
# here: those are ASGI and would need uvicorn instead of gunicorn.
- key: DASH_BACKEND
value: flask
# gunicorn worker processes. 2 is a reasonable default; drop to 1 if the
# free tier's 512 MB feels tight. The Dockerfile also runs 4 threads per
# worker for callback concurrency.
- key: WEB_CONCURRENCY
value: "2"
# Public origin for canonical URLs, sitemap.xml and llms.txt.
#
# BOTH spellings, deliberately. APP_BASE_URL is the network-standard name
# that the shared scripts/ and tests/ expect; DASH_LEAFLET2_BASE_URL is
# what this service has been running with. lib/constants reads the
# standard one first and falls back to the legacy one, so either alone
# works — but dropping the old name from a LIVE service is how a host
# starts advertising the wrong canonical origin, which deindexes it
# quietly. Keep both until the service is confirmed on the new name.
- key: APP_BASE_URL
value: https://leaflet.2plot.dev
- key: DASH_LEAFLET2_BASE_URL
value: https://leaflet.2plot.dev
# --- 2plot.dev ad network (lib/ad_client.py) ---------------------
# Without AD_SERVER_URL reachable the slot simply stays hidden, and a
# 60s circuit breaker stops retrying, so an outage never slows a page.
- key: AD_SERVER_URL
value: https://2plot.dev
# This app's identity in the ad network's /admin/ad-board tables.
# The hub's directory key, NOT the package name. `canonical_app_id()` on
# 2plot.dev folds the legacy `dash-leaflet2` in at ingest, so the change
# is safe mid-flight — but the hub's directory entry lists `dash-leaflet2`
# under `legacy_ids` precisely "until leaflet's own network-standard pass
# sets AD_APP_ID=leaflet". This is that pass.
- key: AD_APP_ID
value: leaflet
# --- 2plot.ai satellite analytics ---------------------------------
# (lib/analytics_tracker.py + lib/traffic_rollup.py +
# lib/satellite_reporter.py — the trio that replaced the Gen-1
# single-module tracker in the fleet's 1.3.x instrumentation sync.)
#
# The shared HMAC secret every satellite holds. Set it in the Render
# dashboard (same value as on 2plot.ai) — never in this file.
# Without it the app still serves /healthz but reports nothing.
# Hub side, to health-sweep this app hourly, add to 2plot.ai:
# PULSE_POLL_TARGETS=...,leaflet=https://leaflet.2plot.dev/healthz
- key: CROSS_APP_WEBHOOK_SECRET
sync: false
# 15 minutes, not the code default of hourly: the fleet is on paid
# instances now and the hub board reads near-real-time. The live
# "active now" number rides the separate presence beacon
# (SATELLITE_PRESENCE_INTERVAL_S, default 60s, 0 disables) — this knob
# only paces the daily rollup.
- key: SATELLITE_REPORT_INTERVAL_S
value: "900"
# The series name this app reports under on 2plot.ai/traffic. "leaflet"
# is this app's 2plot network-directory key — the hub labels and colours
# the series from that directory. (The Gen-1 spelling SATELLITE_APP_ID
# is retired with the module that read it; the trio reads only this
# name. The dashboard may still carry the old variable — harmlessly,
# nothing reads it now.)
- key: SATELLITE_APP_KEY
value: leaflet
# The hub takes the LAST report for a given (app, date). On an ephemeral
# filesystem a mid-day deploy wipes the ledger and the next hourly report
# overwrites the day's real total with whatever accrued since the
# restart. The disk below is what stops that.
- key: TRAFFIC_ANALYTICS_FILE
value: /var/data/visitor_analytics.json
# Behind Cloudflare the country header is already present, so the
# ip-api.com lookup is redundant latency.
- key: ANALYTICS_GEO_LOOKUP
value: "0"
# --- Corpus document tiers (lib/page_tiers.py; registered in run.py) --
# Access tiers for /llms-small.txt and /llms-full.txt
# (dash-improve-my-llms >= 2.4.0). Deliberately set to the default so
# the knob is visible here: the 402 experiment tightens the full corpus
# by flipping this to `auth`, per satellite, with no code change.
- key: LLMS_SMALL_TIER
value: public
- key: LLMS_FULL_TIER
value: public
# --- 2plot.dev network bulletin (lib/bulletin.py) -----------------
# The hub's announcement feed, rendered in the header of this site's
# llms.txt viewer. Opt-in — unset means the feature is simply off.
#
# NOTE: Render applies blueprint envVars on a BLUEPRINT SYNC, not on an
# autoDeploy from a git push. Adding this line alone leaves the variable
# absent on the service and the panel empty, which is exactly how
# email.2plot.dev shipped wired code and an unwired deployment. Sync the
# blueprint or add it in the dashboard.
- key: NETWORK_BULLETIN_URL
value: https://2plot.dev/api/network/bulletin
# --- Clerk satellite auth (lib/auth.py) --------------------------
# All of these are OPTIONAL: with them absent the site runs fully public
# and /admin/control-board returns a 404 (it fails CLOSED — see
# lib.auth.admin_access_open). Set ALL of the first three to turn auth on.
- key: CLERK_SECRET_KEY
sync: false
- key: CLERK_PUBLISHABLE_KEY
sync: false
# The 2plot.ai PRIMARY on its production Clerk instance. These are the
# Account Portal and Frontend API custom domains, not the *.accounts.dev
# hosts — those belong to the dev instance and cannot host satellites.
- key: CLERK_SIGN_IN_URL
value: https://accounts.2plot.ai/sign-in
- key: CLERK_SIGN_UP_URL
value: https://accounts.2plot.ai/sign-up
# THE RETURN TRIP. Without this, sign-in reaches the primary and the
# user never comes back — the exact failure observed on this host
# 2026-08-20.
#
# Two different paths exist, and only one of them is ours:
#
# SET (this): the Sign In button NAVIGATES to
# https://2plot.ai/onboarding?returnTo=<this page>.
# That is a page on the hub's own Dash app, which
# injects a script that validates returnTo against
# lib.auth.allowed_redirect_origins() (leaflet.2plot.dev
# is in that list) and then force-redirects home. The
# network owns and can debug every hop.
#
# UNSET: dash-clerk-auth falls back to
# Clerk.redirectToSignIn(), which goes to
# CLERK_SIGN_IN_URL — accounts.2plot.ai, Clerk's HOSTED
# Account Portal. The hub's Dash app is never in the
# loop, so none of its returnTo machinery runs, and the
# return depends entirely on the Clerk dashboard's own
# allowed-redirect list. When that does not name this
# host, ClerkJS silently drops signInForceRedirectUrl
# and uses the portal's default after-sign-in URL: the
# user authenticates successfully and lands on 2plot.ai.
# No error, client-side or server-side.
#
# dash-clerk-auth reads this name from the environment itself
# (satellite_sign_in_redirect's env fallback), so lib/auth.py does not
# pass it through. `assets/auth_gate.js` appends &mode=signup for the
# "Create free account" button, which /onboarding reads to open the
# sign-UP modal instead — so this one value serves both buttons.
- key: CLERK_SATELLITE_SIGN_IN_REDIRECT
value: https://2plot.ai/onboarding
# REQUIRED in satellite mode. A production custom-domain instance cannot
# derive this from CLERK_SIGN_IN_URL — it must be set explicitly.
- key: CLERK_FRONTEND_API
value: https://clerk.2plot.ai
# The REGISTERED SATELLITE DOMAIN — deliberately NOT the host this app is
# served on. clerk-js derives the Frontend API from this value as
# `clerk.<domain>`, and Clerk only issues those DNS records per satellite
# domain. `clerk.2plot.dev` exists (CNAME -> frontend-api.clerk.services);
# `clerk.leaflet.2plot.dev` is NXDOMAIN, because leaflet.2plot.dev is an
# ALLOWED SUBDOMAIN of the 2plot.dev satellite rather than a satellite of
# its own. Setting the served host here sends the browser to a Frontend
# API that does not resolve, and sign-in hangs on
# /v1/client/sync?redirect_url=...
#
# Session cookies are scoped to .2plot.dev, so leaflet.2plot.dev reads
# them as a subdomain — which is exactly what the allowlist enables.
- key: CLERK_SATELLITE_DOMAIN
value: 2plot.dev
- key: CLERK_IS_SATELLITE
value: "true"
# Signs the session + __dca_identity cookies. Without it dash-clerk-auth
# falls back to a PUBLIC dev default string.
- key: SESSION_SECRET
generateValue: true
# Allowlist for /admin/control-board. The OWNER_EMAIL in lib/auth.py
# always counts, so this is for additional administrators.
- key: ADMIN_EMAILS
sync: false
# --- The interactive gate (lib/access.py) ------------------------
# Baseline tier for pages whose frontmatter does not set one:
# public | auth | admin | hidden.
#
# THIS IS THE FLIP. The gate ships dark — the code is live and every
# verdict answers `allow` while this reads `public`. Changing it to
# `auth` here, in the dashboard, gates every documentation page behind
# a sign-in card on the next request. No redeploy, and the rollback is
# the same edit in reverse (see DEPLOYMENT.md's rollback rehearsal).
#
# `/`, `/llms-small.txt` and `/llms-full.txt` are pinned public in
# run.py and are NOT moved by this — the funnel's front door and the
# corpus documents are deliberate settings, never an ambient default.
#
# PAGE_DEFAULT_TIER is the network-standard name and the canonical one.
# PAGE_DEFAULT_VISIBILITY is this service's older spelling of the SAME
# knob and is still read (lib/page_tiers._DEFAULT_TIER_ENV) — kept
# because dropping a variable a running service depends on is how a
# host changes posture between a push and the next blueprint sync. Set
# ONE of them; if both are set the canonical name wins.
- key: PAGE_DEFAULT_TIER
value: public
- key: PAGE_DEFAULT_VISIBILITY
value: public
# The SECOND axis, deliberately UNSET for the 30-day window.
#
# `tier` says who may use a page in a browser; this says whether its
# machine twin — /<page>/llms.txt, the crawler document, the prerender
# — stays open anyway. Unset means open, which is the window posture:
# humans meet the sign-in card while the crawl-demand dataset keeps
# accruing. The phase-4 agent flip is adding this as `0`, once, which
# closes every page that did not pin the axis in frontmatter.
#
# Do not add it before then. A page can still be closed individually
# from the control board or with `llms_public: false` in frontmatter,
# which is how the pilot's canary leak check is run.
# - key: LLMS_PUBLIC_DEFAULT
# value: "0"
#
# Control-board overrides are written here — on the analytics disk, so
# they OUTLIVE a deploy (they used to live on the ephemeral container
# filesystem and reset with every one). An override written here beats
# both the frontmatter tier and PAGE_DEFAULT_TIER; only the hub's
# ceiling outranks it.
- key: PAGE_VISIBILITY_FILE
value: /var/data/page_visibility.json
# The hub's page-tier ceiling and the ?key= agent lane both need this
# (lib/hub_client.py). It is already set above for the traffic
# reporter — the same secret authenticates this satellite to
# /api/page-tiers and /api/agent-key/*. Without it the gate still
# works locally; only the network ceiling and agent keys go dark.
# --- Optional ----------------------------------------------------
# MUI X Pro licence for the TreeViewPro tile browser on
# /tile-layers-pro. Absent → that one control renders watermarked; the
# rest of the page is unaffected.
- key: MUI_PRO_API_KEY
sync: false
# Persistent storage for the analytics ledger (and the control board's
# visibility overrides). The hub keeps only the LAST report per
# (app, date), so a ledger that dies with the container silently
# under-reports every deploy day.
disk:
name: analytics
mountPath: /var/data
sizeGB: 1