Import postgresql plugin
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
BACKUP_OK=0
|
||||
DA_USER=""
|
||||
DB_NAME=""
|
||||
JOB_ID=""
|
||||
LOG_FILE=""
|
||||
TMP_DB_USER=""
|
||||
TMP_DB_PASS=""
|
||||
TMP_DB_ACTIVE=0
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "da-postgresql: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
urlencode() {
|
||||
local s="$1"
|
||||
local out=""
|
||||
local i c
|
||||
for ((i=0; i<${#s}; i++)); do
|
||||
c="${s:i:1}"
|
||||
case "$c" in
|
||||
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
||||
' ') out+='%20' ;;
|
||||
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
notify_user() {
|
||||
local code="$1"
|
||||
if [ -z "${DA_USER:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$BACKUP_OK" -eq 1 ]; then
|
||||
subject="Backup bazy PostgreSQL zakończony pomyślnie"
|
||||
message="Backup bazy ${DB_NAME} został wykonany. Plik: ${TARGET_FILE:-nieznany}."
|
||||
else
|
||||
subject="Błąd wykonywania backupu bazy PostgreSQL"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Backup bazy ${DB_NAME} nie powiódł się: ${ERROR_MSG}"
|
||||
else
|
||||
message="Backup bazy ${DB_NAME} nie powiódł się. Sprawdź log zadania."
|
||||
fi
|
||||
fi
|
||||
|
||||
local task_queue="/usr/local/directadmin/data/task.queue"
|
||||
local users="select1%3D$(urlencode "$DA_USER")"
|
||||
local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}"
|
||||
printf "%s\n" "$line" >> "$task_queue"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
if [ -n "${LOG_FILE:-}" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${msg}" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
log_unexpected_error() {
|
||||
local line_no="$1"
|
||||
local command="$2"
|
||||
local code="$3"
|
||||
if [ -z "${LOG_FILE:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -n "${ERROR_MSG:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
ERROR_MSG="Nieoczekiwany błąd wykonywania backupu (kod ${code}, linia ${line_no})."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG} CMD: ${command}" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
load_plugin_settings() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Brak pliku konfiguracyjnego pluginu: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
quote_sh_value() {
|
||||
local value="$1"
|
||||
value="${value//\'/\'\\\'\'}"
|
||||
printf "'%s'" "$value"
|
||||
}
|
||||
|
||||
set_job_var() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local tmp
|
||||
local owner_group=""
|
||||
if [ -f "$JOB_FILE" ]; then
|
||||
owner_group="$(stat -c '%u:%g' "$JOB_FILE" 2>/dev/null || stat -f '%u:%g' "$JOB_FILE" 2>/dev/null || true)"
|
||||
fi
|
||||
tmp="$(mktemp)"
|
||||
grep -v "^${key}=" "$JOB_FILE" > "$tmp" 2>/dev/null || true
|
||||
printf "%s=%s\n" "$key" "$(quote_sh_value "$value")" >> "$tmp"
|
||||
mv "$tmp" "$JOB_FILE"
|
||||
chmod 600 "$JOB_FILE" 2>/dev/null || true
|
||||
if [ "$(id -u)" -eq 0 ] && [ -n "$owner_group" ]; then
|
||||
chown "$owner_group" "$JOB_FILE" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
load_pg_credentials() {
|
||||
local creds_file="/usr/local/directadmin/conf/postgresql.conf"
|
||||
PGHOST_VAL=""
|
||||
PGPORT_VAL=""
|
||||
PGUSER_VAL=""
|
||||
PGPASSWORD_VAL=""
|
||||
|
||||
if [ ! -r "$creds_file" ]; then
|
||||
fail "Brak pliku poświadczeń PostgreSQL: $creds_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local line
|
||||
line="$(grep -Ev '^[[:space:]]*($|#)' "$creds_file" | head -n1 || true)"
|
||||
if [ -z "$line" ]; then
|
||||
fail "Pusty plik poświadczeń PostgreSQL: $creds_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$line" == *=* ]]; then
|
||||
while IFS='=' read -r key value; do
|
||||
key="$(echo "$key" | tr -d '[:space:]' | tr '[:lower:]' '[:upper:]')"
|
||||
value="$(echo "$value" | sed -E 's/[[:space:]]+#.*$//' | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
|
||||
case "$key" in
|
||||
PG_HOST) PGHOST_VAL="$value" ;;
|
||||
PG_PORT) PGPORT_VAL="$value" ;;
|
||||
PG_USER) PGUSER_VAL="$value" ;;
|
||||
PG_PASSWORD) PGPASSWORD_VAL="$value" ;;
|
||||
esac
|
||||
done < <(grep -Ev '^[[:space:]]*($|#)' "$creds_file")
|
||||
else
|
||||
local rest
|
||||
PGHOST_VAL="${line%%:*}"
|
||||
rest="${line#*:}"
|
||||
PGPORT_VAL="${rest%%:*}"
|
||||
rest="${rest#*:}"
|
||||
rest="${rest#*:}"
|
||||
PGUSER_VAL="${rest%%:*}"
|
||||
PGPASSWORD_VAL="${rest#*:}"
|
||||
fi
|
||||
|
||||
[ -n "$PGHOST_VAL" ] || PGHOST_VAL="localhost"
|
||||
[ -n "$PGPORT_VAL" ] || PGPORT_VAL="5432"
|
||||
[ -n "$PGUSER_VAL" ] || PGUSER_VAL="postgres"
|
||||
if [ -z "$PGPASSWORD_VAL" ]; then
|
||||
fail "Brak hasła PostgreSQL w /usr/local/directadmin/conf/postgresql.conf"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_bins() {
|
||||
PSQL_BIN="$(command -v psql 2>/dev/null || true)"
|
||||
PG_DUMP_BIN="$(command -v pg_dump 2>/dev/null || true)"
|
||||
|
||||
if [ -z "$PSQL_BIN" ] || [ -z "$PG_DUMP_BIN" ]; then
|
||||
fail "Brak wymaganych binariów PostgreSQL (psql/pg_dump)."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sql_escape_literal() {
|
||||
printf '%s' "$1" | sed "s/'/''/g"
|
||||
}
|
||||
|
||||
psql_admin() {
|
||||
local database="$1"
|
||||
shift
|
||||
PGPASSWORD="$PGPASSWORD_VAL" "$PSQL_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$PGUSER_VAL" -d "$database" -v ON_ERROR_STOP=1 "$@"
|
||||
}
|
||||
|
||||
cleanup_tmp_user() {
|
||||
if [ "$TMP_DB_ACTIVE" -ne 1 ] || [ -z "${TMP_DB_USER:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
psql_admin postgres -c "DROP ROLE IF EXISTS \"$TMP_DB_USER\";" >/dev/null 2>&1 || true
|
||||
TMP_DB_ACTIVE=0
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
if [ "$code" -ne 0 ] && [ -z "${ERROR_MSG:-}" ] && [ -n "${LOG_FILE:-}" ]; then
|
||||
ERROR_MSG="Zadanie backupu zakończyło się błędem (kod ${code}) bez zarejestrowanego szczegółu."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG}" >> "$LOG_FILE"
|
||||
fi
|
||||
cleanup_tmp_user
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'log_unexpected_error "$LINENO" "$BASH_COMMAND" "$?"' ERR
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
random_tmp_password() {
|
||||
local value
|
||||
value="$(od -An -N48 -tx1 /dev/urandom 2>/dev/null | tr -d '[:space:]')"
|
||||
if [ -z "$value" ]; then
|
||||
value="$(date +%s%N)${RANDOM}${RANDOM}"
|
||||
fi
|
||||
printf '%s' "${value:0:32}"
|
||||
}
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
JOB_ID="${JOB_ID:-$(basename "$JOB_FILE" .env)}"
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DATE_DIR="${DATE_DIR:-$(date +%Y-%m-%d)}"
|
||||
COMPRESS_BACKUP="${COMPRESS_BACKUP:-1}"
|
||||
USER_BASE_BACKUP_DIR="${USER_BASE_BACKUP_DIR:-/home/${DA_USER}/postgres_backup}"
|
||||
JOB_COMPRESS_BACKUP="${COMPRESS_BACKUP}"
|
||||
JOB_USER_BASE_BACKUP_DIR="${USER_BASE_BACKUP_DIR}"
|
||||
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Start backup job ${JOB_ID}" > "$LOG_FILE"
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ]; then
|
||||
fail "Brak DA_USER lub DB_NAME w pliku zadania."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* ]]; then
|
||||
fail "Baza ${DB_NAME} nie należy do użytkownika ${DA_USER}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_plugin_settings
|
||||
COMPRESS_BACKUP="${JOB_COMPRESS_BACKUP:-$COMPRESS_BACKUP}"
|
||||
USER_BASE_BACKUP_DIR="${JOB_USER_BASE_BACKUP_DIR:-$USER_BASE_BACKUP_DIR}"
|
||||
load_pg_credentials
|
||||
resolve_bins
|
||||
|
||||
if [[ "$COMPRESS_BACKUP" =~ ^(true|yes|on)$ ]]; then
|
||||
COMPRESS_BACKUP="1"
|
||||
fi
|
||||
if [[ "$COMPRESS_BACKUP" =~ ^(false|no|off)$ ]]; then
|
||||
COMPRESS_BACKUP="0"
|
||||
fi
|
||||
|
||||
TARGET_DIR="${USER_BASE_BACKUP_DIR%/}/${DATE_DIR}"
|
||||
if [[ "$DATE_DIR" == *".."* || "$DATE_DIR" == */* ]]; then
|
||||
fail "Nieprawidłowa nazwa katalogu backupu."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! mkdir -p "$TARGET_DIR" 2>>"$LOG_FILE"; then
|
||||
fail "Brak dostępu do katalogu backupu: ${TARGET_DIR}. Uruchom worker jako root lub popraw uprawnienia USER_BASE_BACKUP_DIR."
|
||||
exit 1
|
||||
fi
|
||||
if ! chmod 755 "$TARGET_DIR" >/dev/null 2>&1; then
|
||||
fail "Nie można ustawić uprawnień katalogu backupu: ${TARGET_DIR}."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(id -u)" -eq 0 ] && [ -n "$DA_USER" ]; then
|
||||
chown "$DA_USER:$DA_USER" "$TARGET_DIR" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql"
|
||||
if [ "$COMPRESS_BACKUP" = "1" ]; then
|
||||
TARGET_FILE="${TARGET_FILE}.gz"
|
||||
fi
|
||||
set_job_var "TARGET_FILE" "$TARGET_FILE"
|
||||
|
||||
DB_ESC="$(sql_escape_literal "$DB_NAME")"
|
||||
DB_EXISTS="$(psql_admin postgres -At -c "SELECT 1 FROM pg_database WHERE datname='${DB_ESC}'" 2>/dev/null || true)"
|
||||
if [ -z "$DB_EXISTS" ]; then
|
||||
fail "Baza danych ${DB_NAME} nie istnieje."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMP_DB_USER="pgtmp_bkp_$(printf '%s' "$JOB_ID" | tr -cd 'A-Za-z0-9' | tr '[:upper:]' '[:lower:]' | head -c 18)"
|
||||
[ -n "$TMP_DB_USER" ] || TMP_DB_USER="pgtmp_bkp_$(date +%s)"
|
||||
TMP_DB_PASS="$(random_tmp_password)"
|
||||
PASS_ESC="$(sql_escape_literal "$TMP_DB_PASS")"
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tworzenie tymczasowego użytkownika backupu ${TMP_DB_USER}" >> "$LOG_FILE"
|
||||
psql_admin postgres -c "SET password_encryption='scram-sha-256'; DROP ROLE IF EXISTS \"$TMP_DB_USER\"; CREATE ROLE \"$TMP_DB_USER\" LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOREPLICATION PASSWORD '${PASS_ESC}';" >> "$LOG_FILE" 2>&1
|
||||
TMP_DB_ACTIVE=1
|
||||
|
||||
psql_admin postgres -c "GRANT CONNECT, TEMPORARY ON DATABASE \"$DB_NAME\" TO \"$TMP_DB_USER\";" >> "$LOG_FILE" 2>&1
|
||||
psql_admin "$DB_NAME" -c "GRANT USAGE ON SCHEMA public TO \"$TMP_DB_USER\"; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"$TMP_DB_USER\"; GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO \"$TMP_DB_USER\"; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO \"$TMP_DB_USER\";" >> "$LOG_FILE" 2>&1
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tworzenie backupu bazy ${DB_NAME}" >> "$LOG_FILE"
|
||||
if [ "$COMPRESS_BACKUP" = "1" ]; then
|
||||
set +e
|
||||
PGPASSWORD="$TMP_DB_PASS" "$PG_DUMP_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$TMP_DB_USER" -d "$DB_NAME" --no-owner --no-privileges --format=plain 2>>"$LOG_FILE" | gzip -9 > "$TARGET_FILE"
|
||||
RC=$?
|
||||
set -e
|
||||
else
|
||||
set +e
|
||||
PGPASSWORD="$TMP_DB_PASS" "$PG_DUMP_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$TMP_DB_USER" -d "$DB_NAME" --no-owner --no-privileges --format=plain > "$TARGET_FILE" 2>>"$LOG_FILE"
|
||||
RC=$?
|
||||
set -e
|
||||
fi
|
||||
|
||||
if [ "$RC" -ne 0 ]; then
|
||||
fail "Nie udało się utworzyć backupu bazy ${DB_NAME}."
|
||||
rm -f "$TARGET_FILE" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" -eq 0 ] && [ -n "$DA_USER" ]; then
|
||||
chown "$DA_USER:$DA_USER" "$TARGET_FILE" 2>/dev/null || true
|
||||
fi
|
||||
chmod 644 "$TARGET_FILE" 2>/dev/null || true
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup zapisany: ${TARGET_FILE}" >> "$LOG_FILE"
|
||||
|
||||
CURRENT_EXT=".sql"
|
||||
if [ "$COMPRESS_BACKUP" = "1" ]; then
|
||||
CURRENT_EXT="${CURRENT_EXT}.gz"
|
||||
fi
|
||||
CURRENT_FILE="${TARGET_DIR}/${DB_NAME}-current${CURRENT_EXT}"
|
||||
if [ -n "$USER_BASE_BACKUP_DIR" ] && [ -d "$USER_BASE_BACKUP_DIR" ]; then
|
||||
find "$USER_BASE_BACKUP_DIR" -maxdepth 3 -type f -name "${DB_NAME}-current.sql*" -exec rm -f {} + >/dev/null 2>&1 || true
|
||||
fi
|
||||
ln -sfn "$TARGET_FILE" "$CURRENT_FILE" 2>/dev/null || cp -f "$TARGET_FILE" "$CURRENT_FILE" >/dev/null 2>&1 || true
|
||||
if [ "$(id -u)" -eq 0 ] && [ -n "$DA_USER" ]; then
|
||||
chown "$DA_USER:$DA_USER" "$CURRENT_FILE" 2>/dev/null || true
|
||||
fi
|
||||
chmod 644 "$CURRENT_FILE" 2>/dev/null || true
|
||||
|
||||
BACKUP_OK=1
|
||||
exit 0
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
SUDOERS_FILE="/etc/sudoers.d/directadmin-postgresql-plugin"
|
||||
CRON_FILE="/etc/cron.d/da-postgresql-jobs"
|
||||
IS_ROOT=0
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
IS_ROOT=1
|
||||
fi
|
||||
|
||||
set_permissions() {
|
||||
local owner="$1"
|
||||
local mode="$2"
|
||||
local path="$3"
|
||||
if [ -e "$path" ]; then
|
||||
chown "$owner" "$path" 2>/dev/null || true
|
||||
chmod "$mode" "$path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
set_mode_if_exists() {
|
||||
local mode="$1"
|
||||
local path="$2"
|
||||
if [ -e "$path" ]; then
|
||||
chmod "$mode" "$path" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_custom_package_items() {
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "postgresql: warning: uruchom jako root, aby zaktualizować $CPIF" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
touch "$CPIF"
|
||||
|
||||
sed -i '/^postgresql_enabled=/d' "$CPIF" || true
|
||||
sed -i '/^postgresql=/d' "$CPIF" || true
|
||||
sed -i '/^upostgresql=/d' "$CPIF" || true
|
||||
sed -i '/^postgresql_max_databases=/d' "$CPIF" || true
|
||||
sed -i '/^postgresql_unlimited=/d' "$CPIF" || true
|
||||
sed -i '/^upostgresql_max_databases=/d' "$CPIF" || true
|
||||
|
||||
cat >> "$CPIF" <<'EOF'
|
||||
postgresql_enabled=type=checkbox&string=Enable da-postgresql&desc=Zezwól na dostęp do baz danych PostgreSQL&default=no
|
||||
postgresql=type=text&string=Bazy PostgreSQL&desc=&default=5
|
||||
upostgresql=type=checkbox&string=Bez ograniczeń&desc=&default=no&custom=autofocus%20onfocus%3D%22%28function%28c%29%7Bif%28c.dataset.pgInit%3D%3D%3D%271%27%29%7Breturn%3B%7Dc.dataset.pgInit%3D%271%27%3Bvar%20l%3Ddocument.querySelector%28%22input%5Bname%3D%27postgresql%27%5D%22%29%3Bif%28%21l%29%7Breturn%3B%7Dvar%20r%3Dc.closest%28%27tr%27%29%3Bvar%20td%3Dl.closest%28%27td%27%29%3Bif%28r%26%26td%29%7Bvar%20w%3Ddocument.getElementById%28%27da-pg-unlim-wrap%27%29%3Bif%28%21w%29%7Bw%3Ddocument.createElement%28%27label%27%29%3Bw.id%3D%27da-pg-unlim-wrap%27%3Bw.style.display%3D%27inline-flex%27%3Bw.style.alignItems%3D%27center%27%3Bw.style.gap%3D%278px%27%3Bw.style.marginLeft%3D%2712px%27%3Bw.appendChild%28c%29%3Bw.appendChild%28document.createTextNode%28%27Bez%20ogranicze%C5%84%27%29%29%3Btd.style.display%3D%27flex%27%3Btd.style.alignItems%3D%27center%27%3Btd.style.justifyContent%3D%27space-between%27%3Btd.appendChild%28w%29%3B%7Dr.style.display%3D%27none%27%3B%7Dvar%20s%3Dfunction%28%29%7Bl.disabled%3D%21%21c.checked%3B%7D%3Bc.addEventListener%28%27change%27%2Cs%29%3Bs%28%29%3B%7D%29%28this%29%22%20onchange%3D%22var%20l%3Ddocument.querySelector%28%22input%5Bname%3D%27postgresql%27%5D%22%29%3Bif%28l%29%7Bl.disabled%3D%21%21this.checked%3B%7D%22
|
||||
EOF
|
||||
|
||||
set_permissions diradmin:diradmin 640 "$CPIF"
|
||||
}
|
||||
|
||||
bootstrap_sudoers() {
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "postgresql: warning: uruchom jako root, aby dodać sudoers dla synchronizacji pg_hba" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
cat > "$SUDOERS_FILE" <<SUDO
|
||||
# DirectAdmin PostgreSQL plugin
|
||||
diradmin ALL=(root) NOPASSWD: /usr/local/directadmin/plugins/da-postgresql/scripts/setup/pg_hba_sync.sh
|
||||
SUDO
|
||||
|
||||
chmod 440 "$SUDOERS_FILE"
|
||||
}
|
||||
|
||||
bootstrap_worker_cron() {
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "postgresql: warning: uruchom jako root, aby dodać cron workera backup/restore" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
touch "$CRON_FILE"
|
||||
if ! grep -Fq '/usr/local/directadmin/plugins/da-postgresql/scripts/worker.sh' "$CRON_FILE"; then
|
||||
echo '* * * * * root /usr/local/directadmin/plugins/da-postgresql/scripts/worker.sh >/dev/null 2>&1' >> "$CRON_FILE"
|
||||
fi
|
||||
chmod 644 "$CRON_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
bootstrap_adminer() {
|
||||
local adminer_setup="$PLUGIN_DIR/scripts/setup/adminer_install.sh"
|
||||
|
||||
if [ "$IS_ROOT" -ne 1 ]; then
|
||||
echo "postgresql: warning: uruchom jako root, aby zainstalować Adminera." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f "$adminer_setup" ]; then
|
||||
echo "postgresql: error: brak skryptu instalacji Adminera: $adminer_setup" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
bash "$adminer_setup"
|
||||
}
|
||||
|
||||
activate_plugin() {
|
||||
local conf="$PLUGIN_DIR/plugin.conf"
|
||||
if [ -f "$conf" ]; then
|
||||
sed -i 's/^active=.*/active=yes/' "$conf" || true
|
||||
sed -i 's/^installed=.*/installed=yes/' "$conf" || true
|
||||
fi
|
||||
}
|
||||
|
||||
mkdir -p "$PLUGIN_DIR/data"
|
||||
mkdir -p "$PLUGIN_DIR/exec" "$PLUGIN_DIR/exec/lib" "$PLUGIN_DIR/exec/handlers"
|
||||
mkdir -p "$PLUGIN_DIR/data/jobs/pending" "$PLUGIN_DIR/data/jobs/processing" "$PLUGIN_DIR/data/jobs/done"
|
||||
mkdir -p "$PLUGIN_DIR/data/logs" "$PLUGIN_DIR/data/lock" "$PLUGIN_DIR/data/pids" "$PLUGIN_DIR/data/cancel"
|
||||
mkdir -p "$PLUGIN_DIR/data/uploads"
|
||||
|
||||
if [ "$IS_ROOT" -eq 1 ]; then
|
||||
chown -R diradmin:diradmin "$PLUGIN_DIR" 2>/dev/null || true
|
||||
else
|
||||
echo "postgresql: warning: uruchom jako root, aby ustawić właściciela plików pluginu (w przeciwnym razie może wystąpić biała strona)." >&2
|
||||
fi
|
||||
|
||||
find "$PLUGIN_DIR" -type d -exec chmod 755 {} + 2>/dev/null || true
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs/pending"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs/processing"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/jobs/done"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/logs"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/lock"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/pids"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/cancel"
|
||||
set_mode_if_exists 700 "$PLUGIN_DIR/data/uploads"
|
||||
find "$PLUGIN_DIR" -type f -name '*.html' -exec chmod 755 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR" -type f -name '*.raw' -exec chmod 755 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR" -type f -name '*.css' -exec chmod 644 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR/scripts" -type f -name '*.sh' -exec chmod 755 {} + 2>/dev/null || true
|
||||
find "$PLUGIN_DIR" -type f -name '*.php' -exec chmod 640 {} + 2>/dev/null || true
|
||||
set_mode_if_exists 644 "$PLUGIN_DIR/plugin.conf"
|
||||
set_mode_if_exists 644 "$PLUGIN_DIR/plugin-settings.conf"
|
||||
set_mode_if_exists 644 "$PLUGIN_DIR/php.ini"
|
||||
|
||||
if [ ! -f "$PLUGIN_DIR/data/secret.key" ]; then
|
||||
tr -dc 'a-f0-9' </dev/urandom | head -c 64 > "$PLUGIN_DIR/data/secret.key" || true
|
||||
fi
|
||||
set_mode_if_exists 600 "$PLUGIN_DIR/data/secret.key"
|
||||
|
||||
touch "$PLUGIN_DIR/error.log" 2>/dev/null || true
|
||||
if [ "$IS_ROOT" -eq 1 ]; then
|
||||
chown diradmin:diradmin "$PLUGIN_DIR/error.log" 2>/dev/null || true
|
||||
fi
|
||||
set_mode_if_exists 640 "$PLUGIN_DIR/error.log"
|
||||
|
||||
bootstrap_custom_package_items
|
||||
bootstrap_sudoers
|
||||
bootstrap_worker_cron
|
||||
bootstrap_adminer
|
||||
activate_plugin
|
||||
|
||||
echo "da-postgresql: instalacja zakończona."
|
||||
echo "Przypomnienie: zapisz pakiety i user.conf w DirectAdmin, aby odświeżyć custom package items."
|
||||
@@ -0,0 +1,578 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ERROR_MSG=""
|
||||
RESTORE_OK=0
|
||||
DA_USER=""
|
||||
DB_NAME=""
|
||||
JOB_ID=""
|
||||
LOG_FILE=""
|
||||
RESTORE_MODE=""
|
||||
SOURCE_DB_NAME=""
|
||||
USE_OWNER_RESTORE=0
|
||||
USE_SANITIZE=0
|
||||
PRE_CLEAN=0
|
||||
TARGET_DB_EMPTY=0
|
||||
TMP_DB_USER=""
|
||||
TMP_DB_PASS=""
|
||||
TMP_DB_ACTIVE=0
|
||||
ORIG_DB_OWNER=""
|
||||
DB_OWNER_SWITCHED=0
|
||||
|
||||
JOB_FILE="${1:-}"
|
||||
if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then
|
||||
echo "da-postgresql: missing job file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf"
|
||||
LOG_DIR="${PLUGIN_DIR}/data/logs"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
urlencode() {
|
||||
local s="$1"
|
||||
local out=""
|
||||
local i c
|
||||
for ((i=0; i<${#s}; i++)); do
|
||||
c="${s:i:1}"
|
||||
case "$c" in
|
||||
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
||||
' ') out+='%20' ;;
|
||||
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
notify_user() {
|
||||
local code="$1"
|
||||
if [ -z "${DA_USER:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local subject message
|
||||
if [ "$code" -eq 0 ] && [ "$RESTORE_OK" -eq 1 ]; then
|
||||
subject="Przywracanie bazy PostgreSQL zakończone pomyślnie"
|
||||
message="Przywracanie bazy ${DB_NAME} z pliku ${SOURCE_FILE:-nieznany} zakończone."
|
||||
else
|
||||
subject="Błąd przywracania bazy PostgreSQL"
|
||||
if [ -n "$ERROR_MSG" ]; then
|
||||
message="Przywracanie bazy ${DB_NAME} nie powiodło się: ${ERROR_MSG}"
|
||||
else
|
||||
message="Przywracanie bazy ${DB_NAME} nie powiodło się. Sprawdź log zadania."
|
||||
fi
|
||||
fi
|
||||
|
||||
local task_queue="/usr/local/directadmin/data/task.queue"
|
||||
local users="select1%3D$(urlencode "$DA_USER")"
|
||||
local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}"
|
||||
printf "%s\n" "$line" >> "$task_queue"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local msg="$1"
|
||||
ERROR_MSG="$msg"
|
||||
if [ -n "${LOG_FILE:-}" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${msg}" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
log_unexpected_error() {
|
||||
local line_no="$1"
|
||||
local command="$2"
|
||||
local code="$3"
|
||||
if [ -z "${LOG_FILE:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -n "${ERROR_MSG:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
ERROR_MSG="Nieoczekiwany błąd wykonywania przywracania (kod ${code}, linia ${line_no})."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG} CMD: ${command}" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
load_plugin_settings() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
fail "Brak pliku konfiguracyjnego pluginu: $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
load_pg_credentials() {
|
||||
local creds_file="/usr/local/directadmin/conf/postgresql.conf"
|
||||
PGHOST_VAL=""
|
||||
PGPORT_VAL=""
|
||||
PGUSER_VAL=""
|
||||
PGPASSWORD_VAL=""
|
||||
|
||||
if [ ! -r "$creds_file" ]; then
|
||||
fail "Brak pliku poświadczeń PostgreSQL: $creds_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local line
|
||||
line="$(grep -Ev '^[[:space:]]*($|#)' "$creds_file" | head -n1 || true)"
|
||||
if [ -z "$line" ]; then
|
||||
fail "Pusty plik poświadczeń PostgreSQL: $creds_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$line" == *=* ]]; then
|
||||
while IFS='=' read -r key value; do
|
||||
key="$(echo "$key" | tr -d '[:space:]' | tr '[:lower:]' '[:upper:]')"
|
||||
value="$(echo "$value" | sed -E 's/[[:space:]]+#.*$//' | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
|
||||
case "$key" in
|
||||
PG_HOST) PGHOST_VAL="$value" ;;
|
||||
PG_PORT) PGPORT_VAL="$value" ;;
|
||||
PG_USER) PGUSER_VAL="$value" ;;
|
||||
PG_PASSWORD) PGPASSWORD_VAL="$value" ;;
|
||||
esac
|
||||
done < <(grep -Ev '^[[:space:]]*($|#)' "$creds_file")
|
||||
else
|
||||
local rest
|
||||
PGHOST_VAL="${line%%:*}"
|
||||
rest="${line#*:}"
|
||||
PGPORT_VAL="${rest%%:*}"
|
||||
rest="${rest#*:}"
|
||||
rest="${rest#*:}"
|
||||
PGUSER_VAL="${rest%%:*}"
|
||||
PGPASSWORD_VAL="${rest#*:}"
|
||||
fi
|
||||
|
||||
[ -n "$PGHOST_VAL" ] || PGHOST_VAL="localhost"
|
||||
[ -n "$PGPORT_VAL" ] || PGPORT_VAL="5432"
|
||||
[ -n "$PGUSER_VAL" ] || PGUSER_VAL="postgres"
|
||||
if [ -z "$PGPASSWORD_VAL" ]; then
|
||||
fail "Brak hasła PostgreSQL w /usr/local/directadmin/conf/postgresql.conf"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_bins() {
|
||||
PSQL_BIN="$(command -v psql 2>/dev/null || true)"
|
||||
if [ -z "$PSQL_BIN" ]; then
|
||||
fail "Brak binarium psql."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
sql_escape_literal() {
|
||||
printf '%s' "$1" | sed "s/'/''/g"
|
||||
}
|
||||
|
||||
psql_admin() {
|
||||
local database="$1"
|
||||
shift
|
||||
PGPASSWORD="$PGPASSWORD_VAL" "$PSQL_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$PGUSER_VAL" -d "$database" -v ON_ERROR_STOP=1 "$@"
|
||||
}
|
||||
|
||||
role_exists() {
|
||||
local role="$1"
|
||||
if [ -z "$role" ]; then
|
||||
return 1
|
||||
fi
|
||||
local esc
|
||||
esc="$(sql_escape_literal "$role")"
|
||||
local ok
|
||||
ok="$(psql_admin postgres -At -c "SELECT 1 FROM pg_roles WHERE rolname='${esc}'" 2>/dev/null || true)"
|
||||
[ -n "$ok" ]
|
||||
}
|
||||
|
||||
resolve_db_owner() {
|
||||
local owner
|
||||
owner="$(psql_admin postgres -At -c "SELECT pg_get_userbyid(datdba) FROM pg_database WHERE datname='${DB_ESC}'" 2>/dev/null | head -n1 | tr -d '\r' || true)"
|
||||
if role_exists "$owner"; then
|
||||
printf '%s' "$owner"
|
||||
return 0
|
||||
fi
|
||||
if role_exists "$DA_USER"; then
|
||||
printf '%s' "$DA_USER"
|
||||
return 0
|
||||
fi
|
||||
if role_exists "$PGUSER_VAL"; then
|
||||
printf '%s' "$PGUSER_VAL"
|
||||
return 0
|
||||
fi
|
||||
printf '%s' ""
|
||||
return 0
|
||||
}
|
||||
|
||||
database_is_empty() {
|
||||
local db_name="$1"
|
||||
local has_objects
|
||||
has_objects="$(
|
||||
psql_admin "$db_name" -At -c "
|
||||
SELECT 1
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname NOT LIKE 'pg_%'
|
||||
AND n.nspname <> 'information_schema'
|
||||
LIMIT 1;
|
||||
" 2>/dev/null || true
|
||||
)"
|
||||
[ -z "$has_objects" ]
|
||||
}
|
||||
|
||||
run_restore_as_owner() {
|
||||
local owner="$1"
|
||||
if [ -z "$owner" ]; then
|
||||
fail "Nie można ustalić właściciela bazy ${DB_NAME}."
|
||||
return 1
|
||||
fi
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Przywracanie jako właściciel ${owner}" >> "$LOG_FILE"
|
||||
if [[ "$SOURCE_FILE" == *.gz ]]; then
|
||||
set +e
|
||||
{ printf 'SET ROLE "%s";\n' "$owner"; gunzip -c "$SOURCE_FILE" | sanitize_restore_compat; } | psql_admin "$DB_NAME" >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
set -e
|
||||
else
|
||||
set +e
|
||||
{ printf 'SET ROLE "%s";\n' "$owner"; sanitize_restore_compat < "$SOURCE_FILE"; } | psql_admin "$DB_NAME" >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
set -e
|
||||
fi
|
||||
return "$RC"
|
||||
}
|
||||
|
||||
sanitize_pg_dump() {
|
||||
local src_db="$1"
|
||||
local dst_db="$2"
|
||||
awk -v src="$src_db" -v dst="$dst_db" '
|
||||
function lower(s) { return tolower(s) }
|
||||
{
|
||||
line = $0
|
||||
l = lower(line)
|
||||
if (l ~ /^[[:space:]]*\\connect/ || l ~ /^[[:space:]]*\\c[[:space:]]/) { next }
|
||||
if (l ~ /^[[:space:]]*create[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*drop[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*alter[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*comment[[:space:]]+on[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*revoke[[:space:]].*[[:space:]]on[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*grant[[:space:]].*[[:space:]]on[[:space:]]+database[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*set[[:space:]]+role[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*set[[:space:]]+session[[:space:]]+authorization/) { next }
|
||||
if (l ~ /^[[:space:]]*reset[[:space:]]+role/) { next }
|
||||
if (l ~ /^[[:space:]]*reset[[:space:]]+session[[:space:]]+authorization/) { next }
|
||||
if (l ~ /^[[:space:]]*alter[[:space:]].*[[:space:]]owner[[:space:]]+to[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*comment[[:space:]]+on[[:space:]]+schema[[:space:]]+public[[:space:]]+is[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*owner[[:space:]]+to[[:space:]]+/) { next }
|
||||
if (l ~ /^[[:space:]]*alter[[:space:]]+default[[:space:]]+privileges/) { next }
|
||||
if (l ~ /^[[:space:]]*reassign[[:space:]]+owned/) { next }
|
||||
if (l ~ /^[[:space:]]*drop[[:space:]]+owned/) { next }
|
||||
print line
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
sanitize_restore_compat() {
|
||||
awk '
|
||||
function lower(s) { return tolower(s) }
|
||||
{
|
||||
line = $0
|
||||
l = lower(line)
|
||||
# PostgreSQL < 17: parameter transaction_timeout does not exist.
|
||||
if (l ~ /^[[:space:]]*set[[:space:]]+"?transaction_timeout"?[[:space:]]*=/) { next }
|
||||
if (l ~ /^[[:space:]]*reset[[:space:]]+"?transaction_timeout"?[[:space:]]*;/) { next }
|
||||
print line
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
preclean_database() {
|
||||
local owner="$1"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Czyszczenie bazy ${DB_NAME} przed przywróceniem" >> "$LOG_FILE"
|
||||
local schemas
|
||||
schemas="$(psql_admin "$DB_NAME" -At -c "SELECT nspname FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname <> 'information_schema'" 2>>"$LOG_FILE" || true)"
|
||||
if [ -n "$schemas" ]; then
|
||||
while IFS= read -r schema; do
|
||||
schema="$(printf '%s' "$schema" | tr -d '\r')"
|
||||
[ -z "$schema" ] && continue
|
||||
psql_admin "$DB_NAME" -c "DROP SCHEMA IF EXISTS \"$schema\" CASCADE;" >> "$LOG_FILE" 2>&1 || true
|
||||
done <<< "$schemas"
|
||||
fi
|
||||
if [ -n "$owner" ]; then
|
||||
psql_admin "$DB_NAME" -c "CREATE SCHEMA IF NOT EXISTS public AUTHORIZATION \"$owner\";" >> "$LOG_FILE" 2>&1 || true
|
||||
psql_admin "$DB_NAME" -c "GRANT ALL ON SCHEMA public TO \"$owner\";" >> "$LOG_FILE" 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
restore_db_owner() {
|
||||
if [ "$DB_OWNER_SWITCHED" -ne 1 ] || [ -z "$ORIG_DB_OWNER" ] || [ -z "${TMP_DB_USER:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! role_exists "$ORIG_DB_OWNER"; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Oryginalny właściciel bazy ${ORIG_DB_OWNER} nie istnieje, pomijam przywrócenie właściciela." >> "$LOG_FILE"
|
||||
DB_OWNER_SWITCHED=0
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Przywracanie właściciela bazy ${DB_NAME} na ${ORIG_DB_OWNER}" >> "$LOG_FILE"
|
||||
set +e
|
||||
psql_admin "$DB_NAME" -c "REASSIGN OWNED BY \"$TMP_DB_USER\" TO \"$ORIG_DB_OWNER\";" >> "$LOG_FILE" 2>&1
|
||||
local rc1=$?
|
||||
psql_admin "$DB_NAME" -c "DROP OWNED BY \"$TMP_DB_USER\";" >> "$LOG_FILE" 2>&1
|
||||
local rc2=$?
|
||||
psql_admin postgres -c "ALTER DATABASE \"$DB_NAME\" OWNER TO \"$ORIG_DB_OWNER\";" >> "$LOG_FILE" 2>&1
|
||||
local rc3=$?
|
||||
set -e
|
||||
|
||||
if [ "$rc1" -ne 0 ] || [ "$rc3" -ne 0 ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Nie udało się w pełni przywrócić właściciela bazy ${DB_NAME}." >> "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
DB_OWNER_SWITCHED=0
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanup_tmp_user() {
|
||||
if [ "$TMP_DB_ACTIVE" -ne 1 ] || [ -z "${TMP_DB_USER:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
psql_admin postgres -c "DROP ROLE IF EXISTS \"$TMP_DB_USER\";" >/dev/null 2>&1 || true
|
||||
TMP_DB_ACTIVE=0
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
local code="$1"
|
||||
if [ "$code" -ne 0 ] && [ -z "${ERROR_MSG:-}" ] && [ -n "${LOG_FILE:-}" ]; then
|
||||
ERROR_MSG="Zadanie przywracania zakończyło się błędem (kod ${code}) bez zarejestrowanego szczegółu."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: ${ERROR_MSG}" >> "$LOG_FILE"
|
||||
fi
|
||||
restore_db_owner || true
|
||||
cleanup_tmp_user
|
||||
notify_user "$code"
|
||||
}
|
||||
trap 'log_unexpected_error "$LINENO" "$BASH_COMMAND" "$?"' ERR
|
||||
trap 'on_exit $?' EXIT
|
||||
|
||||
random_tmp_password() {
|
||||
local value
|
||||
value="$(od -An -N48 -tx1 /dev/urandom 2>/dev/null | tr -d '[:space:]')"
|
||||
if [ -z "$value" ]; then
|
||||
value="$(date +%s%N)${RANDOM}${RANDOM}"
|
||||
fi
|
||||
printf '%s' "${value:0:32}"
|
||||
}
|
||||
|
||||
path_in_dir() {
|
||||
local path="$1"
|
||||
local dir="$2"
|
||||
local real_path real_dir
|
||||
real_path="$(realpath "$path" 2>/dev/null || true)"
|
||||
real_dir="$(realpath "$dir" 2>/dev/null || true)"
|
||||
if [ -z "$real_path" ] || [ -z "$real_dir" ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$real_path" = "$real_dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
case "$real_path" in
|
||||
"$real_dir"/*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$JOB_FILE"
|
||||
|
||||
JOB_ID="${JOB_ID:-$(basename "$JOB_FILE" .env)}"
|
||||
DA_USER="${DA_USER:-}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
SOURCE_FILE="${SOURCE_FILE:-}"
|
||||
SOURCE_KIND="${SOURCE_KIND:-local}"
|
||||
RESTORE_MODE="${RESTORE_MODE:-overwrite}"
|
||||
SOURCE_DB_NAME="${SOURCE_DB_NAME:-}"
|
||||
|
||||
RESTORE_MODE="$(printf '%s' "$RESTORE_MODE" | tr '[:upper:]' '[:lower:]')"
|
||||
if [ "$RESTORE_MODE" != "new_db" ]; then
|
||||
RESTORE_MODE="overwrite"
|
||||
fi
|
||||
|
||||
LOG_FILE="${LOG_DIR}/${JOB_ID}.log"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Start restore job ${JOB_ID}" > "$LOG_FILE"
|
||||
|
||||
if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ] || [ -z "$SOURCE_FILE" ]; then
|
||||
fail "Brak DA_USER, DB_NAME lub SOURCE_FILE w pliku zadania."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$DB_NAME" != "${DA_USER}_"* ]]; then
|
||||
fail "Baza ${DB_NAME} nie należy do użytkownika ${DA_USER}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_plugin_settings
|
||||
load_pg_credentials
|
||||
resolve_bins
|
||||
|
||||
if [ ! -f "$SOURCE_FILE" ] || [ ! -r "$SOURCE_FILE" ]; then
|
||||
fail "Nie można odczytać pliku backupu: ${SOURCE_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$SOURCE_FILE" in
|
||||
*.sql|*.sql.gz|*.gz) ;;
|
||||
*)
|
||||
fail "Dozwolone są wyłącznie pliki .sql, .gz i .sql.gz."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
USER_BACKUP_ROOT="${HITME_BACKUP_LOCATION:-/home/admin/postgres_backup}"
|
||||
UPLOAD_ROOT="${PLUGIN_DIR}/data/uploads/${DA_USER}"
|
||||
if [ "$SOURCE_KIND" = "local" ]; then
|
||||
if ! path_in_dir "$SOURCE_FILE" "$USER_BACKUP_ROOT"; then
|
||||
fail "Plik backupu lokalnego znajduje się poza dozwolonym katalogiem użytkownika."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! path_in_dir "$SOURCE_FILE" "$UPLOAD_ROOT"; then
|
||||
fail "Plik do przywrócenia znajduje się poza katalogiem upload."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
DB_ESC="$(sql_escape_literal "$DB_NAME")"
|
||||
DB_EXISTS="$(psql_admin postgres -At -c "SELECT 1 FROM pg_database WHERE datname='${DB_ESC}'" 2>/dev/null || true)"
|
||||
if [ -z "$DB_EXISTS" ]; then
|
||||
fail "Docelowa baza danych ${DB_NAME} nie istnieje."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$RESTORE_MODE" = "overwrite" ] && [ -n "$SOURCE_DB_NAME" ] && [ "$SOURCE_DB_NAME" = "$DB_NAME" ]; then
|
||||
USE_OWNER_RESTORE=1
|
||||
PRE_CLEAN=1
|
||||
fi
|
||||
if [ "$RESTORE_MODE" = "new_db" ]; then
|
||||
PRE_CLEAN=1
|
||||
fi
|
||||
if [ "$SOURCE_KIND" = "local" ]; then
|
||||
USE_SANITIZE=1
|
||||
fi
|
||||
if [ "$SOURCE_KIND" = "file" ]; then
|
||||
SOURCE_DB_NAME=""
|
||||
USE_SANITIZE=1
|
||||
fi
|
||||
if [ "$RESTORE_MODE" = "new_db" ] && [ -n "$SOURCE_DB_NAME" ] && [ "$SOURCE_DB_NAME" != "$DB_NAME" ]; then
|
||||
USE_SANITIZE=1
|
||||
fi
|
||||
|
||||
ORIG_DB_OWNER="$(resolve_db_owner)"
|
||||
if [ -z "$ORIG_DB_OWNER" ]; then
|
||||
fail "Nie można ustalić właściciela bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$RESTORE_MODE" = "new_db" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Typ przywracania: Przywracanie do nowej bazy" >> "$LOG_FILE"
|
||||
else
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Typ przywracania: Przywracanie z nadpisaniem" >> "$LOG_FILE"
|
||||
fi
|
||||
if [ -n "$SOURCE_DB_NAME" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Baza źródłowa: ${SOURCE_DB_NAME}" >> "$LOG_FILE"
|
||||
fi
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Baza docelowa: ${DB_NAME}" >> "$LOG_FILE"
|
||||
|
||||
if [ "$PRE_CLEAN" -eq 1 ]; then
|
||||
preclean_database "$ORIG_DB_OWNER"
|
||||
fi
|
||||
|
||||
# Jeżeli restore pochodzi z zewnętrznego pliku i baza docelowa jest pusta,
|
||||
# użyj użytkownika przypisanego do bazy (właściciela), bez użytkownika tymczasowego.
|
||||
if database_is_empty "$DB_NAME"; then
|
||||
TARGET_DB_EMPTY=1
|
||||
fi
|
||||
if [ "$SOURCE_KIND" = "file" ] && [ "$TARGET_DB_EMPTY" -eq 1 ] && [ "$USE_OWNER_RESTORE" -eq 0 ]; then
|
||||
USE_OWNER_RESTORE=1
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Wykryto pustą bazę docelową i restore z pliku: używam użytkownika przypisanego do bazy (${ORIG_DB_OWNER})." >> "$LOG_FILE"
|
||||
fi
|
||||
|
||||
if [ "$USE_OWNER_RESTORE" -eq 0 ]; then
|
||||
TMP_DB_USER="pgtmp_rst_$(printf '%s' "$JOB_ID" | tr -cd 'A-Za-z0-9' | tr '[:upper:]' '[:lower:]' | head -c 18)"
|
||||
[ -n "$TMP_DB_USER" ] || TMP_DB_USER="pgtmp_rst_$(date +%s)"
|
||||
TMP_DB_PASS="$(random_tmp_password)"
|
||||
PASS_ESC="$(sql_escape_literal "$TMP_DB_PASS")"
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tworzenie tymczasowego użytkownika restore ${TMP_DB_USER}" >> "$LOG_FILE"
|
||||
psql_admin postgres -c "SET password_encryption='scram-sha-256'; DROP ROLE IF EXISTS \"$TMP_DB_USER\"; CREATE ROLE \"$TMP_DB_USER\" LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOREPLICATION PASSWORD '${PASS_ESC}';" >> "$LOG_FILE" 2>&1
|
||||
TMP_DB_ACTIVE=1
|
||||
|
||||
psql_admin postgres -c "GRANT CONNECT, CREATE, TEMPORARY ON DATABASE \"$DB_NAME\" TO \"$TMP_DB_USER\";" >> "$LOG_FILE" 2>&1
|
||||
psql_admin "$DB_NAME" -c "GRANT USAGE, CREATE ON SCHEMA public TO \"$TMP_DB_USER\"; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"$TMP_DB_USER\"; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO \"$TMP_DB_USER\"; GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO \"$TMP_DB_USER\";" >> "$LOG_FILE" 2>&1
|
||||
|
||||
if [ -n "$ORIG_DB_OWNER" ] && [ "$ORIG_DB_OWNER" != "$TMP_DB_USER" ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tymczasowe przejęcie właściciela bazy ${DB_NAME} przez ${TMP_DB_USER}" >> "$LOG_FILE"
|
||||
psql_admin postgres -c "ALTER DATABASE \"$DB_NAME\" OWNER TO \"$TMP_DB_USER\";" >> "$LOG_FILE" 2>&1
|
||||
DB_OWNER_SWITCHED=1
|
||||
fi
|
||||
|
||||
# Restore z plików dump może zawierać operacje wymagające bycia właścicielem
|
||||
# schematu public (np. COMMENT ON SCHEMA public). Ustaw właściciela na użytkownika
|
||||
# tymczasowego, a po restore owner wróci przez restore_db_owner().
|
||||
psql_admin "$DB_NAME" -c "ALTER SCHEMA public OWNER TO \"$TMP_DB_USER\";" >> "$LOG_FILE" 2>&1 || true
|
||||
fi
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Przywracanie bazy ${DB_NAME} z pliku ${SOURCE_FILE}" >> "$LOG_FILE"
|
||||
if [ "$USE_OWNER_RESTORE" -eq 1 ]; then
|
||||
set +e
|
||||
if [ "$USE_SANITIZE" -eq 1 ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Sanityzacja dumpa: ${SOURCE_DB_NAME} -> ${DB_NAME}" >> "$LOG_FILE"
|
||||
if [[ "$SOURCE_FILE" == *.gz ]]; then
|
||||
{ printf 'SET ROLE "%s";\n' "$ORIG_DB_OWNER"; gunzip -c "$SOURCE_FILE" | sanitize_pg_dump "$SOURCE_DB_NAME" "$DB_NAME" | sanitize_restore_compat; } | psql_admin "$DB_NAME" >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
else
|
||||
{ printf 'SET ROLE "%s";\n' "$ORIG_DB_OWNER"; sanitize_pg_dump "$SOURCE_DB_NAME" "$DB_NAME" < "$SOURCE_FILE" | sanitize_restore_compat; } | psql_admin "$DB_NAME" >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
fi
|
||||
else
|
||||
run_restore_as_owner "$ORIG_DB_OWNER"
|
||||
RC=$?
|
||||
fi
|
||||
set -e
|
||||
else
|
||||
if [[ "$SOURCE_FILE" == *.gz ]]; then
|
||||
set +e
|
||||
if [ "$USE_SANITIZE" -eq 1 ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Sanityzacja dumpa: ${SOURCE_DB_NAME} -> ${DB_NAME}" >> "$LOG_FILE"
|
||||
gunzip -c "$SOURCE_FILE" | sanitize_pg_dump "$SOURCE_DB_NAME" "$DB_NAME" | sanitize_restore_compat | PGPASSWORD="$TMP_DB_PASS" "$PSQL_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$TMP_DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
else
|
||||
gunzip -c "$SOURCE_FILE" | sanitize_restore_compat | PGPASSWORD="$TMP_DB_PASS" "$PSQL_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$TMP_DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
fi
|
||||
set -e
|
||||
else
|
||||
set +e
|
||||
if [ "$USE_SANITIZE" -eq 1 ]; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Sanityzacja dumpa: ${SOURCE_DB_NAME} -> ${DB_NAME}" >> "$LOG_FILE"
|
||||
sanitize_pg_dump "$SOURCE_DB_NAME" "$DB_NAME" < "$SOURCE_FILE" | sanitize_restore_compat | PGPASSWORD="$TMP_DB_PASS" "$PSQL_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$TMP_DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
else
|
||||
sanitize_restore_compat < "$SOURCE_FILE" | PGPASSWORD="$TMP_DB_PASS" "$PSQL_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$TMP_DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 >> "$LOG_FILE" 2>&1
|
||||
RC=$?
|
||||
fi
|
||||
set -e
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$RC" -ne 0 ]; then
|
||||
fail "Nie udało się przywrócić bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$USE_OWNER_RESTORE" -eq 0 ]; then
|
||||
if ! restore_db_owner; then
|
||||
fail "Nie udało się przywrócić właściciela bazy ${DB_NAME}."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Przywracanie zakończone powodzeniem" >> "$LOG_FILE"
|
||||
RESTORE_OK=1
|
||||
exit 0
|
||||
Executable
+704
@@ -0,0 +1,704 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# adminer_install.sh
|
||||
# Instalacja i integracja Adminera z pluginem da-postgresql (DirectAdmin)
|
||||
#
|
||||
# Założenia bezpieczeństwa:
|
||||
# - Adminer dostępny przez alias /adminer
|
||||
# - Tryb public/private odczytywany runtime z /var/www/html/adminer/runtime/config.json
|
||||
# - Tryb publiczny (ADMINER_PUBLIC=true) pozwala na ręczne logowanie do PostgreSQL
|
||||
# - Tryb prywatny (ADMINER_PUBLIC=false) wymaga ticketu SSO z pluginu
|
||||
# - Ticket SSO ma krótki TTL i jest przypięty do User-Agent
|
||||
# - Logowanie odbywa się tymczasową rolą PostgreSQL tworzoną przez plugin
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
sekcja() { echo -e "\n${BOLD}${CYAN}=== $* ===${NC}\n"; }
|
||||
die() { echo -e "${RED}[BŁĄD]${NC} $*" >&2; exit 1; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "Skrypt musi być uruchomiony jako root."
|
||||
|
||||
LOGFILE="/var/log/adminer_install_$(date +%Y%m%d_%H%M%S).log"
|
||||
exec 1> >(tee >(sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' >> "$LOGFILE")) 2>&1
|
||||
info "Pełny log zapisany w: $LOGFILE"
|
||||
|
||||
ADMINER_URL_PATH="/adminer"
|
||||
ADMINER_VERSION="5.4.2"
|
||||
ADMINER_FLAVOR="pgsql"
|
||||
ADMINER_INSTALL_DIR="/var/www/html/adminer"
|
||||
ADMINER_RUNTIME_DIR="${ADMINER_INSTALL_DIR}/runtime"
|
||||
ADMINER_TICKETS_DIR="${ADMINER_RUNTIME_DIR}/tickets"
|
||||
ADMINER_RUNTIME_CONFIG_FILE="${ADMINER_RUNTIME_DIR}/config.json"
|
||||
ADMINER_CORE_FILE="${ADMINER_INSTALL_DIR}/adminer-core.php"
|
||||
ADMINER_EXTENSION_FILE="${ADMINER_INSTALL_DIR}/adminer-extension.php"
|
||||
ADMINER_INDEX_FILE="${ADMINER_INSTALL_DIR}/index.php"
|
||||
PLUGIN_BUNDLED_ADMINER="/usr/local/directadmin/plugins/da-postgresql/assets/adminer/adminer-${ADMINER_VERSION}-${ADMINER_FLAVOR}.php"
|
||||
PLUGIN_BUNDLED_ADMINER_SHA256="059505abc2b56487d78bbfbeb9485ed8c6a10fe357f4ddec9fc69b1043bff4af"
|
||||
PLUGIN_BUNDLED_EXTENSION="/usr/local/directadmin/plugins/da-postgresql/assets/adminer/adminer-extension.php"
|
||||
PLUGIN_BUNDLED_EXTENSION_SHA256="6e2f098b172838ccb3736506867396dc0954a6383fdd7b7c5e7739ab21baafeb"
|
||||
|
||||
CB_DIR="/usr/local/directadmin/custombuild"
|
||||
CB_BUILD="${CB_DIR}/build"
|
||||
APACHE_ALIAS_CUSTOM="${CB_DIR}/custom/ap2/conf/extra/httpd-alias.conf"
|
||||
APACHE_ALIAS_SOURCE="/etc/httpd/conf/extra/httpd-alias.conf"
|
||||
APACHE_ALIAS_CONFIGURE="${CB_DIR}/configure/ap2/conf/extra/httpd-alias.conf"
|
||||
|
||||
PLUGIN_SETTINGS="/usr/local/directadmin/plugins/da-postgresql/plugin-settings.conf"
|
||||
PLUGIN_OWNER="diradmin:diradmin"
|
||||
ADMINER_PUBLIC_DEFAULT="false"
|
||||
ADMINER_PUBLIC_MODE="0"
|
||||
|
||||
detect_apache_group() {
|
||||
local group_name=""
|
||||
|
||||
if [[ -f /etc/httpd/conf/httpd.conf ]]; then
|
||||
group_name="$(awk 'tolower($1)=="group" {print $2; exit}' /etc/httpd/conf/httpd.conf | tr -d '[:space:]')"
|
||||
fi
|
||||
|
||||
if [[ -z "$group_name" && -f /etc/apache2/apache2.conf ]]; then
|
||||
group_name="$(awk 'tolower($1)=="group" {print $2; exit}' /etc/apache2/apache2.conf | tr -d '[:space:]')"
|
||||
fi
|
||||
|
||||
if [[ -z "$group_name" ]]; then
|
||||
for candidate in apache www-data nobody; do
|
||||
if getent group "$candidate" >/dev/null 2>&1; then
|
||||
group_name="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
[[ -n "$group_name" ]] || die "Nie można wykryć grupy użytkownika Apache."
|
||||
echo "$group_name"
|
||||
}
|
||||
|
||||
set_setting() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local file="$3"
|
||||
|
||||
[[ -f "$file" ]] || return 0
|
||||
sed -i "/^${key}=/d" "$file" || true
|
||||
echo "${key}=${value}" >> "$file"
|
||||
}
|
||||
|
||||
set_setting_if_missing() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local file="$3"
|
||||
|
||||
[[ -f "$file" ]] || return 0
|
||||
if grep -q "^${key}=" "$file"; then
|
||||
return 0
|
||||
fi
|
||||
echo "${key}=${value}" >> "$file"
|
||||
}
|
||||
|
||||
bool_is_true() {
|
||||
local v
|
||||
v="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
|
||||
case "$v" in
|
||||
1|true|yes|y|on|tak|t) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
read_adminer_public_mode() {
|
||||
local raw="$ADMINER_PUBLIC_DEFAULT"
|
||||
if [[ -f "$PLUGIN_SETTINGS" ]]; then
|
||||
local found
|
||||
found="$(awk -F= '/^ADMINER_PUBLIC=/{print $2; exit}' "$PLUGIN_SETTINGS" | tr -d '[:space:]')"
|
||||
if [[ -n "$found" ]]; then
|
||||
raw="$found"
|
||||
fi
|
||||
fi
|
||||
|
||||
if bool_is_true "$raw"; then
|
||||
ADMINER_PUBLIC_MODE="1"
|
||||
else
|
||||
ADMINER_PUBLIC_MODE="0"
|
||||
fi
|
||||
}
|
||||
|
||||
write_runtime_public_config() {
|
||||
local public_json="false"
|
||||
if [[ "$ADMINER_PUBLIC_MODE" == "1" ]]; then
|
||||
public_json="true"
|
||||
fi
|
||||
|
||||
cat > "$ADMINER_RUNTIME_CONFIG_FILE" <<EOF
|
||||
{"version":1,"adminer_public":${public_json},"updated_at":$(date +%s)}
|
||||
EOF
|
||||
}
|
||||
|
||||
sha256_file() {
|
||||
local path="$1"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$path" | awk '{print $1}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$path" | awk '{print $1}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl dgst -sha256 "$path" | awk '{print $NF}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
render_adminer_wrapper() {
|
||||
cat > "$ADMINER_INDEX_FILE" <<'PHPEOF'
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
const DA_ADMINER_RUNTIME_DIR = '/var/www/html/adminer/runtime';
|
||||
const DA_ADMINER_TICKETS_DIR = DA_ADMINER_RUNTIME_DIR . '/tickets';
|
||||
const DA_ADMINER_CONFIG_FILE = DA_ADMINER_RUNTIME_DIR . '/config.json';
|
||||
const DA_ADMINER_CORE_FILE = __DIR__ . '/adminer-core.php';
|
||||
const DA_ADMINER_EXTENSION_FILE = __DIR__ . '/adminer-extension.php';
|
||||
const DA_ADMINER_SESSION_KEY = 'da_adminer_auth';
|
||||
const DA_ADMINER_SESSION_NAME = 'DA_POSTGRESQL_ADMINER';
|
||||
const DA_ADMINER_PUBLIC_DEFAULT = __ADMINER_PUBLIC_MODE__;
|
||||
|
||||
header('X-Frame-Options: SAMEORIGIN');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('Referrer-Policy: no-referrer');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
|
||||
session_name(DA_ADMINER_SESSION_NAME);
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/adminer',
|
||||
'secure' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
session_start();
|
||||
|
||||
$now = time();
|
||||
$adminerPublic = load_public_mode();
|
||||
|
||||
if (isset($_GET['logout'])) {
|
||||
clear_session();
|
||||
if (!$adminerPublic) {
|
||||
render_placeholder('Sesja Adminera zostala zamknieta. Otworz Adminera ponownie z panelu DirectAdmin.');
|
||||
}
|
||||
}
|
||||
|
||||
$auth = null;
|
||||
$ticketId = ticket_id_from_request();
|
||||
if ($ticketId !== '') {
|
||||
$ticketAccepted = false;
|
||||
$ticketRejectReason = '';
|
||||
$ticketBinding = '';
|
||||
$ticket = load_ticket($ticketId);
|
||||
if ($ticket !== null) {
|
||||
$ticketBinding = isset($ticket['user_binding']) && is_string($ticket['user_binding']) ? $ticket['user_binding'] : '';
|
||||
}
|
||||
|
||||
if ($ticket === null) {
|
||||
$ticketRejectReason = 'ticket_missing_or_unreadable';
|
||||
} elseif (validate_ticket($ticket, $now, $adminerPublic, $ticketRejectReason)) {
|
||||
$auth = [
|
||||
'host' => 'localhost',
|
||||
'port' => (int)($ticket['port'] ?? 5432),
|
||||
'user' => (string)($ticket['role'] ?? ''),
|
||||
'password' => (string)($ticket['password'] ?? ''),
|
||||
'database' => (string)($ticket['database'] ?? ''),
|
||||
'expires_at' => (int)($ticket['session_expires_at'] ?? ($now + 900)),
|
||||
];
|
||||
$_SESSION[DA_ADMINER_SESSION_KEY] = $auth;
|
||||
@unlink(ticket_path($ticketId));
|
||||
clear_ticket_from_request();
|
||||
$ticketAccepted = true;
|
||||
}
|
||||
|
||||
debug_ticket_event($ticketId, $ticketAccepted, $ticketRejectReason, $ticketBinding, current_user_binding());
|
||||
|
||||
if (!$ticketAccepted && !$adminerPublic) {
|
||||
render_placeholder('Nieprawidlowy lub wygasly link logowania. Otworz Adminera z poziomu pluginu.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($auth === null) {
|
||||
$auth = $_SESSION[DA_ADMINER_SESSION_KEY] ?? null;
|
||||
}
|
||||
if (!is_array($auth) || empty($auth['user']) || empty($auth['password'])) {
|
||||
$auth = null;
|
||||
if (!$adminerPublic) {
|
||||
render_placeholder();
|
||||
}
|
||||
}
|
||||
|
||||
if ($auth !== null) {
|
||||
$expiresAt = (int)($auth['expires_at'] ?? 0);
|
||||
if ($expiresAt <= 0 || $expiresAt < $now) {
|
||||
clear_session();
|
||||
$auth = null;
|
||||
if (!$adminerPublic) {
|
||||
render_placeholder('Sesja Adminera wygasla. Otworz Adminera ponownie z poziomu pluginu.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_forbidden_driver_params()) {
|
||||
clear_session();
|
||||
http_response_code(403);
|
||||
render_placeholder('Dozwolony jest tylko sterownik PostgreSQL.');
|
||||
}
|
||||
|
||||
$_GET['pgsql'] = 'localhost';
|
||||
|
||||
if ($auth !== null) {
|
||||
bootstrap_adminer_session($auth);
|
||||
unset($_POST['auth']);
|
||||
} else {
|
||||
normalize_post_auth();
|
||||
}
|
||||
$GLOBALS['da_adminer_sso_auth'] = $auth;
|
||||
|
||||
require DA_ADMINER_EXTENSION_FILE;
|
||||
require DA_ADMINER_CORE_FILE;
|
||||
exit;
|
||||
|
||||
function ticket_id_from_request(): string
|
||||
{
|
||||
$ticket = $_GET['ticket'] ?? '';
|
||||
if (!is_string($ticket)) {
|
||||
return '';
|
||||
}
|
||||
$ticket = trim($ticket);
|
||||
if ($ticket === '' || !preg_match('/^[A-Za-z0-9_-]{20,128}$/', $ticket)) {
|
||||
return '';
|
||||
}
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
function ticket_path(string $ticketId): string
|
||||
{
|
||||
return DA_ADMINER_TICKETS_DIR . '/' . $ticketId . '.json';
|
||||
}
|
||||
|
||||
function clear_ticket_from_request(): void
|
||||
{
|
||||
unset($_GET['ticket']);
|
||||
|
||||
$uri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
if (!is_string($uri) || $uri === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!is_array($parts)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = isset($parts['path']) && is_string($parts['path']) ? $parts['path'] : '/adminer';
|
||||
$query = [];
|
||||
if (isset($parts['query']) && is_string($parts['query']) && $parts['query'] !== '') {
|
||||
parse_str($parts['query'], $query);
|
||||
if (is_array($query)) {
|
||||
unset($query['ticket']);
|
||||
} else {
|
||||
$query = [];
|
||||
}
|
||||
}
|
||||
|
||||
$newQuery = http_build_query($query);
|
||||
$_SERVER['REQUEST_URI'] = $path . ($newQuery !== '' ? ('?' . $newQuery) : '');
|
||||
}
|
||||
|
||||
function load_ticket(string $ticketId): ?array
|
||||
{
|
||||
$path = ticket_path($ticketId);
|
||||
if (!is_file($path) || !is_readable($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$raw = @file_get_contents($path);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
|
||||
function load_public_mode(): bool
|
||||
{
|
||||
$default = (DA_ADMINER_PUBLIC_DEFAULT === 1);
|
||||
if (!is_file(DA_ADMINER_CONFIG_FILE) || !is_readable(DA_ADMINER_CONFIG_FILE)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$raw = @file_get_contents(DA_ADMINER_CONFIG_FILE);
|
||||
if (!is_string($raw) || $raw === '') {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded) || !array_key_exists('adminer_public', $decoded)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return parse_bool_value($decoded['adminer_public'], $default);
|
||||
}
|
||||
|
||||
function parse_bool_value($value, bool $default = false): bool
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_int($value) || is_float($value)) {
|
||||
return ((int)$value) !== 0;
|
||||
}
|
||||
if (!is_string($value)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$normalized = strtolower(trim($value));
|
||||
if (in_array($normalized, ['1', 'true', 'yes', 'y', 'on', 'tak', 't'], true)) {
|
||||
return true;
|
||||
}
|
||||
if (in_array($normalized, ['0', 'false', 'no', 'n', 'off', 'nie'], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
function validate_ticket(array $ticket, int $now, bool $adminerPublic, string &$reason = ''): bool
|
||||
{
|
||||
$required = ['role', 'password', 'database', 'host', 'port', 'expires_at', 'session_expires_at', 'user_binding'];
|
||||
foreach ($required as $key) {
|
||||
if (!array_key_exists($key, $ticket)) {
|
||||
$reason = 'missing_key:' . $key;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$expiresAt = is_numeric($ticket['expires_at']) ? (int)$ticket['expires_at'] : 0;
|
||||
if ($expiresAt < $now) {
|
||||
$reason = 'ticket_expired';
|
||||
return false;
|
||||
}
|
||||
|
||||
$sessionExpiresAt = is_numeric($ticket['session_expires_at']) ? (int)$ticket['session_expires_at'] : 0;
|
||||
if ($sessionExpiresAt < $now) {
|
||||
$reason = 'session_expired';
|
||||
return false;
|
||||
}
|
||||
|
||||
$role = (string)$ticket['role'];
|
||||
if ($role === '' || !preg_match('/^[a-z0-9_]{1,63}$/', $role)) {
|
||||
$reason = 'invalid_role';
|
||||
return false;
|
||||
}
|
||||
|
||||
$binding = (string)$ticket['user_binding'];
|
||||
if (!is_binding_valid($binding, current_user_binding(), $adminerPublic)) {
|
||||
$reason = 'binding_mismatch';
|
||||
return false;
|
||||
}
|
||||
|
||||
$reason = 'ok';
|
||||
return true;
|
||||
}
|
||||
|
||||
function current_user_binding(): string
|
||||
{
|
||||
$ua = (string)($_SERVER['HTTP_USER_AGENT'] ?? '');
|
||||
return hash('sha256', $ua);
|
||||
}
|
||||
|
||||
function is_binding_valid(string $ticketBinding, string $currentBinding, bool $adminerPublic): bool
|
||||
{
|
||||
if ($adminerPublic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($ticketBinding === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hash_equals($ticketBinding, $currentBinding)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Kompatybilność: starsze ticket-y mogły być wystawione bez HTTP_USER_AGENT po stronie DA.
|
||||
$legacyNoUaBinding = hash('sha256', '');
|
||||
if (hash_equals($ticketBinding, $legacyNoUaBinding)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function debug_ticket_event(
|
||||
string $ticketId,
|
||||
bool $accepted,
|
||||
string $reason,
|
||||
string $ticketBinding,
|
||||
string $currentBinding
|
||||
): void {
|
||||
$line = sprintf(
|
||||
"[%s] ticket=%s accepted=%s reason=%s ticket_binding=%s current_binding=%s remote=%s ua=%s\n",
|
||||
date('c'),
|
||||
$ticketId,
|
||||
$accepted ? '1' : '0',
|
||||
$reason !== '' ? $reason : 'n/a',
|
||||
$ticketBinding !== '' ? $ticketBinding : '-',
|
||||
$currentBinding !== '' ? $currentBinding : '-',
|
||||
(string)($_SERVER['REMOTE_ADDR'] ?? '-'),
|
||||
(string)($_SERVER['HTTP_USER_AGENT'] ?? '-')
|
||||
);
|
||||
|
||||
@error_log($line, 3, '/tmp/da_adminer_wrapper_debug.log');
|
||||
}
|
||||
|
||||
function has_forbidden_driver_params(): bool
|
||||
{
|
||||
$forbidden = ['server', 'sqlite', 'mongo', 'mssql', 'oracle', 'elastic', 'clickhouse'];
|
||||
foreach ($forbidden as $key) {
|
||||
if (isset($_GET[$key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function bootstrap_adminer_session(array $auth): void
|
||||
{
|
||||
$driver = 'pgsql';
|
||||
$server = 'localhost';
|
||||
$user = trim((string)($auth['user'] ?? ''));
|
||||
$password = (string)($auth['password'] ?? '');
|
||||
$database = trim((string)($auth['database'] ?? ''));
|
||||
|
||||
if ($user === '' || $password === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$_GET[$driver] = $server;
|
||||
$_GET['username'] = $user;
|
||||
if ($database !== '' && (!isset($_GET['db']) || !is_string($_GET['db']) || $_GET['db'] === '')) {
|
||||
$_GET['db'] = $database;
|
||||
}
|
||||
|
||||
$_SESSION['pwds'][$driver][$server][$user] = $password;
|
||||
if ($database !== '') {
|
||||
$_SESSION['db'][$driver][$server][$user][$database] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function normalize_post_auth(): void
|
||||
{
|
||||
if (!isset($_POST['auth']) || !is_array($_POST['auth'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$_POST['auth']['driver'] = 'pgsql';
|
||||
$_POST['auth']['server'] = 'localhost';
|
||||
}
|
||||
|
||||
function clear_session(): void
|
||||
{
|
||||
$_SESSION = [];
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
function render_placeholder(string $extra = ''): void
|
||||
{
|
||||
http_response_code(200);
|
||||
$message = 'Dostep do Adminera jest mozliwy wylacznie z poziomu panelu DirectAdmin.';
|
||||
echo '<!doctype html><html lang="pl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">';
|
||||
echo '<title>Adminer - DirectAdmin</title>';
|
||||
echo '<style>body{margin:0;background:#f5f7fb;color:#1f2937;font:16px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}';
|
||||
echo '.wrap{max-width:760px;margin:10vh auto;padding:24px}';
|
||||
echo '.card{background:#fff;border:1px solid #d7dfeb;border-radius:12px;padding:26px;box-shadow:0 6px 22px rgba(18,38,63,.08)}';
|
||||
echo 'h1{margin:0 0 12px;font-size:24px}p{margin:0 0 10px}.muted{color:#52607a;font-size:14px}</style></head><body>';
|
||||
echo '<div class="wrap"><div class="card"><h1>Adminer</h1><p>' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</p>';
|
||||
if ($extra !== '') {
|
||||
echo '<p class="muted">' . htmlspecialchars($extra, ENT_QUOTES, 'UTF-8') . '</p>';
|
||||
}
|
||||
echo '<p class="muted">W panelu pluginu PostgreSQL kliknij przycisk "Adminer", aby otworzyc sesje SSO.</p></div></div></body></html>';
|
||||
exit;
|
||||
}
|
||||
PHPEOF
|
||||
|
||||
sed -i "s/__ADMINER_PUBLIC_MODE__/${ADMINER_PUBLIC_MODE}/g" "$ADMINER_INDEX_FILE"
|
||||
}
|
||||
|
||||
configure_apache_alias() {
|
||||
[[ -d "$CB_DIR" ]] || die "Brak katalogu CustomBuild: ${CB_DIR}"
|
||||
mkdir -p "$(dirname "$APACHE_ALIAS_CUSTOM")"
|
||||
|
||||
if [[ ! -f "$APACHE_ALIAS_CUSTOM" ]]; then
|
||||
if [[ -f "$APACHE_ALIAS_SOURCE" ]]; then
|
||||
cp -p "$APACHE_ALIAS_SOURCE" "$APACHE_ALIAS_CUSTOM"
|
||||
elif [[ -f "$APACHE_ALIAS_CONFIGURE" ]]; then
|
||||
cp -p "$APACHE_ALIAS_CONFIGURE" "$APACHE_ALIAS_CUSTOM"
|
||||
else
|
||||
warn "Nie znaleziono źródłowego httpd-alias.conf – tworzę pusty custom template."
|
||||
: > "$APACHE_ALIAS_CUSTOM"
|
||||
fi
|
||||
fi
|
||||
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
awk '
|
||||
BEGIN { skip = 0 }
|
||||
/^# BEGIN DA_POSTGRESQL_ADMINER$/ { skip = 1; next }
|
||||
/^# END DA_POSTGRESQL_ADMINER$/ { skip = 0; next }
|
||||
skip == 0 { print }
|
||||
' "$APACHE_ALIAS_CUSTOM" > "$tmp_file"
|
||||
|
||||
cat >> "$tmp_file" <<EOF
|
||||
|
||||
# BEGIN DA_POSTGRESQL_ADMINER
|
||||
Alias ${ADMINER_URL_PATH} ${ADMINER_INSTALL_DIR}/index.php
|
||||
Alias ${ADMINER_URL_PATH}/ ${ADMINER_INSTALL_DIR}/index.php
|
||||
|
||||
<Directory "${ADMINER_INSTALL_DIR}">
|
||||
AllowOverride None
|
||||
Options -Indexes
|
||||
Require all granted
|
||||
<FilesMatch "^adminer-(core|extension)\.php$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
</Directory>
|
||||
|
||||
<Directory "${ADMINER_RUNTIME_DIR}">
|
||||
AllowOverride None
|
||||
Options -Indexes
|
||||
Require all denied
|
||||
</Directory>
|
||||
# END DA_POSTGRESQL_ADMINER
|
||||
EOF
|
||||
|
||||
mv "$tmp_file" "$APACHE_ALIAS_CUSTOM"
|
||||
chmod 644 "$APACHE_ALIAS_CUSTOM"
|
||||
}
|
||||
|
||||
sekcja "Weryfikacja środowiska"
|
||||
command -v php >/dev/null 2>&1 || die "Brak polecenia php."
|
||||
id diradmin >/dev/null 2>&1 || die "Brak użytkownika diradmin."
|
||||
|
||||
APACHE_GROUP="$(detect_apache_group)"
|
||||
info "Wykryta grupa Apache: ${APACHE_GROUP}"
|
||||
read_adminer_public_mode
|
||||
if [[ "$ADMINER_PUBLIC_MODE" == "1" ]]; then
|
||||
info "Tryb ADMINER_PUBLIC: true (bezpośredni dostęp do /adminer bez SSO)."
|
||||
else
|
||||
info "Tryb ADMINER_PUBLIC: false (dostęp wyłącznie przez SSO z pluginu)."
|
||||
fi
|
||||
|
||||
sekcja "Instalacja z bundla Adminera ${ADMINER_VERSION}-${ADMINER_FLAVOR}"
|
||||
mkdir -p "$ADMINER_INSTALL_DIR"
|
||||
[[ -f "$PLUGIN_BUNDLED_ADMINER" ]] || die "Brak zbundlowanego pliku Adminera: ${PLUGIN_BUNDLED_ADMINER}"
|
||||
[[ -f "$PLUGIN_BUNDLED_EXTENSION" ]] || die "Brak zbundlowanego rozszerzenia Adminera: ${PLUGIN_BUNDLED_EXTENSION}"
|
||||
|
||||
EXPECTED_SUM="$PLUGIN_BUNDLED_ADMINER_SHA256"
|
||||
ACTUAL_SUM="$(sha256_file "$PLUGIN_BUNDLED_ADMINER" || true)"
|
||||
[[ -n "$ACTUAL_SUM" ]] || die "Nie można policzyć SHA256 dla ${PLUGIN_BUNDLED_ADMINER}."
|
||||
[[ "$ACTUAL_SUM" == "$EXPECTED_SUM" ]] \
|
||||
|| die "Niezgodna suma SHA256 dla zbundlowanego Adminera. Oczekiwano: ${EXPECTED_SUM}, otrzymano: ${ACTUAL_SUM}"
|
||||
|
||||
cp -f "$PLUGIN_BUNDLED_ADMINER" "$ADMINER_CORE_FILE"
|
||||
info "Adminer ${ADMINER_VERSION}-${ADMINER_FLAVOR} skopiowany do: ${ADMINER_CORE_FILE}"
|
||||
|
||||
EXPECTED_EXTENSION_SUM="$PLUGIN_BUNDLED_EXTENSION_SHA256"
|
||||
ACTUAL_EXTENSION_SUM="$(sha256_file "$PLUGIN_BUNDLED_EXTENSION" || true)"
|
||||
[[ -n "$ACTUAL_EXTENSION_SUM" ]] || die "Nie można policzyć SHA256 dla ${PLUGIN_BUNDLED_EXTENSION}."
|
||||
[[ "$ACTUAL_EXTENSION_SUM" == "$EXPECTED_EXTENSION_SUM" ]] \
|
||||
|| die "Niezgodna suma SHA256 dla zbundlowanego rozszerzenia Adminera. Oczekiwano: ${EXPECTED_EXTENSION_SUM}, otrzymano: ${ACTUAL_EXTENSION_SUM}"
|
||||
|
||||
cp -f "$PLUGIN_BUNDLED_EXTENSION" "$ADMINER_EXTENSION_FILE"
|
||||
info "Rozszerzenie Adminera skopiowane do: ${ADMINER_EXTENSION_FILE}"
|
||||
|
||||
sekcja "Konfiguracja wrappera bezpieczeństwa"
|
||||
mkdir -p "$ADMINER_RUNTIME_DIR" "$ADMINER_TICKETS_DIR"
|
||||
render_adminer_wrapper
|
||||
|
||||
chown root:root "$ADMINER_INSTALL_DIR"
|
||||
chmod 755 "$ADMINER_INSTALL_DIR"
|
||||
|
||||
chown root:root "$ADMINER_CORE_FILE" "$ADMINER_EXTENSION_FILE" "$ADMINER_INDEX_FILE"
|
||||
chmod 644 "$ADMINER_CORE_FILE" "$ADMINER_EXTENSION_FILE" "$ADMINER_INDEX_FILE"
|
||||
|
||||
chown diradmin:"$APACHE_GROUP" "$ADMINER_RUNTIME_DIR" "$ADMINER_TICKETS_DIR"
|
||||
chmod 711 "$ADMINER_RUNTIME_DIR"
|
||||
chmod 2733 "$ADMINER_TICKETS_DIR"
|
||||
|
||||
write_runtime_public_config
|
||||
chown diradmin:"$APACHE_GROUP" "$ADMINER_RUNTIME_CONFIG_FILE"
|
||||
chmod 644 "$ADMINER_RUNTIME_CONFIG_FILE"
|
||||
|
||||
if command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled; then
|
||||
warn "Wykryto SELinux: ustawiam kontekst zapisu runtime dla Apache."
|
||||
if command -v chcon >/dev/null 2>&1; then
|
||||
chcon -R -t httpd_sys_rw_content_t "$ADMINER_RUNTIME_DIR" || warn "Nie udało się ustawić kontekstu SELinux dla runtime."
|
||||
chcon -t httpd_sys_content_t "$ADMINER_CORE_FILE" "$ADMINER_EXTENSION_FILE" "$ADMINER_INDEX_FILE" || warn "Nie udało się ustawić kontekstu SELinux dla plików PHP."
|
||||
else
|
||||
warn "Brak chcon - ustaw kontekst SELinux ręcznie."
|
||||
fi
|
||||
fi
|
||||
|
||||
sekcja "Konfiguracja aliasu /adminer (DirectAdmin CustomBuild)"
|
||||
configure_apache_alias
|
||||
|
||||
if [[ -x "$CB_BUILD" ]]; then
|
||||
(cd "$CB_DIR" && ./build rewrite_confs) \
|
||||
|| die "rewrite_confs zakończył się błędem."
|
||||
info "rewrite_confs wykonane pomyślnie."
|
||||
else
|
||||
warn "Brak ${CB_BUILD} - uruchom ręcznie: cd ${CB_DIR} && ./build rewrite_confs"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet httpd 2>/dev/null; then
|
||||
systemctl reload httpd || warn "Nie udało się przeładować httpd."
|
||||
fi
|
||||
|
||||
sekcja "Aktualizacja ustawień pluginu"
|
||||
if [[ -f "$PLUGIN_SETTINGS" ]]; then
|
||||
set_setting "ENABLE_ADMINER" "true" "$PLUGIN_SETTINGS"
|
||||
set_setting_if_missing "ADMINER_PUBLIC" "false" "$PLUGIN_SETTINGS"
|
||||
set_setting_if_missing "ADMINER_PUBLIC_BASE_URL" "" "$PLUGIN_SETTINGS"
|
||||
set_setting "ADMINER_URL_PATH" "$ADMINER_URL_PATH" "$PLUGIN_SETTINGS"
|
||||
set_setting "ADMINER_SSO_DIR" "$ADMINER_RUNTIME_DIR" "$PLUGIN_SETTINGS"
|
||||
set_setting "ADMINER_TICKET_TTL" "120" "$PLUGIN_SETTINGS"
|
||||
set_setting "ADMINER_SESSION_TTL" "900" "$PLUGIN_SETTINGS"
|
||||
set_setting "ADMINER_ROLE_TTL" "1200" "$PLUGIN_SETTINGS"
|
||||
chown "$PLUGIN_OWNER" "$PLUGIN_SETTINGS" 2>/dev/null || true
|
||||
chmod 640 "$PLUGIN_SETTINGS" 2>/dev/null || true
|
||||
info "Zaktualizowano: ${PLUGIN_SETTINGS}"
|
||||
else
|
||||
warn "Nie znaleziono pliku ${PLUGIN_SETTINGS} - ustawienia pluginu pominięte."
|
||||
fi
|
||||
|
||||
sekcja "Instalacja zakończona"
|
||||
echo "Adminer URL: ${ADMINER_URL_PATH}"
|
||||
echo "Katalog: ${ADMINER_INSTALL_DIR}"
|
||||
echo "Runtime ticketów: ${ADMINER_TICKETS_DIR}"
|
||||
echo "Runtime config: ${ADMINER_RUNTIME_CONFIG_FILE}"
|
||||
echo "Alias template: ${APACHE_ALIAS_CUSTOM}"
|
||||
echo "Log: ${LOGFILE}"
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# dbrestore.sh
|
||||
# Przywracanie pojedynczej bazy PostgreSQL z pliku .sql lub .sql.gz
|
||||
# wykonanego przez postgres_backup.sh
|
||||
# =============================================================================
|
||||
|
||||
CREDENTIALS_FILE="/usr/local/directadmin/conf/postgresql.conf"
|
||||
|
||||
# Kolory tylko na terminalu
|
||||
if [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; NC=''
|
||||
fi
|
||||
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
info() { echo -e " $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
die() { echo -e "${RED}[BŁĄD]${NC} $*" >&2; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sposób użycia
|
||||
# ---------------------------------------------------------------------------
|
||||
usage() {
|
||||
cat <<EOF
|
||||
|
||||
Użycie:
|
||||
$(basename "$0") <plik.sql|plik.sql.gz>
|
||||
|
||||
Opis:
|
||||
Przywraca bazę danych z pliku wykonanego przez postgres_backup.sh.
|
||||
Obsługuje pliki nieskompresowane (.sql) i skompresowane (.sql.gz).
|
||||
Jeśli docelowa baza istnieje, zostanie usunięta i odtworzona od nowa.
|
||||
|
||||
Przykłady:
|
||||
$(basename "$0") /home/admin/postgres_backup/19-02-2026/sklep.sql
|
||||
$(basename "$0") /home/admin/postgres_backup/19-02-2026/sklep.sql.gz
|
||||
|
||||
Uwaga:
|
||||
Plik poświadczeń: ${CREDENTIALS_FILE}
|
||||
Baza 'postgres' jest przywracana przez połączenie do 'template1',
|
||||
pozostałe bazy – przez połączenie do 'postgres'.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Argumenty
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ $# -eq 0 ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FILE="$1"
|
||||
|
||||
[[ -f "$FILE" ]] || die "Plik nie istnieje: ${FILE}"
|
||||
|
||||
case "$FILE" in
|
||||
*.sql.gz|*.sql) ;;
|
||||
*) die "Nieobsługiwany format pliku. Oczekiwany: .sql lub .sql.gz" ;;
|
||||
esac
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Poświadczenia
|
||||
# ---------------------------------------------------------------------------
|
||||
[[ -f "$CREDENTIALS_FILE" ]] \
|
||||
|| die "Brak pliku poświadczeń: ${CREDENTIALS_FILE}"
|
||||
|
||||
_perms=$(stat -c '%a' "$CREDENTIALS_FILE")
|
||||
[[ "$_perms" == "600" || "$_perms" == "400" ]] \
|
||||
|| die "Zbyt otwarte uprawnienia na ${CREDENTIALS_FILE} (${_perms}). Wymagane: 600 lub 400."
|
||||
|
||||
_line=$(head -1 "$CREDENTIALS_FILE")
|
||||
PGHOST=$(echo "$_line" | cut -d: -f1)
|
||||
PGPORT=$(echo "$_line" | cut -d: -f2)
|
||||
PGUSER=$(echo "$_line" | cut -d: -f4)
|
||||
|
||||
[[ -n "$PGHOST" && -n "$PGPORT" && -n "$PGUSER" ]] \
|
||||
|| die "Nieprawidłowy format pliku poświadczeń. Oczekiwany: host:port:database:user:password"
|
||||
|
||||
export PGPASSFILE="$CREDENTIALS_FILE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ustal bazę inicjalną (nie można przywracać 'postgres' będąc do niej podłączonym)
|
||||
# ---------------------------------------------------------------------------
|
||||
DB_NAME=$(basename "$FILE" | sed 's/\.sql\.gz$//; s/\.sql$//')
|
||||
|
||||
if [[ "$DB_NAME" == "postgres" ]]; then
|
||||
INIT_DB="template1"
|
||||
else
|
||||
INIT_DB="postgres"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Przywracanie
|
||||
# ---------------------------------------------------------------------------
|
||||
echo ""
|
||||
ok "Plik: ${FILE}"
|
||||
info "Baza: ${DB_NAME}"
|
||||
info "Serwer: ${PGHOST}:${PGPORT} Użytkownik: ${PGUSER}"
|
||||
echo ""
|
||||
|
||||
case "$FILE" in
|
||||
*.sql.gz)
|
||||
gunzip -c "$FILE" \
|
||||
| psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password -d "$INIT_DB"
|
||||
RC=${PIPESTATUS[1]}
|
||||
;;
|
||||
*.sql)
|
||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password -d "$INIT_DB" -f "$FILE"
|
||||
RC=$?
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
if [[ $RC -eq 0 ]]; then
|
||||
ok "Przywracanie bazy '${DB_NAME}' zakończone pomyślnie."
|
||||
else
|
||||
warn "Przywracanie bazy '${DB_NAME}' zakończone z ostrzeżeniami lub błędami (kod: ${RC})."
|
||||
warn "Sprawdź powyższy output w celu identyfikacji problemów."
|
||||
fi
|
||||
|
||||
exit $RC
|
||||
Executable
+636
@@ -0,0 +1,636 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
CREDS_FILE="/usr/local/directadmin/conf/postgresql.conf"
|
||||
PLUGIN_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
PLUGIN_SETTINGS_FILE="$PLUGIN_ROOT/plugin-settings.conf"
|
||||
|
||||
PGHOST_VAL=""
|
||||
PGPORT_VAL="5432"
|
||||
PGUSER_VAL="postgres"
|
||||
PGPASSWORD_VAL=""
|
||||
PG_VERSION_NUM=""
|
||||
|
||||
ALLOW_REMOTE_HOSTS="1"
|
||||
RESTART_POSTGRESQL="0"
|
||||
|
||||
HBA_MANAGED_BLOCK_BEGIN="# DA_POSTGRESQL_PLUGIN_HBA_BEGIN"
|
||||
HBA_MANAGED_BLOCK_END="# DA_POSTGRESQL_PLUGIN_HBA_END"
|
||||
POSTGRESQL_CONF=""
|
||||
HBA_FILE=""
|
||||
|
||||
CSF_ALLOW_FILE="/etc/csf/csf.allow"
|
||||
CSF_INCLUDE_FILE="/etc/csf.allow.postgres"
|
||||
|
||||
PG_CONF_MARKER_BEGIN="# DA_POSTGRESQL_PLUGIN_BEGIN"
|
||||
PG_CONF_MARKER_END="# DA_POSTGRESQL_PLUGIN_END"
|
||||
|
||||
declare -A REMOTE_IP_NOTES=()
|
||||
|
||||
parse_args() {
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--restart-postgresql)
|
||||
RESTART_POSTGRESQL="1"
|
||||
;;
|
||||
*)
|
||||
echo "Nieznany argument: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
find_psql() {
|
||||
if command -v psql >/dev/null 2>&1; then
|
||||
command -v psql
|
||||
return
|
||||
fi
|
||||
|
||||
local candidate
|
||||
candidate="$(ls -1d /usr/pgsql-*/bin/psql 2>/dev/null | sort -V | tail -n1 || true)"
|
||||
if [ -n "$candidate" ] && [ -x "$candidate" ]; then
|
||||
echo "$candidate"
|
||||
return
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
trim() {
|
||||
local value="$1"
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
strip_inline_comment() {
|
||||
local value="$1"
|
||||
value="${value%%#*}"
|
||||
trim "$value"
|
||||
}
|
||||
|
||||
unquote() {
|
||||
local value
|
||||
value="$(trim "$1")"
|
||||
|
||||
if [ "${#value}" -ge 2 ] && [ "${value:0:1}" = "'" ] && [ "${value: -1}" = "'" ]; then
|
||||
value="${value:1:${#value}-2}"
|
||||
elif [ "${#value}" -ge 2 ] && [ "${value:0:1}" = '"' ] && [ "${value: -1}" = '"' ]; then
|
||||
value="${value:1:${#value}-2}"
|
||||
fi
|
||||
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
bool_to_01() {
|
||||
local raw
|
||||
raw="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
|
||||
raw="$(trim "$raw")"
|
||||
case "$raw" in
|
||||
1|true|yes|y|on|checked)
|
||||
echo "1"
|
||||
;;
|
||||
*)
|
||||
echo "0"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_ipv4_strict() {
|
||||
local ip="$1"
|
||||
if [[ ! "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local IFS='.'
|
||||
local o1 o2 o3 o4
|
||||
read -r o1 o2 o3 o4 <<< "$ip"
|
||||
for octet in "$o1" "$o2" "$o3" "$o4"; do
|
||||
if [ "$octet" -gt 255 ]; then
|
||||
return 1
|
||||
fi
|
||||
if [[ "$octet" =~ ^0[0-9]+$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
is_ipv4_cidr_strict() {
|
||||
local cidr="$1"
|
||||
if [[ ! "$cidr" =~ ^([^/]+)/([0-9]{1,2})$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ip="${BASH_REMATCH[1]}"
|
||||
local prefix="${BASH_REMATCH[2]}"
|
||||
is_ipv4_strict "$ip" || return 1
|
||||
[ "$prefix" -ge 0 ] && [ "$prefix" -le 32 ]
|
||||
}
|
||||
|
||||
skip_csf_remote_pattern() {
|
||||
[ "$1" = "0.0.0.0/0" ]
|
||||
}
|
||||
|
||||
sanitize_note() {
|
||||
local note="$1"
|
||||
note="${note//$'\r'/ }"
|
||||
note="${note//$'\n'/ }"
|
||||
note="${note//$'\t'/ }"
|
||||
note="${note//#/ }"
|
||||
note="$(echo "$note" | sed -E 's/[[:space:]]+/ /g; s/^ //; s/ $//')"
|
||||
note="${note:0:180}"
|
||||
printf '%s' "$note"
|
||||
}
|
||||
|
||||
load_plugin_settings() {
|
||||
local plain_allow=""
|
||||
local legacy_allow=""
|
||||
|
||||
if [ ! -r "$PLUGIN_SETTINGS_FILE" ]; then
|
||||
ALLOW_REMOTE_HOSTS="1"
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r raw_line || [ -n "$raw_line" ]; do
|
||||
raw_line="$(trim "$raw_line")"
|
||||
[ -n "$raw_line" ] || continue
|
||||
[[ "$raw_line" =~ ^# ]] && continue
|
||||
[[ "$raw_line" == *"="* ]] || continue
|
||||
|
||||
local key="${raw_line%%=*}"
|
||||
local value="${raw_line#*=}"
|
||||
key="$(trim "$key")"
|
||||
value="$(unquote "$(strip_inline_comment "$value")")"
|
||||
|
||||
case "$key" in
|
||||
allow_remote_hosts)
|
||||
plain_allow="$value"
|
||||
;;
|
||||
PG_PLUGIN_ALLOW_REMOTE_HOSTS)
|
||||
legacy_allow="$value"
|
||||
;;
|
||||
esac
|
||||
done < "$PLUGIN_SETTINGS_FILE"
|
||||
|
||||
if [ -n "$plain_allow" ]; then
|
||||
ALLOW_REMOTE_HOSTS="$(bool_to_01 "$plain_allow")"
|
||||
elif [ -n "$legacy_allow" ]; then
|
||||
ALLOW_REMOTE_HOSTS="$(bool_to_01 "$legacy_allow")"
|
||||
else
|
||||
ALLOW_REMOTE_HOSTS="1"
|
||||
fi
|
||||
}
|
||||
|
||||
load_credentials() {
|
||||
[ -r "$CREDS_FILE" ] || {
|
||||
echo "Brak pliku poświadczeń: $CREDS_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
local line
|
||||
line="$(grep -Ev '^[[:space:]]*($|#)' "$CREDS_FILE" | head -n1 || true)"
|
||||
[ -n "$line" ] || {
|
||||
echo "Pusty plik poświadczeń: $CREDS_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ "$line" == *=* ]]; then
|
||||
while IFS='=' read -r key value; do
|
||||
key="$(echo "$key" | tr -d '[:space:]' | tr '[:lower:]' '[:upper:]')"
|
||||
value="$(trim "$value")"
|
||||
value="$(unquote "$value")"
|
||||
case "$key" in
|
||||
PG_HOST) PGHOST_VAL="$value" ;;
|
||||
PG_PORT) PGPORT_VAL="$value" ;;
|
||||
PG_USER) PGUSER_VAL="$value" ;;
|
||||
PG_PASSWORD) PGPASSWORD_VAL="$value" ;;
|
||||
esac
|
||||
done < <(grep -Ev '^[[:space:]]*($|#)' "$CREDS_FILE")
|
||||
else
|
||||
local rest dbname
|
||||
PGHOST_VAL="${line%%:*}"
|
||||
rest="${line#*:}"
|
||||
PGPORT_VAL="${rest%%:*}"
|
||||
rest="${rest#*:}"
|
||||
dbname="${rest%%:*}"
|
||||
rest="${rest#*:}"
|
||||
PGUSER_VAL="${rest%%:*}"
|
||||
PGPASSWORD_VAL="${rest#*:}"
|
||||
|
||||
[ -n "$dbname" ] || dbname="postgres"
|
||||
fi
|
||||
|
||||
[ -n "$PGHOST_VAL" ] || PGHOST_VAL="localhost"
|
||||
[ -n "$PGPORT_VAL" ] || PGPORT_VAL="5432"
|
||||
[ -n "$PGUSER_VAL" ] || PGUSER_VAL="postgres"
|
||||
[ -n "$PGPASSWORD_VAL" ] || {
|
||||
echo "Brak hasła PostgreSQL w $CREDS_FILE" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
psql_exec() {
|
||||
PGPASSWORD="$PGPASSWORD_VAL" "$PSQL_BIN" -h "$PGHOST_VAL" -p "$PGPORT_VAL" -U "$PGUSER_VAL" -d postgres -v ON_ERROR_STOP=1 -At -F $'\t' "$@"
|
||||
}
|
||||
|
||||
load_server_version_num() {
|
||||
PG_VERSION_NUM="$(psql_exec -c 'SHOW server_version_num;' | tr -d '[:space:]')"
|
||||
[[ "$PG_VERSION_NUM" =~ ^[0-9]+$ ]] || {
|
||||
echo "Nieprawidłowy server_version_num: ${PG_VERSION_NUM}" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
cleanup_hba_managed_entries() {
|
||||
local hba_file="$1"
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
awk -v begin="$HBA_MANAGED_BLOCK_BEGIN" -v end="$HBA_MANAGED_BLOCK_END" '
|
||||
$0 == begin {skip=1; next}
|
||||
$0 == end {skip=0; next}
|
||||
skip == 1 {next}
|
||||
{
|
||||
lower = tolower($0)
|
||||
if (lower ~ /^[[:space:]]*include([[:space:]]|_if_exists)/ && lower ~ /pg_hba_da_plugin\.conf/) {
|
||||
next
|
||||
}
|
||||
print
|
||||
}
|
||||
' "$hba_file" > "$tmp_file"
|
||||
|
||||
install -m 600 "$tmp_file" "$hba_file"
|
||||
chown postgres:postgres "$hba_file" 2>/dev/null || true
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
write_hba_inline_block() {
|
||||
local hba_file="$1"
|
||||
local include_file="$2"
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
cp -p "$hba_file" "${hba_file}.bak.$(date +%s)"
|
||||
cat "$hba_file" > "$tmp_file"
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "$HBA_MANAGED_BLOCK_BEGIN"
|
||||
echo "# Managed by DirectAdmin PostgreSQL plugin (inline mode)."
|
||||
cat "$include_file"
|
||||
echo "$HBA_MANAGED_BLOCK_END"
|
||||
} >> "$tmp_file"
|
||||
|
||||
install -m 600 "$tmp_file" "$hba_file"
|
||||
chown postgres:postgres "$hba_file" 2>/dev/null || true
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
append_remote_ip_note() {
|
||||
local ip="$1"
|
||||
local note="$2"
|
||||
|
||||
if [ -z "${REMOTE_IP_NOTES[$ip]+x}" ]; then
|
||||
REMOTE_IP_NOTES["$ip"]="$note"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$note" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local existing="${REMOTE_IP_NOTES[$ip]}"
|
||||
if [ -z "$existing" ]; then
|
||||
REMOTE_IP_NOTES["$ip"]="$note"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$existing" != *"$note"* ]]; then
|
||||
REMOTE_IP_NOTES["$ip"]="$existing | $note"
|
||||
fi
|
||||
}
|
||||
|
||||
expand_hba_addresses() {
|
||||
local host="$1"
|
||||
case "$host" in
|
||||
localhost)
|
||||
echo "127.0.0.1/32"
|
||||
echo "::1/128"
|
||||
return
|
||||
;;
|
||||
127.0.0.1)
|
||||
echo "127.0.0.1/32"
|
||||
return
|
||||
;;
|
||||
::1)
|
||||
echo "::1/128"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
if is_ipv4_strict "$host"; then
|
||||
echo "$host/32"
|
||||
return
|
||||
fi
|
||||
|
||||
if is_ipv4_cidr_strict "$host"; then
|
||||
echo "$host"
|
||||
fi
|
||||
}
|
||||
|
||||
build_hba_include() {
|
||||
local include_file="$1"
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
{
|
||||
echo "# Auto-generated by DirectAdmin PostgreSQL plugin"
|
||||
echo "# Generated at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo "# Method: scram-sha-256"
|
||||
} > "$tmp_file"
|
||||
|
||||
local sql_query
|
||||
sql_query="SELECT d.datname, h.role_name, h.host_pattern, COALESCE(h.note, '')
|
||||
FROM da_plugin.access_hosts h
|
||||
JOIN pg_roles r ON r.rolname = h.role_name
|
||||
JOIN pg_database d
|
||||
ON d.datistemplate = false
|
||||
AND (
|
||||
(COALESCE(h.db_name, '') <> '' AND d.datname = h.db_name)
|
||||
OR
|
||||
(COALESCE(h.db_name, '') = '' AND left(d.datname, length(h.da_user) + 1) = h.da_user || '_')
|
||||
)
|
||||
WHERE COALESCE(h.db_name, '') <> ''
|
||||
OR has_database_privilege(h.role_name, d.datname, 'CONNECT')
|
||||
ORDER BY d.datname, h.role_name, h.host_pattern;"
|
||||
|
||||
local query_output
|
||||
query_output="$(psql_exec -c "$sql_query")" || {
|
||||
local status=$?
|
||||
rm -f "$tmp_file"
|
||||
echo "Nie udało się odczytać rekordów hostów z da_plugin.access_hosts." >&2
|
||||
return "$status"
|
||||
}
|
||||
|
||||
local access_host_rows="0"
|
||||
local generated_rules="0"
|
||||
|
||||
while IFS=$'\t' read -r db role host note; do
|
||||
[ -n "$db" ] || continue
|
||||
[ -n "$role" ] || continue
|
||||
[ -n "$host" ] || continue
|
||||
access_host_rows=$((access_host_rows + 1))
|
||||
|
||||
local host_note
|
||||
host_note="$(sanitize_note "$note")"
|
||||
local include_host="0"
|
||||
local is_remote="0"
|
||||
|
||||
case "$host" in
|
||||
localhost|127.0.0.1|::1)
|
||||
include_host="1"
|
||||
;;
|
||||
*)
|
||||
if [ "$ALLOW_REMOTE_HOSTS" = "1" ] && { is_ipv4_strict "$host" || is_ipv4_cidr_strict "$host"; }; then
|
||||
include_host="1"
|
||||
is_remote="1"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
[ "$include_host" = "1" ] || continue
|
||||
|
||||
if [ "$is_remote" = "1" ]; then
|
||||
append_remote_ip_note "$host" "$host_note"
|
||||
fi
|
||||
|
||||
local note_label="$host_note"
|
||||
[ -n "$note_label" ] || note_label="(brak komentarza)"
|
||||
printf '# role=%s db=%s host=%s comment=%s\n' "$role" "$db" "$host" "$note_label" >> "$tmp_file"
|
||||
|
||||
while IFS= read -r addr; do
|
||||
[ -n "$addr" ] || continue
|
||||
printf 'host\t%s\t%s\t%s\tscram-sha-256\n' "$db" "$role" "$addr" >> "$tmp_file"
|
||||
generated_rules=$((generated_rules + 1))
|
||||
done < <(expand_hba_addresses "$host")
|
||||
done <<< "$query_output"
|
||||
|
||||
install -m 600 "$tmp_file" "$include_file"
|
||||
chown postgres:postgres "$include_file" 2>/dev/null || true
|
||||
rm -f "$tmp_file"
|
||||
|
||||
echo "Read access host rows: $access_host_rows"
|
||||
echo "Generated pg_hba host rules: $generated_rules"
|
||||
}
|
||||
|
||||
sorted_remote_ips() {
|
||||
if [ "${#REMOTE_IP_NOTES[@]}" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf '%s\n' "${!REMOTE_IP_NOTES[@]}" | sort -V
|
||||
}
|
||||
|
||||
write_postgresql_conf_block() {
|
||||
local conf_file="$1"
|
||||
local listen_value="localhost"
|
||||
local has_remote_ips="0"
|
||||
|
||||
if [ "$ALLOW_REMOTE_HOSTS" = "1" ]; then
|
||||
listen_value="*"
|
||||
fi
|
||||
|
||||
if [ "$ALLOW_REMOTE_HOSTS" = "1" ] && [ "${#REMOTE_IP_NOTES[@]}" -gt 0 ]; then
|
||||
has_remote_ips="1"
|
||||
fi
|
||||
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
awk -v begin="$PG_CONF_MARKER_BEGIN" -v end="$PG_CONF_MARKER_END" '
|
||||
$0 == begin {skip=1; next}
|
||||
$0 == end {skip=0; next}
|
||||
skip != 1 {print}
|
||||
' "$conf_file" > "$tmp_file"
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "$PG_CONF_MARKER_BEGIN"
|
||||
echo "# Managed by DirectAdmin PostgreSQL plugin."
|
||||
echo "listen_addresses = '$listen_value'"
|
||||
echo "# Zdefiniowane hosty IPv4 z pluginu:"
|
||||
if [ "$has_remote_ips" = "1" ]; then
|
||||
while IFS= read -r ip; do
|
||||
[ -n "$ip" ] || continue
|
||||
local note="${REMOTE_IP_NOTES[$ip]}"
|
||||
if [ -n "$note" ]; then
|
||||
printf '# %s ; %s\n' "$ip" "$note"
|
||||
else
|
||||
printf '# %s\n' "$ip"
|
||||
fi
|
||||
done < <(sorted_remote_ips)
|
||||
else
|
||||
echo "# brak zdalnych hostów"
|
||||
fi
|
||||
echo "$PG_CONF_MARKER_END"
|
||||
} >> "$tmp_file"
|
||||
|
||||
install -m 600 "$tmp_file" "$conf_file"
|
||||
chown postgres:postgres "$conf_file" 2>/dev/null || true
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
ensure_csf_include_directive() {
|
||||
local allow_file="$1"
|
||||
local include_file="$2"
|
||||
|
||||
[ -f "$allow_file" ] || return 1
|
||||
|
||||
local include_line="Include $include_file"
|
||||
if ! grep -Fxq "$include_line" "$allow_file"; then
|
||||
echo "$include_line" >> "$allow_file"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
write_csf_include_file() {
|
||||
local include_file="$1"
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
{
|
||||
echo "# Auto-generated by DirectAdmin PostgreSQL plugin"
|
||||
echo "# Generated at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||
} > "$tmp_file"
|
||||
|
||||
if [ "$ALLOW_REMOTE_HOSTS" = "1" ] && [ "${#REMOTE_IP_NOTES[@]}" -gt 0 ]; then
|
||||
while IFS= read -r ip; do
|
||||
[ -n "$ip" ] || continue
|
||||
if skip_csf_remote_pattern "$ip"; then
|
||||
continue
|
||||
fi
|
||||
local note="${REMOTE_IP_NOTES[$ip]}"
|
||||
if [ -n "$note" ]; then
|
||||
printf '%s # DA PostgreSQL: %s\n' "$ip" "$note" >> "$tmp_file"
|
||||
else
|
||||
printf '%s # DA PostgreSQL\n' "$ip" >> "$tmp_file"
|
||||
fi
|
||||
done < <(sorted_remote_ips)
|
||||
fi
|
||||
|
||||
install -m 600 "$tmp_file" "$include_file"
|
||||
chown root:root "$include_file" 2>/dev/null || true
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
reload_postgres_conf() {
|
||||
psql_exec -c 'SELECT pg_reload_conf();' >/dev/null
|
||||
}
|
||||
|
||||
restart_postgresql_service() {
|
||||
if ! command -v systemctl >/dev/null 2>&1; then
|
||||
echo "Nie znaleziono systemctl, nie można zrestartować PostgreSQL." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local major=""
|
||||
if [[ "$PG_VERSION_NUM" =~ ^[0-9]+$ ]] && [ "$PG_VERSION_NUM" -ge 100000 ]; then
|
||||
major="$((PG_VERSION_NUM / 10000))"
|
||||
fi
|
||||
|
||||
local candidates=()
|
||||
if [ -n "$major" ]; then
|
||||
candidates+=("postgresql-${major}" "postgresql@${major}-main")
|
||||
fi
|
||||
candidates+=("postgresql")
|
||||
|
||||
local service
|
||||
for service in "${candidates[@]}"; do
|
||||
if systemctl list-unit-files --no-legend "${service}.service" 2>/dev/null | grep -q . || systemctl status "$service" >/dev/null 2>&1; then
|
||||
if systemctl restart "$service"; then
|
||||
echo "Restarted PostgreSQL service: $service"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Nie znaleziono usługi PostgreSQL do restartu. Próbowano: ${candidates[*]}" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
reload_csf_if_available() {
|
||||
if command -v csf >/dev/null 2>&1; then
|
||||
csf -r >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
parse_args "$@"
|
||||
load_plugin_settings
|
||||
load_credentials
|
||||
|
||||
PSQL_BIN="$(find_psql)"
|
||||
[ -n "$PSQL_BIN" ] || {
|
||||
echo "Nie znaleziono binarki psql" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
HBA_FILE="$(psql_exec -c 'SHOW hba_file;')"
|
||||
[ -n "$HBA_FILE" ] || {
|
||||
echo "Nie udało się odczytać ścieżki do pg_hba.conf" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
POSTGRESQL_CONF="$(psql_exec -c 'SHOW config_file;')"
|
||||
[ -n "$POSTGRESQL_CONF" ] || {
|
||||
echo "Nie udało się odczytać ścieżki do postgresql.conf" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
load_server_version_num
|
||||
|
||||
HBA_DIR="$(dirname "$HBA_FILE")"
|
||||
HBA_INCLUDE_FILE="$HBA_DIR/pg_hba_da_plugin.conf"
|
||||
|
||||
build_hba_include "$HBA_INCLUDE_FILE"
|
||||
cleanup_hba_managed_entries "$HBA_FILE"
|
||||
write_hba_inline_block "$HBA_FILE" "$HBA_INCLUDE_FILE"
|
||||
|
||||
CSF_SYNCED="0"
|
||||
CSF_SKIP_REASON=""
|
||||
|
||||
if [ "$ALLOW_REMOTE_HOSTS" = "1" ]; then
|
||||
write_postgresql_conf_block "$POSTGRESQL_CONF"
|
||||
|
||||
if ensure_csf_include_directive "$CSF_ALLOW_FILE" "$CSF_INCLUDE_FILE"; then
|
||||
write_csf_include_file "$CSF_INCLUDE_FILE"
|
||||
reload_csf_if_available
|
||||
CSF_SYNCED="1"
|
||||
else
|
||||
CSF_SKIP_REASON="CSF allow file not found: $CSF_ALLOW_FILE"
|
||||
echo "Ostrzeżenie: nie znaleziono $CSF_ALLOW_FILE, pomijam aktualizację CSF." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
reload_postgres_conf
|
||||
|
||||
if [ "$RESTART_POSTGRESQL" = "1" ]; then
|
||||
restart_postgresql_service
|
||||
fi
|
||||
|
||||
echo "Synchronized pg_hba inline block in: $HBA_FILE"
|
||||
echo "Generated pg_hba include file (diagnostic copy): $HBA_INCLUDE_FILE"
|
||||
if [ "$ALLOW_REMOTE_HOSTS" = "1" ]; then
|
||||
echo "Synchronized postgresql.conf: $POSTGRESQL_CONF"
|
||||
echo "PostgreSQL restart required for listen_addresses changes"
|
||||
if [ "$CSF_SYNCED" = "1" ]; then
|
||||
echo "Synchronized CSF include: $CSF_INCLUDE_FILE"
|
||||
else
|
||||
echo "Skipped CSF synchronization: ${CSF_SKIP_REASON:-CSF allow file unavailable}"
|
||||
fi
|
||||
else
|
||||
echo "Remote hosts disabled: skipped postgresql.conf and CSF synchronization"
|
||||
fi
|
||||
Executable
+226
@@ -0,0 +1,226 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# postgres_backup.sh
|
||||
# Backup wszystkich baz PostgreSQL na serwerze
|
||||
#
|
||||
# Przeznaczenie: cron (nie interaktywny)
|
||||
# Wymagania: pg_dump, psql, dostęp do pliku poświadczeń (format pgpass)
|
||||
#
|
||||
# Przywracanie pojedynczej bazy:
|
||||
# psql -h localhost -U postgres -d postgres -f nazwa_bazy.sql
|
||||
# gunzip -c nazwa_bazy.sql.gz | psql -h localhost -U postgres -d postgres
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# KONFIGURACJA
|
||||
# =============================================================================
|
||||
|
||||
BASE_DIR="/home/admin/postgres_backup"
|
||||
|
||||
# Format daty katalogu backupu (składnia date +FORMAT)
|
||||
DATE_FORMAT="%d-%m-%Y"
|
||||
|
||||
# 1 = dodaj godzinę i minuty do nazwy katalogu (np. 19-02-2026_14-35)
|
||||
ADD_TIME=0
|
||||
|
||||
# Liczba dni przechowywania backupów (0 = bez automatycznego usuwania)
|
||||
RETENTION=7
|
||||
|
||||
# 1 = kompresuj do .sql.gz | 0 = zostawiaj jako .sql
|
||||
ENABLE_COMPRESSION=1
|
||||
|
||||
# Plik poświadczeń w formacie pgpass: host:port:database:user:password
|
||||
# Musi mieć uprawnienia 600 lub 400
|
||||
CREDENTIALS_FILE="/usr/local/directadmin/conf/postgresql.conf"
|
||||
|
||||
# Bazy wykluczone z backupu (oddzielone spacją)
|
||||
EXCLUDE_DBS="template0 template1"
|
||||
|
||||
# =============================================================================
|
||||
# WEWNĘTRZNA KONFIGURACJA – nie zmieniaj
|
||||
# =============================================================================
|
||||
|
||||
LOCK_FILE="/var/run/postgres_backup.lock"
|
||||
LOG_FILE="/var/log/postgres_backup.log"
|
||||
|
||||
# =============================================================================
|
||||
# INICJALIZACJA
|
||||
# =============================================================================
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Kolory tylko na terminalu
|
||||
if [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; NC=''
|
||||
fi
|
||||
|
||||
ts() { date '+%Y-%m-%d %H:%M:%S'; }
|
||||
log() { echo "[$(ts)] $*" | tee -a "$LOG_FILE"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} [$(ts)] $*" | tee -a "$LOG_FILE"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} [$(ts)] $*" | tee -a "$LOG_FILE"; }
|
||||
die() { echo -e "${RED}[BŁĄD]${NC} [$(ts)] $*" | tee -a "$LOG_FILE" >&2; cleanup; exit 1; }
|
||||
|
||||
cleanup() { rm -f "$LOCK_FILE"; }
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Blokada przed równoległym uruchomieniem
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ -f "$LOCK_FILE" ]]; then
|
||||
OLD_PID=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
|
||||
if [[ -n "$OLD_PID" ]] && kill -0 "$OLD_PID" 2>/dev/null; then
|
||||
die "Backup jest już uruchomiony (PID: ${OLD_PID}). Przerywam."
|
||||
fi
|
||||
warn "Stały plik blokady (PID ${OLD_PID} nie istnieje) – usuwam i kontynuuję."
|
||||
rm -f "$LOCK_FILE"
|
||||
fi
|
||||
echo $$ > "$LOCK_FILE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Weryfikacja pliku poświadczeń
|
||||
# ---------------------------------------------------------------------------
|
||||
[[ -f "$CREDENTIALS_FILE" ]] \
|
||||
|| die "Brak pliku poświadczeń: ${CREDENTIALS_FILE}"
|
||||
|
||||
_perms=$(stat -c '%a' "$CREDENTIALS_FILE")
|
||||
[[ "$_perms" == "600" || "$_perms" == "400" ]] \
|
||||
|| die "Plik ${CREDENTIALS_FILE} ma zbyt otwarte uprawnienia (${_perms}). Wymagane: 600 lub 400."
|
||||
|
||||
# Parsowanie: host:port:database:user:password
|
||||
_line=$(head -1 "$CREDENTIALS_FILE")
|
||||
PGHOST=$(echo "$_line" | cut -d: -f1)
|
||||
PGPORT=$(echo "$_line" | cut -d: -f2)
|
||||
PGUSER=$(echo "$_line" | cut -d: -f4)
|
||||
|
||||
[[ -n "$PGHOST" && -n "$PGPORT" && -n "$PGUSER" ]] \
|
||||
|| die "Nieprawidłowy format pliku poświadczeń. Oczekiwany: host:port:database:user:password"
|
||||
|
||||
# Hasło przekazywane przez PGPASSFILE – nie pojawia się w środowisku ani linii poleceń
|
||||
export PGPASSFILE="$CREDENTIALS_FILE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Katalog backupu
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "$ADD_TIME" == "1" ]]; then
|
||||
DATE_STR=$(date +"${DATE_FORMAT}_%H-%M")
|
||||
else
|
||||
DATE_STR=$(date +"${DATE_FORMAT}")
|
||||
fi
|
||||
|
||||
BACKUP_DIR="${BASE_DIR}/${DATE_STR}"
|
||||
mkdir -p "$BACKUP_DIR" || die "Nie można utworzyć katalogu backupu: ${BACKUP_DIR}"
|
||||
|
||||
# =============================================================================
|
||||
# BACKUP
|
||||
# =============================================================================
|
||||
|
||||
log "================================================================="
|
||||
log "Rozpoczęcie backupu PostgreSQL"
|
||||
log "Host: ${PGHOST}:${PGPORT} Użytkownik: ${PGUSER}"
|
||||
log "Katalog: ${BACKUP_DIR}"
|
||||
log "Kompresja: ${ENABLE_COMPRESSION} Retencja: ${RETENTION} dni"
|
||||
log "================================================================="
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Globals: użytkownicy, role, hasła, przynależności (pg_dumpall --globals-only)
|
||||
# Wymagane do pełnego disaster recovery – przywróć jako pierwsze
|
||||
# ---------------------------------------------------------------------------
|
||||
ERRORS=0
|
||||
BACKED_UP=0
|
||||
GLOBALS_SUCCESS=true
|
||||
|
||||
if [[ "$ENABLE_COMPRESSION" == "1" ]]; then
|
||||
GLOBALS_FILE="${BACKUP_DIR}/_globals.sql.gz"
|
||||
pg_dumpall \
|
||||
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password --globals-only --clean --if-exists \
|
||||
2>>"$LOG_FILE" \
|
||||
| gzip -9 > "$GLOBALS_FILE" \
|
||||
|| GLOBALS_SUCCESS=false
|
||||
else
|
||||
GLOBALS_FILE="${BACKUP_DIR}/_globals.sql"
|
||||
pg_dumpall \
|
||||
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password --globals-only --clean --if-exists \
|
||||
> "$GLOBALS_FILE" 2>>"$LOG_FILE" \
|
||||
|| GLOBALS_SUCCESS=false
|
||||
fi
|
||||
|
||||
if [[ "$GLOBALS_SUCCESS" == "true" ]]; then
|
||||
SIZE=$(du -sh "$GLOBALS_FILE" 2>/dev/null | cut -f1)
|
||||
ok "_globals → ${GLOBALS_FILE##*/} (${SIZE})"
|
||||
else
|
||||
warn "_globals: backup ról i użytkowników nie powiódł się – plik usunięty."
|
||||
rm -f "$GLOBALS_FILE"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
# Lista baz danych
|
||||
DATABASES=$(
|
||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password -t -A \
|
||||
-c "SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;" \
|
||||
2>>"$LOG_FILE"
|
||||
) || die "Nie można pobrać listy baz. Sprawdź połączenie i poświadczenia."
|
||||
|
||||
# Zbuduj wzorzec wykluczeń
|
||||
EXCLUDE_PATTERN=$(echo "$EXCLUDE_DBS" | tr ' ' '|')
|
||||
|
||||
for DB in $DATABASES; do
|
||||
# Pomiń wykluczone
|
||||
if echo "$DB" | grep -qE "^(${EXCLUDE_PATTERN})$"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
SUCCESS=true
|
||||
|
||||
if [[ "$ENABLE_COMPRESSION" == "1" ]]; then
|
||||
OUT_FILE="${BACKUP_DIR}/${DB}.sql.gz"
|
||||
pg_dump \
|
||||
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password --create --clean --if-exists \
|
||||
-F p "$DB" 2>>"$LOG_FILE" \
|
||||
| gzip -9 > "$OUT_FILE" \
|
||||
|| SUCCESS=false
|
||||
else
|
||||
OUT_FILE="${BACKUP_DIR}/${DB}.sql"
|
||||
pg_dump \
|
||||
-h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password --create --clean --if-exists \
|
||||
-F p "$DB" > "$OUT_FILE" 2>>"$LOG_FILE" \
|
||||
|| SUCCESS=false
|
||||
fi
|
||||
|
||||
if [[ "$SUCCESS" == "true" ]]; then
|
||||
SIZE=$(du -sh "$OUT_FILE" 2>/dev/null | cut -f1)
|
||||
ok "${DB} → ${OUT_FILE##*/} (${SIZE})"
|
||||
BACKED_UP=$((BACKED_UP + 1))
|
||||
else
|
||||
warn "${DB}: backup nie powiódł się – plik usunięty."
|
||||
rm -f "$OUT_FILE"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
log "Wynik: ${BACKED_UP} OK, ${ERRORS} błędów."
|
||||
|
||||
# =============================================================================
|
||||
# RETENCJA
|
||||
# =============================================================================
|
||||
|
||||
if [[ "$RETENTION" -gt 0 ]]; then
|
||||
log "Usuwanie backupów starszych niż ${RETENTION} dni..."
|
||||
find "$BASE_DIR" -maxdepth 1 -mindepth 1 -type d -mtime +"$RETENTION" \
|
||||
-exec rm -rf {} + 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# ZAKOŃCZENIE
|
||||
# =============================================================================
|
||||
|
||||
log "Backup zakończony."
|
||||
|
||||
[[ "$ERRORS" -eq 0 ]] || exit 1
|
||||
exit 0
|
||||
Executable
+786
@@ -0,0 +1,786 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# postgresql_install.sh
|
||||
# Instalacja PostgreSQL na AlmaLinux 8/9, CloudLinux 8/9
|
||||
# i systemach kompatybilnych z RHEL pod kontrolą DirectAdmin
|
||||
#
|
||||
# Polityka bezpieczeństwa:
|
||||
# - Dostęp wyłącznie przez localhost (listen_addresses = 'localhost')
|
||||
# - Uwierzytelnianie hasłem dla WSZYSTKICH połączeń (brak peer, brak trust)
|
||||
# - Połączenia replikacji odrzucone
|
||||
#
|
||||
# Rozszerzenia PHP:
|
||||
# - Podejście przez custom configure (zapewnia pgsql + pdo_pgsql)
|
||||
# - Obsługa: apache, nginx, nginx_apache, litespeed, openlitespeed
|
||||
# - Tryby PHP: php-fpm, fastcgi, lsphp
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kolory
|
||||
# ---------------------------------------------------------------------------
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
sekcja() { echo -e "\n${BOLD}${CYAN}=== $* ===${NC}\n"; }
|
||||
die() { echo -e "${RED}[BŁĄD]${NC} $*" >&2; exit 1; }
|
||||
|
||||
CB_PHP_WEBSERVER=""
|
||||
CB_PHP_OPTIONS_CONF=""
|
||||
DA_PG_RUNTIME_LIBDIR=""
|
||||
DA_PG_RUNTIME_LIBPQ=""
|
||||
declare -ag CB_PHP_VERSIONS=()
|
||||
declare -ag CB_PHP_CODES=()
|
||||
declare -ag CB_PHP_MODES=()
|
||||
|
||||
cb_pgsql_reset_php_targets() {
|
||||
CB_PHP_VERSIONS=()
|
||||
CB_PHP_CODES=()
|
||||
CB_PHP_MODES=()
|
||||
}
|
||||
|
||||
cb_pgsql_get_cb_option() {
|
||||
local options_conf="$1"
|
||||
local key="$2"
|
||||
local value=""
|
||||
|
||||
[[ -f "$options_conf" ]] || return 1
|
||||
value="$(awk -F= -v key="$key" '
|
||||
$1 == key {
|
||||
sub(/^[[:space:]]+/, "", $2)
|
||||
sub(/[[:space:]]+$/, "", $2)
|
||||
print $2
|
||||
exit
|
||||
}
|
||||
' "$options_conf")"
|
||||
[[ -n "$value" ]] || return 1
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
cb_pgsql_php_code_from_version() {
|
||||
printf '%s\n' "${1//./}"
|
||||
}
|
||||
|
||||
cb_pgsql_php_version_from_code() {
|
||||
local code="$1"
|
||||
if [[ "$code" == *.* ]]; then
|
||||
printf '%s\n' "$code"
|
||||
elif [[ "$code" =~ ^[0-9]{2,3}$ ]]; then
|
||||
printf '%s.%s\n' "${code:0:1}" "${code:1}"
|
||||
else
|
||||
printf '%s\n' "$code"
|
||||
fi
|
||||
}
|
||||
|
||||
cb_pgsql_add_php_target() {
|
||||
local version="$1"
|
||||
local mode="$2"
|
||||
local code=""
|
||||
local idx=""
|
||||
|
||||
[[ -n "$version" ]] || return 0
|
||||
code="$(cb_pgsql_php_code_from_version "$version")"
|
||||
for idx in "${!CB_PHP_CODES[@]}"; do
|
||||
if [[ "${CB_PHP_CODES[$idx]}" == "$code" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
CB_PHP_VERSIONS+=("$version")
|
||||
CB_PHP_CODES+=("$code")
|
||||
CB_PHP_MODES+=("${mode:-php-fpm}")
|
||||
}
|
||||
|
||||
cb_pgsql_detect_webserver() {
|
||||
local cb_dir="$1"
|
||||
|
||||
CB_PHP_OPTIONS_CONF="${cb_dir}/options.conf"
|
||||
CB_PHP_WEBSERVER="apache"
|
||||
|
||||
if [[ -f "$CB_PHP_OPTIONS_CONF" ]]; then
|
||||
CB_PHP_WEBSERVER="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "webserver" || true)"
|
||||
[[ -n "$CB_PHP_WEBSERVER" ]] || CB_PHP_WEBSERVER="apache"
|
||||
fi
|
||||
}
|
||||
|
||||
cb_pgsql_collect_php_targets() {
|
||||
local cb_dir="$1"
|
||||
local slot=""
|
||||
local release=""
|
||||
local mode=""
|
||||
local default_mode=""
|
||||
|
||||
cb_pgsql_reset_php_targets
|
||||
cb_pgsql_detect_webserver "$cb_dir"
|
||||
|
||||
# CloudLinux może używać lsphp także przy webserver=apache.
|
||||
# Dlatego najpierw honorujemy phpN_mode/php1_mode z options.conf,
|
||||
# a dopiero przy ich braku stosujemy domyślny tryb wynikający z webserwera.
|
||||
case "$CB_PHP_WEBSERVER" in
|
||||
litespeed|openlitespeed) default_mode="lsphp" ;;
|
||||
*) default_mode="php-fpm" ;;
|
||||
esac
|
||||
|
||||
for slot in 1 2 3 4 5 6 7 8 9; do
|
||||
release="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php${slot}_release" || true)"
|
||||
mode="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php${slot}_mode" || true)"
|
||||
[[ -n "$mode" ]] || mode="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php1_mode" || true)"
|
||||
[[ -n "$mode" ]] || mode="$default_mode"
|
||||
case "${release,,}" in
|
||||
""|no|off) continue ;;
|
||||
esac
|
||||
case "${mode,,}" in
|
||||
no|off) continue ;;
|
||||
esac
|
||||
cb_pgsql_add_php_target "$release" "$mode"
|
||||
done
|
||||
}
|
||||
|
||||
cb_pgsql_build_file_candidates() {
|
||||
local cb_dir="$1"
|
||||
local mode="$2"
|
||||
local code="$3"
|
||||
local -n out_ref="$4"
|
||||
|
||||
out_ref=()
|
||||
if [[ "$mode" == "lsphp" ]]; then
|
||||
case "$CB_PHP_WEBSERVER" in
|
||||
litespeed)
|
||||
out_ref+=("${cb_dir}/configure/litespeed/configure.php${code}")
|
||||
;;
|
||||
openlitespeed)
|
||||
out_ref+=("${cb_dir}/configure/openlitespeed/configure.php${code}")
|
||||
;;
|
||||
esac
|
||||
out_ref+=("${cb_dir}/configure/lsphp/configure.lsphp${code}")
|
||||
out_ref+=("${cb_dir}/configure/lsphp/configure.php${code}")
|
||||
fi
|
||||
out_ref+=("${cb_dir}/configure/php/configure.php${code}")
|
||||
}
|
||||
|
||||
cb_pgsql_resolve_config_paths() {
|
||||
local cb_dir="$1"
|
||||
local version="$2"
|
||||
local mode="$3"
|
||||
local source_ref_name="$4"
|
||||
local custom_ref_name="$5"
|
||||
local code=""
|
||||
local -a candidates=()
|
||||
local candidate=""
|
||||
|
||||
code="$(cb_pgsql_php_code_from_version "$version")"
|
||||
printf -v "$source_ref_name" '%s' ""
|
||||
printf -v "$custom_ref_name" '%s' ""
|
||||
|
||||
cb_pgsql_build_file_candidates "$cb_dir" "$mode" "$code" candidates
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
if [[ -f "$candidate" ]]; then
|
||||
printf -v "$source_ref_name" '%s' "$candidate"
|
||||
printf -v "$custom_ref_name" '%s' "$(printf '%s\n' "$candidate" | sed "s#^${cb_dir}/configure/#${cb_dir}/custom/#")"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
candidate="$(printf '%s\n' "$candidate" | sed "s#^${cb_dir}/configure/#${cb_dir}/custom/#")"
|
||||
if [[ -f "$candidate" ]]; then
|
||||
printf -v "$source_ref_name" '%s' "$candidate"
|
||||
printf -v "$custom_ref_name" '%s' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
cb_pgsql_insert_flags() {
|
||||
local config_file="$1"
|
||||
local pg_prefix="$2"
|
||||
local tmp_file="${config_file}.tmp"
|
||||
|
||||
sed -i '/--with-pgsql=/d; /--with-pdo-pgsql=/d' "$config_file"
|
||||
|
||||
if grep -q -- '--with-pdo-mysql' "$config_file"; then
|
||||
awk -v prefix="$pg_prefix" '
|
||||
/--with-pdo-mysql/ {
|
||||
print
|
||||
print "\t--with-pgsql=" prefix " \\"
|
||||
print "\t--with-pdo-pgsql=" prefix " \\"
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$config_file" > "$tmp_file"
|
||||
else
|
||||
awk -v prefix="$pg_prefix" '
|
||||
/^[[:space:]]*--with/ { last = NR }
|
||||
{ lines[NR] = $0 }
|
||||
END {
|
||||
if (last == 0) {
|
||||
for (i = 1; i <= NR; i++) print lines[i]
|
||||
exit
|
||||
}
|
||||
for (i = 1; i <= NR; i++) {
|
||||
print lines[i]
|
||||
if (i == last) {
|
||||
print "\t--with-pgsql=" prefix " \\"
|
||||
print "\t--with-pdo-pgsql=" prefix " \\"
|
||||
}
|
||||
}
|
||||
}
|
||||
' "$config_file" > "$tmp_file"
|
||||
fi
|
||||
|
||||
mv "$tmp_file" "$config_file"
|
||||
}
|
||||
|
||||
cb_pgsql_prepare_custom_config() {
|
||||
local cb_dir="$1"
|
||||
local version="$2"
|
||||
local mode="$3"
|
||||
local pg_prefix="$4"
|
||||
local source_conf=""
|
||||
local custom_conf=""
|
||||
local ts=""
|
||||
|
||||
if ! cb_pgsql_resolve_config_paths "$cb_dir" "$version" "$mode" source_conf custom_conf; then
|
||||
warn "Nie znaleziono configure dla PHP ${version} (tryb ${mode}, webserver ${CB_PHP_WEBSERVER})."
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$custom_conf")"
|
||||
ts="$(date +%s)"
|
||||
|
||||
if [[ "$source_conf" != "$custom_conf" && ! -f "$custom_conf" ]]; then
|
||||
cp -p "$source_conf" "$custom_conf"
|
||||
elif [[ -f "$custom_conf" ]]; then
|
||||
cp -p "$custom_conf" "${custom_conf}.bak.${ts}"
|
||||
fi
|
||||
|
||||
cb_pgsql_insert_flags "$custom_conf" "$pg_prefix"
|
||||
chmod +x "$custom_conf" || true
|
||||
info "Przygotowano configure dla PHP ${version}: ${custom_conf}"
|
||||
return 0
|
||||
}
|
||||
|
||||
cb_pgsql_configure_runtime_libpq() {
|
||||
local pg_prefix="$1"
|
||||
local pg_libdir="${pg_prefix}/lib"
|
||||
local libpq_so="${pg_libdir}/libpq.so.5"
|
||||
local ld_conf="/etc/ld.so.conf.d/00-da-postgresql-libpq.conf"
|
||||
local old_ld_conf="/etc/ld.so.conf.d/da-postgresql-libpq.conf"
|
||||
local preferred=""
|
||||
|
||||
[[ -d "$pg_libdir" ]] || die "Nie znaleziono katalogu bibliotek PostgreSQL: ${pg_libdir}"
|
||||
[[ -r "$libpq_so" ]] || die "Nie znaleziono biblioteki libpq: ${libpq_so}"
|
||||
|
||||
printf '%s\n' "$pg_libdir" > "$ld_conf"
|
||||
chmod 644 "$ld_conf"
|
||||
[[ "$old_ld_conf" == "$ld_conf" ]] || rm -f "$old_ld_conf"
|
||||
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
ldconfig || die "Nie udało się odświeżyć cache bibliotek przez ldconfig."
|
||||
preferred="$(ldconfig -p 2>/dev/null | awk '/libpq\.so\.5/{print $NF; exit}')"
|
||||
if [[ -n "$preferred" && "$preferred" != "$libpq_so" ]]; then
|
||||
warn "Domyślna libpq to ${preferred}; wymuszę właściwą bibliotekę przez LD_PRELOAD podczas kompilacji PHP."
|
||||
fi
|
||||
else
|
||||
warn "Brak ldconfig. Kontynuuję z LD_LIBRARY_PATH."
|
||||
fi
|
||||
|
||||
DA_PG_RUNTIME_LIBDIR="$pg_libdir"
|
||||
DA_PG_RUNTIME_LIBPQ="$libpq_so"
|
||||
info "Skonfigurowano bibliotekę runtime PostgreSQL: ${pg_libdir}"
|
||||
}
|
||||
|
||||
cb_pgsql_find_php_bin_for_version() {
|
||||
local version="$1"
|
||||
local code=""
|
||||
local candidate=""
|
||||
|
||||
code="$(cb_pgsql_php_code_from_version "$version")"
|
||||
for candidate in \
|
||||
"/usr/local/php${code}/bin/php" \
|
||||
"/usr/local/php${code}/bin/lsphp" \
|
||||
"/usr/local/lsphp${code}/bin/lsphp" \
|
||||
"/usr/local/php${code}/bin/php-cgi"; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
cb_pgsql_probe_php_extensions() {
|
||||
local php_bin="$1"
|
||||
local pgsql_ref_name="$2"
|
||||
local pdo_ref_name="$3"
|
||||
local modules=""
|
||||
local modules_lc=""
|
||||
local probe_pgsql_status="NIE"
|
||||
local probe_pdo_status="NIE"
|
||||
|
||||
modules="$("$php_bin" -m 2>/dev/null || true)"
|
||||
if [[ -z "$modules" ]]; then
|
||||
probe_pgsql_status="BŁĄD"
|
||||
probe_pdo_status="BŁĄD"
|
||||
printf -v "$pgsql_ref_name" '%s' "$probe_pgsql_status"
|
||||
printf -v "$pdo_ref_name" '%s' "$probe_pdo_status"
|
||||
return 1
|
||||
fi
|
||||
|
||||
modules_lc="$(printf '%s\n' "$modules" | tr '[:upper:]' '[:lower:]')"
|
||||
if printf '%s\n' "$modules_lc" | grep -qx 'pgsql'; then
|
||||
probe_pgsql_status="OK"
|
||||
fi
|
||||
if printf '%s\n' "$modules_lc" | grep -qx 'pdo_pgsql'; then
|
||||
probe_pdo_status="OK"
|
||||
fi
|
||||
|
||||
printf -v "$pgsql_ref_name" '%s' "$probe_pgsql_status"
|
||||
printf -v "$pdo_ref_name" '%s' "$probe_pdo_status"
|
||||
return 0
|
||||
}
|
||||
|
||||
cb_pgsql_render_extensions_table() {
|
||||
local found=0
|
||||
local idx=""
|
||||
local version=""
|
||||
local php_bin=""
|
||||
local pgsql_status=""
|
||||
local pdo_pgsql_status=""
|
||||
|
||||
echo "+------------+-------+-----------+"
|
||||
printf "| %-10s | %-5s | %-9s |\n" "Wersja PHP" "PGSQL" "PDO_PGSQL"
|
||||
echo "+------------+-------+-----------+"
|
||||
|
||||
for idx in "${!CB_PHP_VERSIONS[@]}"; do
|
||||
version="${CB_PHP_VERSIONS[$idx]}"
|
||||
php_bin="$(cb_pgsql_find_php_bin_for_version "$version" || true)"
|
||||
if [[ -z "$php_bin" ]]; then
|
||||
printf "| %-10s | %-5s | %-9s |\n" "PHP ${version}" "BRAK" "BRAK"
|
||||
found=1
|
||||
continue
|
||||
fi
|
||||
|
||||
cb_pgsql_probe_php_extensions "$php_bin" pgsql_status pdo_pgsql_status >/dev/null 2>&1 || true
|
||||
printf "| %-10s | %-5s | %-9s |\n" "PHP ${version}" "$pgsql_status" "$pdo_pgsql_status"
|
||||
found=1
|
||||
done
|
||||
|
||||
if [[ $found -eq 1 ]]; then
|
||||
echo "+------------+-------+-----------+"
|
||||
else
|
||||
warn "Nie znaleziono wersji PHP do raportu."
|
||||
fi
|
||||
}
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
POSTGRES_BACKUP_SRC="${SCRIPT_DIR}/postgres_backup.sh"
|
||||
POSTGRES_BACKUP_DST="/opt/postgres_backup"
|
||||
POSTGRES_BACKUP_CRON_LINE="0 2 * * * /opt/postgres_backup >/dev/null 2>&1"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wymagany root
|
||||
# ---------------------------------------------------------------------------
|
||||
[[ $EUID -eq 0 ]] || die "Skrypt musi być uruchomiony jako root."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plik logu – terminal: kolorowy output / plik: czysty tekst (bez ANSI)
|
||||
# ---------------------------------------------------------------------------
|
||||
LOGFILE="/var/log/postgresql_install_$(date +%Y%m%d_%H%M%S).log"
|
||||
exec 1> >(tee >(sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' >> "$LOGFILE")) 2>&1
|
||||
info "Pełny log zapisany w: $LOGFILE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wykrywanie systemu operacyjnego
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Wykrywanie systemu operacyjnego"
|
||||
|
||||
[[ -f /etc/os-release ]] || die "Nie można wykryć systemu: brak pliku /etc/os-release."
|
||||
source /etc/os-release
|
||||
|
||||
OS_ID="${ID:-}"
|
||||
OS_MAJOR="${VERSION_ID%%.*}"
|
||||
|
||||
case "$OS_ID" in
|
||||
almalinux) OS_LABEL="AlmaLinux ${VERSION_ID}" ;;
|
||||
cloudlinux) OS_LABEL="CloudLinux ${VERSION_ID}" ;;
|
||||
centos|rhel|rocky|ol)
|
||||
OS_LABEL="${NAME} ${VERSION_ID}"
|
||||
warn "System '${OS_LABEL}' jest kompatybilny z RHEL, ale nie jest głównym celem skryptu."
|
||||
;;
|
||||
*)
|
||||
die "Nieobsługiwany system: ${OS_ID}. Skrypt obsługuje AlmaLinux/CloudLinux 8 i 9."
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "$OS_MAJOR" == "8" || "$OS_MAJOR" == "9" ]] \
|
||||
|| die "Nieobsługiwana wersja główna ${OS_MAJOR}. Obsługiwane są tylko wersje 8 i 9."
|
||||
[[ "$(uname -m)" == "x86_64" ]] || die "Obsługiwana jest tylko architektura x86_64."
|
||||
|
||||
info "Wykryto: ${OS_LABEL} (EL${OS_MAJOR})"
|
||||
|
||||
# =============================================================================
|
||||
# PYTANIA INTERAKTYWNE
|
||||
# =============================================================================
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# P1: Wersja PostgreSQL
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Wybór wersji PostgreSQL"
|
||||
|
||||
AVAILABLE_VERSIONS=(14 15 16 17 18)
|
||||
|
||||
echo "Dostępne wersje PostgreSQL:"
|
||||
idx=1
|
||||
for v in "${AVAILABLE_VERSIONS[@]}"; do
|
||||
echo " [${idx}] PostgreSQL ${v}"
|
||||
((idx++))
|
||||
done
|
||||
echo " [${idx}] Inna (podaj ręcznie numer wersji)"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
read -rp "Wybierz opcję [1-${idx}, domyślnie 4]: " _ver
|
||||
_ver="${_ver:-4}"
|
||||
if [[ ! "$_ver" =~ ^[0-9]+$ ]] || (( _ver < 1 || _ver > idx )); then
|
||||
warn "Podaj liczbę od 1 do ${idx}."
|
||||
continue
|
||||
fi
|
||||
|
||||
if (( _ver < idx )); then
|
||||
PG_VERSION="${AVAILABLE_VERSIONS[$((_ver-1))]}"
|
||||
break
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -rp "Podaj ręcznie numer wersji PostgreSQL (np. 16 lub 17): " PG_VERSION
|
||||
if [[ ! "$PG_VERSION" =~ ^[0-9]+$ ]]; then
|
||||
warn "Dozwolone są tylko liczby całkowite, np. 16 lub 17."
|
||||
continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
break
|
||||
done
|
||||
|
||||
info "Wybrano: PostgreSQL ${PG_VERSION}"
|
||||
|
||||
PG_SERVICE="postgresql-${PG_VERSION}"
|
||||
PG_BINDIR="/usr/pgsql-${PG_VERSION}/bin"
|
||||
PG_DATADIR="/var/lib/pgsql/${PG_VERSION}/data"
|
||||
PG_HBA="${PG_DATADIR}/pg_hba.conf"
|
||||
PG_CONF="${PG_DATADIR}/postgresql.conf"
|
||||
PG_AUTH="scram-sha-256"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# P2: Rozszerzenia PHP
|
||||
# ---------------------------------------------------------------------------
|
||||
CB_DIR="/usr/local/directadmin/custombuild"
|
||||
INSTALL_PHP="nie"
|
||||
|
||||
if [[ -x "${CB_DIR}/build" ]]; then
|
||||
while true; do
|
||||
read -rp "Zainstalować rozszerzenia pgsql i pdo_pgsql dla PHP? [t/n, domyślnie t]: " _php
|
||||
_php="${_php:-t}"
|
||||
case "${_php,,}" in
|
||||
t|tak) INSTALL_PHP="tak"; break ;;
|
||||
n|nie) INSTALL_PHP="nie"; break ;;
|
||||
*) warn "Podaj t lub n." ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
warn "Nie znaleziono DirectAdmin CustomBuild w ${CB_DIR}/build – pomijam instalację rozszerzeń PHP."
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# INSTALACJA
|
||||
# =============================================================================
|
||||
|
||||
genpasswd() { tr -dc 'A-Za-z0-9_' </dev/urandom | head -c "${1:-32}"; echo; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Repozytorium PGDG
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Instalacja repozytorium PGDG (EL${OS_MAJOR})"
|
||||
|
||||
PGDG_RPM="https://download.postgresql.org/pub/repos/yum/reporpms/EL-${OS_MAJOR}-x86_64/pgdg-redhat-repo-latest.noarch.rpm"
|
||||
|
||||
if rpm -q pgdg-redhat-repo &>/dev/null; then
|
||||
info "Usuwam istniejące repozytorium PGDG."
|
||||
dnf remove -y pgdg-redhat-repo
|
||||
fi
|
||||
|
||||
info "Instaluję: ${PGDG_RPM}"
|
||||
dnf install -y "$PGDG_RPM" \
|
||||
|| die "Nie udało się zainstalować repozytorium PGDG."
|
||||
|
||||
dnf -qy module disable postgresql 2>/dev/null || true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Instalacja pakietów PostgreSQL
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Instalacja pakietów PostgreSQL ${PG_VERSION}"
|
||||
|
||||
dnf install -y \
|
||||
"postgresql${PG_VERSION}-server" \
|
||||
"postgresql${PG_VERSION}-contrib" \
|
||||
"postgresql${PG_VERSION}" \
|
||||
|| die "Nie udało się zainstalować pakietów PostgreSQL."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Inicjalizacja klastra
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Inicjalizacja klastra PostgreSQL"
|
||||
|
||||
if [[ -f "${PG_DATADIR}/PG_VERSION" ]]; then
|
||||
warn "Klaster jest już zainicjalizowany w ${PG_DATADIR}. Pomijam initdb."
|
||||
else
|
||||
"${PG_BINDIR}/postgresql-${PG_VERSION}-setup" initdb \
|
||||
|| die "Inicjalizacja klastra nie powiodła się."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Zabezpieczenie postgresql.conf
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Zabezpieczanie postgresql.conf"
|
||||
|
||||
cp -p "$PG_CONF" "${PG_CONF}.bak.$(date +%s)"
|
||||
|
||||
pg_param() {
|
||||
local param="$1" value="$2"
|
||||
sed -i "s|^\s*${param}\s*=.*|#&|" "$PG_CONF"
|
||||
echo "${param} = ${value}" >> "$PG_CONF"
|
||||
}
|
||||
|
||||
pg_param listen_addresses "'localhost'"
|
||||
pg_param password_encryption "scram-sha-256"
|
||||
pg_param max_connections 100
|
||||
pg_param unix_socket_permissions 0700
|
||||
pg_param log_connections on
|
||||
pg_param log_disconnections on
|
||||
pg_param log_hostname off
|
||||
pg_param log_line_prefix "'%m [%p] %q%u@%d '"
|
||||
|
||||
info "postgresql.conf zaktualizowany."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Tymczasowy pg_hba.conf (tylko trust dla postgres przez socket)
|
||||
# ---------------------------------------------------------------------------
|
||||
cp -p "$PG_HBA" "${PG_HBA}.orig.$(date +%s)"
|
||||
|
||||
cat > "$PG_HBA" <<EOF
|
||||
# TYMCZASOWY – zastąpiony po ustawieniu hasła
|
||||
local all postgres trust
|
||||
host all all 127.0.0.1/32 reject
|
||||
host all all ::1/128 reject
|
||||
EOF
|
||||
|
||||
chown postgres:postgres "$PG_HBA"
|
||||
chmod 600 "$PG_HBA"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Uruchomienie PostgreSQL
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Uruchamianie PostgreSQL ${PG_VERSION}"
|
||||
|
||||
systemctl enable "${PG_SERVICE}"
|
||||
systemctl start "${PG_SERVICE}" \
|
||||
|| die "Nie udało się uruchomić ${PG_SERVICE}. Sprawdź: journalctl -xe"
|
||||
|
||||
sleep 2
|
||||
systemctl is-active --quiet "${PG_SERVICE}" \
|
||||
|| die "Serwis ${PG_SERVICE} nie jest aktywny po uruchomieniu."
|
||||
info "Serwis ${PG_SERVICE} działa."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. Ustawienie hasła superużytkownika postgres
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Ustawianie hasła superużytkownika postgres"
|
||||
|
||||
POSTGRES_PASS=$(genpasswd 32)
|
||||
|
||||
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD '${POSTGRES_PASS}';" \
|
||||
|| die "Nie udało się ustawić hasła dla użytkownika postgres."
|
||||
|
||||
info "Hasło użytkownika postgres ustawione."
|
||||
|
||||
# Zapis poświadczeń w formacie pgpass (host:port:db:user:password)
|
||||
CRED_FILE="/usr/local/directadmin/conf/postgresql.conf"
|
||||
mkdir -p "$(dirname "$CRED_FILE")"
|
||||
printf 'localhost:5432:*:postgres:%s\n' "$POSTGRES_PASS" > "$CRED_FILE"
|
||||
chmod 600 "$CRED_FILE"
|
||||
if id diradmin &>/dev/null; then
|
||||
chown diradmin:diradmin "$CRED_FILE"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 8. Finalny pg_hba.conf
|
||||
# ---------------------------------------------------------------------------
|
||||
cat > "$PG_HBA" <<EOF
|
||||
# pg_hba.conf – zarządzany przez postgresql_install.sh
|
||||
# Wygenerowano: $(date)
|
||||
#
|
||||
# Polityka bezpieczeństwa:
|
||||
# - Uwierzytelnianie hasłem dla WSZYSTKICH połączeń (brak peer, brak trust)
|
||||
# - Wyłącznie localhost (127.0.0.1/32 i ::1/128)
|
||||
# - Połączenia zdalne zablokowane przez listen_addresses = 'localhost'
|
||||
# - Replikacja jawnie odrzucona
|
||||
#
|
||||
# RODZAJ BAZA UŻYTKOWNIK ADRES METODA
|
||||
|
||||
local all all ${PG_AUTH}
|
||||
host all all 127.0.0.1/32 ${PG_AUTH}
|
||||
host all all ::1/128 ${PG_AUTH}
|
||||
local replication all reject
|
||||
host replication all 127.0.0.1/32 reject
|
||||
host replication all ::1/128 reject
|
||||
EOF
|
||||
|
||||
chown postgres:postgres "$PG_HBA"
|
||||
chmod 600 "$PG_HBA"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 9. Przeładowanie konfiguracji + weryfikacja połączenia
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Przeładowanie konfiguracji PostgreSQL"
|
||||
|
||||
systemctl reload "${PG_SERVICE}" \
|
||||
|| die "Nie udało się przeładować ${PG_SERVICE}."
|
||||
info "Konfiguracja przeładowana."
|
||||
|
||||
sleep 1
|
||||
|
||||
info "Test uwierzytelniania hasłem..."
|
||||
if PGPASSWORD="$POSTGRES_PASS" \
|
||||
psql -U postgres -h 127.0.0.1 -p 5432 -d postgres -c "SELECT version();" &>/dev/null; then
|
||||
info "Test połączenia: OK"
|
||||
else
|
||||
warn "Test połączenia nie powiódł się. Sprawdź pg_hba.conf i password_encryption."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 10. Rozszerzenia PHP pgsql i pdo_pgsql przez CustomBuild
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "$INSTALL_PHP" == "tak" ]]; then
|
||||
sekcja "Rozszerzenia PHP pgsql i pdo_pgsql – DirectAdmin CustomBuild"
|
||||
|
||||
cb_pgsql_collect_php_targets "$CB_DIR"
|
||||
|
||||
info "Web serwer: ${CB_PHP_WEBSERVER}"
|
||||
if [[ ${#CB_PHP_VERSIONS[@]} -gt 0 ]]; then
|
||||
info "Wykryte wersje PHP: $(IFS=', '; echo "${CB_PHP_VERSIONS[*]}")"
|
||||
else
|
||||
warn "Nie wykryto wersji PHP w DirectAdmin CustomBuild."
|
||||
fi
|
||||
|
||||
info "Instaluję postgresql${PG_VERSION}-devel..."
|
||||
dnf install -y "postgresql${PG_VERSION}-devel" \
|
||||
"postgresql${PG_VERSION}-libs" \
|
||||
|| die "Nie udało się zainstalować postgresql${PG_VERSION}-devel/libs."
|
||||
|
||||
PG_PREFIX="/usr/pgsql-${PG_VERSION}"
|
||||
PG_CONFIG_SRC="${PG_BINDIR}/pg_config"
|
||||
PG_CONFIG_LINK="/usr/local/bin/pg_config"
|
||||
|
||||
if [[ -x "$PG_CONFIG_SRC" ]]; then
|
||||
if [[ -e "$PG_CONFIG_LINK" && ! -L "$PG_CONFIG_LINK" ]]; then
|
||||
warn "${PG_CONFIG_LINK} istnieje i nie jest dowiązaniem – pomijam symlink."
|
||||
else
|
||||
ln -sf "$PG_CONFIG_SRC" "$PG_CONFIG_LINK"
|
||||
fi
|
||||
else
|
||||
die "Nie znaleziono pg_config w ${PG_CONFIG_SRC}."
|
||||
fi
|
||||
|
||||
cb_pgsql_configure_runtime_libpq "$PG_PREFIX"
|
||||
|
||||
PHP_PREPARED=0
|
||||
for idx in "${!CB_PHP_VERSIONS[@]}"; do
|
||||
if cb_pgsql_prepare_custom_config "$CB_DIR" "${CB_PHP_VERSIONS[$idx]}" "${CB_PHP_MODES[$idx]}" "$PG_PREFIX"; then
|
||||
PHP_PREPARED=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$PHP_PREPARED" -eq 1 ]]; then
|
||||
cd "$CB_DIR"
|
||||
LD_LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" \
|
||||
LD_PRELOAD="${DA_PG_RUNTIME_LIBPQ}${LD_PRELOAD:+:${LD_PRELOAD}}" \
|
||||
LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LIBRARY_PATH:+:${LIBRARY_PATH}}" \
|
||||
PKG_CONFIG_PATH="${DA_PG_RUNTIME_LIBDIR}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}" \
|
||||
LDFLAGS="-L${DA_PG_RUNTIME_LIBDIR} ${LDFLAGS:-}" \
|
||||
CPPFLAGS="-I${PG_PREFIX}/include ${CPPFLAGS:-}" \
|
||||
./build php \
|
||||
|| die "Rekompilacja PHP nie powiodła się. Sprawdź logi w ${CB_DIR}/."
|
||||
./build rewrite_confs \
|
||||
|| warn "rewrite_confs zakończył się błędem – sprawdź konfigurację ręcznie."
|
||||
cd - > /dev/null
|
||||
info "Rekompilacja PHP zakończona."
|
||||
else
|
||||
warn "Nie przygotowano żadnych plików configure dla PHP – pomijam rekompilację."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 11. Instalacja narzędzia backupu w /opt + cron root (02:00)
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Instalacja narzędzia backupu i konfiguracja crona"
|
||||
|
||||
[[ -f "$POSTGRES_BACKUP_SRC" ]] || die "Brak pliku: ${POSTGRES_BACKUP_SRC}"
|
||||
|
||||
cp -f "$POSTGRES_BACKUP_SRC" "$POSTGRES_BACKUP_DST"
|
||||
|
||||
chown root:root "$POSTGRES_BACKUP_DST"
|
||||
chmod 700 "$POSTGRES_BACKUP_DST"
|
||||
|
||||
CURRENT_CRONTAB="$(crontab -l 2>/dev/null || true)"
|
||||
if printf '%s\n' "$CURRENT_CRONTAB" | grep -Fqx "$POSTGRES_BACKUP_CRON_LINE"; then
|
||||
info "Wpis cron już istnieje w crontab roota: /opt/postgres_backup"
|
||||
else
|
||||
TMP_CRON="$(mktemp)"
|
||||
if [[ -n "$CURRENT_CRONTAB" ]]; then
|
||||
printf '%s\n' "$CURRENT_CRONTAB" > "$TMP_CRON"
|
||||
fi
|
||||
printf '%s\n' "$POSTGRES_BACKUP_CRON_LINE" >> "$TMP_CRON"
|
||||
crontab "$TMP_CRON"
|
||||
rm -f "$TMP_CRON"
|
||||
info "Dodano wpis do crontab roota (02:00): /opt/postgres_backup"
|
||||
fi
|
||||
|
||||
info "Skopiowano: ${POSTGRES_BACKUP_DST}"
|
||||
|
||||
# =============================================================================
|
||||
# PODSUMOWANIE
|
||||
# =============================================================================
|
||||
|
||||
sekcja "Instalacja zakończona"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tabela rozszerzeń PHP
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "$INSTALL_PHP" == "tak" ]]; then
|
||||
echo -e "${BOLD}Rozszerzenia PHP:${NC}"
|
||||
echo ""
|
||||
cb_pgsql_collect_php_targets "$CB_DIR"
|
||||
cb_pgsql_render_extensions_table
|
||||
echo ""
|
||||
fi
|
||||
|
||||
info "Pełny log: ${LOGFILE}"
|
||||
echo -e "${BOLD}Podsumowanie instalacji:${NC}"
|
||||
printf "Host:\t%s\n" "localhost"
|
||||
printf "Login:\t%s\n" "postgres"
|
||||
printf "Hasło:\t%s\n" "${POSTGRES_PASS}"
|
||||
printf "Port:\t%s\n" "5432"
|
||||
printf "Plik danych dostępowych:\t%s\n" "${CRED_FILE}"
|
||||
echo ""
|
||||
exit 0
|
||||
Executable
+1371
File diff suppressed because it is too large
Load Diff
Executable
+520
@@ -0,0 +1,520 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# recompile_php.sh
|
||||
# Weryfikacja i rekompilacja wersji PHP w DirectAdmin CustomBuild
|
||||
# tak, aby każda aktywna wersja miała rozszerzenia pgsql i pdo_pgsql.
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
sekcja() { echo -e "\n${BOLD}${CYAN}=== $* ===${NC}\n"; }
|
||||
die() { echo -e "${RED}[BŁĄD]${NC} $*" >&2; exit 1; }
|
||||
|
||||
CB_PHP_WEBSERVER=""
|
||||
CB_PHP_OPTIONS_CONF=""
|
||||
CB_PG_PREFIX=""
|
||||
CB_PG_VERSION=""
|
||||
CB_PG_CONFIG=""
|
||||
DA_PG_RUNTIME_LIBDIR=""
|
||||
DA_PG_RUNTIME_LIBPQ=""
|
||||
declare -ag CB_PHP_VERSIONS=()
|
||||
declare -ag CB_PHP_CODES=()
|
||||
declare -ag CB_PHP_MODES=()
|
||||
|
||||
cb_pgsql_reset_php_targets() {
|
||||
CB_PHP_VERSIONS=()
|
||||
CB_PHP_CODES=()
|
||||
CB_PHP_MODES=()
|
||||
}
|
||||
|
||||
cb_pgsql_get_cb_option() {
|
||||
local options_conf="$1"
|
||||
local key="$2"
|
||||
local value=""
|
||||
|
||||
[[ -f "$options_conf" ]] || return 1
|
||||
value="$(awk -F= -v key="$key" '
|
||||
$1 == key {
|
||||
sub(/^[[:space:]]+/, "", $2)
|
||||
sub(/[[:space:]]+$/, "", $2)
|
||||
print $2
|
||||
exit
|
||||
}
|
||||
' "$options_conf")"
|
||||
[[ -n "$value" ]] || return 1
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
cb_pgsql_php_code_from_version() {
|
||||
printf '%s\n' "${1//./}"
|
||||
}
|
||||
|
||||
cb_pgsql_php_version_from_code() {
|
||||
local code="$1"
|
||||
if [[ "$code" == *.* ]]; then
|
||||
printf '%s\n' "$code"
|
||||
elif [[ "$code" =~ ^[0-9]{2,3}$ ]]; then
|
||||
printf '%s.%s\n' "${code:0:1}" "${code:1}"
|
||||
else
|
||||
printf '%s\n' "$code"
|
||||
fi
|
||||
}
|
||||
|
||||
cb_pgsql_add_php_target() {
|
||||
local version="$1"
|
||||
local mode="$2"
|
||||
local code=""
|
||||
local idx=""
|
||||
|
||||
[[ -n "$version" ]] || return 0
|
||||
code="$(cb_pgsql_php_code_from_version "$version")"
|
||||
for idx in "${!CB_PHP_CODES[@]}"; do
|
||||
if [[ "${CB_PHP_CODES[$idx]}" == "$code" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
CB_PHP_VERSIONS+=("$version")
|
||||
CB_PHP_CODES+=("$code")
|
||||
CB_PHP_MODES+=("${mode:-php-fpm}")
|
||||
}
|
||||
|
||||
cb_pgsql_detect_webserver() {
|
||||
local cb_dir="$1"
|
||||
|
||||
CB_PHP_OPTIONS_CONF="${cb_dir}/options.conf"
|
||||
CB_PHP_WEBSERVER="apache"
|
||||
|
||||
if [[ -f "$CB_PHP_OPTIONS_CONF" ]]; then
|
||||
CB_PHP_WEBSERVER="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "webserver" || true)"
|
||||
[[ -n "$CB_PHP_WEBSERVER" ]] || CB_PHP_WEBSERVER="apache"
|
||||
fi
|
||||
}
|
||||
|
||||
cb_pgsql_collect_php_targets() {
|
||||
local cb_dir="$1"
|
||||
local slot=""
|
||||
local release=""
|
||||
local mode=""
|
||||
local default_mode=""
|
||||
|
||||
cb_pgsql_reset_php_targets
|
||||
cb_pgsql_detect_webserver "$cb_dir"
|
||||
|
||||
# CloudLinux może używać lsphp także przy webserver=apache.
|
||||
# Dlatego najpierw honorujemy phpN_mode/php1_mode z options.conf,
|
||||
# a dopiero przy ich braku stosujemy domyślny tryb wynikający z webserwera.
|
||||
case "$CB_PHP_WEBSERVER" in
|
||||
litespeed|openlitespeed) default_mode="lsphp" ;;
|
||||
*) default_mode="php-fpm" ;;
|
||||
esac
|
||||
|
||||
for slot in 1 2 3 4 5 6 7 8 9; do
|
||||
release="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php${slot}_release" || true)"
|
||||
mode="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php${slot}_mode" || true)"
|
||||
[[ -n "$mode" ]] || mode="$(cb_pgsql_get_cb_option "$CB_PHP_OPTIONS_CONF" "php1_mode" || true)"
|
||||
[[ -n "$mode" ]] || mode="$default_mode"
|
||||
case "${release,,}" in
|
||||
""|no|off) continue ;;
|
||||
esac
|
||||
case "${mode,,}" in
|
||||
no|off) continue ;;
|
||||
esac
|
||||
cb_pgsql_add_php_target "$release" "$mode"
|
||||
done
|
||||
}
|
||||
|
||||
cb_pgsql_build_file_candidates() {
|
||||
local cb_dir="$1"
|
||||
local mode="$2"
|
||||
local code="$3"
|
||||
local -n out_ref="$4"
|
||||
|
||||
out_ref=()
|
||||
if [[ "$mode" == "lsphp" ]]; then
|
||||
case "$CB_PHP_WEBSERVER" in
|
||||
litespeed)
|
||||
out_ref+=("${cb_dir}/configure/litespeed/configure.php${code}")
|
||||
;;
|
||||
openlitespeed)
|
||||
out_ref+=("${cb_dir}/configure/openlitespeed/configure.php${code}")
|
||||
;;
|
||||
esac
|
||||
out_ref+=("${cb_dir}/configure/lsphp/configure.lsphp${code}")
|
||||
out_ref+=("${cb_dir}/configure/lsphp/configure.php${code}")
|
||||
fi
|
||||
out_ref+=("${cb_dir}/configure/php/configure.php${code}")
|
||||
}
|
||||
|
||||
cb_pgsql_resolve_config_paths() {
|
||||
local cb_dir="$1"
|
||||
local version="$2"
|
||||
local mode="$3"
|
||||
local source_ref_name="$4"
|
||||
local custom_ref_name="$5"
|
||||
local code=""
|
||||
local -a candidates=()
|
||||
local candidate=""
|
||||
|
||||
code="$(cb_pgsql_php_code_from_version "$version")"
|
||||
printf -v "$source_ref_name" '%s' ""
|
||||
printf -v "$custom_ref_name" '%s' ""
|
||||
|
||||
cb_pgsql_build_file_candidates "$cb_dir" "$mode" "$code" candidates
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
if [[ -f "$candidate" ]]; then
|
||||
printf -v "$source_ref_name" '%s' "$candidate"
|
||||
printf -v "$custom_ref_name" '%s' "$(printf '%s\n' "$candidate" | sed "s#^${cb_dir}/configure/#${cb_dir}/custom/#")"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
for candidate in "${candidates[@]}"; do
|
||||
candidate="$(printf '%s\n' "$candidate" | sed "s#^${cb_dir}/configure/#${cb_dir}/custom/#")"
|
||||
if [[ -f "$candidate" ]]; then
|
||||
printf -v "$source_ref_name" '%s' "$candidate"
|
||||
printf -v "$custom_ref_name" '%s' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
cb_pgsql_insert_flags() {
|
||||
local config_file="$1"
|
||||
local pg_prefix="$2"
|
||||
local tmp_file="${config_file}.tmp"
|
||||
|
||||
sed -i '/--with-pgsql=/d; /--with-pdo-pgsql=/d' "$config_file"
|
||||
|
||||
if grep -q -- '--with-pdo-mysql' "$config_file"; then
|
||||
awk -v prefix="$pg_prefix" '
|
||||
/--with-pdo-mysql/ {
|
||||
print
|
||||
print "\t--with-pgsql=" prefix " \\"
|
||||
print "\t--with-pdo-pgsql=" prefix " \\"
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$config_file" > "$tmp_file"
|
||||
else
|
||||
awk -v prefix="$pg_prefix" '
|
||||
/^[[:space:]]*--with/ { last = NR }
|
||||
{ lines[NR] = $0 }
|
||||
END {
|
||||
if (last == 0) {
|
||||
for (i = 1; i <= NR; i++) print lines[i]
|
||||
exit
|
||||
}
|
||||
for (i = 1; i <= NR; i++) {
|
||||
print lines[i]
|
||||
if (i == last) {
|
||||
print "\t--with-pgsql=" prefix " \\"
|
||||
print "\t--with-pdo-pgsql=" prefix " \\"
|
||||
}
|
||||
}
|
||||
}
|
||||
' "$config_file" > "$tmp_file"
|
||||
fi
|
||||
|
||||
mv "$tmp_file" "$config_file"
|
||||
}
|
||||
|
||||
cb_pgsql_prepare_custom_config() {
|
||||
local cb_dir="$1"
|
||||
local version="$2"
|
||||
local mode="$3"
|
||||
local pg_prefix="$4"
|
||||
local source_conf=""
|
||||
local custom_conf=""
|
||||
local ts=""
|
||||
|
||||
if ! cb_pgsql_resolve_config_paths "$cb_dir" "$version" "$mode" source_conf custom_conf; then
|
||||
warn "Nie znaleziono configure dla PHP ${version} (tryb ${mode}, webserver ${CB_PHP_WEBSERVER})."
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$custom_conf")"
|
||||
ts="$(date +%s)"
|
||||
|
||||
if [[ "$source_conf" != "$custom_conf" && ! -f "$custom_conf" ]]; then
|
||||
cp -p "$source_conf" "$custom_conf"
|
||||
elif [[ -f "$custom_conf" ]]; then
|
||||
cp -p "$custom_conf" "${custom_conf}.bak.${ts}"
|
||||
fi
|
||||
|
||||
cb_pgsql_insert_flags "$custom_conf" "$pg_prefix"
|
||||
chmod +x "$custom_conf" || true
|
||||
info "Przygotowano configure dla PHP ${version}: ${custom_conf}"
|
||||
return 0
|
||||
}
|
||||
|
||||
cb_pgsql_configure_runtime_libpq() {
|
||||
local pg_prefix="$1"
|
||||
local pg_libdir="${pg_prefix}/lib"
|
||||
local libpq_so="${pg_libdir}/libpq.so.5"
|
||||
local ld_conf="/etc/ld.so.conf.d/00-da-postgresql-libpq.conf"
|
||||
local old_ld_conf="/etc/ld.so.conf.d/da-postgresql-libpq.conf"
|
||||
local preferred=""
|
||||
|
||||
[[ -d "$pg_libdir" ]] || die "Nie znaleziono katalogu bibliotek PostgreSQL: ${pg_libdir}"
|
||||
[[ -r "$libpq_so" ]] || die "Nie znaleziono biblioteki libpq: ${libpq_so}"
|
||||
|
||||
printf '%s\n' "$pg_libdir" > "$ld_conf"
|
||||
chmod 644 "$ld_conf"
|
||||
[[ "$old_ld_conf" == "$ld_conf" ]] || rm -f "$old_ld_conf"
|
||||
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
ldconfig || die "Nie udało się odświeżyć cache bibliotek przez ldconfig."
|
||||
preferred="$(ldconfig -p 2>/dev/null | awk '/libpq\.so\.5/{print $NF; exit}')"
|
||||
if [[ -n "$preferred" && "$preferred" != "$libpq_so" ]]; then
|
||||
warn "Domyślna libpq to ${preferred}; wymuszę właściwą bibliotekę przez LD_PRELOAD podczas kompilacji PHP."
|
||||
fi
|
||||
else
|
||||
warn "Brak ldconfig. Kontynuuję z LD_LIBRARY_PATH."
|
||||
fi
|
||||
|
||||
DA_PG_RUNTIME_LIBDIR="$pg_libdir"
|
||||
DA_PG_RUNTIME_LIBPQ="$libpq_so"
|
||||
info "Skonfigurowano bibliotekę runtime PostgreSQL: ${pg_libdir}"
|
||||
}
|
||||
|
||||
cb_pgsql_detect_pg_prefix() {
|
||||
local candidate=""
|
||||
|
||||
CB_PG_PREFIX=""
|
||||
CB_PG_VERSION=""
|
||||
CB_PG_CONFIG=""
|
||||
|
||||
if [[ -x /usr/local/bin/pg_config ]]; then
|
||||
candidate="$(readlink -f /usr/local/bin/pg_config 2>/dev/null || true)"
|
||||
if [[ -x "$candidate" ]]; then
|
||||
CB_PG_CONFIG="$candidate"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$CB_PG_CONFIG" ]]; then
|
||||
candidate="$(ls -1 /usr/pgsql-*/bin/pg_config 2>/dev/null | sort -V | tail -n1 || true)"
|
||||
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
||||
CB_PG_CONFIG="$candidate"
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ -n "$CB_PG_CONFIG" ]] || return 1
|
||||
|
||||
CB_PG_PREFIX="$(cd "$(dirname "$CB_PG_CONFIG")/.." && pwd)"
|
||||
if [[ "$CB_PG_PREFIX" =~ /usr/pgsql-([0-9]+)$ ]]; then
|
||||
CB_PG_VERSION="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
[[ -n "$CB_PG_PREFIX" ]] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
cb_pgsql_find_php_bin_for_version() {
|
||||
local version="$1"
|
||||
local code=""
|
||||
local candidate=""
|
||||
|
||||
code="$(cb_pgsql_php_code_from_version "$version")"
|
||||
for candidate in \
|
||||
"/usr/local/php${code}/bin/php" \
|
||||
"/usr/local/php${code}/bin/lsphp" \
|
||||
"/usr/local/lsphp${code}/bin/lsphp" \
|
||||
"/usr/local/php${code}/bin/php-cgi"; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
cb_pgsql_probe_php_extensions() {
|
||||
local php_bin="$1"
|
||||
local pgsql_ref_name="$2"
|
||||
local pdo_ref_name="$3"
|
||||
local modules=""
|
||||
local modules_lc=""
|
||||
local probe_pgsql_status="NIE"
|
||||
local probe_pdo_status="NIE"
|
||||
|
||||
modules="$("$php_bin" -m 2>/dev/null || true)"
|
||||
if [[ -z "$modules" ]]; then
|
||||
probe_pgsql_status="BŁĄD"
|
||||
probe_pdo_status="BŁĄD"
|
||||
printf -v "$pgsql_ref_name" '%s' "$probe_pgsql_status"
|
||||
printf -v "$pdo_ref_name" '%s' "$probe_pdo_status"
|
||||
return 1
|
||||
fi
|
||||
|
||||
modules_lc="$(printf '%s\n' "$modules" | tr '[:upper:]' '[:lower:]')"
|
||||
if printf '%s\n' "$modules_lc" | grep -qx 'pgsql'; then
|
||||
probe_pgsql_status="OK"
|
||||
fi
|
||||
if printf '%s\n' "$modules_lc" | grep -qx 'pdo_pgsql'; then
|
||||
probe_pdo_status="OK"
|
||||
fi
|
||||
|
||||
printf -v "$pgsql_ref_name" '%s' "$probe_pgsql_status"
|
||||
printf -v "$pdo_ref_name" '%s' "$probe_pdo_status"
|
||||
return 0
|
||||
}
|
||||
|
||||
cb_pgsql_render_extensions_table() {
|
||||
local found=0
|
||||
local idx=""
|
||||
local version=""
|
||||
local php_bin=""
|
||||
local pgsql_status=""
|
||||
local pdo_pgsql_status=""
|
||||
|
||||
echo "+------------+-------+-----------+"
|
||||
printf "| %-10s | %-5s | %-9s |\n" "Wersja PHP" "PGSQL" "PDO_PGSQL"
|
||||
echo "+------------+-------+-----------+"
|
||||
|
||||
for idx in "${!CB_PHP_VERSIONS[@]}"; do
|
||||
version="${CB_PHP_VERSIONS[$idx]}"
|
||||
php_bin="$(cb_pgsql_find_php_bin_for_version "$version" || true)"
|
||||
if [[ -z "$php_bin" ]]; then
|
||||
printf "| %-10s | %-5s | %-9s |\n" "PHP ${version}" "BRAK" "BRAK"
|
||||
found=1
|
||||
continue
|
||||
fi
|
||||
|
||||
cb_pgsql_probe_php_extensions "$php_bin" pgsql_status pdo_pgsql_status >/dev/null 2>&1 || true
|
||||
printf "| %-10s | %-5s | %-9s |\n" "PHP ${version}" "$pgsql_status" "$pdo_pgsql_status"
|
||||
found=1
|
||||
done
|
||||
|
||||
if [[ $found -eq 1 ]]; then
|
||||
echo "+------------+-------+-----------+"
|
||||
else
|
||||
warn "Nie znaleziono wersji PHP do raportu."
|
||||
fi
|
||||
}
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "Skrypt musi być uruchomiony jako root."
|
||||
|
||||
LOGFILE="/var/log/recompile_php_$(date +%Y%m%d_%H%M%S).log"
|
||||
exec 1> >(tee >(sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' >> "$LOGFILE")) 2>&1
|
||||
info "Pełny log zapisany w: $LOGFILE"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CB_DIR="/usr/local/directadmin/custombuild"
|
||||
|
||||
[[ -x "${CB_DIR}/build" ]] || die "Nie znaleziono DirectAdmin CustomBuild: ${CB_DIR}/build"
|
||||
|
||||
sekcja "Wykrywanie środowiska DirectAdmin"
|
||||
|
||||
cb_pgsql_collect_php_targets "$CB_DIR"
|
||||
[[ ${#CB_PHP_VERSIONS[@]} -gt 0 ]] || die "Nie wykryto aktywnych wersji PHP w DirectAdmin CustomBuild."
|
||||
|
||||
info "Web serwer: ${CB_PHP_WEBSERVER}"
|
||||
info "Wykryte wersje PHP: $(IFS=', '; echo "${CB_PHP_VERSIONS[*]}")"
|
||||
|
||||
sekcja "Wykrywanie PostgreSQL dla kompilacji PHP"
|
||||
|
||||
cb_pgsql_detect_pg_prefix || die "Nie znaleziono pg_config. Zainstaluj PostgreSQL lub ustaw /usr/local/bin/pg_config."
|
||||
[[ -n "$CB_PG_VERSION" ]] || die "Nie udało się ustalić wersji PostgreSQL z ${CB_PG_CONFIG}."
|
||||
|
||||
info "Wykryty prefix PostgreSQL: ${CB_PG_PREFIX}"
|
||||
info "Wykryta wersja PostgreSQL: ${CB_PG_VERSION}"
|
||||
|
||||
dnf install -y "postgresql${CB_PG_VERSION}-devel" \
|
||||
"postgresql${CB_PG_VERSION}-libs" \
|
||||
|| die "Nie udało się zainstalować postgresql${CB_PG_VERSION}-devel/libs."
|
||||
|
||||
if [[ -x "$CB_PG_CONFIG" ]]; then
|
||||
if [[ -e /usr/local/bin/pg_config && ! -L /usr/local/bin/pg_config ]]; then
|
||||
warn "/usr/local/bin/pg_config istnieje i nie jest dowiązaniem – pomijam aktualizację symlinku."
|
||||
else
|
||||
ln -sf "$CB_PG_CONFIG" /usr/local/bin/pg_config
|
||||
info "Zaktualizowano symlink: /usr/local/bin/pg_config -> ${CB_PG_CONFIG}"
|
||||
fi
|
||||
fi
|
||||
|
||||
cb_pgsql_configure_runtime_libpq "$CB_PG_PREFIX"
|
||||
|
||||
sekcja "Analiza rozszerzeń PHP"
|
||||
|
||||
declare -a REBUILD_VERSIONS=()
|
||||
declare -a REBUILD_MODES=()
|
||||
for idx in "${!CB_PHP_VERSIONS[@]}"; do
|
||||
php_version="${CB_PHP_VERSIONS[$idx]}"
|
||||
php_mode="${CB_PHP_MODES[$idx]}"
|
||||
php_bin="$(cb_pgsql_find_php_bin_for_version "$php_version" || true)"
|
||||
pgsql_status="BRAK"
|
||||
pdo_pgsql_status="BRAK"
|
||||
|
||||
if [[ -z "$php_bin" ]]; then
|
||||
warn "Nie znaleziono binarki PHP dla wersji ${php_version}. Dodaję do rekompilacji."
|
||||
REBUILD_VERSIONS+=("$php_version")
|
||||
REBUILD_MODES+=("$php_mode")
|
||||
continue
|
||||
fi
|
||||
|
||||
cb_pgsql_probe_php_extensions "$php_bin" pgsql_status pdo_pgsql_status >/dev/null 2>&1 || true
|
||||
if [[ "$pgsql_status" != "OK" || "$pdo_pgsql_status" != "OK" ]]; then
|
||||
info "PHP ${php_version}: brakujące rozszerzenia (PGSQL=${pgsql_status}, PDO_PGSQL=${pdo_pgsql_status})"
|
||||
REBUILD_VERSIONS+=("$php_version")
|
||||
REBUILD_MODES+=("$php_mode")
|
||||
else
|
||||
info "PHP ${php_version}: rozszerzenia pgsql i pdo_pgsql są już dostępne."
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#REBUILD_VERSIONS[@]} -eq 0 ]]; then
|
||||
sekcja "Tabela rozszerzeń PHP"
|
||||
cb_pgsql_render_extensions_table
|
||||
info "Pełny log: ${LOGFILE}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sekcja "Przygotowanie configure.phpXX"
|
||||
|
||||
prepared_any=0
|
||||
for idx in "${!REBUILD_VERSIONS[@]}"; do
|
||||
if cb_pgsql_prepare_custom_config "$CB_DIR" "${REBUILD_VERSIONS[$idx]}" "${REBUILD_MODES[$idx]}" "$CB_PG_PREFIX"; then
|
||||
prepared_any=1
|
||||
fi
|
||||
done
|
||||
|
||||
[[ "$prepared_any" -eq 1 ]] || die "Nie udało się przygotować żadnego pliku configure dla rekompilacji PHP."
|
||||
|
||||
sekcja "Rekompilacja PHP"
|
||||
|
||||
cd "$CB_DIR"
|
||||
for php_version in "${REBUILD_VERSIONS[@]}"; do
|
||||
info "Uruchamiam: ./build php ${php_version}"
|
||||
LD_LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" \
|
||||
LD_PRELOAD="${DA_PG_RUNTIME_LIBPQ}${LD_PRELOAD:+:${LD_PRELOAD}}" \
|
||||
LIBRARY_PATH="${DA_PG_RUNTIME_LIBDIR}${LIBRARY_PATH:+:${LIBRARY_PATH}}" \
|
||||
PKG_CONFIG_PATH="${DA_PG_RUNTIME_LIBDIR}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}" \
|
||||
LDFLAGS="-L${DA_PG_RUNTIME_LIBDIR} ${LDFLAGS:-}" \
|
||||
CPPFLAGS="-I${CB_PG_PREFIX}/include ${CPPFLAGS:-}" \
|
||||
./build php "$php_version" \
|
||||
|| die "Rekompilacja PHP ${php_version} nie powiodła się."
|
||||
done
|
||||
|
||||
./build rewrite_confs \
|
||||
|| warn "rewrite_confs zakończył się błędem – sprawdź konfigurację ręcznie."
|
||||
cd - >/dev/null
|
||||
|
||||
sekcja "Tabela rozszerzeń PHP po rekompilacji"
|
||||
|
||||
cb_pgsql_collect_php_targets "$CB_DIR"
|
||||
cb_pgsql_render_extensions_table
|
||||
|
||||
info "Pełny log: ${LOGFILE}"
|
||||
exit 0
|
||||
Executable
+211
@@ -0,0 +1,211 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# restore_all.sh
|
||||
# Przywracanie wszystkich baz PostgreSQL z katalogu backupu
|
||||
# wykonanego przez postgres_backup.sh
|
||||
# =============================================================================
|
||||
|
||||
CREDENTIALS_FILE="/usr/local/directadmin/conf/postgresql.conf"
|
||||
|
||||
# Kolory tylko na terminalu
|
||||
if [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BOLD='\033[1m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
else
|
||||
RED=''; GREEN=''; YELLOW=''; BOLD=''; CYAN=''; NC=''
|
||||
fi
|
||||
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
info() { echo -e " $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
die() { echo -e "${RED}[BŁĄD]${NC} $*" >&2; exit 1; }
|
||||
sekcja() { echo -e "\n${BOLD}${CYAN}--- $* ---${NC}\n"; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sposób użycia
|
||||
# ---------------------------------------------------------------------------
|
||||
usage() {
|
||||
cat <<EOF
|
||||
|
||||
Użycie:
|
||||
$(basename "$0") <katalog_backupu> [--restore-users]
|
||||
|
||||
Opis:
|
||||
Przywraca wszystkie bazy danych z podanego katalogu backupu.
|
||||
Pliki w katalogu powinny być wykonane przez postgres_backup.sh.
|
||||
|
||||
Flagi:
|
||||
--restore-users Przed przywracaniem baz odtworzy użytkowników, role
|
||||
i ich hasła z pliku _globals.sql[.gz] (jeśli istnieje).
|
||||
WYMAGANE do pełnego disaster recovery.
|
||||
|
||||
Kolejność przywracania:
|
||||
1. _globals.sql[.gz] – użytkownicy i role (tylko z --restore-users)
|
||||
2. *.sql[.gz] – bazy danych (alfabetycznie, z pominięciem _globals)
|
||||
|
||||
Przykłady:
|
||||
$(basename "$0") /home/admin/postgres_backup/19-02-2026
|
||||
$(basename "$0") /home/admin/postgres_backup/19-02-2026 --restore-users
|
||||
|
||||
Uwaga:
|
||||
Każda przywracana baza zostanie usunięta i odtworzona od nowa.
|
||||
Plik poświadczeń: ${CREDENTIALS_FILE}
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parsowanie argumentów
|
||||
# ---------------------------------------------------------------------------
|
||||
BACKUP_DIR=""
|
||||
RESTORE_USERS=false
|
||||
|
||||
for ARG in "$@"; do
|
||||
case "$ARG" in
|
||||
--restore-users) RESTORE_USERS=true ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
-*) die "Nieznana flaga: ${ARG}. Użyj --help aby zobaczyć pomoc." ;;
|
||||
*)
|
||||
[[ -z "$BACKUP_DIR" ]] || die "Podano więcej niż jeden katalog. Użyj --help aby zobaczyć pomoc."
|
||||
BACKUP_DIR="$ARG"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$BACKUP_DIR" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[[ -d "$BACKUP_DIR" ]] || die "Katalog nie istnieje: ${BACKUP_DIR}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Poświadczenia
|
||||
# ---------------------------------------------------------------------------
|
||||
[[ -f "$CREDENTIALS_FILE" ]] \
|
||||
|| die "Brak pliku poświadczeń: ${CREDENTIALS_FILE}"
|
||||
|
||||
_perms=$(stat -c '%a' "$CREDENTIALS_FILE")
|
||||
[[ "$_perms" == "600" || "$_perms" == "400" ]] \
|
||||
|| die "Zbyt otwarte uprawnienia na ${CREDENTIALS_FILE} (${_perms}). Wymagane: 600 lub 400."
|
||||
|
||||
_line=$(head -1 "$CREDENTIALS_FILE")
|
||||
PGHOST=$(echo "$_line" | cut -d: -f1)
|
||||
PGPORT=$(echo "$_line" | cut -d: -f2)
|
||||
PGUSER=$(echo "$_line" | cut -d: -f4)
|
||||
|
||||
[[ -n "$PGHOST" && -n "$PGPORT" && -n "$PGUSER" ]] \
|
||||
|| die "Nieprawidłowy format pliku poświadczeń. Oczekiwany: host:port:database:user:password"
|
||||
|
||||
export PGPASSFILE="$CREDENTIALS_FILE"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Funkcja przywracająca jeden plik
|
||||
# ---------------------------------------------------------------------------
|
||||
restore_file() {
|
||||
local file="$1"
|
||||
local init_db="$2"
|
||||
|
||||
case "$file" in
|
||||
*.sql.gz)
|
||||
gunzip -c "$file" \
|
||||
| psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password -d "$init_db"
|
||||
return ${PIPESTATUS[1]}
|
||||
;;
|
||||
*.sql)
|
||||
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" \
|
||||
--no-password -d "$init_db" -f "$file"
|
||||
return $?
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Start
|
||||
# ---------------------------------------------------------------------------
|
||||
echo ""
|
||||
info "Katalog: ${BACKUP_DIR}"
|
||||
info "Serwer: ${PGHOST}:${PGPORT} Użytkownik: ${PGUSER}"
|
||||
info "Restore users: ${RESTORE_USERS}"
|
||||
echo ""
|
||||
|
||||
ERRORS=0
|
||||
RESTORED=0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Globals (użytkownicy, role) – tylko z flagą --restore-users
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "$RESTORE_USERS" == "true" ]]; then
|
||||
sekcja "Przywracanie użytkowników i ról (_globals)"
|
||||
|
||||
GLOBALS_FILE=""
|
||||
for _f in "${BACKUP_DIR}/_globals.sql.gz" "${BACKUP_DIR}/_globals.sql"; do
|
||||
[[ -f "$_f" ]] && GLOBALS_FILE="$_f" && break
|
||||
done
|
||||
|
||||
if [[ -z "$GLOBALS_FILE" ]]; then
|
||||
warn "Nie znaleziono pliku _globals.sql[.gz] w ${BACKUP_DIR} – pomijam użytkowników."
|
||||
else
|
||||
info "Plik: ${GLOBALS_FILE##*/}"
|
||||
# Globals przywracamy do postgres; DROP/CREATE ROLE może generować ostrzeżenia
|
||||
# jeśli role już istnieją – to normalne, kontynuujemy
|
||||
if restore_file "$GLOBALS_FILE" "postgres"; then
|
||||
ok "Użytkownicy i role przywrócone."
|
||||
RESTORED=$((RESTORED + 1))
|
||||
else
|
||||
warn "Przywracanie globals zakończone z ostrzeżeniami – sprawdź output powyżej."
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Bazy danych (pliki *.sql i *.sql.gz z pominięciem _globals.*)
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Przywracanie baz danych"
|
||||
|
||||
# Zbierz pliki, posortuj alfabetycznie
|
||||
mapfile -t DB_FILES < <(
|
||||
find "$BACKUP_DIR" -maxdepth 1 -type f \( -name '*.sql' -o -name '*.sql.gz' \) \
|
||||
! -name '_globals.*' \
|
||||
| sort
|
||||
)
|
||||
|
||||
if [[ ${#DB_FILES[@]} -eq 0 ]]; then
|
||||
warn "Nie znaleziono plików .sql ani .sql.gz w katalogu ${BACKUP_DIR} (poza _globals)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for FILE in "${DB_FILES[@]}"; do
|
||||
DB_NAME=$(basename "$FILE" | sed 's/\.sql\.gz$//; s/\.sql$//')
|
||||
|
||||
# Baza 'postgres' nie może być przywracana przez siebie (DROP DATABASE blokuje aktywne połączenie)
|
||||
if [[ "$DB_NAME" == "postgres" ]]; then
|
||||
INIT_DB="template1"
|
||||
else
|
||||
INIT_DB="postgres"
|
||||
fi
|
||||
|
||||
info "→ ${DB_NAME} (${FILE##*/})"
|
||||
|
||||
if restore_file "$FILE" "$INIT_DB"; then
|
||||
ok "${DB_NAME}: przywrócona."
|
||||
RESTORED=$((RESTORED + 1))
|
||||
else
|
||||
warn "${DB_NAME}: przywracanie zakończone z błędem lub ostrzeżeniami."
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Podsumowanie
|
||||
# ---------------------------------------------------------------------------
|
||||
sekcja "Podsumowanie"
|
||||
info "Przywrócono: ${RESTORED} | Błędy: ${ERRORS}"
|
||||
echo ""
|
||||
|
||||
[[ "$ERRORS" -eq 0 ]] || exit 1
|
||||
exit 0
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf"
|
||||
SUDOERS_FILE="/etc/sudoers.d/directadmin-postgresql-plugin"
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
if [ -f "$CPIF" ]; then
|
||||
sed -i '/^postgresql_enabled=/d' "$CPIF" || true
|
||||
sed -i '/^postgresql=/d' "$CPIF" || true
|
||||
sed -i '/^upostgresql=/d' "$CPIF" || true
|
||||
sed -i '/^postgresql_max_databases=/d' "$CPIF" || true
|
||||
sed -i '/^postgresql_unlimited=/d' "$CPIF" || true
|
||||
sed -i '/^upostgresql_max_databases=/d' "$CPIF" || true
|
||||
chown diradmin:diradmin "$CPIF" 2>/dev/null || true
|
||||
chmod 640 "$CPIF" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$SUDOERS_FILE" || true
|
||||
else
|
||||
echo "postgresql: warning: uruchom jako root, aby usunąć wpisy z custom_package_items i sudoers" >&2
|
||||
fi
|
||||
|
||||
if [ -f "$PLUGIN_DIR/plugin.conf" ]; then
|
||||
sed -i 's/^active=.*/active=no/' "$PLUGIN_DIR/plugin.conf" || true
|
||||
sed -i 's/^installed=.*/installed=no/' "$PLUGIN_DIR/plugin.conf" || true
|
||||
fi
|
||||
|
||||
echo "da-postgresql: deaktywowany."
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
"$(cd "$(dirname "$0")" && pwd)/install.sh"
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DATA_DIR="$PLUGIN_DIR/data"
|
||||
JOB_DIR="$DATA_DIR/jobs/pending"
|
||||
PROCESSING_DIR="$DATA_DIR/jobs/processing"
|
||||
DONE_DIR="$DATA_DIR/jobs/done"
|
||||
WORKER_LOCK_DIR="$DATA_DIR/worker.lock"
|
||||
DB_LOCK_DIR="$DATA_DIR/lock"
|
||||
PID_DIR="$DATA_DIR/pids"
|
||||
CANCEL_DIR="$DATA_DIR/cancel"
|
||||
|
||||
mkdir -p "$JOB_DIR" "$PROCESSING_DIR" "$DONE_DIR" "$PID_DIR" "$CANCEL_DIR" "$DB_LOCK_DIR"
|
||||
|
||||
if ! mkdir "$WORKER_LOCK_DIR" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
rmdir "$WORKER_LOCK_DIR" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
db_lock_path_for_db() {
|
||||
local db_name="$1"
|
||||
local safe
|
||||
safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')"
|
||||
printf '%s/%s.lock' "$DB_LOCK_DIR" "$safe"
|
||||
}
|
||||
|
||||
release_db_lock_later() {
|
||||
local lock_path="$1"
|
||||
if [ -z "$lock_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
(
|
||||
sleep 60
|
||||
rm -f "$lock_path" 2>/dev/null || true
|
||||
) >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
while true; do
|
||||
JOB_FILE="$(ls -1 "$JOB_DIR"/*.env 2>/dev/null | head -n 1 || true)"
|
||||
if [ -z "${JOB_FILE:-}" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
BASE_NAME="$(basename "$JOB_FILE")"
|
||||
RUN_FILE="$PROCESSING_DIR/$BASE_NAME"
|
||||
mv "$JOB_FILE" "$RUN_FILE"
|
||||
JOB_ID="${BASE_NAME%.env}"
|
||||
PID_FILE="$PID_DIR/${JOB_ID}.pid"
|
||||
|
||||
JOB_TYPE="restore"
|
||||
DB_NAME=""
|
||||
DB_LOCK_FILE=""
|
||||
# shellcheck disable=SC1090
|
||||
source "$RUN_FILE" || true
|
||||
JOB_TYPE="${JOB_TYPE:-restore}"
|
||||
DB_NAME="${DB_NAME:-}"
|
||||
DB_LOCK_FILE="${DB_LOCK_FILE:-}"
|
||||
|
||||
LOCK_PATH=""
|
||||
if [ -n "$DB_LOCK_FILE" ]; then
|
||||
case "$DB_LOCK_FILE" in
|
||||
"$DB_LOCK_DIR"/*.lock) LOCK_PATH="$DB_LOCK_FILE" ;;
|
||||
*) LOCK_PATH="" ;;
|
||||
esac
|
||||
fi
|
||||
if [ -z "$LOCK_PATH" ] && [ -n "$DB_NAME" ]; then
|
||||
LOCK_PATH="$(db_lock_path_for_db "$DB_NAME")"
|
||||
fi
|
||||
|
||||
RUNNER="$PLUGIN_DIR/scripts/restore_job.sh"
|
||||
if [ "$JOB_TYPE" = "backup" ]; then
|
||||
RUNNER="$PLUGIN_DIR/scripts/backup_job.sh"
|
||||
fi
|
||||
|
||||
set +e
|
||||
"$RUNNER" "$RUN_FILE" &
|
||||
RUN_PID=$!
|
||||
echo "PID=${RUN_PID}" >> "$RUN_FILE"
|
||||
echo "${RUN_PID}" > "$PID_FILE"
|
||||
wait "$RUN_PID"
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
CANCEL_FLAG="${CANCEL_DIR}/${JOB_ID}.flag"
|
||||
if [ -f "$CANCEL_FLAG" ]; then
|
||||
rm -f "$CANCEL_FLAG"
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.cancel"
|
||||
elif [ "$EXIT_CODE" -eq 0 ]; then
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.ok"
|
||||
else
|
||||
mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.fail"
|
||||
fi
|
||||
|
||||
release_db_lock_later "$LOCK_PATH"
|
||||
done
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user