feat(postgres-dump): 重构脚本以提高可读性和模块化

This commit is contained in:
严浩
2025-09-22 19:22:27 +08:00
parent 70da4316d6
commit 2d46c292e3

View File

@@ -2,12 +2,23 @@
set -eu set -eu
# PostgreSQL backup triggered through the Docker Engine unix socket. # PostgreSQL backup triggered through the Docker Engine unix socket.
# Example: ./postgres-dump-zstd-docker-sock.sh --socket=/var/run/docker.sock --api-version=v1.51 --backup-dir=/backups --container=postgres17 --backup-prefix=pgvector17_all_databases_zstd_ # Example: ./postgres-dump-zstd-docker-sock.sh --container=postgres17 --backup-prefix=pgvector17_all_databases_zstd_ --socket=/var/run/docker.sock --api-version=v1.51 --backup-dir=/backups
# Remote example: curl -fsSL https://git.1-h.cc/Scripts/Linux/raw/branch/main/postgres-dump-zstd-docker-sock.sh | sh -s -- --socket=/var/run/docker.sock --api-version=v1.51 --backup-dir=/backups --container=postgres17 --backup-prefix=pgvector17_all_databases_zstd_ # Remote example: curl -fsSL https://git.1-h.cc/Scripts/Linux/raw/branch/main/postgres-dump-zstd-docker-sock.sh | sh -s -- --container=postgres17 --backup-prefix=pgvector17_all_databases_zstd_ --socket=/var/run/docker.sock --api-version=v1.51 --backup-dir=/backups
log() { log() {
printf '%s\n' "[cron] $(date -u '+%Y-%m-%dT%H:%M:%SZ') $*" printf '%s\n' "[cron] $(date -u '+%Y-%m-%dT%H:%M:%SZ') $*"
} }
log_stream() {
log_stream_prefix=$1
log_stream_payload=$2
if [ -n "$log_stream_payload" ]; then
printf '%s\n' "$log_stream_payload" | while IFS= read -r log_stream_line; do
log "$log_stream_prefix: $log_stream_line"
done
fi
}
print_usage() { print_usage() {
cat <<EOF >&2 cat <<EOF >&2
Usage: $0 [--socket=PATH] [--api-version=VERSION] --container=NAME [--backup-dir=DIR] --backup-prefix=PREFIX Usage: $0 [--socket=PATH] [--api-version=VERSION] --container=NAME [--backup-dir=DIR] --backup-prefix=PREFIX
@@ -28,12 +39,75 @@ PG_CONTAINER_NAME=""
BACKUP_DIR="/backups" BACKUP_DIR="/backups"
BACKUP_PREFIX="" BACKUP_PREFIX=""
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
printf '%s requires %s in PATH\n' "$0" "$1" >&2
exit 1
fi
}
missing_value() { missing_value() {
printf 'Missing value for %s\n' "$1" >&2 printf 'Missing value for %s\n' "$1" >&2
print_usage print_usage
exit 1 exit 1
} }
docker_exec_create() {
docker_exec_create_desc=$1
docker_exec_create_cmd=$2
docker_exec_create_payload=$(jq -n --arg cmd "$docker_exec_create_cmd" '{
AttachStdin: false,
AttachStdout: true,
AttachStderr: true,
Tty: true,
Cmd: ["bash", "-lc", $cmd]
}')
DOCKER_LAST_RESPONSE=$(curl --fail --silent --show-error --unix-socket "$DOCKER_SOCKET" \
-X POST \
-H "Content-Type: application/json" \
-d "$docker_exec_create_payload" \
"$exec_create_endpoint")
docker_exec_create_id=$(printf '%s' "$DOCKER_LAST_RESPONSE" | jq -r '.Id // empty')
if [ -z "$docker_exec_create_id" ]; then
log "failed to create $docker_exec_create_desc exec for $PG_CONTAINER_NAME"
log "docker response: $DOCKER_LAST_RESPONSE"
return 1
fi
printf '%s' "$docker_exec_create_id"
}
docker_exec_start() {
docker_exec_start_id=$1
docker_exec_start_desc=$2
docker_exec_start_endpoint="${docker_api_base}/exec/${docker_exec_start_id}/start"
DOCKER_LAST_RESPONSE=$(curl --fail --show-error --silent --unix-socket "$DOCKER_SOCKET" \
-X POST \
-H "Content-Type: application/json" \
-d '{"Detach": false, "Tty": true}' \
"$docker_exec_start_endpoint")
log_stream "$docker_exec_start_desc output" "$DOCKER_LAST_RESPONSE"
printf '%s' "$DOCKER_LAST_RESPONSE"
}
docker_exec_exit_code() {
docker_exec_exit_id=$1
docker_exec_inspect_endpoint="${docker_api_base}/exec/${docker_exec_exit_id}/json"
DOCKER_LAST_RESPONSE=$(curl --fail --silent --show-error --unix-socket "$DOCKER_SOCKET" "$docker_exec_inspect_endpoint")
docker_exec_exit_code_value=$(printf '%s' "$DOCKER_LAST_RESPONSE" | jq -r '.ExitCode // empty')
if [ -z "$docker_exec_exit_code_value" ]; then
return 1
fi
printf '%s' "$docker_exec_exit_code_value"
}
while [ "$#" -gt 0 ]; do while [ "$#" -gt 0 ]; do
case $1 in case $1 in
--socket=*) --socket=*)
@@ -110,6 +184,9 @@ if [ "$#" -gt 0 ]; then
exit 1 exit 1
fi fi
require_command curl
require_command jq
if [ -z "$DOCKER_SOCKET" ] || [ -z "$DOCKER_API_VERSION" ] || [ -z "$PG_CONTAINER_NAME" ] || [ -z "$BACKUP_DIR" ] || [ -z "$BACKUP_PREFIX" ]; then if [ -z "$DOCKER_SOCKET" ] || [ -z "$DOCKER_API_VERSION" ] || [ -z "$PG_CONTAINER_NAME" ] || [ -z "$BACKUP_DIR" ] || [ -z "$BACKUP_PREFIX" ]; then
print_usage print_usage
exit 1 exit 1
@@ -128,57 +205,26 @@ exec_create_endpoint="${docker_api_base}/containers/${PG_CONTAINER_NAME}/exec"
log "preparing database backup at $backup_path via docker unix socket" log "preparing database backup at $backup_path via docker unix socket"
cmd="set -o pipefail && pg_dumpall --username=\"\${POSTGRES_USER:-postgres}\" --clean | zstd > ${backup_path}" cmd="set -o pipefail && pg_dumpall --username=\"\${POSTGRES_USER:-postgres}\" --clean | zstd > ${backup_path}"
create_payload=$(jq -n --arg cmd "$cmd" '{
AttachStdin: false,
AttachStdout: true,
AttachStderr: true,
Tty: true,
Cmd: ["bash", "-lc", $cmd]
}')
create_response=$(curl --fail --silent --show-error --unix-socket "$DOCKER_SOCKET" \ if ! exec_id=$(docker_exec_create "backup" "$cmd"); then
-X POST \
-H "Content-Type: application/json" \
-d "$create_payload" \
"$exec_create_endpoint")
exec_id=$(printf '%s' "$create_response" | jq -r '.Id // empty')
if [ -z "$exec_id" ]; then
log "failed to create exec for $PG_CONTAINER_NAME"
log "docker response: $create_response"
exit 1 exit 1
fi fi
log "starting exec $exec_id" log "starting exec $exec_id"
exec_start_endpoint="${docker_api_base}/exec/${exec_id}/start" if ! start_output=$(docker_exec_start "$exec_id" "exec"); then
start_output=$(curl --fail --show-error --silent --unix-socket "$DOCKER_SOCKET" \ exit 1
-X POST \
-H "Content-Type: application/json" \
-d '{"Detach": false, "Tty": true}' \
"$exec_start_endpoint")
if [ -n "$start_output" ]; then
printf '%s\n' "$start_output" | while IFS= read -r line; do
log "exec output: $line"
done
fi fi
exec_inspect_endpoint="${docker_api_base}/exec/${exec_id}/json" if ! exit_code=$(docker_exec_exit_code "$exec_id"); then
inspect_response=$(curl --fail --silent --show-error --unix-socket "$DOCKER_SOCKET" "$exec_inspect_endpoint")
exit_code=$(printf '%s' "$inspect_response" | jq -r '.ExitCode // empty')
if [ -z "$exit_code" ]; then
log "could not determine exec exit code" log "could not determine exec exit code"
log "docker inspect response: $inspect_response" log "docker inspect response: $DOCKER_LAST_RESPONSE"
exit 1 exit 1
fi fi
if [ "$exit_code" != "0" ]; then if [ "$exit_code" != "0" ]; then
log "backup exec exited with status $exit_code" log "backup exec exited with status $exit_code"
log "docker inspect response: $inspect_response" log "docker inspect response: $DOCKER_LAST_RESPONSE"
exit "$exit_code" exit "$exit_code"
fi fi
@@ -193,25 +239,8 @@ else
log "backup file not found on host; verifying inside container $PG_CONTAINER_NAME" log "backup file not found on host; verifying inside container $PG_CONTAINER_NAME"
verify_cmd="set -eo pipefail && if [ -f \"${backup_path}\" ]; then du -h \"${backup_path}\" 2>/dev/null | awk 'NR==1{print \$1}' || printf 'exists'; else exit 44; fi" verify_cmd="set -eo pipefail && if [ -f \"${backup_path}\" ]; then du -h \"${backup_path}\" 2>/dev/null | awk 'NR==1{print \$1}' || printf 'exists'; else exit 44; fi"
verify_payload=$(jq -n --arg cmd "$verify_cmd" '{
AttachStdin: false,
AttachStdout: true,
AttachStderr: true,
Tty: true,
Cmd: ["bash", "-lc", $cmd]
}')
verify_create_response=$(curl --fail --silent --show-error --unix-socket "$DOCKER_SOCKET" \ if ! verify_exec_id=$(docker_exec_create "verification" "$verify_cmd"); then
-X POST \
-H "Content-Type: application/json" \
-d "$verify_payload" \
"$exec_create_endpoint")
verify_exec_id=$(printf '%s' "$verify_create_response" | jq -r '.Id // empty')
if [ -z "$verify_exec_id" ]; then
log "failed to create verification exec for $PG_CONTAINER_NAME"
log "docker response: $verify_create_response"
log "backup command succeeded, but file $backup_path not found" log "backup command succeeded, but file $backup_path not found"
log "database backup finished" log "database backup finished"
exit 0 exit 0
@@ -219,27 +248,15 @@ else
log "starting verification exec $verify_exec_id" log "starting verification exec $verify_exec_id"
verify_start_endpoint="${docker_api_base}/exec/${verify_exec_id}/start" if ! verify_start_output=$(docker_exec_start "$verify_exec_id" "verify"); then
verify_start_output=$(curl --fail --show-error --silent --unix-socket "$DOCKER_SOCKET" \ log "backup command succeeded, but file $backup_path not confirmed"
-X POST \ log "database backup finished"
-H "Content-Type: application/json" \ exit 0
-d '{"Detach": false, "Tty": true}' \
"$verify_start_endpoint")
if [ -n "$verify_start_output" ]; then
printf '%s\n' "$verify_start_output" | while IFS= read -r line; do
log "verify output: $line"
done
fi fi
verify_inspect_endpoint="${docker_api_base}/exec/${verify_exec_id}/json" if ! verify_exit_code=$(docker_exec_exit_code "$verify_exec_id"); then
verify_inspect_response=$(curl --fail --silent --show-error --unix-socket "$DOCKER_SOCKET" "$verify_inspect_endpoint")
verify_exit_code=$(printf '%s' "$verify_inspect_response" | jq -r '.ExitCode // empty')
if [ -z "$verify_exit_code" ]; then
log "could not determine verification exec exit code" log "could not determine verification exec exit code"
log "docker inspect response: $verify_inspect_response" log "docker inspect response: $DOCKER_LAST_RESPONSE"
log "backup command succeeded, but file $backup_path not found" log "backup command succeeded, but file $backup_path not found"
elif [ "$verify_exit_code" = "0" ]; then elif [ "$verify_exit_code" = "0" ]; then
verify_size=$(printf '%s' "$verify_start_output" | awk 'NF {last=$0} END {print last}') verify_size=$(printf '%s' "$verify_start_output" | awk 'NF {last=$0} END {print last}')
@@ -253,7 +270,7 @@ else
log "backup command succeeded, but file $backup_path not found inside container $PG_CONTAINER_NAME" log "backup command succeeded, but file $backup_path not found inside container $PG_CONTAINER_NAME"
else else
log "verification exec exited with status $verify_exit_code" log "verification exec exited with status $verify_exit_code"
log "docker inspect response: $verify_inspect_response" log "docker inspect response: $DOCKER_LAST_RESPONSE"
log "backup command succeeded, but file $backup_path not confirmed" log "backup command succeeded, but file $backup_path not confirmed"
fi fi
fi fi