Repository navigation
Expand file tree
/
Copy pathrender.yaml
More file actions
203 lines (203 loc) · 9.59 KB
/
Copy pathrender.yaml
File metadata and controls
203 lines (203 loc) · 9.59 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
# =============================================================================
# RETIRED — ROLLBACK HOST ONLY.
#
# Production runs on the Oracle A1 VM (Docker + Caddy) and deploys via the
# host's auto-deploy timer; see DEPLOY.md and deploy/oracle/README.md. This
# Blueprint is kept functional solely so the suspended Render service
# `api-usage-monitor` can be revived in a deliberate, owner-directed rollback.
# The Oracle deploy gate verifies on every deploy that the live service stays
# user-suspended, auto-deploy stays disabled, and USAGE_SCHEDULER_ENABLED
# stays "false" — do not resume the service or re-enable auto-deploy outside
# a rollback, and never point DNS back at its stale database without
# restoring the latest verified Garage lineage first. Historical runbook:
# deploy/render/RETIRED-rollback.md.
# =============================================================================
#
# Custom domain: usage.jays.services
# DNS is managed by Cloudflare (CNAME to api-usage-monitor.onrender.com, proxied)
# SSL: Cloudflare "Full (strict)" mode + Render's own cert
#
# This app runs as a single Render web service - no separate database or cron
# resource - to minimize hosting cost. Data lives in a SQLite file on a Render
# disk, and the periodic usage-polling job that used to be a separate cron
# service now runs in-process inside the web service (see
# src/instrumentation.ts / src/lib/usage-recorder.ts).
services:
- type: web
name: api-usage-monitor
runtime: node
plan: starter
autoDeployTrigger: checksPass
# Render uses this as a liveness/restart signal. Keep it independent of
# SQLite readiness: restarting during an exclusive maintenance operation
# restarts that same operation and can livelock the only instance.
# /api/ready keeps strict dependency diagnostics in its JSON body but is
# temporarily HTTP-liveness-safe because Render's existing service config
# can lag this Blueprint field and continue probing that endpoint.
healthCheckPath: /api/health
buildFilter:
ignoredPaths:
- docs/**
- "**/*.md"
disk:
name: api-usage-monitor-data
mountPath: /data
sizeGB: 1
# Render only mounts a service's persistent disk at RUNTIME, not during the
# build step (the build environment's filesystem is read-only at /data) -
# confirmed empirically: `prisma db push` in buildCommand failed with
# "Read-only file system" trying to create /data/prod.db. So the schema
# push has to run in startCommand instead, once the disk is actually
# mounted, before `npm start` boots the server.
#
# migrate-safe.mjs runs at startup and:
# - Creates the DB from scratch if it doesn't exist (first deploy)
# - Applies additive-only changes (new tables, new columns) safely
# - Refuses to apply destructive changes (removed tables/columns,
# table recreation) and prints manual-migration instructions
# This replaces the previous `prisma db push --accept-data-loss` which
# could silently drop and recreate tables on every deploy.
#
# fetch-litestream.sh downloads a pinned Litestream binary into ./bin at
# build time (idempotent; cheap no-op if the disk-backed build cache
# already has the right version). start-with-litestream.sh decides at
# runtime whether to actually replicate, based on the LITESTREAM_S3_*
# envVars below: unset (the default) -> take a bounded verified local
# pre-migration backup, migrate, and start without replication; the four
# required vars set (BUCKET/ENDPOINT/ACCESS_KEY_ID/SECRET_ACCESS_KEY;
# REGION is optional) -> restore if needed, take the same local backup,
# migrate, then continuously replicate /data/prod.db to Cloudflare R2 via
# litestream.yml. See docs/litestream.md.
buildCommand: npm ci && npm run build && bash scripts/fetch-litestream.sh
startCommand: bash scripts/start-with-litestream.sh
envVars:
# Keep local development, CI, and Render on the same supported runtime.
# This value has higher precedence than .node-version on Render.
- key: NODE_VERSION
value: 24.14.1
- key: NODE_OPTIONS
value: --max-old-space-size=350
- key: DATABASE_URL
value: file:/data/prod.db
# Keep the newest verified transaction-consistent snapshots beside the
# SQLite DB. Startup creates one before every migrate-safe run and fails
# closed for an existing DB if backup or integrity verification fails.
- key: SQLITE_PRE_MIGRATION_BACKUP_RETENTION
value: "1"
- key: ENCRYPTION_KEY
generateValue: true
- key: CRON_SECRET
generateValue: true
- key: USAGE_INGEST_TOKEN
generateValue: true
- key: BILLING_RECEIPT_INGEST_TOKEN
sync: false
- key: BILLING_RECEIPT_IDENTITY_KEY
sync: false
- key: BILLING_RECEIPT_HMAC_KEY
sync: false
# Optional read-only view of the separate Cloudflare Email Routing/R2
# receipt quarantine. The Worker never receives the billing receipt
# ingest/signing credentials and cannot create money events.
- key: RECEIPT_INBOX_READ_TOKEN
sync: false
# GET /api/budget-status authenticates with USAGE_READ_TOKEN, falling back
# to USAGE_INGEST_TOKEN when unset (see src/app/api/budget-status/route.ts).
# We intentionally do NOT provision a distinct USAGE_READ_TOKEN here: prod
# read access reuses the ingest token, so sibling apps polling spend use the
# value they already hold. To hand out read-only access without ingest
# rights, add a distinct USAGE_READ_TOKEN in the Render dashboard.
- key: DASHBOARD_PASSWORD
generateValue: true
- key: SESSION_SECRET
generateValue: true
# Optional Infisical provider-credential rotation. Project/path defaults
# are verified in code, so production only needs the three Universal Auth
# ID/secret pairs. Render never generates or copies these values.
- key: INFISICAL_PROVIDER_SYNC_ENABLED
value: "true"
# Default-off one-time create-only bootstrap for the exact current ST
# Gemini provider. Enable only for the reviewed migration run, then set
# it back to false immediately; this is not a reverse-sync switch.
- key: INFISICAL_ST_GEMINI_BOOTSTRAP_ENABLED
value: "false"
# Optional exact UUID for the reviewed legacy Cloudflare Subscription.
# Unset is disabled; Render never generates or discovers this identity.
- key: CLOUDFLARE_LEGACY_HANDOFF_SUBSCRIPTION_ID
sync: false
- key: INFISICAL_BASE_URL
value: https://app.infisical.com
- key: INFISICAL_ENV
value: prod
- key: INFISICAL_ST_CLIENT_ID
sync: false
- key: INFISICAL_ST_CLIENT_SECRET
sync: false
- key: INFISICAL_CT_CLIENT_ID
sync: false
- key: INFISICAL_CT_CLIENT_SECRET
sync: false
- key: INFISICAL_SHARED_CLIENT_ID
sync: false
- key: INFISICAL_SHARED_CLIENT_SECRET
sync: false
# Optional: enables the read-only Sentry Health dashboard card
# (GET /api/sentry-health). `sync: false` means Render won't generate
# or sync a value - set these manually in the dashboard's Environment
# tab if/when wanted; the card renders nothing while they're unset.
- key: SENTRY_READ_TOKEN
sync: false
- key: SENTRY_ORG
sync: false
# Optional: outbound provider-alert delivery. Leave unset for dashboard-only alerts.
- key: ALERT_SLACK_WEBHOOK_URL
sync: false
- key: ALERT_WEBHOOK_URL
sync: false
- key: ALERT_RESEND_API_KEY
sync: false
- key: ALERT_EMAIL_FROM
sync: false
- key: ALERT_EMAIL_TO
sync: false
- key: ALERT_PAGERDUTY_ROUTING_KEY
sync: false
- key: ALERT_MIN_SEVERITY
value: warning
- key: ALERT_REMINDER_HOURS
value: "24"
# Raw-row retention before daily rollup + pruning. External usage rows are
# never pruned from the current UTC month because budget math reads them.
- key: USAGE_SNAPSHOT_RAW_RETENTION_DAYS
value: "45"
- key: EXTERNAL_USAGE_EVENT_RAW_RETENTION_DAYS
value: "90"
- key: EXTERNAL_USAGE_EVENT_TOMBSTONE_RETENTION_DAYS
value: "180"
# Optional: continuous Litestream replication of /data/prod.db to an
# S3-compatible bucket (Cloudflare R2), for backup/durability beyond
# the Render disk itself. The four of BUCKET/ENDPOINT/ACCESS_KEY_ID/
# SECRET_ACCESS_KEY must all be set to enable replication; REGION is
# optional (an empty region is fine for R2, which accepts any region
# for SigV4). `sync: false` means Render won't generate or sync values
# - set these manually in the dashboard's Environment tab if/when
# wanted. Leave the four required unset (the default) and
# start-with-litestream.sh runs the server exactly as it does today,
# with no replication and no litestream process. See docs/litestream.md
# for setup + restore runbook.
- key: LITESTREAM_S3_BUCKET
sync: false
- key: LITESTREAM_S3_REGION
sync: false
- key: LITESTREAM_S3_ENDPOINT
sync: false
- key: LITESTREAM_S3_ACCESS_KEY_ID
sync: false
- key: LITESTREAM_S3_SECRET_ACCESS_KEY
sync: false
# Set to true after the replica credentials are provisioned and a restore
# drill succeeds. Partial/configured-but-unavailable replication already
# fails closed regardless of this flag; true additionally forbids an
# entirely unconfigured backup path.
- key: LITESTREAM_REQUIRED
sync: false