create-meith 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +109 -97
- package/package.json +1 -1
- package/src/bin.ts +1 -1
- package/src/scaffold.ts +108 -97
package/dist/bin.mjs
CHANGED
|
@@ -176,25 +176,24 @@ function vercelEnvExample(name) {
|
|
|
176
176
|
# no other instance can read and that is discarded with the instance \u2014 on Vercel
|
|
177
177
|
# the board refuses it outright rather than losing uploads quietly.
|
|
178
178
|
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
179
|
+
# The deploy form asks for none of them. On Vercel the board works each one out
|
|
180
|
+
# from what the linked stores publish: a DATABASE_URL means postgres for the
|
|
181
|
+
# data source and the queue, a Redis connection string means CACHE_DRIVER=redis,
|
|
182
|
+
# a Blob store's read-write token means FILESTORE_DRIVER=blob, and a
|
|
183
|
+
# RESEND_API_KEY with a MAIL_FROM beside it means mail over the provider's HTTPS
|
|
184
|
+
# API. Setting one here overrides the derivation, which is what this file is for
|
|
185
|
+
# when you copy it to .env.local.
|
|
184
186
|
#
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
187
|
+
# Every one of those derivations is scoped to Vercel, and each fires only from a
|
|
188
|
+
# value that is unambiguously the thing itself \u2014 a redis:// or rediss:// URL, a
|
|
189
|
+
# read-write token. A board you run anywhere else is untouched by all of it and
|
|
190
|
+
# still takes these values from this file, exactly as it did before.
|
|
189
191
|
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
# serves whatever that instance last saw, for up to a minute, however many
|
|
196
|
-
# instances there are. That one degrades quietly, which is exactly why it is
|
|
197
|
-
# worth typing rather than leaving to a default.
|
|
192
|
+
# A derivation that cannot resolve is a configuration error, not an invitation
|
|
193
|
+
# to pick something safe-looking. On Vercel, with the cache or the object store
|
|
194
|
+
# missing, the board refuses to boot and names every variable it looked at. It
|
|
195
|
+
# will not quietly cache inside the instance, and it will not quietly write
|
|
196
|
+
# uploads to a disk that is about to disappear.
|
|
198
197
|
DATA_SOURCE=postgres
|
|
199
198
|
QUEUE_DRIVER=postgres
|
|
200
199
|
CACHE_DRIVER=redis
|
|
@@ -208,16 +207,20 @@ DATABASE_URL=
|
|
|
208
207
|
|
|
209
208
|
${ENV_DIRECT_DATABASE_URL_PROSE}
|
|
210
209
|
#
|
|
211
|
-
# On Vercel this is not optional
|
|
212
|
-
# build runs \`community migrate\` against it, and
|
|
213
|
-
# those two session locks on first run.
|
|
214
|
-
#
|
|
210
|
+
# On Vercel this is not optional, and it is no longer yours to copy. DATABASE_URL
|
|
211
|
+
# here is the pooler string, the build runs \`community migrate\` against it, and
|
|
212
|
+
# /install takes the second of those two session locks on first run. Left blank,
|
|
213
|
+
# the board reads Neon's own direct string \u2014 \`DATABASE_URL_UNPOOLED\` first, then
|
|
214
|
+
# \`POSTGRES_URL_NON_POOLING\` \u2014 and refuses to boot if neither is there, naming
|
|
215
|
+
# both. Never \`POSTGRES_URL\`: that one is pooled.
|
|
215
216
|
DIRECT_DATABASE_URL=
|
|
216
217
|
|
|
217
218
|
# The shared cache \u2014 a Redis or Valkey endpoint, \`rediss://\` for TLS. Redis
|
|
218
219
|
# holds cache entries and nothing else: losing it costs the board a warm cache,
|
|
219
|
-
# not data, and signs nobody out.
|
|
220
|
-
#
|
|
220
|
+
# not data, and signs nobody out. Left blank on Vercel, the board reads the
|
|
221
|
+
# Upstash store's own \`KV_URL\`, which is the one variable it publishes that
|
|
222
|
+
# speaks the Redis protocol \u2014 \`KV_REST_API_URL\` is an HTTPS endpoint and is
|
|
223
|
+
# never used for this. A name we do not know goes here by hand.
|
|
221
224
|
REDIS_URL=
|
|
222
225
|
|
|
223
226
|
${ENV_AUTH_SECRET_PROSE}
|
|
@@ -233,15 +236,26 @@ ${ENV_TICK_SECRET_PROSE}
|
|
|
233
236
|
CRON_SECRET=
|
|
234
237
|
# TICK_SECRET=
|
|
235
238
|
|
|
236
|
-
# Uploads, in the Vercel Blob store the Deploy Button provisions.
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
239
|
+
# Uploads, in the Vercel Blob store the Deploy Button provisions. A store
|
|
240
|
+
# attached to the project publishes BLOB_STORE_ID and nothing else \u2014 no token \u2014
|
|
241
|
+
# because the SDK authenticates with the deployment's own OIDC identity: the
|
|
242
|
+
# board hands it the store id and lets it fetch the credential. There is nothing
|
|
243
|
+
# to type and nothing to mistype, and FILESTORE_DRIVER=blob derives from this
|
|
244
|
+
# variable being present.
|
|
242
245
|
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
246
|
+
# BLOB_READ_WRITE_TOKEN is the other way in, and you make it yourself on the
|
|
247
|
+
# store. Set it when something has to reach the store from OUTSIDE a Vercel
|
|
248
|
+
# deployment \u2014 \`community backup\` run on your own machine is the case that
|
|
249
|
+
# matters \u2014 because there is no OIDC identity there to borrow. Set both and the
|
|
250
|
+
# board prefers the store id, unless the token names a different store, in which
|
|
251
|
+
# case the token wins: naming another store is a deliberate act.
|
|
252
|
+
#
|
|
253
|
+
# Every object is written with private access: an object URL is not a public
|
|
254
|
+
# link, and member content is served by the board, which is where permissions
|
|
255
|
+
# are checked. An upload is held whole in the instance's memory on the way in
|
|
256
|
+
# and on the way out, so the function's memory limit, not the store, is what
|
|
257
|
+
# caps a file.
|
|
258
|
+
BLOB_STORE_ID=
|
|
245
259
|
BLOB_READ_WRITE_TOKEN=
|
|
246
260
|
|
|
247
261
|
# Uploads in an S3-compatible bucket instead \u2014 AWS, R2, MinIO, Spaces. This is
|
|
@@ -264,10 +278,10 @@ BLOB_READ_WRITE_TOKEN=
|
|
|
264
278
|
# board refuses it on Vercel; 587 with STARTTLS may work, but an API does not
|
|
265
279
|
# depend on the platform's egress rules staying as they are.
|
|
266
280
|
#
|
|
267
|
-
# MAIL_FROM is yours to decide and
|
|
268
|
-
# must be an address at a domain the provider has
|
|
269
|
-
# default is right
|
|
270
|
-
#
|
|
281
|
+
# MAIL_FROM is yours to decide, and it belongs to the step that adds Resend
|
|
282
|
+
# rather than to the deploy: it must be an address at a domain the provider has
|
|
283
|
+
# verified for you, no default is right, and until a provider is added the board
|
|
284
|
+
# cannot send from any address at all. Set it alongside the integration.
|
|
271
285
|
MAIL_FROM=
|
|
272
286
|
|
|
273
287
|
# Add the Resend integration to the project from Vercel's marketplace and it
|
|
@@ -308,21 +322,14 @@ var SELF_HOST_DEPLOY_KIT = [
|
|
|
308
322
|
"docker-entrypoint.sh",
|
|
309
323
|
"docker-healthcheck.sh"
|
|
310
324
|
];
|
|
311
|
-
var VERCEL_TYPED_DRIVERS = ["CACHE_DRIVER=redis", "FILESTORE_DRIVER=blob"];
|
|
312
325
|
var VERCEL_DERIVED_DRIVERS = [
|
|
313
326
|
"DATA_SOURCE=postgres",
|
|
314
327
|
"QUEUE_DRIVER=postgres",
|
|
328
|
+
"CACHE_DRIVER=redis",
|
|
329
|
+
"FILESTORE_DRIVER=blob",
|
|
315
330
|
"MAIL_DRIVER=http"
|
|
316
331
|
];
|
|
317
|
-
var VERCEL_PROMPTED_ENV = [
|
|
318
|
-
"CACHE_DRIVER",
|
|
319
|
-
"FILESTORE_DRIVER",
|
|
320
|
-
"DIRECT_DATABASE_URL",
|
|
321
|
-
"REDIS_URL",
|
|
322
|
-
"AUTH_SECRET",
|
|
323
|
-
"CRON_SECRET",
|
|
324
|
-
"MAIL_FROM"
|
|
325
|
-
];
|
|
332
|
+
var VERCEL_PROMPTED_ENV = ["AUTH_SECRET", "CRON_SECRET"];
|
|
326
333
|
var VERCEL_MARKETPLACE_STORES = [
|
|
327
334
|
{ type: "integration", integrationSlug: "neon", productSlug: "neon", protocol: "storage" },
|
|
328
335
|
{
|
|
@@ -341,7 +348,7 @@ function deployButtonUrl(templateRepositoryUrl) {
|
|
|
341
348
|
["env", VERCEL_PROMPTED_ENV.join(",")],
|
|
342
349
|
[
|
|
343
350
|
"envDescription",
|
|
344
|
-
"Two
|
|
351
|
+
"Two secrets, generated rather than chosen \u2014 32 characters or more each. Everything else the board reads from the database, cache and blob store this form links."
|
|
345
352
|
],
|
|
346
353
|
["envLink", `${templateRepositoryUrl}/blob/main/.env.example`],
|
|
347
354
|
["stores", JSON.stringify(VERCEL_MARKETPLACE_STORES)],
|
|
@@ -992,11 +999,14 @@ A forum, built on [Meith](${repositoryUrl}), running as Vercel functions.
|
|
|
992
999
|
- **A Neon Postgres database**, attached to the project. Neon publishes the
|
|
993
1000
|
pooled connection string as \`DATABASE_URL\` and the direct one as
|
|
994
1001
|
\`DATABASE_URL_UNPOOLED\`.
|
|
995
|
-
- **An Upstash Redis store**, attached the same way, for the shared cache.
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1002
|
+
- **An Upstash Redis store**, attached the same way, for the shared cache. It
|
|
1003
|
+
publishes \`KV_URL\`, which the board reads as \`REDIS_URL\` \u2014 \`KV_REST_API_URL\`
|
|
1004
|
+
beside it is an HTTPS endpoint and is not used for this.
|
|
1005
|
+
- **A Vercel Blob store** for uploads, which publishes \`BLOB_STORE_ID\` into the
|
|
1006
|
+
project by itself. That is the whole credential: the board hands the id to
|
|
1007
|
+
Vercel's SDK, which authenticates with the deployment's own OIDC identity, so
|
|
1008
|
+
there is no token to copy. This is what used to be four hand-typed \`S3_*\`
|
|
1009
|
+
secrets.
|
|
1000
1010
|
- **A Vercel project** carrying \`vercel.json\` \u2014 the build command
|
|
1001
1011
|
\`${VERCEL_BUILD_COMMAND}\`,
|
|
1002
1012
|
which applies the schema before it builds, materializes the board's app at
|
|
@@ -1004,8 +1014,11 @@ A forum, built on [Meith](${repositoryUrl}), running as Vercel functions.
|
|
|
1004
1014
|
entry that drives the tick.
|
|
1005
1015
|
|
|
1006
1016
|
**Mail is the one thing the button does not set up**, and it takes one click
|
|
1007
|
-
after the deploy \u2014 see *Mail, in one click* below.
|
|
1008
|
-
|
|
1017
|
+
after the deploy \u2014 see *Mail, in one click* below. That is also where the
|
|
1018
|
+
address the board sends from goes: it has to be at a domain your provider has
|
|
1019
|
+
verified, which cannot be true of any domain before a provider exists. The
|
|
1020
|
+
board boots and runs without mail, and delivers nothing, silently, until it is
|
|
1021
|
+
done.
|
|
1009
1022
|
|
|
1010
1023
|
## What to type into the deploy form
|
|
1011
1024
|
|
|
@@ -1018,54 +1031,43 @@ openssl rand -hex 32 # CRON_SECRET
|
|
|
1018
1031
|
\`\`\`
|
|
1019
1032
|
|
|
1020
1033
|
\`CRON_SECRET\` is the name Vercel Cron sends, as \`Authorization: Bearer\`, and it
|
|
1021
|
-
cannot be told to send another
|
|
1034
|
+
cannot be told to send another \u2014 the caller is the platform, so this one has to
|
|
1035
|
+
be an environment variable both ends can read, and cannot be something the
|
|
1036
|
+
board makes up for itself. Note that this floor is stricter than the 16
|
|
1022
1037
|
characters Vercel's own cron documentation suggests \u2014 a value generated by
|
|
1023
1038
|
following those instructions is refused here, and the fix is a longer secret.
|
|
1024
1039
|
|
|
1025
|
-
|
|
1026
|
-
the
|
|
1027
|
-
|
|
1028
|
-
\`\`\`ini
|
|
1029
|
-
${VERCEL_TYPED_DRIVERS.join("\n")}
|
|
1030
|
-
\`\`\`
|
|
1031
|
-
|
|
1032
|
-
Neither is inferred, deliberately: the board caches in Redis and puts uploads
|
|
1033
|
-
in an object store because you said so, never because a connection string or a
|
|
1034
|
-
token happened to be present.
|
|
1040
|
+
\`AUTH_SECRET\` seals members' two-factor secrets and signs the unsubscribe links
|
|
1041
|
+
in outgoing mail. It stays in the environment deliberately: a copy of the
|
|
1042
|
+
database is then not enough to forge either.
|
|
1035
1043
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
this platform rather than write uploads to a disk that is about to disappear.
|
|
1039
|
-
Blank \`CACHE_DRIVER\` complains about nothing: the board falls back to caching
|
|
1040
|
-
inside each instance, and every instance then serves whatever it last saw for
|
|
1041
|
-
up to a minute. That one degrades quietly, so type it.
|
|
1042
|
-
|
|
1043
|
-
The remaining three drivers do derive themselves, and the form does not ask:
|
|
1044
|
-
a \`DATABASE_URL\` means \`DATA_SOURCE=postgres\` and \`QUEUE_DRIVER=postgres\`, and
|
|
1045
|
-
a \`RESEND_API_KEY\` with a \`MAIL_FROM\` beside it means \`MAIL_DRIVER=http\`:
|
|
1044
|
+
**That is the whole form.** Everything else the board works out from the stores
|
|
1045
|
+
this button just linked to the project:
|
|
1046
1046
|
|
|
1047
1047
|
\`\`\`ini
|
|
1048
1048
|
${VERCEL_DERIVED_DRIVERS.join("\n")}
|
|
1049
1049
|
\`\`\`
|
|
1050
1050
|
|
|
1051
|
-
|
|
1052
|
-
\`
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1051
|
+
\`DIRECT_DATABASE_URL\` comes from Neon's own \`DATABASE_URL_UNPOOLED\`, or
|
|
1052
|
+
\`POSTGRES_URL_NON_POOLING\` if that one is absent \u2014 migrations and the first-run
|
|
1053
|
+
installer each hold a session-level advisory lock, which the pooled
|
|
1054
|
+
\`DATABASE_URL\` cannot hold. \`REDIS_URL\` comes from Upstash's \`KV_URL\`, the one
|
|
1055
|
+
variable it publishes that speaks the Redis protocol.
|
|
1056
|
+
|
|
1057
|
+
Every one of those derivations is scoped to this platform, fires only where you
|
|
1058
|
+
have not set the variable yourself, and **refuses to boot rather than guess**.
|
|
1059
|
+
If a store is missing, or publishes a name this board does not know, the deploy
|
|
1060
|
+
stops with a message naming every variable it looked at \u2014 it will not fall back
|
|
1061
|
+
to caching inside each instance, or to uploads on a disk that is discarded with
|
|
1062
|
+
the instance. When the name is one we do not know, set \`REDIS_URL\` or
|
|
1063
|
+
\`DIRECT_DATABASE_URL\` in the project's environment settings and the derivation
|
|
1064
|
+
stands aside.
|
|
1065
|
+
|
|
1066
|
+
If you would rather keep uploads somewhere you hold yourself \u2014 see *Leaving
|
|
1067
|
+
Vercel* below for why that matters \u2014 set \`FILESTORE_DRIVER=s3\` and add
|
|
1068
|
+
\`S3_BUCKET\`, \`S3_REGION\`, \`S3_ACCESS_KEY_ID\` and \`S3_SECRET_ACCESS_KEY\` in the
|
|
1069
|
+
project's environment settings, with \`S3_ENDPOINT\` for a bucket that is not AWS
|
|
1070
|
+
(\`S3_REGION=auto\` for R2). The same board runs either way.
|
|
1069
1071
|
|
|
1070
1072
|
## Mail, in one click
|
|
1071
1073
|
|
|
@@ -1077,11 +1079,15 @@ invite anybody.
|
|
|
1077
1079
|
the project and connects your sending domain.
|
|
1078
1080
|
2. Verify that domain in the Resend dashboard if you have not already. Resend
|
|
1079
1081
|
refuses to send from an address at a domain it has not verified.
|
|
1080
|
-
3.
|
|
1082
|
+
3. Add \`MAIL_FROM\` to the project's environment settings \u2014 an address at that
|
|
1083
|
+
verified domain, and the only mail value you ever type. The deploy form does
|
|
1084
|
+
not ask for it, because a sender address is not something you can know
|
|
1085
|
+
before there is a provider to verify it.
|
|
1086
|
+
4. Redeploy, or let the next push redeploy.
|
|
1081
1087
|
|
|
1082
1088
|
That is all. The integration publishes its key into the project as
|
|
1083
1089
|
\`RESEND_API_KEY\`, and the board reads that name: with it set, and \`MAIL_FROM\`
|
|
1084
|
-
|
|
1090
|
+
beside it, mail sends over Resend's HTTPS API with nothing further to
|
|
1085
1091
|
configure.
|
|
1086
1092
|
|
|
1087
1093
|
The board is not tied to Resend. Its mail driver is a plain JSON-over-HTTPS
|
|
@@ -1178,13 +1184,19 @@ a Blob store does not:
|
|
|
1178
1184
|
DATABASE_URL=\u2026 # Neon's pooled string
|
|
1179
1185
|
DIRECT_DATABASE_URL=\u2026 # Neon's DATABASE_URL_UNPOOLED
|
|
1180
1186
|
FILESTORE_DRIVER=blob
|
|
1181
|
-
BLOB_READ_WRITE_TOKEN=\u2026 #
|
|
1187
|
+
BLOB_READ_WRITE_TOKEN=\u2026 # create one on the store; see below
|
|
1182
1188
|
npm run community -- backup
|
|
1183
1189
|
\`\`\`
|
|
1184
1190
|
|
|
1185
1191
|
Run that from a checkout of this repository, with those four values in the
|
|
1186
1192
|
environment \u2014 the CLI talks to Neon and to the Blob store over the network, so
|
|
1187
|
-
it does not have to run on Vercel.
|
|
1193
|
+
it does not have to run on Vercel.
|
|
1194
|
+
|
|
1195
|
+
That last one is the one value this route asks you to make by hand, and only
|
|
1196
|
+
here. On the deployment the board reaches the store with \`BLOB_STORE_ID\` and
|
|
1197
|
+
the deployment's OIDC identity, which a command on your own machine does not
|
|
1198
|
+
have. Open the store under **Storage**, create a read-write token, and use it
|
|
1199
|
+
for the backup; the board itself never needs it. The bundle it writes holds the dump *and*
|
|
1188
1200
|
every object. Check the last line it prints: if it says *no uploads*, the
|
|
1189
1201
|
uploads are not in the bundle and restoring it gives a board whose posts have
|
|
1190
1202
|
broken images.
|
|
@@ -1317,7 +1329,7 @@ async function run(argv, version) {
|
|
|
1317
1329
|
}
|
|
1318
1330
|
|
|
1319
1331
|
// src/bin.ts
|
|
1320
|
-
var result = await run(process.argv.slice(2), "0.
|
|
1332
|
+
var result = await run(process.argv.slice(2), "0.19.0");
|
|
1321
1333
|
for (const line of result.lines) {
|
|
1322
1334
|
if (result.code === 0) console.log(line);
|
|
1323
1335
|
else console.error(line);
|
package/package.json
CHANGED
package/src/bin.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { run } from './cli'
|
|
3
3
|
|
|
4
|
-
const result = await run(process.argv.slice(2), '0.
|
|
4
|
+
const result = await run(process.argv.slice(2), '0.19.0')
|
|
5
5
|
for (const line of result.lines) {
|
|
6
6
|
if (result.code === 0) console.log(line)
|
|
7
7
|
else console.error(line)
|
package/src/scaffold.ts
CHANGED
|
@@ -200,25 +200,24 @@ function vercelEnvExample(name: string): string {
|
|
|
200
200
|
# no other instance can read and that is discarded with the instance — on Vercel
|
|
201
201
|
# the board refuses it outright rather than losing uploads quietly.
|
|
202
202
|
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
203
|
+
# The deploy form asks for none of them. On Vercel the board works each one out
|
|
204
|
+
# from what the linked stores publish: a DATABASE_URL means postgres for the
|
|
205
|
+
# data source and the queue, a Redis connection string means CACHE_DRIVER=redis,
|
|
206
|
+
# a Blob store's read-write token means FILESTORE_DRIVER=blob, and a
|
|
207
|
+
# RESEND_API_KEY with a MAIL_FROM beside it means mail over the provider's HTTPS
|
|
208
|
+
# API. Setting one here overrides the derivation, which is what this file is for
|
|
209
|
+
# when you copy it to .env.local.
|
|
208
210
|
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
211
|
+
# Every one of those derivations is scoped to Vercel, and each fires only from a
|
|
212
|
+
# value that is unambiguously the thing itself — a redis:// or rediss:// URL, a
|
|
213
|
+
# read-write token. A board you run anywhere else is untouched by all of it and
|
|
214
|
+
# still takes these values from this file, exactly as it did before.
|
|
213
215
|
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
# serves whatever that instance last saw, for up to a minute, however many
|
|
220
|
-
# instances there are. That one degrades quietly, which is exactly why it is
|
|
221
|
-
# worth typing rather than leaving to a default.
|
|
216
|
+
# A derivation that cannot resolve is a configuration error, not an invitation
|
|
217
|
+
# to pick something safe-looking. On Vercel, with the cache or the object store
|
|
218
|
+
# missing, the board refuses to boot and names every variable it looked at. It
|
|
219
|
+
# will not quietly cache inside the instance, and it will not quietly write
|
|
220
|
+
# uploads to a disk that is about to disappear.
|
|
222
221
|
DATA_SOURCE=postgres
|
|
223
222
|
QUEUE_DRIVER=postgres
|
|
224
223
|
CACHE_DRIVER=redis
|
|
@@ -232,16 +231,20 @@ DATABASE_URL=
|
|
|
232
231
|
|
|
233
232
|
${ENV_DIRECT_DATABASE_URL_PROSE}
|
|
234
233
|
#
|
|
235
|
-
# On Vercel this is not optional
|
|
236
|
-
# build runs \`community migrate\` against it, and
|
|
237
|
-
# those two session locks on first run.
|
|
238
|
-
#
|
|
234
|
+
# On Vercel this is not optional, and it is no longer yours to copy. DATABASE_URL
|
|
235
|
+
# here is the pooler string, the build runs \`community migrate\` against it, and
|
|
236
|
+
# /install takes the second of those two session locks on first run. Left blank,
|
|
237
|
+
# the board reads Neon's own direct string — \`DATABASE_URL_UNPOOLED\` first, then
|
|
238
|
+
# \`POSTGRES_URL_NON_POOLING\` — and refuses to boot if neither is there, naming
|
|
239
|
+
# both. Never \`POSTGRES_URL\`: that one is pooled.
|
|
239
240
|
DIRECT_DATABASE_URL=
|
|
240
241
|
|
|
241
242
|
# The shared cache — a Redis or Valkey endpoint, \`rediss://\` for TLS. Redis
|
|
242
243
|
# holds cache entries and nothing else: losing it costs the board a warm cache,
|
|
243
|
-
# not data, and signs nobody out.
|
|
244
|
-
#
|
|
244
|
+
# not data, and signs nobody out. Left blank on Vercel, the board reads the
|
|
245
|
+
# Upstash store's own \`KV_URL\`, which is the one variable it publishes that
|
|
246
|
+
# speaks the Redis protocol — \`KV_REST_API_URL\` is an HTTPS endpoint and is
|
|
247
|
+
# never used for this. A name we do not know goes here by hand.
|
|
245
248
|
REDIS_URL=
|
|
246
249
|
|
|
247
250
|
${ENV_AUTH_SECRET_PROSE}
|
|
@@ -257,15 +260,26 @@ ${ENV_TICK_SECRET_PROSE}
|
|
|
257
260
|
CRON_SECRET=
|
|
258
261
|
# TICK_SECRET=
|
|
259
262
|
|
|
260
|
-
# Uploads, in the Vercel Blob store the Deploy Button provisions.
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
#
|
|
263
|
+
# Uploads, in the Vercel Blob store the Deploy Button provisions. A store
|
|
264
|
+
# attached to the project publishes BLOB_STORE_ID and nothing else — no token —
|
|
265
|
+
# because the SDK authenticates with the deployment's own OIDC identity: the
|
|
266
|
+
# board hands it the store id and lets it fetch the credential. There is nothing
|
|
267
|
+
# to type and nothing to mistype, and FILESTORE_DRIVER=blob derives from this
|
|
268
|
+
# variable being present.
|
|
266
269
|
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
270
|
+
# BLOB_READ_WRITE_TOKEN is the other way in, and you make it yourself on the
|
|
271
|
+
# store. Set it when something has to reach the store from OUTSIDE a Vercel
|
|
272
|
+
# deployment — \`community backup\` run on your own machine is the case that
|
|
273
|
+
# matters — because there is no OIDC identity there to borrow. Set both and the
|
|
274
|
+
# board prefers the store id, unless the token names a different store, in which
|
|
275
|
+
# case the token wins: naming another store is a deliberate act.
|
|
276
|
+
#
|
|
277
|
+
# Every object is written with private access: an object URL is not a public
|
|
278
|
+
# link, and member content is served by the board, which is where permissions
|
|
279
|
+
# are checked. An upload is held whole in the instance's memory on the way in
|
|
280
|
+
# and on the way out, so the function's memory limit, not the store, is what
|
|
281
|
+
# caps a file.
|
|
282
|
+
BLOB_STORE_ID=
|
|
269
283
|
BLOB_READ_WRITE_TOKEN=
|
|
270
284
|
|
|
271
285
|
# Uploads in an S3-compatible bucket instead — AWS, R2, MinIO, Spaces. This is
|
|
@@ -288,10 +302,10 @@ BLOB_READ_WRITE_TOKEN=
|
|
|
288
302
|
# board refuses it on Vercel; 587 with STARTTLS may work, but an API does not
|
|
289
303
|
# depend on the platform's egress rules staying as they are.
|
|
290
304
|
#
|
|
291
|
-
# MAIL_FROM is yours to decide and
|
|
292
|
-
# must be an address at a domain the provider has
|
|
293
|
-
# default is right
|
|
294
|
-
#
|
|
305
|
+
# MAIL_FROM is yours to decide, and it belongs to the step that adds Resend
|
|
306
|
+
# rather than to the deploy: it must be an address at a domain the provider has
|
|
307
|
+
# verified for you, no default is right, and until a provider is added the board
|
|
308
|
+
# cannot send from any address at all. Set it alongside the integration.
|
|
295
309
|
MAIL_FROM=
|
|
296
310
|
|
|
297
311
|
# Add the Resend integration to the project from Vercel's marketplace and it
|
|
@@ -335,23 +349,15 @@ const SELF_HOST_DEPLOY_KIT = [
|
|
|
335
349
|
'docker-healthcheck.sh',
|
|
336
350
|
] as const
|
|
337
351
|
|
|
338
|
-
const VERCEL_TYPED_DRIVERS = ['CACHE_DRIVER=redis', 'FILESTORE_DRIVER=blob'] as const
|
|
339
|
-
|
|
340
352
|
const VERCEL_DERIVED_DRIVERS = [
|
|
341
353
|
'DATA_SOURCE=postgres',
|
|
342
354
|
'QUEUE_DRIVER=postgres',
|
|
355
|
+
'CACHE_DRIVER=redis',
|
|
356
|
+
'FILESTORE_DRIVER=blob',
|
|
343
357
|
'MAIL_DRIVER=http',
|
|
344
358
|
] as const
|
|
345
359
|
|
|
346
|
-
export const VERCEL_PROMPTED_ENV = [
|
|
347
|
-
'CACHE_DRIVER',
|
|
348
|
-
'FILESTORE_DRIVER',
|
|
349
|
-
'DIRECT_DATABASE_URL',
|
|
350
|
-
'REDIS_URL',
|
|
351
|
-
'AUTH_SECRET',
|
|
352
|
-
'CRON_SECRET',
|
|
353
|
-
'MAIL_FROM',
|
|
354
|
-
] as const
|
|
360
|
+
export const VERCEL_PROMPTED_ENV = ['AUTH_SECRET', 'CRON_SECRET'] as const
|
|
355
361
|
|
|
356
362
|
export const VERCEL_MARKETPLACE_STORES = [
|
|
357
363
|
{ type: 'integration', integrationSlug: 'neon', productSlug: 'neon', protocol: 'storage' },
|
|
@@ -372,7 +378,7 @@ export function deployButtonUrl(templateRepositoryUrl: string): string {
|
|
|
372
378
|
['env', VERCEL_PROMPTED_ENV.join(',')],
|
|
373
379
|
[
|
|
374
380
|
'envDescription',
|
|
375
|
-
'Two
|
|
381
|
+
'Two secrets, generated rather than chosen — 32 characters or more each. Everything else the board reads from the database, cache and blob store this form links.',
|
|
376
382
|
],
|
|
377
383
|
['envLink', `${templateRepositoryUrl}/blob/main/.env.example`],
|
|
378
384
|
['stores', JSON.stringify(VERCEL_MARKETPLACE_STORES)],
|
|
@@ -1054,11 +1060,14 @@ A forum, built on [Meith](${repositoryUrl}), running as Vercel functions.
|
|
|
1054
1060
|
- **A Neon Postgres database**, attached to the project. Neon publishes the
|
|
1055
1061
|
pooled connection string as \`DATABASE_URL\` and the direct one as
|
|
1056
1062
|
\`DATABASE_URL_UNPOOLED\`.
|
|
1057
|
-
- **An Upstash Redis store**, attached the same way, for the shared cache.
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1063
|
+
- **An Upstash Redis store**, attached the same way, for the shared cache. It
|
|
1064
|
+
publishes \`KV_URL\`, which the board reads as \`REDIS_URL\` — \`KV_REST_API_URL\`
|
|
1065
|
+
beside it is an HTTPS endpoint and is not used for this.
|
|
1066
|
+
- **A Vercel Blob store** for uploads, which publishes \`BLOB_STORE_ID\` into the
|
|
1067
|
+
project by itself. That is the whole credential: the board hands the id to
|
|
1068
|
+
Vercel's SDK, which authenticates with the deployment's own OIDC identity, so
|
|
1069
|
+
there is no token to copy. This is what used to be four hand-typed \`S3_*\`
|
|
1070
|
+
secrets.
|
|
1062
1071
|
- **A Vercel project** carrying \`vercel.json\` — the build command
|
|
1063
1072
|
\`${VERCEL_BUILD_COMMAND}\`,
|
|
1064
1073
|
which applies the schema before it builds, materializes the board's app at
|
|
@@ -1066,8 +1075,11 @@ A forum, built on [Meith](${repositoryUrl}), running as Vercel functions.
|
|
|
1066
1075
|
entry that drives the tick.
|
|
1067
1076
|
|
|
1068
1077
|
**Mail is the one thing the button does not set up**, and it takes one click
|
|
1069
|
-
after the deploy — see *Mail, in one click* below.
|
|
1070
|
-
|
|
1078
|
+
after the deploy — see *Mail, in one click* below. That is also where the
|
|
1079
|
+
address the board sends from goes: it has to be at a domain your provider has
|
|
1080
|
+
verified, which cannot be true of any domain before a provider exists. The
|
|
1081
|
+
board boots and runs without mail, and delivers nothing, silently, until it is
|
|
1082
|
+
done.
|
|
1071
1083
|
|
|
1072
1084
|
## What to type into the deploy form
|
|
1073
1085
|
|
|
@@ -1080,54 +1092,43 @@ openssl rand -hex 32 # CRON_SECRET
|
|
|
1080
1092
|
\`\`\`
|
|
1081
1093
|
|
|
1082
1094
|
\`CRON_SECRET\` is the name Vercel Cron sends, as \`Authorization: Bearer\`, and it
|
|
1083
|
-
cannot be told to send another
|
|
1095
|
+
cannot be told to send another — the caller is the platform, so this one has to
|
|
1096
|
+
be an environment variable both ends can read, and cannot be something the
|
|
1097
|
+
board makes up for itself. Note that this floor is stricter than the 16
|
|
1084
1098
|
characters Vercel's own cron documentation suggests — a value generated by
|
|
1085
1099
|
following those instructions is refused here, and the fix is a longer secret.
|
|
1086
1100
|
|
|
1087
|
-
|
|
1088
|
-
the
|
|
1101
|
+
\`AUTH_SECRET\` seals members' two-factor secrets and signs the unsubscribe links
|
|
1102
|
+
in outgoing mail. It stays in the environment deliberately: a copy of the
|
|
1103
|
+
database is then not enough to forge either.
|
|
1089
1104
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
\`\`\`
|
|
1093
|
-
|
|
1094
|
-
Neither is inferred, deliberately: the board caches in Redis and puts uploads
|
|
1095
|
-
in an object store because you said so, never because a connection string or a
|
|
1096
|
-
token happened to be present.
|
|
1097
|
-
|
|
1098
|
-
Leaving them blank fails differently, which is why the form asks for both.
|
|
1099
|
-
Blank \`FILESTORE_DRIVER\` means \`local\`, and the board **refuses to boot** on
|
|
1100
|
-
this platform rather than write uploads to a disk that is about to disappear.
|
|
1101
|
-
Blank \`CACHE_DRIVER\` complains about nothing: the board falls back to caching
|
|
1102
|
-
inside each instance, and every instance then serves whatever it last saw for
|
|
1103
|
-
up to a minute. That one degrades quietly, so type it.
|
|
1104
|
-
|
|
1105
|
-
The remaining three drivers do derive themselves, and the form does not ask:
|
|
1106
|
-
a \`DATABASE_URL\` means \`DATA_SOURCE=postgres\` and \`QUEUE_DRIVER=postgres\`, and
|
|
1107
|
-
a \`RESEND_API_KEY\` with a \`MAIL_FROM\` beside it means \`MAIL_DRIVER=http\`:
|
|
1105
|
+
**That is the whole form.** Everything else the board works out from the stores
|
|
1106
|
+
this button just linked to the project:
|
|
1108
1107
|
|
|
1109
1108
|
\`\`\`ini
|
|
1110
1109
|
${VERCEL_DERIVED_DRIVERS.join('\n')}
|
|
1111
1110
|
\`\`\`
|
|
1112
1111
|
|
|
1113
|
-
|
|
1114
|
-
\`
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1112
|
+
\`DIRECT_DATABASE_URL\` comes from Neon's own \`DATABASE_URL_UNPOOLED\`, or
|
|
1113
|
+
\`POSTGRES_URL_NON_POOLING\` if that one is absent — migrations and the first-run
|
|
1114
|
+
installer each hold a session-level advisory lock, which the pooled
|
|
1115
|
+
\`DATABASE_URL\` cannot hold. \`REDIS_URL\` comes from Upstash's \`KV_URL\`, the one
|
|
1116
|
+
variable it publishes that speaks the Redis protocol.
|
|
1117
|
+
|
|
1118
|
+
Every one of those derivations is scoped to this platform, fires only where you
|
|
1119
|
+
have not set the variable yourself, and **refuses to boot rather than guess**.
|
|
1120
|
+
If a store is missing, or publishes a name this board does not know, the deploy
|
|
1121
|
+
stops with a message naming every variable it looked at — it will not fall back
|
|
1122
|
+
to caching inside each instance, or to uploads on a disk that is discarded with
|
|
1123
|
+
the instance. When the name is one we do not know, set \`REDIS_URL\` or
|
|
1124
|
+
\`DIRECT_DATABASE_URL\` in the project's environment settings and the derivation
|
|
1125
|
+
stands aside.
|
|
1126
|
+
|
|
1127
|
+
If you would rather keep uploads somewhere you hold yourself — see *Leaving
|
|
1128
|
+
Vercel* below for why that matters — set \`FILESTORE_DRIVER=s3\` and add
|
|
1129
|
+
\`S3_BUCKET\`, \`S3_REGION\`, \`S3_ACCESS_KEY_ID\` and \`S3_SECRET_ACCESS_KEY\` in the
|
|
1130
|
+
project's environment settings, with \`S3_ENDPOINT\` for a bucket that is not AWS
|
|
1131
|
+
(\`S3_REGION=auto\` for R2). The same board runs either way.
|
|
1131
1132
|
|
|
1132
1133
|
## Mail, in one click
|
|
1133
1134
|
|
|
@@ -1139,11 +1140,15 @@ invite anybody.
|
|
|
1139
1140
|
the project and connects your sending domain.
|
|
1140
1141
|
2. Verify that domain in the Resend dashboard if you have not already. Resend
|
|
1141
1142
|
refuses to send from an address at a domain it has not verified.
|
|
1142
|
-
3.
|
|
1143
|
+
3. Add \`MAIL_FROM\` to the project's environment settings — an address at that
|
|
1144
|
+
verified domain, and the only mail value you ever type. The deploy form does
|
|
1145
|
+
not ask for it, because a sender address is not something you can know
|
|
1146
|
+
before there is a provider to verify it.
|
|
1147
|
+
4. Redeploy, or let the next push redeploy.
|
|
1143
1148
|
|
|
1144
1149
|
That is all. The integration publishes its key into the project as
|
|
1145
1150
|
\`RESEND_API_KEY\`, and the board reads that name: with it set, and \`MAIL_FROM\`
|
|
1146
|
-
|
|
1151
|
+
beside it, mail sends over Resend's HTTPS API with nothing further to
|
|
1147
1152
|
configure.
|
|
1148
1153
|
|
|
1149
1154
|
The board is not tied to Resend. Its mail driver is a plain JSON-over-HTTPS
|
|
@@ -1240,13 +1245,19 @@ a Blob store does not:
|
|
|
1240
1245
|
DATABASE_URL=… # Neon's pooled string
|
|
1241
1246
|
DIRECT_DATABASE_URL=… # Neon's DATABASE_URL_UNPOOLED
|
|
1242
1247
|
FILESTORE_DRIVER=blob
|
|
1243
|
-
BLOB_READ_WRITE_TOKEN=… #
|
|
1248
|
+
BLOB_READ_WRITE_TOKEN=… # create one on the store; see below
|
|
1244
1249
|
npm run community -- backup
|
|
1245
1250
|
\`\`\`
|
|
1246
1251
|
|
|
1247
1252
|
Run that from a checkout of this repository, with those four values in the
|
|
1248
1253
|
environment — the CLI talks to Neon and to the Blob store over the network, so
|
|
1249
|
-
it does not have to run on Vercel.
|
|
1254
|
+
it does not have to run on Vercel.
|
|
1255
|
+
|
|
1256
|
+
That last one is the one value this route asks you to make by hand, and only
|
|
1257
|
+
here. On the deployment the board reaches the store with \`BLOB_STORE_ID\` and
|
|
1258
|
+
the deployment's OIDC identity, which a command on your own machine does not
|
|
1259
|
+
have. Open the store under **Storage**, create a read-write token, and use it
|
|
1260
|
+
for the backup; the board itself never needs it. The bundle it writes holds the dump *and*
|
|
1250
1261
|
every object. Check the last line it prints: if it says *no uploads*, the
|
|
1251
1262
|
uploads are not in the bundle and restoring it gives a board whose posts have
|
|
1252
1263
|
broken images.
|