-
Notifications
You must be signed in to change notification settings - Fork 71
Expand file tree
/
Copy pathgenerate-env-files.sh
More file actions
executable file
·418 lines (377 loc) · 11.4 KB
/
generate-env-files.sh
File metadata and controls
executable file
·418 lines (377 loc) · 11.4 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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
#!/usr/bin/env bash
set -e
command -v openssl >/dev/null 2>&1 || { echo >&2 "openssl is required but not installed."; exit 1; }
# Helper functions for random values
gen_hex() { openssl rand -hex "$1"; }
gen_str() { openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c "$1"; }
# Function to check if a string is a valid domain (not an IP address)
is_valid_domain() {
local domain="$1"
# Check if it's an IPv4 address
if [[ $domain =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
return 1
fi
# Check if it's an IPv6 address
if [[ $domain =~ ^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$ ]]; then
return 1
fi
# Basic domain validation (letters, numbers, dashes, dots)
if [[ $domain =~ ^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$ ]]; then
return 0
fi
return 1
}
# Parse arguments
dry_run=false
for arg in "$@"; do
if [ "$arg" = "--dry-run" ]; then
dry_run=true
fi
done
# Pre-generate all needed random values
ENCRYPTION_KEY=$(gen_hex 16) # 32 hex chars
ENCRYPTION_JWT_SECRET=$(gen_hex 32) # 64 hex chars
BACKEND_SECRET=$(gen_str 32)
SOKETI_SECRET=$(gen_str 32)
BULLBOARD_PASSWORD=$(gen_str 16)
PM2_SECRET=$(gen_str 32)
FIREBASE_SIGNER_KEY=$(openssl rand -base64 64 | sed 's/"/\\"/g')
FIREBASE_SALT_SEPARATOR=$(openssl rand -base64 16 | sed 's/"/\\"/g')
POSTGRES_HOST="postgres"
POSTGRES_USER="postgres"
POSTGRES_PASSWORD=$(openssl rand -hex 16)
POSTGRES_DB="ethernal"
# Generate md5-hashed password for PgBouncer (md5 + md5(PASSWORD + USERNAME))
HASH_INPUT="${POSTGRES_PASSWORD}${POSTGRES_USER}"
HASHED_PASS="md5$(echo -n "$HASH_INPUT" | md5sum | awk '{print $1}')"
echo ""
echo "######### Starting Ethernal Setup #########"
echo ""
# Prompt for values
read -p "Enter domain name (APEX, without www) or server IP address: " APP_URL
# Strip http:// or https:// from APP_URL if present
APP_URL=${APP_URL#http://}
APP_URL=${APP_URL#https://}
# Validate domain or IP
if ! is_valid_domain "$APP_URL" && \
! [[ $APP_URL =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && \
! [[ $APP_URL =~ ^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$ ]]; then
echo "Invalid domain/ip"
exit 1
fi
# Always ask for port, default to 80, validate
read -p "Enter port to serve the app on [80]: " EXPOSED_PORT
EXPOSED_PORT="${EXPOSED_PORT:-80}"
if ! [[ $EXPOSED_PORT =~ ^[0-9]+$ ]] || [ "$EXPOSED_PORT" -lt 1 ] || [ "$EXPOSED_PORT" -gt 65535 ]; then
echo "Invalid port"
exit 1
fi
# Set ETHERNAL_HOST based on EXPOSED_PORT
if [ "$EXPOSED_PORT" = "80" ]; then
ETHERNAL_HOST="$APP_URL"
else
ETHERNAL_HOST="$APP_URL:$EXPOSED_PORT"
fi
# Ask about SSL if domain is valid
SSL_ENABLED="true"
if is_valid_domain "$APP_URL"; then
read -p "Do you want to enable SSL (HTTPS) for this domain? [Y/n]: " ENABLE_SSL
case "$ENABLE_SSL" in
[nN]|[nN][oO])
SSL_ENABLED="false"
;;
*)
SSL_ENABLED="true"
;;
esac
fi
# Compose env file contents
BACKEND_ENV_CONTENT="ENCRYPTION_KEY=$ENCRYPTION_KEY
ENCRYPTION_JWT_SECRET=$ENCRYPTION_JWT_SECRET
SECRET=$BACKEND_SECRET
CORS_DOMAIN=*
NODE_ENV=production
REDIS_URL=redis://redis:6379
DB_USER=$POSTGRES_USER
DB_PASSWORD=$POSTGRES_PASSWORD
DB_NAME=$POSTGRES_DB
DB_HOST=pgbouncer
DB_PORT=5433
SOKETI_DEFAULT_APP_ID=default-app
SOKETI_DEFAULT_APP_KEY=app-key
SOKETI_DEFAULT_APP_SECRET=$SOKETI_SECRET
SOKETI_HOST=soketi
SOKETI_PORT=6001
PM2_HOST=pm2:9090
PM2_SECRET=$PM2_SECRET
BULLBOARD_USERNAME=ethernal
BULLBOARD_PASSWORD=$BULLBOARD_PASSWORD
APP_DOMAIN=$ETHERNAL_HOST
FIREBASE_SIGNER_KEY="\"$FIREBASE_SIGNER_KEY\""
FIREBASE_SALT_SEPARATOR="\"$FIREBASE_SALT_SEPARATOR\""
FIREBASE_ROUNDS=8
FIREBASE_MEM_COST=14
APP_URL=$APP_URL
SELF_HOSTED=true
PORT=8888
DEFAULT_PLAN_SLUG=self-hosted"
PM2_ENV_CONTENT="SECRET=$PM2_SECRET
ETHERNAL_SECRET=$BACKEND_SECRET
PORT=9090
ETHERNAL_REDIS_URL=redis://redis:6379/0
ETHERNAL_HOST=http://backend:8888"
POSTGRES_ENV_CONTENT="POSTGRES_HOST=postgres
POSTGRES_USER=$POSTGRES_USER
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
POSTGRES_DB=$POSTGRES_DB
POSTGRES_PORT=5432"
PGBOUNCER_ENV_CONTENT="PGBOUNCER_USER=$POSTGRES_USER
PGBOUNCER_PASSWORD=$POSTGRES_PASSWORD"
PGBOUNCER_USERLIST_CONTENT="\"${POSTGRES_USER}\" \"${HASHED_PASS}\""
PGBOUNCER_INIT_CONTENT="[databases]
${POSTGRES_DB} = host=postgres port=5432 dbname=${POSTGRES_DB} user=${POSTGRES_USER} password=${POSTGRES_PASSWORD}
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 5433
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
admin_users = ${POSTGRES_USER}
pool_mode = session
max_client_conn = 500
default_pool_size = 10
reserve_pool_size = 5
reserve_pool_timeout = 5.0
server_idle_timeout = 30.0
ignore_startup_parameters = extra_float_digits
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1"
append_to_gitignore() {
local paths=(
"run/.env.prod"
"pm2-server/.env.prod"
".env.docker-compose.prod"
".env.postgres.prod"
"pgbouncer/.env.pgbouncer.prod"
"pgbouncer/userlist.txt"
"pgbouncer/pgbouncer.ini"
)
for path in "${paths[@]}"; do
grep -qxF "$path" .gitignore 2>/dev/null || echo "$path" >> .gitignore
done
echo "Updated .gitignore with env/config files."
}
output_caddyfile() {
local caddy_staging=""
if [ "${CADDY_STAGING}" = "true" ]; then
caddy_staging=" acme_ca https://acme-staging-v02.api.letsencrypt.org/directory"
fi
# Shared Caddyfile body (reused in all cases)
local caddyfile_body
caddyfile_body=" handle /api/* {
reverse_proxy backend:8888
}
handle_path /bull* {
reverse_proxy backend:8888
}
# WebSocket traffic (Soketi)
handle /app* {
reverse_proxy soketi:6001 {
header_up Upgrade \"websocket\"
header_up Connection \"Upgrade\"
}
}
handle {
reverse_proxy frontend:8080
}
encode gzip"
if is_valid_domain "$APP_URL"; then
# Determine domain for Caddyfile (no port)
local domain_block
local apex_domain
apex_domain="$APP_URL"
if [ "$SSL_ENABLED" = "false" ]; then
domain_block="http://${apex_domain}, http://*.${apex_domain}"
else
domain_block="*.${apex_domain}, ${apex_domain}"
fi
local caddyfile_content
if [ "$SSL_ENABLED" = "false" ]; then
# HTTP only, no TLS, but with domain
caddyfile_content="{
auto_https off
}
${domain_block} {
${caddyfile_body}
}"
if [ "$dry_run" = true ]; then
printf '\n--- Caddyfile ---\n'
printf "%s\n" "$caddyfile_content"
echo "Printed Caddyfile for domain: ${domain_block} (HTTP only, no TLS, per user choice) (dry run)"
else
printf "%s\n" "$caddyfile_content" > Caddyfile
echo "Wrote Caddyfile for domain: ${domain_block} (HTTP only, no TLS, per user choice)"
fi
else
# SSL enabled (default)
caddyfile_content="{
on_demand_tls {
ask http://backend:8888/api/caddy/validDomain
}
${caddy_staging}
}
${domain_block} {
tls {
on_demand
}
${caddyfile_body}
}"
if [ "$dry_run" = true ]; then
printf '\n--- Caddyfile ---\n'
printf "%s\n" "$caddyfile_content"
echo "Printed Caddyfile for domain: ${domain_block} (dry run)"
else
printf "%s\n" "$caddyfile_content" > Caddyfile
echo "Wrote Caddyfile for domain: ${domain_block} (HTTPS with on-demand TLS)"
fi
fi
else
# Assume it's an IP address, always use :80 for HTTP-only Caddyfile
caddyfile_content=":80 {
${caddyfile_body}
}"
if [ "$dry_run" = true ]; then
printf '\n--- Caddyfile ---\n'
printf "%s\n" "$caddyfile_content"
echo "Printed Caddyfile for IP address: $ETHERNAL_HOST (HTTP only, no TLS) on port 80 (dry run)"
echo "WARNING: Serving over HTTP only. SSL/TLS is not available for IP addresses. Not recommended for production."
else
printf "%s\n" "$caddyfile_content" > Caddyfile
echo "Wrote Caddyfile for IP address: $ETHERNAL_HOST (HTTP only, no TLS) on port 80"
echo "WARNING: Serving over HTTP only. SSL/TLS is not available for IP addresses. Not recommended for production."
fi
fi
}
# Output functions
output_backend_env() {
if [ "$dry_run" = true ]; then
printf '\n--- run/.env.prod ---\n'
printf "%s\n" "$BACKEND_ENV_CONTENT"
echo "Printed run/.env.prod (dry run)"
else
mkdir -p run
printf "%s\n" "$BACKEND_ENV_CONTENT" > run/.env.prod
echo "Wrote run/.env.prod"
fi
}
output_pm2_env() {
if [ "$dry_run" = true ]; then
printf '\n--- pm2-server/.env.prod ---\n'
printf "%s\n" "$PM2_ENV_CONTENT"
echo "Printed pm2-server/.env.prod (dry run)"
else
mkdir -p pm2-server
printf "%s\n" "$PM2_ENV_CONTENT" > pm2-server/.env.prod
echo "Wrote pm2-server/.env.prod"
fi
}
output_docker_compose_env() {
local docker_env_content
if is_valid_domain "$APP_URL"; then
if [ "$EXPOSED_PORT" = "80" ]; then
docker_env_content="EXPOSED_PORT=80
EXPOSED_SSL_PORT=443"
else
if [ "$SSL_ENABLED" = "true" ]; then
docker_env_content="EXPOSED_PORT=80
EXPOSED_SSL_PORT=$EXPOSED_PORT"
else
docker_env_content="EXPOSED_PORT=$EXPOSED_PORT
EXPOSED_SSL_PORT=443"
fi
fi
else
docker_env_content="EXPOSED_PORT=$EXPOSED_PORT"
fi
if [ "$dry_run" = true ]; then
printf '\n--- .env.docker-compose.prod ---\n'
printf "%s\n" "$docker_env_content"
echo "Printed .env.docker-compose.prod (dry run)"
else
printf "%s\n" "$docker_env_content" > .env.docker-compose.prod
echo "Wrote .env.docker-compose.prod"
fi
}
output_postgres_env() {
if [ "$dry_run" = true ]; then
printf '\n--- .env.postgres.prod ---\n'
printf "%s\n" "$POSTGRES_ENV_CONTENT"
echo "Printed .env.postgres.prod (dry run)"
else
printf "%s\n" "$POSTGRES_ENV_CONTENT" > .env.postgres.prod
echo "Wrote .env.postgres.prod"
fi
}
output_pgbouncer_env() {
if [ "$dry_run" = true ]; then
printf '\n--- pgbouncer/.env.pgbouncer.prod ---\n'
printf "%s\n" "$PGBOUNCER_ENV_CONTENT"
echo "Printed pgbouncer/.env.pgbouncer.prod (dry run)"
else
mkdir -p pgbouncer
printf "%s\n" "$PGBOUNCER_ENV_CONTENT" > pgbouncer/.env.pgbouncer.prod
echo "Wrote pgbouncer/.env.pgbouncer.prod"
fi
}
output_pgbouncer_userlist() {
if [ "$dry_run" = true ]; then
printf '\n--- pgbouncer/userlist.txt ---\n'
printf "%s\n" "$PGBOUNCER_USERLIST_CONTENT"
echo "Printed pgbouncer/userlist.txt (dry run)"
else
mkdir -p pgbouncer
printf "%s\n" "$PGBOUNCER_USERLIST_CONTENT" > pgbouncer/userlist.txt
echo "Wrote pgbouncer/userlist.txt"
fi
}
output_pgbouncer_ini() {
if [ "$dry_run" = true ]; then
printf '\n--- pgbouncer/pgbouncer.ini ---\n'
printf "%s\n" "$PGBOUNCER_INIT_CONTENT"
echo "Printed pgbouncer/pgbouncer.ini (dry run)"
else
mkdir -p pgbouncer
printf "%s\n" "$PGBOUNCER_INIT_CONTENT" > pgbouncer/pgbouncer.ini
echo "Wrote pgbouncer/pgbouncer.ini"
fi
}
# Output soketi env file
output_soketi_env() {
local soketi_env_content="SOKETI_DEFAULT_APP_ID=default-app
SOKETI_DEFAULT_APP_KEY=app-key
SOKETI_DEFAULT_APP_SECRET=$SOKETI_SECRET
SOKETI_HOST=0.0.0.0
SOKETI_PORT=6001"
if [ "$dry_run" = true ]; then
printf '\n--- .env.soketi.prod ---\n'
printf "%s\n" "$soketi_env_content"
echo "Printed .env.soketi.prod (dry run)"
else
printf "%s\n" "$soketi_env_content" > .env.soketi.prod
echo "Wrote .env.soketi.prod"
fi
}
# Main output
output_backend_env
output_pm2_env
output_docker_compose_env
output_postgres_env
output_pgbouncer_env
output_pgbouncer_userlist
output_pgbouncer_ini
output_soketi_env
# Generate Caddyfile if ETHERNAL_HOST is a valid domain
output_caddyfile
if [ "$dry_run" = false ]; then
append_to_gitignore
fi