Files
postgresql/customer/usr/local/bin/common.sh
T

608 lines
24 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
}
# 检测以 "<PREFIX>" 开头的环境变量,并更新指定配置文件中对应配置项的值
# 如果需要全部转换为小写,可使用命令: tr '[:upper:]' '[:lower:]'
# 环境变量与配置项替换规则 : 环境变量中下划线 ==> 配置参数中特殊字符
# - "_" ==> "_"(下划线)
# - "__" ==> "."(半角点)
# - "___" ==> "-"(中划线)
#
# 变量:
# $1 - 配置文件
# $2 - <PREFIX>前缀(不含结束的"_")
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 访问策略为 trusthost IP range 为 127.0.0.1/32 及 ::1/128
# - replication 数据库,,local / host 访问策略为 trusthost 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 <<EOF >"${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
}