create-nextblock 0.8.11 → 0.9.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/bin/create-nextblock.js +91 -5
- package/docker-template/.dockerignore +23 -0
- package/docker-template/.env.docker.example +55 -0
- package/docker-template/Dockerfile +68 -0
- package/docker-template/docker/db/init/99-jwt.sql +6 -0
- package/docker-template/docker/db/init/99-roles.sql +25 -0
- package/docker-template/docker/kong/kong.yml +112 -0
- package/docker-template/docker/migrate/run-migrations.sh +51 -0
- package/docker-template/docker-compose.yml +215 -0
- package/docker-template/scripts/docker-setup.mjs +227 -0
- package/package.json +1 -1
- package/scripts/sync-template.js +29 -0
- package/templates/nextblock-template/.dockerignore +23 -0
- package/templates/nextblock-template/Dockerfile +68 -0
- package/templates/nextblock-template/app/api/upload/presigned-url/route.ts +9 -9
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -0
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -0
- package/templates/nextblock-template/docker/kong/kong.yml +112 -0
- package/templates/nextblock-template/docker/migrate/run-migrations.sh +51 -0
- package/templates/nextblock-template/docker-compose.yml +215 -0
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +5 -5
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +2 -2
- package/templates/nextblock-template/next.config.js +9 -0
- package/templates/nextblock-template/package.json +6 -2
- package/templates/nextblock-template/scripts/docker-setup.mjs +227 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Kong declarative (DB-less) gateway config for the NextBlock self-hosted stack.
|
|
2
|
+
#
|
|
3
|
+
# $SUPABASE_ANON_KEY and $SUPABASE_SERVICE_KEY are substituted at container start by the kong
|
|
4
|
+
# service entrypoint (docker-compose.yml) from the generated ANON_KEY / SERVICE_ROLE_KEY env.
|
|
5
|
+
# Trimmed to just the Auth (GoTrue) and PostgREST routes the app uses.
|
|
6
|
+
_format_version: '2.1'
|
|
7
|
+
_transform: true
|
|
8
|
+
|
|
9
|
+
consumers:
|
|
10
|
+
- username: anon
|
|
11
|
+
keyauth_credentials:
|
|
12
|
+
- key: $SUPABASE_ANON_KEY
|
|
13
|
+
- username: service_role
|
|
14
|
+
keyauth_credentials:
|
|
15
|
+
- key: $SUPABASE_SERVICE_KEY
|
|
16
|
+
|
|
17
|
+
acls:
|
|
18
|
+
- consumer: anon
|
|
19
|
+
group: anon
|
|
20
|
+
- consumer: service_role
|
|
21
|
+
group: admin
|
|
22
|
+
|
|
23
|
+
services:
|
|
24
|
+
- name: auth-v1-open
|
|
25
|
+
url: http://auth:9999/verify
|
|
26
|
+
routes:
|
|
27
|
+
- name: auth-v1-open
|
|
28
|
+
strip_path: true
|
|
29
|
+
paths:
|
|
30
|
+
- /auth/v1/verify
|
|
31
|
+
plugins:
|
|
32
|
+
- name: cors
|
|
33
|
+
- name: auth-v1-open-callback
|
|
34
|
+
url: http://auth:9999/callback
|
|
35
|
+
routes:
|
|
36
|
+
- name: auth-v1-open-callback
|
|
37
|
+
strip_path: true
|
|
38
|
+
paths:
|
|
39
|
+
- /auth/v1/callback
|
|
40
|
+
plugins:
|
|
41
|
+
- name: cors
|
|
42
|
+
- name: auth-v1-open-authorize
|
|
43
|
+
url: http://auth:9999/authorize
|
|
44
|
+
routes:
|
|
45
|
+
- name: auth-v1-open-authorize
|
|
46
|
+
strip_path: true
|
|
47
|
+
paths:
|
|
48
|
+
- /auth/v1/authorize
|
|
49
|
+
plugins:
|
|
50
|
+
- name: cors
|
|
51
|
+
|
|
52
|
+
- name: auth-v1
|
|
53
|
+
url: http://auth:9999/
|
|
54
|
+
routes:
|
|
55
|
+
- name: auth-v1-all
|
|
56
|
+
strip_path: true
|
|
57
|
+
paths:
|
|
58
|
+
- /auth/v1/
|
|
59
|
+
plugins:
|
|
60
|
+
- name: cors
|
|
61
|
+
- name: key-auth
|
|
62
|
+
config:
|
|
63
|
+
hide_credentials: false
|
|
64
|
+
- name: acl
|
|
65
|
+
config:
|
|
66
|
+
hide_groups_header: true
|
|
67
|
+
allow:
|
|
68
|
+
- admin
|
|
69
|
+
- anon
|
|
70
|
+
|
|
71
|
+
- name: rest-v1
|
|
72
|
+
url: http://rest:3000/
|
|
73
|
+
routes:
|
|
74
|
+
- name: rest-v1-all
|
|
75
|
+
strip_path: true
|
|
76
|
+
paths:
|
|
77
|
+
- /rest/v1/
|
|
78
|
+
plugins:
|
|
79
|
+
- name: cors
|
|
80
|
+
- name: key-auth
|
|
81
|
+
config:
|
|
82
|
+
hide_credentials: true
|
|
83
|
+
- name: acl
|
|
84
|
+
config:
|
|
85
|
+
hide_groups_header: true
|
|
86
|
+
allow:
|
|
87
|
+
- admin
|
|
88
|
+
- anon
|
|
89
|
+
|
|
90
|
+
- name: graphql-v1
|
|
91
|
+
url: http://rest:3000/rpc/graphql
|
|
92
|
+
routes:
|
|
93
|
+
- name: graphql-v1-all
|
|
94
|
+
strip_path: true
|
|
95
|
+
paths:
|
|
96
|
+
- /graphql/v1
|
|
97
|
+
plugins:
|
|
98
|
+
- name: cors
|
|
99
|
+
- name: key-auth
|
|
100
|
+
config:
|
|
101
|
+
hide_credentials: false
|
|
102
|
+
- name: request-transformer
|
|
103
|
+
config:
|
|
104
|
+
add:
|
|
105
|
+
headers:
|
|
106
|
+
- 'Content-Profile: graphql_public'
|
|
107
|
+
- name: acl
|
|
108
|
+
config:
|
|
109
|
+
hide_groups_header: true
|
|
110
|
+
allow:
|
|
111
|
+
- admin
|
|
112
|
+
- anon
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Applies the NextBlock SQL migrations (mounted at /migrations) in chronological filename order,
|
|
3
|
+
# once GoTrue has provisioned the auth schema (profiles etc. FK to auth.users). Idempotent: a
|
|
4
|
+
# tracking table records applied versions so restarts never re-run a migration. Each file runs
|
|
5
|
+
# in a single transaction (ON_ERROR_STOP).
|
|
6
|
+
set -eu
|
|
7
|
+
|
|
8
|
+
PGHOST="${POSTGRES_HOST:-db}"
|
|
9
|
+
PGPORT="${POSTGRES_PORT:-5432}"
|
|
10
|
+
PGUSER="postgres"
|
|
11
|
+
PGDATABASE="${POSTGRES_DB:-postgres}"
|
|
12
|
+
export PGPASSWORD="${POSTGRES_PASSWORD}"
|
|
13
|
+
|
|
14
|
+
psql_cmd() {
|
|
15
|
+
psql -v ON_ERROR_STOP=1 -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" "$@"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
echo "[migrate] waiting for Postgres at ${PGHOST}:${PGPORT}..."
|
|
19
|
+
until psql_cmd -c 'select 1;' >/dev/null 2>&1; do
|
|
20
|
+
sleep 2
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
echo "[migrate] waiting for GoTrue to create auth.users..."
|
|
24
|
+
until [ "$(psql_cmd -tAc "select to_regclass('auth.users') is not null;")" = "t" ]; do
|
|
25
|
+
sleep 2
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
psql_cmd -c "create table if not exists public._nextblock_docker_migrations (
|
|
29
|
+
version text primary key,
|
|
30
|
+
applied_at timestamptz not null default now()
|
|
31
|
+
);"
|
|
32
|
+
|
|
33
|
+
applied_any=0
|
|
34
|
+
for file in $(ls /migrations/*.sql | sort); do
|
|
35
|
+
version="$(basename "$file" .sql)"
|
|
36
|
+
already="$(psql_cmd -tAc "select 1 from public._nextblock_docker_migrations where version = '${version}';")"
|
|
37
|
+
if [ "$already" = "1" ]; then
|
|
38
|
+
echo "[migrate] skip ${version} (already applied)"
|
|
39
|
+
continue
|
|
40
|
+
fi
|
|
41
|
+
echo "[migrate] applying ${version}"
|
|
42
|
+
psql_cmd --single-transaction -f "$file"
|
|
43
|
+
psql_cmd -c "insert into public._nextblock_docker_migrations (version) values ('${version}');"
|
|
44
|
+
applied_any=1
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
if [ "$applied_any" = "1" ]; then
|
|
48
|
+
echo "[migrate] migrations applied successfully."
|
|
49
|
+
else
|
|
50
|
+
echo "[migrate] database already up to date."
|
|
51
|
+
fi
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Self-hosted NextBlock CMS stack for a standalone project. Generated/driven by
|
|
2
|
+
# `npm run docker:setup`, which writes the .env this file reads. Mirrors the production Supabase
|
|
3
|
+
# topology: Postgres + GoTrue + PostgREST behind a Kong gateway, plus MinIO for S3 media.
|
|
4
|
+
name: nextblock
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
db:
|
|
8
|
+
image: ${SUPABASE_DB_IMAGE:-supabase/postgres:15.8.1.085}
|
|
9
|
+
restart: unless-stopped
|
|
10
|
+
environment:
|
|
11
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
12
|
+
POSTGRES_DB: ${POSTGRES_DB:-postgres}
|
|
13
|
+
JWT_SECRET: ${JWT_SECRET}
|
|
14
|
+
JWT_EXP: ${JWT_EXP:-3600}
|
|
15
|
+
volumes:
|
|
16
|
+
- nextblock_db_store:/var/lib/postgresql/data
|
|
17
|
+
- ./docker/db/init/99-roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:ro
|
|
18
|
+
- ./docker/db/init/99-jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:ro
|
|
19
|
+
healthcheck:
|
|
20
|
+
test: ['CMD', 'pg_isready', '-U', 'postgres', '-h', 'localhost']
|
|
21
|
+
interval: 5s
|
|
22
|
+
timeout: 5s
|
|
23
|
+
retries: 12
|
|
24
|
+
# supabase/postgres runs a heavy first-init; give it time before counting health failures.
|
|
25
|
+
start_period: 60s
|
|
26
|
+
ports:
|
|
27
|
+
- '${POSTGRES_PORT_EXTERNAL:-54322}:5432'
|
|
28
|
+
|
|
29
|
+
auth:
|
|
30
|
+
image: ${SUPABASE_GOTRUE_IMAGE:-supabase/gotrue:v2.189.0}
|
|
31
|
+
restart: unless-stopped
|
|
32
|
+
depends_on:
|
|
33
|
+
db:
|
|
34
|
+
condition: service_healthy
|
|
35
|
+
environment:
|
|
36
|
+
GOTRUE_API_HOST: 0.0.0.0
|
|
37
|
+
GOTRUE_API_PORT: 9999
|
|
38
|
+
API_EXTERNAL_URL: ${API_EXTERNAL_URL:-http://localhost:8000}
|
|
39
|
+
GOTRUE_DB_DRIVER: postgres
|
|
40
|
+
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-postgres}
|
|
41
|
+
GOTRUE_SITE_URL: ${SITE_URL:-http://localhost:3000}
|
|
42
|
+
GOTRUE_URI_ALLOW_LIST: '*'
|
|
43
|
+
GOTRUE_DISABLE_SIGNUP: 'false'
|
|
44
|
+
GOTRUE_JWT_ADMIN_ROLES: service_role
|
|
45
|
+
GOTRUE_JWT_AUD: authenticated
|
|
46
|
+
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
|
47
|
+
GOTRUE_JWT_EXP: ${JWT_EXP:-3600}
|
|
48
|
+
GOTRUE_JWT_SECRET: ${JWT_SECRET}
|
|
49
|
+
GOTRUE_JWT_ISSUER: ${API_EXTERNAL_URL:-http://localhost:8000}/auth/v1
|
|
50
|
+
GOTRUE_EXTERNAL_EMAIL_ENABLED: 'true'
|
|
51
|
+
GOTRUE_MAILER_AUTOCONFIRM: ${GOTRUE_MAILER_AUTOCONFIRM:-true}
|
|
52
|
+
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_FROM_EMAIL:-admin@example.com}
|
|
53
|
+
GOTRUE_SMTP_HOST: ${SMTP_HOST:-}
|
|
54
|
+
# Must be a valid integer even when SMTP is skipped (GoTrue parses it as int and crashes on
|
|
55
|
+
# ''). With no host + autoconfirm on, no mail is sent, so the value is otherwise inert.
|
|
56
|
+
GOTRUE_SMTP_PORT: ${SMTP_PORT:-2500}
|
|
57
|
+
GOTRUE_SMTP_USER: ${SMTP_USER:-}
|
|
58
|
+
GOTRUE_SMTP_PASS: ${SMTP_PASS:-}
|
|
59
|
+
GOTRUE_SMTP_SENDER_NAME: ${SMTP_FROM_NAME:-NextBlock}
|
|
60
|
+
GOTRUE_MAILER_URLPATHS_CONFIRMATION: /auth/v1/verify
|
|
61
|
+
GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/v1/verify
|
|
62
|
+
GOTRUE_MAILER_URLPATHS_INVITE: /auth/v1/verify
|
|
63
|
+
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/v1/verify
|
|
64
|
+
healthcheck:
|
|
65
|
+
test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:9999/health']
|
|
66
|
+
interval: 5s
|
|
67
|
+
timeout: 5s
|
|
68
|
+
retries: 15
|
|
69
|
+
|
|
70
|
+
rest:
|
|
71
|
+
image: ${SUPABASE_POSTGREST_IMAGE:-postgrest/postgrest:v12.2.12}
|
|
72
|
+
restart: unless-stopped
|
|
73
|
+
depends_on:
|
|
74
|
+
db:
|
|
75
|
+
condition: service_healthy
|
|
76
|
+
environment:
|
|
77
|
+
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-postgres}
|
|
78
|
+
PGRST_DB_SCHEMAS: public,graphql_public
|
|
79
|
+
PGRST_DB_ANON_ROLE: anon
|
|
80
|
+
PGRST_JWT_SECRET: ${JWT_SECRET}
|
|
81
|
+
PGRST_DB_USE_LEGACY_GUCS: 'false'
|
|
82
|
+
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
|
|
83
|
+
PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXP:-3600}
|
|
84
|
+
PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
|
|
85
|
+
|
|
86
|
+
kong:
|
|
87
|
+
image: ${SUPABASE_KONG_IMAGE:-kong:2.8.1}
|
|
88
|
+
restart: unless-stopped
|
|
89
|
+
depends_on:
|
|
90
|
+
auth:
|
|
91
|
+
condition: service_started
|
|
92
|
+
rest:
|
|
93
|
+
condition: service_started
|
|
94
|
+
environment:
|
|
95
|
+
KONG_DATABASE: 'off'
|
|
96
|
+
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
|
|
97
|
+
KONG_DNS_ORDER: LAST,A,CNAME
|
|
98
|
+
KONG_PLUGINS: request-transformer,cors,key-auth,acl
|
|
99
|
+
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
|
|
100
|
+
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
|
|
101
|
+
SUPABASE_ANON_KEY: ${ANON_KEY}
|
|
102
|
+
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
|
103
|
+
volumes:
|
|
104
|
+
- ./docker/kong/kong.yml:/home/kong/temp.yml:ro
|
|
105
|
+
entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
|
|
106
|
+
ports:
|
|
107
|
+
- '${KONG_HTTP_PORT:-8000}:8000'
|
|
108
|
+
|
|
109
|
+
minio:
|
|
110
|
+
image: ${MINIO_IMAGE:-minio/minio:latest}
|
|
111
|
+
restart: unless-stopped
|
|
112
|
+
command: server /data --console-address ":9001"
|
|
113
|
+
environment:
|
|
114
|
+
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
|
115
|
+
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
|
116
|
+
volumes:
|
|
117
|
+
- nextblock_media:/data
|
|
118
|
+
ports:
|
|
119
|
+
- '${MINIO_S3_PORT:-9000}:9000'
|
|
120
|
+
- '${MINIO_CONSOLE_PORT:-9001}:9001'
|
|
121
|
+
|
|
122
|
+
minio-init:
|
|
123
|
+
image: ${MINIO_MC_IMAGE:-minio/mc:latest}
|
|
124
|
+
restart: 'no'
|
|
125
|
+
depends_on:
|
|
126
|
+
minio:
|
|
127
|
+
condition: service_started
|
|
128
|
+
environment:
|
|
129
|
+
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
|
130
|
+
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
|
131
|
+
STORAGE_BUCKET: ${STORAGE_BUCKET:-nextblock}
|
|
132
|
+
entrypoint: >
|
|
133
|
+
/bin/sh -c "
|
|
134
|
+
until mc alias set local http://minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD; do echo 'waiting for minio...'; sleep 2; done &&
|
|
135
|
+
mc mb --ignore-existing local/$$STORAGE_BUCKET &&
|
|
136
|
+
mc anonymous set download local/$$STORAGE_BUCKET &&
|
|
137
|
+
echo 'minio bucket ready'
|
|
138
|
+
"
|
|
139
|
+
|
|
140
|
+
migrate:
|
|
141
|
+
image: ${MIGRATE_IMAGE:-postgres:15-alpine}
|
|
142
|
+
restart: 'no'
|
|
143
|
+
depends_on:
|
|
144
|
+
db:
|
|
145
|
+
condition: service_healthy
|
|
146
|
+
auth:
|
|
147
|
+
condition: service_healthy
|
|
148
|
+
environment:
|
|
149
|
+
POSTGRES_HOST: db
|
|
150
|
+
POSTGRES_PORT: 5432
|
|
151
|
+
POSTGRES_DB: ${POSTGRES_DB:-postgres}
|
|
152
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
153
|
+
volumes:
|
|
154
|
+
- ./supabase/migrations:/migrations:ro
|
|
155
|
+
- ./docker/migrate/run-migrations.sh:/run-migrations.sh:ro
|
|
156
|
+
entrypoint: ['sh', '/run-migrations.sh']
|
|
157
|
+
|
|
158
|
+
nextblock-cms:
|
|
159
|
+
build:
|
|
160
|
+
context: .
|
|
161
|
+
dockerfile: Dockerfile
|
|
162
|
+
args:
|
|
163
|
+
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL:-http://localhost:8000}
|
|
164
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
|
|
165
|
+
NEXT_PUBLIC_URL: ${NEXT_PUBLIC_URL:-http://localhost:3000}
|
|
166
|
+
NEXT_PUBLIC_R2_PUBLIC_URL: ${NEXT_PUBLIC_R2_PUBLIC_URL}
|
|
167
|
+
NEXT_PUBLIC_R2_BASE_URL: ${NEXT_PUBLIC_R2_BASE_URL}
|
|
168
|
+
NEXT_PUBLIC_TURNSTILE_SITE_KEY: ${NEXT_PUBLIC_TURNSTILE_SITE_KEY:-}
|
|
169
|
+
NEXT_PUBLIC_IS_SANDBOX: ${NEXT_PUBLIC_IS_SANDBOX:-true}
|
|
170
|
+
restart: unless-stopped
|
|
171
|
+
depends_on:
|
|
172
|
+
migrate:
|
|
173
|
+
condition: service_completed_successfully
|
|
174
|
+
kong:
|
|
175
|
+
condition: service_started
|
|
176
|
+
minio-init:
|
|
177
|
+
condition: service_completed_successfully
|
|
178
|
+
environment:
|
|
179
|
+
NODE_ENV: production
|
|
180
|
+
PORT: 3000
|
|
181
|
+
HOSTNAME: 0.0.0.0
|
|
182
|
+
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL:-http://localhost:8000}
|
|
183
|
+
SUPABASE_INTERNAL_URL: ${SUPABASE_INTERNAL_URL:-http://kong:8000}
|
|
184
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
|
|
185
|
+
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
|
|
186
|
+
NEXT_PUBLIC_URL: ${NEXT_PUBLIC_URL:-http://localhost:3000}
|
|
187
|
+
NEXT_PUBLIC_IS_SANDBOX: ${NEXT_PUBLIC_IS_SANDBOX:-true}
|
|
188
|
+
CRON_SECRET: ${CRON_SECRET}
|
|
189
|
+
DRAFT_MODE_SECRET: ${DRAFT_MODE_SECRET}
|
|
190
|
+
REVALIDATE_SECRET_TOKEN: ${REVALIDATE_SECRET_TOKEN}
|
|
191
|
+
CORTEX_AI_ENCRYPTION_KEY: ${CORTEX_AI_ENCRYPTION_KEY:-}
|
|
192
|
+
R2_ACCOUNT_ID: ${R2_ACCOUNT_ID:-minio}
|
|
193
|
+
R2_BUCKET_NAME: ${STORAGE_BUCKET:-nextblock}
|
|
194
|
+
R2_ACCESS_KEY_ID: ${MINIO_ROOT_USER}
|
|
195
|
+
R2_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD}
|
|
196
|
+
R2_REGION: ${R2_REGION:-us-east-1}
|
|
197
|
+
R2_S3_ENDPOINT: ${R2_S3_ENDPOINT:-http://minio:9000}
|
|
198
|
+
R2_S3_PUBLIC_ENDPOINT: ${R2_S3_PUBLIC_ENDPOINT:-http://localhost:9000}
|
|
199
|
+
R2_FORCE_PATH_STYLE: 'true'
|
|
200
|
+
NEXT_PUBLIC_R2_PUBLIC_URL: ${NEXT_PUBLIC_R2_PUBLIC_URL}
|
|
201
|
+
NEXT_PUBLIC_R2_BASE_URL: ${NEXT_PUBLIC_R2_BASE_URL}
|
|
202
|
+
SMTP_HOST: ${SMTP_HOST:-}
|
|
203
|
+
SMTP_PORT: ${SMTP_PORT:-}
|
|
204
|
+
SMTP_USER: ${SMTP_USER:-}
|
|
205
|
+
SMTP_PASS: ${SMTP_PASS:-}
|
|
206
|
+
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
|
|
207
|
+
SMTP_FROM_NAME: ${SMTP_FROM_NAME:-}
|
|
208
|
+
TURNSTILE_SECRET_KEY: ${TURNSTILE_SECRET_KEY:-}
|
|
209
|
+
NEXT_PUBLIC_TURNSTILE_SITE_KEY: ${NEXT_PUBLIC_TURNSTILE_SITE_KEY:-}
|
|
210
|
+
ports:
|
|
211
|
+
- '${APP_PORT:-3000}:3000'
|
|
212
|
+
|
|
213
|
+
volumes:
|
|
214
|
+
nextblock_db_store:
|
|
215
|
+
nextblock_media:
|
|
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
3
3
|
vi.mock('server-only', () => ({}));
|
|
4
4
|
|
|
5
5
|
const mocks = vi.hoisted(() => ({
|
|
6
|
-
|
|
6
|
+
getS3PresignClientMock: vi.fn(),
|
|
7
7
|
getSignedUrlMock: vi.fn(),
|
|
8
8
|
getUserMock: vi.fn(),
|
|
9
9
|
profileSingleMock: vi.fn(),
|
|
@@ -28,7 +28,7 @@ vi.mock('@nextblock-cms/db/server', () => ({
|
|
|
28
28
|
}));
|
|
29
29
|
|
|
30
30
|
vi.mock('@nextblock-cms/utils/server', () => ({
|
|
31
|
-
|
|
31
|
+
getS3PresignClient: mocks.getS3PresignClientMock,
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
34
|
vi.mock('@aws-sdk/client-s3', () => ({
|
|
@@ -82,7 +82,7 @@ describe('custom block R2 presigned upload flow', () => {
|
|
|
82
82
|
);
|
|
83
83
|
|
|
84
84
|
expect(response.status).toBe(401);
|
|
85
|
-
expect(mocks.
|
|
85
|
+
expect(mocks.getS3PresignClientMock).not.toHaveBeenCalled();
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('rejects oversized files for authorized writers', async () => {
|
|
@@ -103,13 +103,13 @@ describe('custom block R2 presigned upload flow', () => {
|
|
|
103
103
|
|
|
104
104
|
expect(response.status).toBe(400);
|
|
105
105
|
expect(payload.error).toContain('10 MB');
|
|
106
|
-
expect(mocks.
|
|
106
|
+
expect(mocks.getS3PresignClientMock).not.toHaveBeenCalled();
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
it('returns a direct PUT upload space for authorized writers', async () => {
|
|
110
110
|
mocks.getUserMock.mockResolvedValueOnce({ data: { user: { id: 'user-1' } }, error: null });
|
|
111
111
|
mocks.profileSingleMock.mockResolvedValueOnce({ data: { role: 'ADMIN' }, error: null });
|
|
112
|
-
mocks.
|
|
112
|
+
mocks.getS3PresignClientMock.mockResolvedValueOnce({ send: vi.fn() });
|
|
113
113
|
mocks.getSignedUrlMock.mockResolvedValueOnce('https://r2.example.test/presigned-put');
|
|
114
114
|
|
|
115
115
|
const response = await POST(
|
|
@@ -2,7 +2,7 @@ import 'server-only';
|
|
|
2
2
|
|
|
3
3
|
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
|
4
4
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
5
|
-
import {
|
|
5
|
+
import { getS3PresignClient } from '@nextblock-cms/utils/server';
|
|
6
6
|
|
|
7
7
|
import { resolveMediaUrl } from './media/resolveMediaUrl';
|
|
8
8
|
import {
|
|
@@ -35,7 +35,7 @@ export async function createR2PresignedUpload(
|
|
|
35
35
|
throw new R2PresignedUploadError('File uploads are not configured on this server.', 500);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const s3Client = await
|
|
38
|
+
const s3Client = await getS3PresignClient();
|
|
39
39
|
if (!s3Client) {
|
|
40
40
|
throw new R2PresignedUploadError('File uploads are not configured on this server.', 500);
|
|
41
41
|
}
|
|
@@ -81,8 +81,17 @@ const securityHeaders = [
|
|
|
81
81
|
},
|
|
82
82
|
];
|
|
83
83
|
|
|
84
|
+
// Self-hosted Docker images build a standalone server (`node apps/nextblock/server.js`) instead
|
|
85
|
+
// of `next start` + a full node_modules tree. Gated on DOCKER_BUILD so Vercel/cloud builds are
|
|
86
|
+
// completely untouched. outputFileTracingRoot is pinned to the monorepo root so the standalone
|
|
87
|
+
// output nests predictably under apps/nextblock (see the root Dockerfile runner stage).
|
|
88
|
+
const isDockerStandalone = process.env.DOCKER_BUILD === 'true';
|
|
89
|
+
|
|
84
90
|
/** @type {import('next').NextConfig} */
|
|
85
91
|
const nextConfig = {
|
|
92
|
+
...(isDockerStandalone
|
|
93
|
+
? { output: 'standalone', outputFileTracingRoot: path.join(__dirname, '../../') }
|
|
94
|
+
: {}),
|
|
86
95
|
experimental: {
|
|
87
96
|
optimizePackageImports: ['@nextblock-cms/ui', '@nextblock-cms/utils'],
|
|
88
97
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextblock-cms/template",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "next dev",
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
"start": "next start",
|
|
9
9
|
"lint": "next lint",
|
|
10
10
|
"deploy:supabase": "node tools/deploy-supabase.js",
|
|
11
|
-
"configure:supabase-auth": "node tools/configure-supabase-auth.js"
|
|
11
|
+
"configure:supabase-auth": "node tools/configure-supabase-auth.js",
|
|
12
|
+
"docker:setup": "node scripts/docker-setup.mjs",
|
|
13
|
+
"docker:up": "docker compose up -d --build",
|
|
14
|
+
"docker:down": "docker compose down",
|
|
15
|
+
"docker:logs": "docker compose logs -f nextblock-cms"
|
|
12
16
|
},
|
|
13
17
|
"dependencies": {
|
|
14
18
|
"@ai-sdk/openai-compatible": "^2.0.42",
|