create-nextblock 0.12.4 → 0.12.5
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/docker-template/.env.docker.example +13 -0
- package/docker-template/docker-compose.yml +7 -2
- package/docker-template/scripts/docker-setup.mjs +104 -10
- package/package.json +1 -1
- package/templates/nextblock-template/docker-compose.yml +7 -2
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/scripts/docker-setup.mjs +104 -10
|
@@ -15,6 +15,19 @@ CRON_SECRET=replace-me-generated
|
|
|
15
15
|
DRAFT_MODE_SECRET=replace-me-generated
|
|
16
16
|
REVALIDATE_SECRET_TOKEN=replace-me-generated
|
|
17
17
|
|
|
18
|
+
# --- Host port mappings ----------------------------------------------------------------------
|
|
19
|
+
# The published host ports for each service. docker:setup auto-picks a FREE port when a default
|
|
20
|
+
# is taken or reserved by the OS (common on Windows, e.g. Postgres 54322), then keeps the URLs
|
|
21
|
+
# below and LOOPBACK_PROXIES in lockstep. Change a port here only if you also update its URL.
|
|
22
|
+
APP_PORT=3000
|
|
23
|
+
KONG_HTTP_PORT=8000
|
|
24
|
+
MINIO_S3_PORT=9000
|
|
25
|
+
MINIO_CONSOLE_PORT=9001
|
|
26
|
+
POSTGRES_PORT_EXTERNAL=54322
|
|
27
|
+
# In-container loopback (socat): maps the localhost:<port> server code derives from the public
|
|
28
|
+
# URLs to the real service. Left-hand ports MUST match NEXT_PUBLIC_SUPABASE_URL / R2_S3_PUBLIC_ENDPOINT.
|
|
29
|
+
LOOPBACK_PROXIES=8000:kong:8000 9000:minio:9000
|
|
30
|
+
|
|
18
31
|
# --- Supabase wiring (internal container network vs browser) ---------------------------------
|
|
19
32
|
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
|
|
20
33
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=${ANON_KEY}
|
|
@@ -182,8 +182,13 @@ services:
|
|
|
182
182
|
NODE_ENV: production
|
|
183
183
|
PORT: 3000
|
|
184
184
|
HOSTNAME: 0.0.0.0
|
|
185
|
-
#
|
|
186
|
-
#
|
|
185
|
+
# In-container loopback proxies (see Dockerfile entrypoint): map the localhost:<port> that
|
|
186
|
+
# server-side code derives from the public URLs to the real service. docker:setup rewrites
|
|
187
|
+
# this when a default host port is taken/reserved and it remaps Kong/MinIO to a free port,
|
|
188
|
+
# so the left-hand ports here always match NEXT_PUBLIC_SUPABASE_URL / R2_S3_PUBLIC_ENDPOINT.
|
|
189
|
+
LOOPBACK_PROXIES: ${LOOPBACK_PROXIES:-8000:kong:8000 9000:minio:9000}
|
|
190
|
+
# Browser AND server both use the same Supabase URL; the loopback proxy above forwards it to
|
|
191
|
+
# Kong, keeping the host-derived Supabase auth cookie key consistent for SSR.
|
|
187
192
|
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL:-http://localhost:8000}
|
|
188
193
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
|
|
189
194
|
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
|
|
@@ -12,6 +12,7 @@ import { readFile, writeFile, access } from 'node:fs/promises';
|
|
|
12
12
|
import { resolve, dirname } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
import { spawn, spawnSync } from 'node:child_process';
|
|
15
|
+
import { createServer } from 'node:net';
|
|
15
16
|
|
|
16
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const PROJECT_ROOT = resolve(__dirname, '..');
|
|
@@ -79,6 +80,41 @@ const pathExists = async (p) => {
|
|
|
79
80
|
|
|
80
81
|
const commandWorks = (cmd, args) => spawnSync(cmd, args, { stdio: 'ignore' }).status === 0;
|
|
81
82
|
|
|
83
|
+
// Can the host PUBLISH (bind) this TCP port? Docker publishes on 0.0.0.0, and on Windows the
|
|
84
|
+
// Hyper-V/WinNAT excluded port ranges (which sit in the 49152+ ephemeral band — e.g. the default
|
|
85
|
+
// Postgres port 54322) or any process already holding the port make the bind fail with
|
|
86
|
+
// EACCES ("access permissions") / EADDRINUSE — exactly what `docker compose up` fails on. Probing
|
|
87
|
+
// here lets us pick a working host port BEFORE compose tries and aborts the whole stack.
|
|
88
|
+
function canBindPort(port) {
|
|
89
|
+
return new Promise((res) => {
|
|
90
|
+
const srv = createServer();
|
|
91
|
+
srv.once('error', () => res(false));
|
|
92
|
+
srv.once('listening', () => srv.close(() => res(true)));
|
|
93
|
+
try {
|
|
94
|
+
srv.listen(port, '0.0.0.0');
|
|
95
|
+
} catch {
|
|
96
|
+
res(false);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Prefer the conventional port; if it's unavailable, fall back into the registered-port band
|
|
102
|
+
// (1024–49151, below the ephemeral range WinNAT reserves) rather than just incrementing — a
|
|
103
|
+
// reserved/used port often sits inside a contiguous block, so +1 tends to be taken too.
|
|
104
|
+
async function findAvailablePort(preferred, fallbackBase, taken) {
|
|
105
|
+
const candidates = [preferred];
|
|
106
|
+
for (let i = 0; i < 100; i++) candidates.push(fallbackBase + i);
|
|
107
|
+
for (const p of candidates) {
|
|
108
|
+
if (taken.has(p)) continue;
|
|
109
|
+
if (await canBindPort(p)) {
|
|
110
|
+
taken.add(p);
|
|
111
|
+
return p;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
taken.add(preferred);
|
|
115
|
+
return preferred; // give up gracefully — let compose surface the real error
|
|
116
|
+
}
|
|
117
|
+
|
|
82
118
|
function detectCompose() {
|
|
83
119
|
if (commandWorks('docker', ['compose', 'version'])) return { cmd: 'docker', args: ['compose'] };
|
|
84
120
|
if (commandWorks('docker-compose', ['version'])) return { cmd: 'docker-compose', args: [] };
|
|
@@ -138,6 +174,41 @@ async function main() {
|
|
|
138
174
|
const minioPassword = reuse('MINIO_ROOT_PASSWORD', generateSecret);
|
|
139
175
|
const bucket = readEnvValue(existing, 'STORAGE_BUCKET') || 'nextblock';
|
|
140
176
|
|
|
177
|
+
// Resolve host ports up front so a port that's in use or reserved by the OS (very common on
|
|
178
|
+
// Windows for the default Postgres port 54322) never aborts `docker compose up`. Reuse a port
|
|
179
|
+
// already encoded in .env — the explicit *_PORT* var, else the port parsed from a coupled URL —
|
|
180
|
+
// so re-runs stay stable and never disturb an already-running stack; only a FRESH install probes
|
|
181
|
+
// for free ports. Coupled ports (Kong/MinIO/app) also drive the URLs + loopback proxy below.
|
|
182
|
+
const takenPorts = new Set();
|
|
183
|
+
const reusedPort = (key, urlKey) => {
|
|
184
|
+
const direct = parseInt(readEnvValue(existing, key), 10);
|
|
185
|
+
if (Number.isInteger(direct) && direct > 0) return direct;
|
|
186
|
+
if (urlKey) {
|
|
187
|
+
const m = readEnvValue(existing, urlKey).match(/:(\d{2,5})(?:\/|$)/);
|
|
188
|
+
if (m) return parseInt(m[1], 10);
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
};
|
|
192
|
+
const resolvePort = async (key, preferred, fallbackBase, urlKey) => {
|
|
193
|
+
const prev = reusedPort(key, urlKey);
|
|
194
|
+
if (prev) {
|
|
195
|
+
takenPorts.add(prev);
|
|
196
|
+
return prev;
|
|
197
|
+
}
|
|
198
|
+
if (existing) {
|
|
199
|
+
// An existing .env with no recorded port: keep the default rather than reshuffle a setup
|
|
200
|
+
// the user may already be running.
|
|
201
|
+
takenPorts.add(preferred);
|
|
202
|
+
return preferred;
|
|
203
|
+
}
|
|
204
|
+
return findAvailablePort(preferred, fallbackBase, takenPorts);
|
|
205
|
+
};
|
|
206
|
+
const appPort = await resolvePort('APP_PORT', 3000, 13000, 'NEXT_PUBLIC_URL');
|
|
207
|
+
const kongPort = await resolvePort('KONG_HTTP_PORT', 8000, 18000, 'NEXT_PUBLIC_SUPABASE_URL');
|
|
208
|
+
const minioS3Port = await resolvePort('MINIO_S3_PORT', 9000, 19000, 'R2_S3_PUBLIC_ENDPOINT');
|
|
209
|
+
const minioConsolePort = await resolvePort('MINIO_CONSOLE_PORT', 9001, 19001, null);
|
|
210
|
+
const dbPort = await resolvePort('POSTGRES_PORT_EXTERNAL', 54322, 15432, null);
|
|
211
|
+
|
|
141
212
|
const replacements = {
|
|
142
213
|
POSTGRES_PASSWORD: `POSTGRES_PASSWORD=${postgresPassword}`,
|
|
143
214
|
POSTGRES_DB: 'POSTGRES_DB=postgres',
|
|
@@ -145,12 +216,22 @@ async function main() {
|
|
|
145
216
|
JWT_EXP: 'JWT_EXP=3600',
|
|
146
217
|
ANON_KEY: `ANON_KEY=${anonKey}`,
|
|
147
218
|
SERVICE_ROLE_KEY: `SERVICE_ROLE_KEY=${serviceRoleKey}`,
|
|
148
|
-
|
|
219
|
+
// Host port mappings (compose reads these) + the coupled public URLs, kept in lockstep so a
|
|
220
|
+
// remapped port stays consistent everywhere it's referenced.
|
|
221
|
+
APP_PORT: `APP_PORT=${appPort}`,
|
|
222
|
+
KONG_HTTP_PORT: `KONG_HTTP_PORT=${kongPort}`,
|
|
223
|
+
MINIO_S3_PORT: `MINIO_S3_PORT=${minioS3Port}`,
|
|
224
|
+
MINIO_CONSOLE_PORT: `MINIO_CONSOLE_PORT=${minioConsolePort}`,
|
|
225
|
+
POSTGRES_PORT_EXTERNAL: `POSTGRES_PORT_EXTERNAL=${dbPort}`,
|
|
226
|
+
// In-container loopback: server code hits localhost:<kongPort> / 127.0.0.1:<minioS3Port> and
|
|
227
|
+
// socat forwards to the real service. Left ports MUST match the URLs below.
|
|
228
|
+
LOOPBACK_PROXIES: `LOOPBACK_PROXIES=${kongPort}:kong:8000 ${minioS3Port}:minio:9000`,
|
|
229
|
+
NEXT_PUBLIC_SUPABASE_URL: `NEXT_PUBLIC_SUPABASE_URL=http://localhost:${kongPort}`,
|
|
149
230
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: `NEXT_PUBLIC_SUPABASE_ANON_KEY=${anonKey}`,
|
|
150
231
|
SUPABASE_SERVICE_ROLE_KEY: `SUPABASE_SERVICE_ROLE_KEY=${serviceRoleKey}`,
|
|
151
|
-
API_EXTERNAL_URL:
|
|
152
|
-
SITE_URL:
|
|
153
|
-
NEXT_PUBLIC_URL:
|
|
232
|
+
API_EXTERNAL_URL: `API_EXTERNAL_URL=http://localhost:${kongPort}`,
|
|
233
|
+
SITE_URL: `SITE_URL=http://localhost:${appPort}`,
|
|
234
|
+
NEXT_PUBLIC_URL: `NEXT_PUBLIC_URL=http://localhost:${appPort}`,
|
|
154
235
|
NEXT_PUBLIC_IS_SANDBOX: 'NEXT_PUBLIC_IS_SANDBOX=true',
|
|
155
236
|
CRON_SECRET: `CRON_SECRET=${cronSecret}`,
|
|
156
237
|
DRAFT_MODE_SECRET: `DRAFT_MODE_SECRET=${draftSecret}`,
|
|
@@ -165,10 +246,10 @@ async function main() {
|
|
|
165
246
|
// port-scoped, so the browser would send the app's Supabase auth cookies to MinIO too — and
|
|
166
247
|
// MinIO rejects oversized header sets (MetadataTooLarge), breaking image display once cookies
|
|
167
248
|
// grow. 127.0.0.1 is a different cookie host, so the browser never sends them there.
|
|
168
|
-
R2_S3_PUBLIC_ENDPOINT:
|
|
249
|
+
R2_S3_PUBLIC_ENDPOINT: `R2_S3_PUBLIC_ENDPOINT=http://127.0.0.1:${minioS3Port}`,
|
|
169
250
|
R2_FORCE_PATH_STYLE: 'R2_FORCE_PATH_STYLE=true',
|
|
170
|
-
NEXT_PUBLIC_R2_BASE_URL: `NEXT_PUBLIC_R2_BASE_URL=http://127.0.0.1
|
|
171
|
-
NEXT_PUBLIC_R2_PUBLIC_URL: `NEXT_PUBLIC_R2_PUBLIC_URL=http://127.0.0.1
|
|
251
|
+
NEXT_PUBLIC_R2_BASE_URL: `NEXT_PUBLIC_R2_BASE_URL=http://127.0.0.1:${minioS3Port}/${bucket}`,
|
|
252
|
+
NEXT_PUBLIC_R2_PUBLIC_URL: `NEXT_PUBLIC_R2_PUBLIC_URL=http://127.0.0.1:${minioS3Port}/${bucket}`,
|
|
172
253
|
NEXT_PUBLIC_TURNSTILE_SITE_KEY: `NEXT_PUBLIC_TURNSTILE_SITE_KEY=${turnstileSiteKey}`,
|
|
173
254
|
TURNSTILE_SECRET_KEY: `TURNSTILE_SECRET_KEY=${turnstileSecretKey}`,
|
|
174
255
|
GOTRUE_MAILER_AUTOCONFIRM: `GOTRUE_MAILER_AUTOCONFIRM=${mailerAutoconfirm}`,
|
|
@@ -186,6 +267,19 @@ async function main() {
|
|
|
186
267
|
await writeFile(ENV_PATH, nextEnv, 'utf8');
|
|
187
268
|
console.log('✓ Wrote .env (Postgres, JWT secret + signed anon/service keys, MinIO, app secrets).\n');
|
|
188
269
|
|
|
270
|
+
const remapped = [
|
|
271
|
+
appPort !== 3000 && `app ${appPort} (default 3000)`,
|
|
272
|
+
kongPort !== 8000 && `Supabase API ${kongPort} (default 8000)`,
|
|
273
|
+
minioS3Port !== 9000 && `MinIO S3 ${minioS3Port} (default 9000)`,
|
|
274
|
+
minioConsolePort !== 9001 && `MinIO console ${minioConsolePort} (default 9001)`,
|
|
275
|
+
dbPort !== 54322 && `Postgres ${dbPort} (default 54322)`,
|
|
276
|
+
].filter(Boolean);
|
|
277
|
+
if (remapped.length) {
|
|
278
|
+
console.log(
|
|
279
|
+
`↔ Some default host ports were unavailable (in use, or reserved by the OS — common on Windows).\n Remapped to free ports: ${remapped.join(', ')}.\n`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
189
283
|
// A brand-new .env means brand-new secrets. Postgres only runs its init scripts (which set role
|
|
190
284
|
// passwords) on an EMPTY volume, so a leftover volume from a previous install would keep the old
|
|
191
285
|
// credentials and GoTrue/PostgREST could not log in. Reset volumes when the config is fresh.
|
|
@@ -202,10 +296,10 @@ async function main() {
|
|
|
202
296
|
await run(compose.cmd, [...compose.args, 'up', '-d', '--build'], { cwd: PROJECT_ROOT });
|
|
203
297
|
|
|
204
298
|
console.log('\n🎉 Stack is up!');
|
|
205
|
-
console.log(
|
|
206
|
-
console.log(
|
|
299
|
+
console.log(` 1. Open the app: http://localhost:${appPort}`);
|
|
300
|
+
console.log(` 2. Finish setup: complete the browser wizard at http://localhost:${appPort}/setup`);
|
|
207
301
|
console.log(' (creates your first admin — auto-confirmed, no email needed).');
|
|
208
|
-
console.log(
|
|
302
|
+
console.log(` 3. Supabase API: http://localhost:${kongPort} MinIO console: http://localhost:${minioConsolePort}`);
|
|
209
303
|
const composeStr = `${compose.cmd} ${compose.args.join(' ')}`.trim();
|
|
210
304
|
console.log(`\n Logs: ${composeStr} logs -f nextblock-cms | Stop: ${composeStr} down (add -v to wipe data)`);
|
|
211
305
|
}
|
package/package.json
CHANGED
|
@@ -182,8 +182,13 @@ services:
|
|
|
182
182
|
NODE_ENV: production
|
|
183
183
|
PORT: 3000
|
|
184
184
|
HOSTNAME: 0.0.0.0
|
|
185
|
-
#
|
|
186
|
-
#
|
|
185
|
+
# In-container loopback proxies (see Dockerfile entrypoint): map the localhost:<port> that
|
|
186
|
+
# server-side code derives from the public URLs to the real service. docker:setup rewrites
|
|
187
|
+
# this when a default host port is taken/reserved and it remaps Kong/MinIO to a free port,
|
|
188
|
+
# so the left-hand ports here always match NEXT_PUBLIC_SUPABASE_URL / R2_S3_PUBLIC_ENDPOINT.
|
|
189
|
+
LOOPBACK_PROXIES: ${LOOPBACK_PROXIES:-8000:kong:8000 9000:minio:9000}
|
|
190
|
+
# Browser AND server both use the same Supabase URL; the loopback proxy above forwards it to
|
|
191
|
+
# Kong, keeping the host-derived Supabase auth cookie key consistent for SSR.
|
|
187
192
|
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL:-http://localhost:8000}
|
|
188
193
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
|
|
189
194
|
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
|
|
@@ -12,6 +12,7 @@ import { readFile, writeFile, access } from 'node:fs/promises';
|
|
|
12
12
|
import { resolve, dirname } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
import { spawn, spawnSync } from 'node:child_process';
|
|
15
|
+
import { createServer } from 'node:net';
|
|
15
16
|
|
|
16
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const PROJECT_ROOT = resolve(__dirname, '..');
|
|
@@ -79,6 +80,41 @@ const pathExists = async (p) => {
|
|
|
79
80
|
|
|
80
81
|
const commandWorks = (cmd, args) => spawnSync(cmd, args, { stdio: 'ignore' }).status === 0;
|
|
81
82
|
|
|
83
|
+
// Can the host PUBLISH (bind) this TCP port? Docker publishes on 0.0.0.0, and on Windows the
|
|
84
|
+
// Hyper-V/WinNAT excluded port ranges (which sit in the 49152+ ephemeral band — e.g. the default
|
|
85
|
+
// Postgres port 54322) or any process already holding the port make the bind fail with
|
|
86
|
+
// EACCES ("access permissions") / EADDRINUSE — exactly what `docker compose up` fails on. Probing
|
|
87
|
+
// here lets us pick a working host port BEFORE compose tries and aborts the whole stack.
|
|
88
|
+
function canBindPort(port) {
|
|
89
|
+
return new Promise((res) => {
|
|
90
|
+
const srv = createServer();
|
|
91
|
+
srv.once('error', () => res(false));
|
|
92
|
+
srv.once('listening', () => srv.close(() => res(true)));
|
|
93
|
+
try {
|
|
94
|
+
srv.listen(port, '0.0.0.0');
|
|
95
|
+
} catch {
|
|
96
|
+
res(false);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Prefer the conventional port; if it's unavailable, fall back into the registered-port band
|
|
102
|
+
// (1024–49151, below the ephemeral range WinNAT reserves) rather than just incrementing — a
|
|
103
|
+
// reserved/used port often sits inside a contiguous block, so +1 tends to be taken too.
|
|
104
|
+
async function findAvailablePort(preferred, fallbackBase, taken) {
|
|
105
|
+
const candidates = [preferred];
|
|
106
|
+
for (let i = 0; i < 100; i++) candidates.push(fallbackBase + i);
|
|
107
|
+
for (const p of candidates) {
|
|
108
|
+
if (taken.has(p)) continue;
|
|
109
|
+
if (await canBindPort(p)) {
|
|
110
|
+
taken.add(p);
|
|
111
|
+
return p;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
taken.add(preferred);
|
|
115
|
+
return preferred; // give up gracefully — let compose surface the real error
|
|
116
|
+
}
|
|
117
|
+
|
|
82
118
|
function detectCompose() {
|
|
83
119
|
if (commandWorks('docker', ['compose', 'version'])) return { cmd: 'docker', args: ['compose'] };
|
|
84
120
|
if (commandWorks('docker-compose', ['version'])) return { cmd: 'docker-compose', args: [] };
|
|
@@ -138,6 +174,41 @@ async function main() {
|
|
|
138
174
|
const minioPassword = reuse('MINIO_ROOT_PASSWORD', generateSecret);
|
|
139
175
|
const bucket = readEnvValue(existing, 'STORAGE_BUCKET') || 'nextblock';
|
|
140
176
|
|
|
177
|
+
// Resolve host ports up front so a port that's in use or reserved by the OS (very common on
|
|
178
|
+
// Windows for the default Postgres port 54322) never aborts `docker compose up`. Reuse a port
|
|
179
|
+
// already encoded in .env — the explicit *_PORT* var, else the port parsed from a coupled URL —
|
|
180
|
+
// so re-runs stay stable and never disturb an already-running stack; only a FRESH install probes
|
|
181
|
+
// for free ports. Coupled ports (Kong/MinIO/app) also drive the URLs + loopback proxy below.
|
|
182
|
+
const takenPorts = new Set();
|
|
183
|
+
const reusedPort = (key, urlKey) => {
|
|
184
|
+
const direct = parseInt(readEnvValue(existing, key), 10);
|
|
185
|
+
if (Number.isInteger(direct) && direct > 0) return direct;
|
|
186
|
+
if (urlKey) {
|
|
187
|
+
const m = readEnvValue(existing, urlKey).match(/:(\d{2,5})(?:\/|$)/);
|
|
188
|
+
if (m) return parseInt(m[1], 10);
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
};
|
|
192
|
+
const resolvePort = async (key, preferred, fallbackBase, urlKey) => {
|
|
193
|
+
const prev = reusedPort(key, urlKey);
|
|
194
|
+
if (prev) {
|
|
195
|
+
takenPorts.add(prev);
|
|
196
|
+
return prev;
|
|
197
|
+
}
|
|
198
|
+
if (existing) {
|
|
199
|
+
// An existing .env with no recorded port: keep the default rather than reshuffle a setup
|
|
200
|
+
// the user may already be running.
|
|
201
|
+
takenPorts.add(preferred);
|
|
202
|
+
return preferred;
|
|
203
|
+
}
|
|
204
|
+
return findAvailablePort(preferred, fallbackBase, takenPorts);
|
|
205
|
+
};
|
|
206
|
+
const appPort = await resolvePort('APP_PORT', 3000, 13000, 'NEXT_PUBLIC_URL');
|
|
207
|
+
const kongPort = await resolvePort('KONG_HTTP_PORT', 8000, 18000, 'NEXT_PUBLIC_SUPABASE_URL');
|
|
208
|
+
const minioS3Port = await resolvePort('MINIO_S3_PORT', 9000, 19000, 'R2_S3_PUBLIC_ENDPOINT');
|
|
209
|
+
const minioConsolePort = await resolvePort('MINIO_CONSOLE_PORT', 9001, 19001, null);
|
|
210
|
+
const dbPort = await resolvePort('POSTGRES_PORT_EXTERNAL', 54322, 15432, null);
|
|
211
|
+
|
|
141
212
|
const replacements = {
|
|
142
213
|
POSTGRES_PASSWORD: `POSTGRES_PASSWORD=${postgresPassword}`,
|
|
143
214
|
POSTGRES_DB: 'POSTGRES_DB=postgres',
|
|
@@ -145,12 +216,22 @@ async function main() {
|
|
|
145
216
|
JWT_EXP: 'JWT_EXP=3600',
|
|
146
217
|
ANON_KEY: `ANON_KEY=${anonKey}`,
|
|
147
218
|
SERVICE_ROLE_KEY: `SERVICE_ROLE_KEY=${serviceRoleKey}`,
|
|
148
|
-
|
|
219
|
+
// Host port mappings (compose reads these) + the coupled public URLs, kept in lockstep so a
|
|
220
|
+
// remapped port stays consistent everywhere it's referenced.
|
|
221
|
+
APP_PORT: `APP_PORT=${appPort}`,
|
|
222
|
+
KONG_HTTP_PORT: `KONG_HTTP_PORT=${kongPort}`,
|
|
223
|
+
MINIO_S3_PORT: `MINIO_S3_PORT=${minioS3Port}`,
|
|
224
|
+
MINIO_CONSOLE_PORT: `MINIO_CONSOLE_PORT=${minioConsolePort}`,
|
|
225
|
+
POSTGRES_PORT_EXTERNAL: `POSTGRES_PORT_EXTERNAL=${dbPort}`,
|
|
226
|
+
// In-container loopback: server code hits localhost:<kongPort> / 127.0.0.1:<minioS3Port> and
|
|
227
|
+
// socat forwards to the real service. Left ports MUST match the URLs below.
|
|
228
|
+
LOOPBACK_PROXIES: `LOOPBACK_PROXIES=${kongPort}:kong:8000 ${minioS3Port}:minio:9000`,
|
|
229
|
+
NEXT_PUBLIC_SUPABASE_URL: `NEXT_PUBLIC_SUPABASE_URL=http://localhost:${kongPort}`,
|
|
149
230
|
NEXT_PUBLIC_SUPABASE_ANON_KEY: `NEXT_PUBLIC_SUPABASE_ANON_KEY=${anonKey}`,
|
|
150
231
|
SUPABASE_SERVICE_ROLE_KEY: `SUPABASE_SERVICE_ROLE_KEY=${serviceRoleKey}`,
|
|
151
|
-
API_EXTERNAL_URL:
|
|
152
|
-
SITE_URL:
|
|
153
|
-
NEXT_PUBLIC_URL:
|
|
232
|
+
API_EXTERNAL_URL: `API_EXTERNAL_URL=http://localhost:${kongPort}`,
|
|
233
|
+
SITE_URL: `SITE_URL=http://localhost:${appPort}`,
|
|
234
|
+
NEXT_PUBLIC_URL: `NEXT_PUBLIC_URL=http://localhost:${appPort}`,
|
|
154
235
|
NEXT_PUBLIC_IS_SANDBOX: 'NEXT_PUBLIC_IS_SANDBOX=true',
|
|
155
236
|
CRON_SECRET: `CRON_SECRET=${cronSecret}`,
|
|
156
237
|
DRAFT_MODE_SECRET: `DRAFT_MODE_SECRET=${draftSecret}`,
|
|
@@ -165,10 +246,10 @@ async function main() {
|
|
|
165
246
|
// port-scoped, so the browser would send the app's Supabase auth cookies to MinIO too — and
|
|
166
247
|
// MinIO rejects oversized header sets (MetadataTooLarge), breaking image display once cookies
|
|
167
248
|
// grow. 127.0.0.1 is a different cookie host, so the browser never sends them there.
|
|
168
|
-
R2_S3_PUBLIC_ENDPOINT:
|
|
249
|
+
R2_S3_PUBLIC_ENDPOINT: `R2_S3_PUBLIC_ENDPOINT=http://127.0.0.1:${minioS3Port}`,
|
|
169
250
|
R2_FORCE_PATH_STYLE: 'R2_FORCE_PATH_STYLE=true',
|
|
170
|
-
NEXT_PUBLIC_R2_BASE_URL: `NEXT_PUBLIC_R2_BASE_URL=http://127.0.0.1
|
|
171
|
-
NEXT_PUBLIC_R2_PUBLIC_URL: `NEXT_PUBLIC_R2_PUBLIC_URL=http://127.0.0.1
|
|
251
|
+
NEXT_PUBLIC_R2_BASE_URL: `NEXT_PUBLIC_R2_BASE_URL=http://127.0.0.1:${minioS3Port}/${bucket}`,
|
|
252
|
+
NEXT_PUBLIC_R2_PUBLIC_URL: `NEXT_PUBLIC_R2_PUBLIC_URL=http://127.0.0.1:${minioS3Port}/${bucket}`,
|
|
172
253
|
NEXT_PUBLIC_TURNSTILE_SITE_KEY: `NEXT_PUBLIC_TURNSTILE_SITE_KEY=${turnstileSiteKey}`,
|
|
173
254
|
TURNSTILE_SECRET_KEY: `TURNSTILE_SECRET_KEY=${turnstileSecretKey}`,
|
|
174
255
|
GOTRUE_MAILER_AUTOCONFIRM: `GOTRUE_MAILER_AUTOCONFIRM=${mailerAutoconfirm}`,
|
|
@@ -186,6 +267,19 @@ async function main() {
|
|
|
186
267
|
await writeFile(ENV_PATH, nextEnv, 'utf8');
|
|
187
268
|
console.log('✓ Wrote .env (Postgres, JWT secret + signed anon/service keys, MinIO, app secrets).\n');
|
|
188
269
|
|
|
270
|
+
const remapped = [
|
|
271
|
+
appPort !== 3000 && `app ${appPort} (default 3000)`,
|
|
272
|
+
kongPort !== 8000 && `Supabase API ${kongPort} (default 8000)`,
|
|
273
|
+
minioS3Port !== 9000 && `MinIO S3 ${minioS3Port} (default 9000)`,
|
|
274
|
+
minioConsolePort !== 9001 && `MinIO console ${minioConsolePort} (default 9001)`,
|
|
275
|
+
dbPort !== 54322 && `Postgres ${dbPort} (default 54322)`,
|
|
276
|
+
].filter(Boolean);
|
|
277
|
+
if (remapped.length) {
|
|
278
|
+
console.log(
|
|
279
|
+
`↔ Some default host ports were unavailable (in use, or reserved by the OS — common on Windows).\n Remapped to free ports: ${remapped.join(', ')}.\n`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
189
283
|
// A brand-new .env means brand-new secrets. Postgres only runs its init scripts (which set role
|
|
190
284
|
// passwords) on an EMPTY volume, so a leftover volume from a previous install would keep the old
|
|
191
285
|
// credentials and GoTrue/PostgREST could not log in. Reset volumes when the config is fresh.
|
|
@@ -202,10 +296,10 @@ async function main() {
|
|
|
202
296
|
await run(compose.cmd, [...compose.args, 'up', '-d', '--build'], { cwd: PROJECT_ROOT });
|
|
203
297
|
|
|
204
298
|
console.log('\n🎉 Stack is up!');
|
|
205
|
-
console.log(
|
|
206
|
-
console.log(
|
|
299
|
+
console.log(` 1. Open the app: http://localhost:${appPort}`);
|
|
300
|
+
console.log(` 2. Finish setup: complete the browser wizard at http://localhost:${appPort}/setup`);
|
|
207
301
|
console.log(' (creates your first admin — auto-confirmed, no email needed).');
|
|
208
|
-
console.log(
|
|
302
|
+
console.log(` 3. Supabase API: http://localhost:${kongPort} MinIO console: http://localhost:${minioConsolePort}`);
|
|
209
303
|
const composeStr = `${compose.cmd} ${compose.args.join(' ')}`.trim();
|
|
210
304
|
console.log(`\n Logs: ${composeStr} logs -f nextblock-cms | Stop: ${composeStr} down (add -v to wipe data)`);
|
|
211
305
|
}
|