Import postgresql plugin

This commit is contained in:
Marek Miklewicz
2026-07-04 16:00:04 +02:00
commit ddc971567f
74 changed files with 18735 additions and 0 deletions
+704
View File
@@ -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}"
+127
View File
@@ -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
+636
View File
@@ -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
+226
View File
@@ -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
+786
View File
@@ -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
+1371
View File
File diff suppressed because it is too large Load Diff
+520
View File
@@ -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
+211
View File
@@ -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