#!/bin/sh set -eu # PostgreSQL backup triggered through the Docker Engine unix socket. # Example: ./postgres-dump-zstd-via-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-via-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() { printf '%s\n' "$(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() { cat <&2 Usage: $0 [--socket=PATH] [--api-version=VERSION] --container=NAME [--backup-dir=DIR] --backup-prefix=PREFIX Options: --socket=PATH Docker Engine unix socket path (default: /var/run/docker.sock) --api-version=VERSION Docker API version (default: v1.51) --container=NAME PostgreSQL container name --backup-dir=DIR Directory to store backups (default: /backups) --backup-prefix=PREFIX Backup filename prefix --help Show this help message EOF } DOCKER_SOCKET="/var/run/docker.sock" DOCKER_API_VERSION="v1.51" PG_CONTAINER_NAME="" BACKUP_DIR="/backups" 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() { printf 'Missing value for %s\n' "$1" >&2 print_usage exit 1 } docker_exec() { exec_container_name=$1 exec_cmd=$2 curl -fsSL "https://Git.1-H.CC/Scripts/Linux/raw/branch/main/docker-exec-via-sock.sh" | sh -s -- \ --socket="$DOCKER_SOCKET" \ --api-version="$DOCKER_API_VERSION" \ --container="$exec_container_name" \ --cmd="$exec_cmd" } while [ "$#" -gt 0 ]; do case $1 in --socket=*) DOCKER_SOCKET="${1#*=}" ;; --socket) if [ "$#" -lt 2 ]; then missing_value '--socket' fi shift DOCKER_SOCKET="$1" ;; --api-version=*) DOCKER_API_VERSION="${1#*=}" ;; --api-version) if [ "$#" -lt 2 ]; then missing_value '--api-version' fi shift DOCKER_API_VERSION="$1" ;; --container=*) PG_CONTAINER_NAME="${1#*=}" ;; --container) if [ "$#" -lt 2 ]; then missing_value '--container' fi shift PG_CONTAINER_NAME="$1" ;; --backup-dir=*) BACKUP_DIR="${1#*=}" ;; --backup-dir) if [ "$#" -lt 2 ]; then missing_value '--backup-dir' fi shift BACKUP_DIR="$1" ;; --backup-prefix=*) BACKUP_PREFIX="${1#*=}" ;; --backup-prefix) if [ "$#" -lt 2 ]; then missing_value '--backup-prefix' fi shift BACKUP_PREFIX="$1" ;; --help) print_usage exit 0 ;; --) shift break ;; *) printf 'Unknown option: %s\n' "$1" >&2 print_usage exit 1 ;; esac shift done if [ "$#" -gt 0 ]; then printf 'Unexpected positional arguments: %s\n' "$*" >&2 print_usage exit 1 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 print_usage exit 1 fi if [ ! -S "$DOCKER_SOCKET" ]; then log "docker socket $DOCKER_SOCKET not found" exit 1 fi timestamp=$(date +%Y-%m-%d_%H-%M-%S) backup_path="$BACKUP_DIR/${BACKUP_PREFIX}${timestamp}.sql.zst" 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}" log_border="----------------------------------------------------------------------" printf '%s\n' "$log_border" >&2 if ! docker_exec "$PG_CONTAINER_NAME" "$cmd"; then printf '%s\n' "$log_border" >&2 log "backup command failed" exit 1 fi printf '%s\n' "$log_border" >&2 printf '\n' >&2 # Add a blank line for visual grouping log "backup command finished, verifying file size inside container" verify_cmd="du -h \"${backup_path}\" 2>/dev/null | awk 'NR==1{print \$1}'" printf '%s\n' "$log_border" >&2 size=$(docker_exec "$PG_CONTAINER_NAME" "$verify_cmd" | tr -d '\r') printf '%s\n' "$log_border" >&2 printf '\n' >&2 # Add a blank line for visual grouping if [ -n "$size" ]; then log "backup completed inside container: $backup_path ($size)" else log "backup completed, but could not determine file size inside container." fi printf '\n' >&2 # Add a blank line for visual grouping log "database backup finished"