-
-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathdocker-entrypoint.sh
More file actions
executable file
·424 lines (365 loc) · 12.1 KB
/
docker-entrypoint.sh
File metadata and controls
executable file
·424 lines (365 loc) · 12.1 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
419
420
421
422
423
424
#!/bin/bash
set -eo pipefail
# _log prints a timestamped (ISO 8601), color-coded structured log message.
# The message includes a log level and the message itself.
# If <MESSAGE> is omitted, it reads from stdin, allowing multi-line input.
#
# Arguments:
# $1 - <LEVEL> : Log level (e.g., Warn, Error, Debug)
# $2 - <MESSAGE> : Message to log; if omitted, the function reads from stdin
#
# Usage:
# _log <LEVEL> [MESSAGE]
# _log Warn "Disk space low"
# echo "Database connection lost" | _log Error
#
# Output:
# 2025-10-16T12:34:56+00:00 [Warn] [Entrypoint] Disk space low
# 2025-10-16T12:35:01+00:00 [Error] [Entrypoint] Database connection lost
_log() {
local level="$1"; shift
local dt
dt="$(date --rfc-3339=seconds)"
local color_reset="\033[0m"
local color=""
case "$level" in
Warn) color="\033[1;33m" ;; # yellow
Error) color="\033[1;31m" ;; # red
Debug) color="\033[1;34m" ;; # blue
esac
local msg="$*"
if [ "$#" -eq 0 ]; then
msg="$(cat)"
fi
printf '%b%s [%s] [Entrypoint] %s%b\n' "$color" "$dt" "$level" "$msg" "$color_reset"
}
# _dbg logs a message of type 'Debug' using log.
_dbg() {
_log Debug "$@"
}
# mysql_note logs a message of type 'Note' using log.
note() {
_log Note "$@"
}
# mysql_warn logs a message of type 'Warning' using log and writes to stderr.
warn() {
_log Warn "$@" >&2
}
# log_error logs a message of type 'ERROR' using log, writes to stderr, prints a container removal hint, and
# exits with status 1.
log_error() {
_log Error "$@" >&2
note "Remove this container with 'docker rm -f <container_name>' before retrying"
exit 1
}
# exec_sql executes a local SQL query, retrying until success or timeout. Ensures reliability during slow
# container or resource startup. On timeout, it prints the provided error prefix followed by filtered Doltgres output.
# Errors are parsed to remove blank lines and extract only relevant error text. Use --show-result to display successful
# query results.
#
# Usage:
# exec_sql [--show-result] "<ERROR_MESSAGE>" "<QUERY>"
# exec_sql [--show-result] "<ERROR_MESSAGE>" < /docker-entrypoint-initdb.d/init.sql
# cat /docker-entrypoint-initdb.d/init.sql | exec_sql [--show-result] "<ERROR_MESSAGE>"
#
# Output:
# Prints query output only if --show-result is specified.
exec_sql() {
local show_result=0
if [ "$1" = "--show-result" ]; then
show_result=1
shift
fi
local error_message="$1"
local query="${2:-}"
local timeout="${DOLTGRES_SERVER_TIMEOUT:-300}"
local start_time now output status
start_time=$(date +%s)
echo "running psql command found in: "
echo $(which psql)
while true; do
if [ -n "$query" ]; then
set +e
output=$(PGPASSWORD=password psql -h 127.0.0.1 -U postgres -c "$query" 2>&1)
status=$?
set -e
else
set +e # tmp disabled to initdb.d/ file err
output=$(PGPASSWORD=password psql -h 127.0.0.1 -U postgres < /dev/stdin 2>&1)
status=$?
set -e
fi
if [ "$status" -eq 0 ]; then
[ "$show_result" -eq 1 ] && echo "$output" | grep -v "^$" || true
return 0
fi
if echo "$output" | grep -qiE "Error [0-9]+ \([A-Z0-9]+\)"; then
log_error "$error_message$(echo "$output" | grep -iE "Error|error")"
fi
if [ "$timeout" -ne 0 ]; then
now=$(date +%s)
if [ $((now - start_time)) -ge "$timeout" ]; then
log_error "$error_message$(echo "$output" | grep -iE "Error|error" || true)"
fi
fi
sleep 1
done
}
CONTAINER_DATA_DIR="/var/lib/doltgres"
INIT_COMPLETED="$CONTAINER_DATA_DIR/.init_completed"
SERVER_CONFIG_DIR="/etc/doltgres/servercfg.d"
SERVER_PID=-1
# check_for_doltgres_binary verifies that the dolt binary is present and executable in the system PATH.
# If not found or not executable, it logs an error and exits.
check_for_doltgres_binary() {
local doltgres_bin
doltgres_bin=$(which doltgres)
if [ ! -x "$doltgres_bin" ]; then
log_error "doltgres binary executable not found"
fi
}
# get_env_var returns the value of an environment variable, preferring DOLTGRES_* over POSTGRES_*.
# Arguments:
# $1 - The base variable name (e.g., "USER" for POSTGRES_USER or DOLTGRES_USER)
# Output:
# Prints the value of the first set variable, or an empty string if neither is set.
get_env_var() {
local var_name="$1"
local doltgres_var="DOLTGRES_${var_name}"
local postgres_var="POSTGRES_${var_name}"
if [ -n "${!doltgres_var}" ]; then
echo "${!doltgres_var}"
elif [ -n "${!postgres_var}" ]; then
echo "${!postgres_var}"
else
echo ""
fi
}
# get_pgdata_var returns the value of either the DOLTGRES_DATA var or the PGDATA var, in that order.
get_pgdata_var() {
if [ -n "${DOLTGRES_DATA}" ]; then
echo "${DOLTGRES_DATA}"
elif [ -n "${PGDATA}" ]; then
echo "${PGDATA}"
else
echo ""
fi
}
# get_env_var_name returns the name of the environment variable that is set, preferring DOLTGRES_* over POSTGRES_*.
# Arguments:
# $1 - The base variable name (e.g., "USER" for POSTGRES_USER or DOLTGRES_USER)
# Output:
# Prints the name of the first set variable, or both names if neither is set.
get_env_var_name() {
local var_name="$1"
local doltgres_var="DOLTGRES_${var_name}"
local postgres_var="POSTGRES_${var_name}"
if [ -n "${!doltgres_var}" ]; then
echo "DOLTGRES_${var_name}"
elif [ -n "${!postgres_var}" ]; then
echo "POSTGRES_${var_name}"
else
echo "POSTGRES_${var_name}/DOLTGRES_${var_name}"
fi
}
# get_config_file_path_if_exists checks for config files of a given type in a directory.
# Arguments:
# $1 - Directory to search in
# $2 - File type/extension to search for (e.g., 'json', 'yaml')
# Output:
# Sets CONFIG_PROVIDED to the path of the config file if exactly one is found, or empty otherwise.
# Logs a warning if multiple config files are found and uses the default config.
get_config_file_path_if_exists() {
CONFIG_PROVIDED=
local CONFIG_DIR=$1
local FILE_TYPE=$2
if [ -d "$CONFIG_DIR" ]; then
note "Checking for config provided in $CONFIG_DIR"
local number_of_files_found
number_of_files_found=$(find "$CONFIG_DIR" -type f -name "*.$FILE_TYPE" | wc -l)
if [ "$number_of_files_found" -gt 1 ]; then
CONFIG_PROVIDED=
warn "Multiple config files found in $CONFIG_DIR, using default config"
elif [ "$number_of_files_found" -eq 1 ]; then
local files_found
files_found=$(ls "$CONFIG_DIR"/*."$FILE_TYPE")
note "$files_found file is found"
CONFIG_PROVIDED=$files_found
else
CONFIG_PROVIDED=
fi
else
note "No config dir found in $CONFIG_DIR"
fi
}
# docker_process_init_files Runs files found in /docker-entrypoint-initdb.d before the server is started.
# Taken from https://github.com/docker-library/mysql/blob/master/8.0/docker-entrypoint.sh
# Usage:
# docker_process_init_files [file [file ...]]
# e.g., docker_process_init_files /always-initdb.d/*
# Processes initializer files based on file extensions.
docker_process_init_files() {
local f
echo
for f; do
case "$f" in
*.sh)
if [ -x "$f" ]; then
note "$0: running $f"
if ! "$f"; then
log_error "Failed to execute $f: "
fi
else
note "$0: sourcing $f"
if ! . "$f"; then
log_error "Failed to execute $f: "
fi
fi
;;
*.sql)
note "$0: running $f"
exec_sql --show-result "Failed to execute $f: " < "$f"
;;
*.sql.bz2)
note "$0: running $f"
bunzip2 -c "$f" | exec_sql --show-result "Failed to execute $f: "
;;
*.sql.gz)
note "$0: running $f"
gunzip -c "$f" | exec_sql --show-result "Failed to execute $f: "
;;
*.sql.xz)
note "$0: running $f"
xzcat "$f" | exec_sql --show-result "Failed to execute $f: "
;;
*.sql.zst)
note "$0: running $f"
zstd -dc "$f" | exec_sql --show-result "Failed to execute $f: "
;;
*)
warn "$0: ignoring $f"
;;
esac
echo
done
}
# is_port_open checks if a TCP port is open on a given host.
# Arguments:
# $1 - Host (IP or hostname)
# $2 - Port number
# Returns:
# 0 if the port is open, non-zero otherwise.
is_port_open() {
local host="$1"
local port="$2"
timeout 1 bash -c "cat < /dev/null > /dev/tcp/$host/$port" &>/dev/null
return $?
}
# start_server starts the Doltgres server in the background and waits until it is ready to accept connections.
# It manages the server process, restarts it if necessary, and checks for readiness by probing the configured port.
# The function retries until the server is available or a timeout is reached, handling process management and logging.
# Arguments:
# $@ - Additional arguments to pass to `doltgres`
# Returns:
# 0 if the server starts successfully and is ready to accept connections; exits with error otherwise.
start_server() {
local timeout="${DOLTGRES_SERVER_TIMEOUT:-300}"
local start_time
start_time=$(date +%s)
# If any of the doltgres or postgres specific user, password, and DB env vars are set, export them
# to the ones that doltgres understands so they can be used during initialization.
user=$(get_env_var "USER")
password=$(get_env_var "PASSWORD")
db=$(get_env_var "DB")
export DOLTGRES_USER=$user
export DOLTGRES_PASSWORD=$password
export DOLTGRES_DB=$db
SERVER_PID=-1
trap 'note "Caught Ctrl+C, shutting down Doltgres server..."; [ $SERVER_PID -ne -1 ] && kill "$SERVER_PID"; exit 1' INT TERM
while true; do
if [ "$SERVER_PID" -eq -1 ] || ! kill -0 "$SERVER_PID" 2>/dev/null; then
[ "$SERVER_PID" -ne -1 ] && wait "$SERVER_PID" 2>/dev/null || true
SERVER_PID=-1
# echo "running dlv --listen=:2345 --headless=true --api-version=2 exec /usr/local/bin/doltgres -- $@"
# dlv --listen=:2345 --headless=true --api-version=2 exec /usr/local/bin/doltgres -- "$@" 2>&1 &
echo "running doltgres $@"
doltgres "$@" 2>&1 &
SERVER_PID=$!
fi
if is_port_open "0.0.0.0" 5432; then
note "Doltgres server started."
return 0
fi
local now elapsed
now=$(date +%s)
elapsed=$((now - start_time))
if [ "$elapsed" -ge "$timeout" ]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
SERVER_PID=-1
log_error "Doltgres server failed to start within $timeout seconds"
fi
sleep 1
done
}
write_default_config_yaml() {
data_dir=$(get_pgdata_var)
if [ -z $data_dir ]; then
data_dir="/var/lib/doltgres/"
fi
if [ ! -d $data_dir ]; then
mkdir -p $data_dir
fi
cat <<EOF > config.yaml
log_level: info
encode_logged_query: false
behavior:
read_only: false
dolt_transaction_commit: false
listener:
host: 0.0.0.0
port: 5432
read_timeout_millis: 28800000
write_timeout_millis: 28800000
allow_cleartext_passwords: false
data_dir: $data_dir
cfg_dir: .doltcfg
privilege_file: .doltcfg/privileges.db
branch_control_file: .doltcfg/branch_control.db
EOF
}
# _main is the main entrypoint for the Doltgres Docker container initialization.
_main() {
check_for_doltgres_binary
local doltgres_version
doltgres_version=$(doltgres --version | cut -f3 -d " ")
note "Entrypoint script for Doltgres Server $doltgres_version starting..."
declare -g CONFIG_PROVIDED
CONFIG_PROVIDED=
# if there is a single yaml provided in /etc/doltgres/servercfg.d directory,
# it will be used to start the server with --config flag.
get_config_file_path_if_exists "$SERVER_CONFIG_DIR" "yaml"
if [ -n "$CONFIG_PROVIDED" ]; then
set -- "$@" --config="$CONFIG_PROVIDED"
elif [ -e config.yaml ]; then
set -- "$@" --config=config.yaml
else
echo "generating config.yaml for first run"
write_default_config_yaml
cat config.yaml
set -- "$@" --config=config.yaml
fi
note "Starting Doltgres server"
start_server "$@"
if [[ ! -f $INIT_COMPLETED ]]; then
if ls /docker-entrypoint-initdb.d/* >/dev/null 2>&1; then
docker_process_init_files /docker-entrypoint-initdb.d/*
else
warn "No files found in /docker-entrypoint-initdb.d/ to process"
fi
touch "$INIT_COMPLETED"
fi
note "Doltgres running. Ready for connections."
wait "$SERVER_PID"
}
_main "$@"