#!/bin/bash # Ver: 1.5 by Endial Fang (endial@126.com) # # 应用通用业务处理函数 . /colovu/lib/libcommon.sh # 通用函数库 . /colovu/lib/libfile.sh . /colovu/lib/libfs.sh . /colovu/lib/liblog.sh . /colovu/lib/libos.sh . /colovu/lib/libservice.sh . /colovu/lib/libvalidations.sh # 检测应用相应的配置文件是否存在,如果不存在,则从默认配置文件目录拷贝一份 # 默认配置文件路径:/etc/${APP_NAME} # 目标配置文件路径:/srv/conf/${APP_NAME} # 参数: # $1 - 目标路径 # $2 - 源路径 # $* - 基础路径下的文件及目录列表,以" "分割 # 例子: # ensure_config_file_exist /etc/${APP_NAME} conf.d server.conf app_ensure_config_file_exist() { local -r dist_path="${1:?dist paths is missing}" local -r base_path="${2:?source paths is missing}" local f="" shift 2 LOG_D "List to check in ${base_path}: $@" while [ "$#" -gt 0 ]; do f="${1}" LOG_D " Process \"${f}\"" if [ -d "${base_path}/${f}" ]; then [[ ! -d "${dist_path}/${f}" ]] && LOG_D " Create directory: ${dist_path}/${f}" && mkdir -p "${dist_path}/${f}" [[ ! -z $(ls -A "${base_path}/${f}") ]] && app_ensure_config_file_exist "${dist_path}/${f}" "${base_path}/${f}" $(ls -A "${base_path}/${f}") else [[ ! -e "${dist_path}/${f}" ]] && LOG_D " Copy: ${base_path}/${f} to ${dist_path}" && cp "${base_path}/${f}" "${dist_path}" fi shift done } # 检测以 "" 开头的环境变量,并更新指定配置文件中对应配置项的值 # 如果需要全部转换为小写,可使用命令: tr '[:upper:]' '[:lower:]' # 环境变量与配置项替换规则 : 环境变量中下划线 ==> 配置参数中特殊字符 # - "_" ==> "_"(下划线) # - "__" ==> "."(半角点) # - "___" ==> "-"(中划线) # # 变量: # $1 - 配置文件 # $2 - 前缀(不含结束的"_") app_configure_from_environment() { local confFile="${1:?missing file}" local envPrefix="${2:-APP_CFG}" LOG_D "Configuration File: ${confFile}" for var in $(eval echo \${!${envPrefix}_@}); do key="$(echo "$var" | sed -e 's/^'${envPrefix}'_//g' -e 's/___/-/g' -e 's/__/./g' | tr '[:upper:]' '[:lower:]')" value="${!var}" LOG_D " ${key}: ${value}" app_common_conf_set "${PG_CONF_FILE}" "$key" "$value" done } # 将变量配置更新至配置文件 # 参数: # $1 - 文件 # $2 - 变量 # $3 - 值(列表) app_common_conf_set() { local file="${1:?missing file}" local key="${2:?missing key}" local value="${3:?missing value}" if grep -q "^#*${key}" "$file" >/dev/null; then replace_in_file "$file" "^#*${key}\s*=.*$" "${key} = '${value}'" false else echo "\n${key} = '${value}'" >>"$file" fi } # 使用环境变量中配置,更新配置文件 app_update_conf() { LOG_I "Update configure files from PG_CFG_*..." if (( PG_SYNCHRONOUS_REPLICAS_NUM > 0 )); then app_common_conf_set "${PG_CONF_FILE}" "synchronous_standby_names" "${PG_SYNCHRONOUS_REPLICAS_METHOD} ${PG_SYNCHRONOUS_REPLICAS_NUM} (\"${PG_SYNCHRONOUS_REPLICAS_NAMES}\")" fi [[ "${PG_REPLICATION_MODE}" == "standby" ]] && pg_configure_recovery app_configure_from_environment "${PG_CONF_FILE}" "PG_CFG" pg_update_hba_conf } # 检测用户参数信息是否满足条件; 针对部分权限过于开放情况,打印提示信息 app_verify_minimum_env() { local error_code=0 LOG_D "Validating settings in ENV vars..." print_validation_error() { LOG_E "$1" error_code=1 } empty_password_error() { print_validation_error "The $1 environment variable is empty or not set. Set the environment variable ALLOW_ANONYMOUS=yes to allow the container to be started with blank passwords. This is recommended only for development." } # 检测认证设置。如果不允许匿名登录,检测登录用户名及密码是否设置 if is_boolean_yes "${ALLOW_ANONYMOUS}"; then LOG_W "You have set the environment variable ALLOW_ANONYMOUS=${ALLOW_ANONYMOUS}. For safety reasons, do not use this flag in a production environment." else if [[ -z "${PG_PASSWORD}" ]]; then empty_password_error "{PG_PASSWORD}" fi if (( ${#PG_PASSWORD} > 100 )); then print_validation_error "The password cannot be longer than 100 characters. Set the environment variable PG_PASSWORD with a shorter value" fi if [[ -n "${PG_USERNAME}" ]] && [[ -z "${PG_PASSWORD}" ]]; then empty_password_error "{PG_PASSWORD}" fi if [[ -n "${PG_USERNAME}" ]] && [[ "${PG_USERNAME}" != "postgres" ]] && [[ -n "${PG_PASSWORD}" ]] && [[ -z "${PG_DATABASE}" ]]; then print_validation_error "In order to use a custom PostgreSQL user you need to set the environment variable PG_DATABASE as well" fi fi if [[ "${PG_REPLICATION_MODE}" = "primary" ]]; then if (( PG_SYNCHRONOUS_REPLICAS_NUM < 0 )); then print_validation_error "The number of synchronous replicas cannot be less than 0. Set the environment variable PG_SYNCHRONOUS_REPLICAS_NUM" fi elif [[ "${PG_REPLICATION_MODE}" = "standby" ]]; then if [[ -z "${PG_REPLICATION_HOST}" ]]; then print_validation_error "Slave replication mode chosen without setting the environment variable PG_REPLICATION_HOST. Use it to indicate where the Master node is running" fi if [[ -z "${PG_REPLICATION_USER}" ]]; then print_validation_error "Slave replication mode chosen without setting the environment variable PG_REPLICATION_USER. Make sure that the primary also has this parameter set" fi if [[ -n "${PG_REPLICATION_USER}" ]] && [[ -z "${PG_REPLICATION_PASSWORD}" ]]; then empty_password_error "{PG_REPLICATION_PASSWORD}" fi else print_validation_error "Invalid replication mode. Available options are 'primary/standby'" fi if is_boolean_yes "${PG_ENABLE_LDAP}" && [[ -n "${PG_LDAP_URL}" ]] && [[ -n "${PG_LDAP_SERVER}" ]]; then empty_password_error "You can not set PG_LDAP_URL and PG_LDAP_SERVER at the same time. Check your LDAP configuration." fi if [[ "${PG_CFG_SSL:-off}" == "on" ]]; then if [[ -z "${PG_CFG_ssl_cert_file:-}" ]]; then print_validation_error "You must provide a X.509 certificate in order to use TLS" elif [[ ! -f "${PG_CFG_ssl_cert_file}" ]]; then print_validation_error "The X.509 certificate file in the specified path ${PG_CFG_ssl_cert_file} does not exist" fi if [[ -z "${PG_CFG_ssl_key_file:-}" ]]; then print_validation_error "You must provide a private key in order to use TLS" elif [[ ! -f "${PG_CFG_ssl_key_file}" ]]; then print_validation_error "The private key file in the specified path ${PG_CFG_ssl_key_file} does not exist" fi if [[ -z "${PG_CFG_ssl_ca_file:-}" ]]; then LOG_W "A CA X.509 certificate was not provided. Client verification will not be performed in TLS connections" elif [[ ! -f "${PG_CFG_ssl_ca_file}" ]]; then print_validation_error "The CA X.509 certificate file in the specified path ${PG_CFG_ssl_ca_file} does not exist" fi if [[ -n "${PG_CFG_ssl_crl_file:-}" ]] && [[ ! -f "${PG_CFG_ssl_crl_file}" ]]; then print_validation_error "The CRL file in the specified path ${PG_CFG_ssl_crl_file} does not exist" fi if ! is_yes_no_value "${PG_CFG_ssl_prefer_server_ciphers:-}"; then print_validation_error "The values allowed for prefer_server_ciphers are: on or off" fi fi [[ "$error_code" -eq 0 ]] || exit "$error_code" } # 更改默认监听地址为 "*" 或 "0.0.0.0",以对容器外提供服务 app_enable_remote_connections() { LOG_I "Modify default config to ENABLE external IP access" app_common_conf_set "${PG_CONF_FILE}" "listen_addresses" "0.0.0.0" } # 更改默认监听地址为 "localhost" 或 "127.0.0.1",以禁止对容器外提供服务 app_disable_remote_connections() { LOG_I "Modify default config to DISABLE external IP access" app_common_conf_set "${PG_CONF_FILE}" "listen_addresses" "127.0.0.1" } # 以后台方式启动应用服务,并等待启动就绪 app_start_server_bg() { LOG_I "Starting ${APP_NAME} in background..." # 使用 pg_ctl 命令,以服务方式启动 PostgreSQL local pg_ctl_cmd=$(command -v pg_ctl) local pg_ctl_flags=("-D" "${PGDATA}") pg_ctl_flags+=("-l" "${PG_CFG_LOG_DIRECTORY}/postgres_init.log") #pg_ctl_flags+=("-o" "'-c listen_addresses=127.0.0.1 -p 5432'") pg_ctl_flags+=("-w" "-t" "30") LOG_D " extra arguments: ${pg_ctl_flags[@]}" debug_execute "${pg_ctl_cmd}" "start" "${pg_ctl_flags[@]}" } # 停止应用服务 app_stop_server() { LOG_I "Stopping ${APP_NAME} background service..." # 使用 pg_ctl 命令关闭服务 local pg_ctl_cmd=$(command -v pg_ctl) local pg_ctl_flags=("-D" "${PGDATA}") pg_ctl_flags+=("-m" "fast") pg_ctl_flags+=("-w" "-t" "30") LOG_D " extra arguments: ${pg_ctl_flags[@]}" debug_execute "${pg_ctl_cmd}" "stop" "${pg_ctl_flags[@]}" } # 检测应用服务是否在后台运行中 app_is_server_running() { local pid pid="$(get_pid_from_file ${PG_CFG_EXTERNAL_PID_FILE})" LOG_D "Checking ${APP_NAME} running status with PID: ${pid}" if [[ -n "${pid}" ]]; then is_service_running "${pid}" else false fi } app_is_server_not_running() { if [[ app_is_server_running == false ]]; then true else false fi } # 清理初始化应用时生成的临时文件 app_clean_tmp_file() { LOG_D "Clean ${APP_NAME} tmp files ..." local -r -a files=( "${PGDATA}/postmaster.pid" "${PG_CFG_EXTERNAL_PID_FILE}" ) for file in ${files[@]}; do if [[ -f "$file" ]]; then LOG_D " Remove $file" rm -rf "$file" fi done } # 用户自定义的前置初始化操作,依次执行目录 preinit.d 中的初始化脚本 # 执行完毕后,生成以当前时间命名的'.tar'的文件,包含执行记录、已执行的脚本,同时删除已执行的脚本 app_custom_preinit() { LOG_I "Process pre-init for ${APP_NAME}..." # 检测用户配置文件目录是否存在 preinit.d 文件夹,如果存在,尝试执行目录中的初始化脚本 if [[ -d "${APP_CONF_DIR}/preinit.d" ]] && [[ -n $(find "${APP_CONF_DIR}/preinit.d/" -type f -regex ".*\.\(sh\)") ]]; then echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." > "${APP_CONF_DIR}/preinit.d/preinit_info" local tarFile="preinit-$(date '+%Y-%m-%d-%H%M%S').tar" tar --remove-files -cf "${tarFile}" "${APP_CONF_DIR}/preinit.d/preinit_info" for f in $(find "${APP_CONF_DIR}/preinit.d/" -type f -regex ".*\.\(sh\)" | sort); do LOG_D " Process: ${f}" process_init_files "${f}" tar --remove-files -f "${tarFile}" -r "${f}" done fi } # 应用默认初始化操作 app_default_init() { LOG_I "Process default init for ${APP_NAME}..." app_clean_tmp_file if is_dir_empty "${PGDATA}"; then LOG_I "Deploying ${APP_NAME} from scratch..." if [[ "${PG_REPLICATION_MODE}" == "primary" ]]; then # 使用 initdb 工具初始化集群仓库(包含配置文件的初始化) pg_primary_init_db app_disable_remote_connections app_start_server_bg [[ "${PG_DATABASE}" != "postgres" ]] && pg_create_custom_database # 为数据库授权;默认用户不为 postgres 时,需要创建管理员账户 LOG_D "Set password for postgres user" if [[ "${PG_USERNAME}" == "postgres" ]]; then [[ -n "${PG_PASSWORD}" ]] && pg_alter_postgres_user "${PG_PASSWORD}" else [[ -n "${PG_POSTGRES_PASSWORD}" ]] && pg_alter_postgres_user "${PG_POSTGRES_PASSWORD}" pg_create_admin_user fi [[ -n "${PG_REPLICATION_USER:-}" ]] && pg_create_replication_user app_stop_server app_enable_remote_connections else pg_standby_init_db fi elif [[ -f "${PGDATA}/PG_VERSION" ]]; then local -r db_version="$(< "${PGDATA}/PG_VERSION")" local -r curr_version="$(pg_get_major_version)" if [[ "$db_version" -ne "$curr_version" ]]; then LOG_E "PGDATA directory is not empty with INCORRECT version, application version is $curr_version, while DB version is $db_version" exit 1 fi LOG_I "Deploying ${APP_NAME} with persisted data..." else LOG_E "${PGDATA} not empty" exit 1 fi # 根据环境变量配置,更新 postgresql.conf / pg_hba.conf / pg_ident.conf 等 # 注意:如果在数据库客户端内使用 ALTER 更新配置,则存储在 postgresql.auto.conf 文件中,优先级高于 postgresql.conf app_update_conf } # 用户自定义的应用初始化操作,依次执行目录 initdb.d 中的初始化脚本 # 执行完毕后,生成以当前时间命名的'.tar'的文件,包含执行记录、已执行的脚本,同时删除已执行的脚本 app_custom_init() { LOG_I "Process DB-init for ${APP_NAME}..." # 检测用户配置文件目录是否存在 preinit.d 文件夹,如果存在,尝试执行目录中的初始化脚本 if [[ -d "${APP_CONF_DIR}/initdb.d" ]] && [[ -n $(find "${APP_CONF_DIR}/initdb.d/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)") ]]; then echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." > "${APP_CONF_DIR}/initdb.d/dbinit_info" local tarFile="initdb-$(date '+%Y-%m-%d-%H%M%S').tar" tar --remove-files -cf "${tarFile}" "${APP_CONF_DIR}/initdb.d/initdb_info" # 禁用远程访问,并启动后台服务 app_disable_remote_connections app_start_server_bg for f in $(find "${APP_CONF_DIR}/initdb.d/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)" | sort); do case "$f" in *.sh) LOG_D " Process Shell: ${f}" process_init_files "$f" ;; *.sql) LOG_D " Process SQL: ${f}"; pg_execute "${PG_DATABASE}" "${PG_INITSCRIPTS_USERNAME}" "${PG_INITSCRIPTS_PASSWORD}" < "$f" ;; *.sql.gz) LOG_D " Process SQLs in ${f}"; gunzip -c "$f" | pg_execute "${PG_DATABASE}" "${PG_INITSCRIPTS_USERNAME}" "${PG_INITSCRIPTS_PASSWORD}" ;; *) LOG_D " Ignoring $f" ;; esac tar --remove-files -f "${tarFile}" -r "${f}" done # 停止后台服务,并启用远程访问 app_stop_server app_clean_tmp_file app_enable_remote_connections fi } # 获取软件主版本号 pg_get_major_version() { psql --version | grep -oE "[0-9]+\.[0-9]+" | grep -oE "^[0-9]+" } # 根据环境变量,更新 HBA 配置文件 pg_update_hba_conf() { LOG_I "Update pg_hba.conf with environment values..." # 默认生成的配置中: # - 默认数据库,local / host 访问策略为 trust,host IP range 为 127.0.0.1/32 及 ::1/128 # - replication 数据库,,local / host 访问策略为 trust,host IP range 为 127.0.0.1/32 及 ::1/128 pg_hba_update_host_auth pg_hba_update_replication_auth if is_boolean_yes "${PG_ENABLE_LDAP}"; then pg_hba_allow_ldap_auth fi if [[ "${PG_CFG_SSL:-off}" == "on" ]]; then pg_hba_allow_tls_connection fi } # 更新 pg_hba.conf 文件,所有数据库仅允许基于密码认证的访问 pg_hba_update_host_auth() { LOG_D " Update default host auth configs..." local host_auth="trust" if [[ -n "${PG_PASSWORD}" ]]; then host_auth="md5" fi sed -i -E "s@(host)\s+(all)\s+(all)\s+([0-9\.\/]+)\s+.*@\1\t\2\t\3\t${PG_USER_IP4_RANGE}\t${host_auth}@g" "${PG_CFG_HBA_FILE}" sed -i -E "s@(host)\s+(all)\s+(all)\s+([a-z0-9\/\:]+)\s+.*@\1\t\2\t\3\t${PG_USER_IP6_RANGE}\t${host_auth}@g" "${PG_CFG_HBA_FILE}" } # 更新 pg_hba.conf 文件,replication 仅允许基于密码认证的访问 pg_hba_update_replication_auth() { LOG_D " Update replication host auth configs..." local replication_auth="trust" if [[ -n "${PG_REPLICATION_PASSWORD:-}" ]]; then replication_auth="md5" fi sed -i -E "s@(host)\s+(replication)\s+(all)\s+([0-9\.\/]+)\s+.*@\1\t\2\t\3\t${PG_REPLICATION_IP4_RANGE}\t${replication_auth}@g" "${PG_CFG_HBA_FILE}" sed -i -E "s@(host)\s+(replication)\s+(all)\s+([a-z0-9\/\:]+)\s+.*@\1\t\2\t\3\t${PG_REPLICATION_IP6_RANGE}\t${replication_auth}@g" "${PG_CFG_HBA_FILE}" } # 更新 pg_hba.conf 文件,增加 LDAP 配置;同时增加 postgres 用户的本地认证 pg_hba_allow_ldap_auth() { LOG_I "Enabling LDAP authentication" local ldap_configuration="" if [[ -n "${PG_LDAP_URL}" ]]; then ldap_configuration="ldapurl=\"${PG_LDAP_URL}\"" else ldap_configuration="ldapserver=${PG_LDAP_SERVER}" [[ -n "${PG_LDAP_PREFIX}" ]] && ldap_configuration+=" ldapprefix=\"${PG_LDAP_PREFIX}\"" [[ -n "${PG_LDAP_SUFFIX}" ]] && ldap_configuration+=" ldapsuffix=\"${PG_LDAP_SUFFIX}\"" [[ -n "${PG_LDAP_PORT}" ]] && ldap_configuration+=" ldapport=${PG_LDAP_PORT}" [[ -n "${PG_LDAP_BASE_DN}" ]] && ldap_configuration+=" ldapbasedn=\"${PG_LDAP_BASE_DN}\"" [[ -n "${PG_LDAP_BIND_DN}" ]] && ldap_configuration+=" ldapbinddn=\"${PG_LDAP_BIND_DN}\"" [[ -n "${PG_LDAP_BIND_PASSWORD}" ]] && ldap_configuration+=" ldapbindpasswd=${PG_LDAP_BIND_PASSWORD}" [[ -n "${PG_LDAP_SEARCH_ATTR}" ]] && ldap_configuration+=" ldapsearchattribute=${PG_LDAP_SEARCH_ATTR}" [[ -n "${PG_LDAP_SEARCH_FILTER}" ]] && ldap_configuration+=" ldapsearchfilter=\"${PG_LDAP_SEARCH_FILTER}\"" [[ -n "${PG_LDAP_TLS}" ]] && ldap_configuration+=" ldaptls=${PG_LDAP_TLS}" [[ -n "${PG_LDAP_SCHEME}" ]] && ldap_configuration+=" ldapscheme=${PG_LDAP_SCHEME}" fi sed -i -E "s@(host)\s+(all)\s+(all)\s+([0-9\.\/]+)\s+.*@\1\t\2\t\3\t${PG_REPLICATION_IP4_RANGE}\tldap ${ldap_configuration}@g" "${PG_CFG_HBA_FILE}" sed -i -E "s@(host)\s+(all)\s+(all)\s+([a-z0-9\/\:]+)\s+.*@\1\t\2\t\3\t${PG_REPLICATION_IP6_RANGE}\tldap ${ldap_configuration}@g" "${PG_CFG_HBA_FILE}" LOG_D " Add new postgres authentication when auth with LDAP" local postgres_auth="trust" if [[ -n "${PG_POSTGRES_PASSWORD}" ]]; then postgres_auth="md5" fi if ! $(grep -iE "(host)\s+(all)\s+(postgres).*" "${PG_CFG_HBA_FILE}"); then cat <"${PG_CFG_HBA_FILE}" host all postgres ${PG_REPLICATION_IP4_RANGE} ${postgres_auth} host all postgres ${PG_REPLICATION_IP6_RANGE} ${postgres_auth} EOF fi } # 设置 pg_hba.conf 文件,增加 TLS 配置 pg_hba_allow_tls_connection() { LOG_I "Enabling TLS client authentication" cat <<"EOF" >>"${PG_CFG_HBA_FILE}" hostssl all all ${PG_USER_IP4_RANGE} cert hostssl all all ${PG_USER_IP6_RANGE} cert EOF } # 为 Slava 模式工作的节点创建 recovery.conf 文件 pg_configure_recovery() { LOG_I "Setting up streaming replication standby..." # Recover 配置信息在不同版本保存位置不一样: # 版本为12及以上时, Slave 节点配置保存在 postgresql.conf 文件中 # 版本低于12时, Slave 节点配置保存在 recover.conf 文件中 app_common_conf_set "${PG_CONF_FILE}" "primary_conninfo" "host=${PG_REPLICATION_HOST} port=${PG_REPLICATION_PORT} user=${PG_REPLICATION_USER} password=${PG_REPLICATION_PASSWORD} application_name=${PG_REPLICATION_APP_NAME} connect_timeout=${PG_REPLICATION_CONNECT_TIMEOUT}" app_common_conf_set "${PG_CONF_FILE}" "promote_trigger_file" "/tmp/postgresql.trigger.${PG_REPLICATION_PORT}" touch "${PGDATA}/standby.signal" } # 为默认的数据库用户 postgres 设置密码 # 参数: # $1 - 用户密码 pg_alter_postgres_user() { local -r escaped_password="${1//\'/\'\'}" LOG_I "Changing password of postgres" echo "ALTER ROLE postgres WITH PASSWORD '$escaped_password';" | pg_execute if [[ -n "${PG_DB_CONNECTION_LIMIT}" ]]; then echo "ALTER ROLE postgres WITH CONNECTION LIMIT ${PG_DB_CONNECTION_LIMIT};" | pg_execute fi } # 为数据库 $PG_DATABASE 创建管理员账户 pg_create_admin_user() { local -r escaped_password="${PG_PASSWORD//\'/\'\'}" local connlimit_string="" if [[ -n "${PG_USER_CONNECTION_LIMIT}" ]]; then connlimit_string="CONNECTION LIMIT ${PG_USER_CONNECTION_LIMIT}" fi LOG_I "Creating user ${PG_USERNAME}" echo "CREATE ROLE \"${PG_USERNAME}\" WITH LOGIN ${connlimit_string} CREATEDB PASSWORD '${escaped_password}';" | pg_execute LOG_I "Granting access to \"${PG_USERNAME}\" to the database \"${PG_DATABASE}\"" echo "GRANT ALL PRIVILEGES ON DATABASE \"${PG_DATABASE}\" TO \"${PG_USERNAME}\"\;" | pg_execute "" "postgres" "${PG_POSTGRES_PASSWORD}" } # 为 primary-standby 复制模式创建用户 pg_create_replication_user() { local -r escaped_password="${PG_REPLICATION_PASSWORD//\'/\'\'}" LOG_I "Creating replication user ${PG_REPLICATION_USER}" echo "CREATE ROLE \"${PG_REPLICATION_USER}\" WITH REPLICATION LOGIN ENCRYPTED PASSWORD '$escaped_password'" | pg_execute } # 创建用户自定义数据库 $PG_DATABASE pg_create_custom_database() { LOG_I "Creating custom database ${PG_DATABASE}" echo "CREATE DATABASE \"${PG_DATABASE}\"" | pg_execute "" "postgres" "" "localhost" } # 使用运行中的 PostgreSQL 服务执行 SQL 操作 # 参数: # $1 - 需要操作的数据库名 # $2 - 操作使用的用户名 # $3 - 操作用户密码 # $4 - 主机 # $5 - 端口 # $6 - 扩展参数 (如: -tA) pg_execute() { local -r db="${1:-}" local -r user="${2:-postgres}" local -r pass="${3:-}" local -r host="${4:-localhost}" local -r port="${5:-${PG_PORT_NUMBER}}" local -r opts="${6:-}" local cmd="$(command -v psql)" local args=("-h" "$host" "-p" "$port" "-U" "$user") [[ -n "$db" ]] && args+=("-d" "$db") [[ -n "$opts" ]] && args+=("$opts") LOG_D "Execute args: ${args[@]}" debug_execute PGPASSWORD=$pass "${cmd}" "${args[@]}" } # 初始化 Master 节点数据库 pg_primary_init_db() { LOG_I "Initializing PostgreSQL database" local initdb_cmd="$(command -v initdb)" local envExtraFlags=() local initdb_args=() if [[ -n "${PG_INITDB_ARGS}" ]]; then read -r -a envExtraFlags <<< "${PG_INITDB_ARGS}" initdb_args+=("${envExtraFlags[@]}") fi [[ "${PG_INITDB_WAL_DIR}" != "${PGDATA}/pg_wal" ]] && initdb_args+=("--waldir=${PG_INITDB_WAL_DIR}") # 配置 local / host 访问策略都为 trust initdb_args+=("--auth=trust") LOG_D " extra initdb arguments: ${initdb_args[*]}" debug_execute "${initdb_cmd}" -E UTF8 -D "${PGDATA}" -U "postgres" "${initdb_args[@]}" } # 初始化 Slave 节点数据库 pg_standby_init_db() { LOG_I "Waiting for replication primary to accept connections..." local check_cmd="$(command -v pg_isready)" local check_args=("-h" "${PG_REPLICATION_HOST}" "-p" "${PG_REPLICATION_PORT}" "-U" "${PG_REPLICATION_USER}" "-d" "${PG_DATABASE}" "-t" "1") local ready_counter=${PG_INIT_MAX_TIMEOUT} while ! PGPASSWORD=${PG_REPLICATION_PASSWORD} "${check_cmd}" "${check_args[@]}" >/dev/null 2>&1;do sleep 1 ready_counter=$(( ready_counter - 1 )) if (( ready_counter <= 0 )); then LOG_E "PostgreSQL primary is not ready after ${PG_INIT_MAX_TIMEOUT} seconds" exit 1 fi done LOG_I "Replicating the database from node primary..." local backup_cmd="$(command -v pg_basebackup)" local backup_args=("-D" "${PGDATA}") backup_args+=("-h" "${PG_REPLICATION_HOST}" "-p" "${PG_REPLICATION_PORT}" "-U" "${PG_REPLICATION_USER}" "-W") backup_args+=("-C" "-S" "${PG_REPLICATION_APP_NAME}") [[ "${PG_INITDB_WAL_DIR}" != "${PGDATA}/pg_wal" ]] && backup_args+=("--waldir=${PG_INITDB_WAL_DIR}") backup_args+=("-X" "stream" "-v" "-R" "-F" "plain") local replication_counter=0 while ! PGPASSWORD=${PG_REPLICATION_PASSWORD} "${backup_cmd}" "${backup_args[@]}";do replication_counter=$(( replication_counter + 1 )) LOG_D "Backup command failed ${replication_counter} times. Sleeping and trying again later[]" sleep 5 done }