Import alt-mysql plugin

This commit is contained in:
Marek Miklewicz
2026-07-04 15:48:19 +02:00
commit d71fcf9ace
101 changed files with 18635 additions and 0 deletions
+486
View File
@@ -0,0 +1,486 @@
#!/bin/bash
set -euo pipefail
PHPMYADMIN_PRIVATE_DIR="${PHPMYADMIN_PRIVATE_DIR:-${PHPMYADMIN_DIR:-/var/www/html/alt-mysql-phpmyadmin}}"
PHPMYADMIN_VERSION="${PHPMYADMIN_VERSION:-5.2.3}"
PHPMYADMIN_TARBALL_URL="${PHPMYADMIN_TARBALL_URL:-https://files.phpmyadmin.net/phpMyAdmin/5.2.3/phpMyAdmin-5.2.3-all-languages.tar.gz}"
PHPMYADMIN_DOWNLOAD_DIR="${PHPMYADMIN_DOWNLOAD_DIR:-/usr/local/src}"
PHPMYADMIN_SOURCE_DIR="${PHPMYADMIN_SOURCE_DIR:-}"
RUNTIME_DIR="${PHPMYADMIN_PRIVATE_DIR}/runtime"
TICKETS_DIR="${RUNTIME_DIR}/tickets"
CONFIG_INC="${PHPMYADMIN_PRIVATE_DIR}/config.inc.php"
SIGNON_SCRIPT="${PHPMYADMIN_PRIVATE_DIR}/da_signon.php"
SIGNON_URL_PAGE="${PHPMYADMIN_PRIVATE_DIR}/da_login.php"
BLOWFISH_SECRET_FILE="${RUNTIME_DIR}/blowfish_secret"
if [ "${PHPMYADMIN_INSTALL_ASSUME_ROOT:-0}" != "1" ] && [ "${EUID:-$(id -u)}" -ne 0 ]; then
echo "Skrypt musi być uruchomiony jako root." >&2
exit 1
fi
log() {
printf 'phpMyAdmin alt-mysql: %s\n' "$*"
}
die() {
printf 'phpMyAdmin alt-mysql: %s\n' "$*" >&2
exit 1
}
install_private_copy_from_source() {
local source_dir="$1"
[ -d "$source_dir" ] || die "Katalog źródłowy phpMyAdmin nie istnieje: $source_dir"
[ -f "$source_dir/index.php" ] || die "Katalog źródłowy nie wygląda jak phpMyAdmin: $source_dir"
mkdir -p "$PHPMYADMIN_PRIVATE_DIR"
cp -a "$source_dir"/. "$PHPMYADMIN_PRIVATE_DIR"/
}
download_private_copy() {
local tarball extracted tmp_dir
command -v curl >/dev/null 2>&1 || die "Brak curl; ustaw PHPMYADMIN_SOURCE_DIR albo doinstaluj curl."
command -v tar >/dev/null 2>&1 || die "Brak tar."
mkdir -p "$PHPMYADMIN_DOWNLOAD_DIR"
tarball="$PHPMYADMIN_DOWNLOAD_DIR/phpMyAdmin-${PHPMYADMIN_VERSION}-all-languages.tar.gz"
if [ ! -s "$tarball" ]; then
log "Pobieram prywatny phpMyAdmin ${PHPMYADMIN_VERSION}."
curl -fL --retry 3 --connect-timeout 20 -o "$tarball" "$PHPMYADMIN_TARBALL_URL"
fi
tmp_dir="$(mktemp -d)"
tar -xzf "$tarball" -C "$tmp_dir"
extracted="$(find "$tmp_dir" -mindepth 1 -maxdepth 1 -type d -name 'phpMyAdmin-*' -print -quit)"
[ -n "$extracted" ] || {
rm -rf "$tmp_dir"
die "Tarball phpMyAdmin nie zawiera oczekiwanego katalogu."
}
install_private_copy_from_source "$extracted"
rm -rf "$tmp_dir"
}
ensure_private_copy() {
if [ -f "$PHPMYADMIN_PRIVATE_DIR/index.php" ]; then
return 0
fi
rm -rf "$PHPMYADMIN_PRIVATE_DIR"
if [ -n "$PHPMYADMIN_SOURCE_DIR" ]; then
install_private_copy_from_source "$PHPMYADMIN_SOURCE_DIR"
else
download_private_copy
fi
}
generate_secret() {
if [ -r "$BLOWFISH_SECRET_FILE" ]; then
local existing
existing="$(tr -d '\r\n' < "$BLOWFISH_SECRET_FILE")"
if [ -n "$existing" ]; then
printf '%s\n' "$existing"
return 0
fi
fi
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 32
return 0
fi
tr -dc 'a-f0-9' </dev/urandom | head -c 64
printf '\n'
}
write_private_config() {
local secret
mkdir -p "$RUNTIME_DIR" "$TICKETS_DIR"
secret="$(generate_secret)"
printf '%s\n' "$secret" > "$BLOWFISH_SECRET_FILE"
chmod 600 "$BLOWFISH_SECRET_FILE" 2>/dev/null || true
cat > "$RUNTIME_DIR/.htaccess" <<'EOF'
Require all denied
Deny from all
EOF
cat > "$TICKETS_DIR/.htaccess" <<'EOF'
Require all denied
Deny from all
EOF
printf '%s\n' '<!doctype html><title>Forbidden</title>' > "$RUNTIME_DIR/index.html"
printf '%s\n' '<!doctype html><title>Forbidden</title>' > "$TICKETS_DIR/index.html"
cat > "$CONFIG_INC" <<'PHP'
<?php
declare(strict_types=1);
const DA_MYSQL_PMA_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
const DA_MYSQL_PMA_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
const DA_MYSQL_PMA_BLOWFISH_SECRET_FILE = __DIR__ . '/runtime/blowfish_secret';
function da_mysql_phpmyadmin_runtime_config(): array
{
if (!is_readable(DA_MYSQL_PMA_RUNTIME_CONFIG)) {
return [];
}
$raw = file_get_contents(DA_MYSQL_PMA_RUNTIME_CONFIG);
if (!is_string($raw) || $raw === '') {
return [];
}
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
}
function da_mysql_phpmyadmin_parse_conf(string $path): array
{
$data = [
'host' => '127.0.0.1',
'port' => '33033',
'socket' => '',
];
if (!is_readable($path)) {
return $data;
}
$lines = file($path, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
return $data;
}
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#' || strpos($line, '=') === false) {
continue;
}
[$key, $value] = explode('=', $line, 2);
$key = strtolower(trim($key));
$value = trim(trim($value), "\"'");
if ($key === 'host' && $value !== '') {
$data['host'] = $value;
} elseif (($key === 'port' || $key === 'mysql_port') && ctype_digit($value)) {
$data['port'] = $value;
} elseif (($key === 'socket' || $key === 'mysql_socket') && $value !== '') {
$data['socket'] = $value;
}
}
if ($data['host'] === '' || $data['host'] === 'localhost') {
$data['host'] = '127.0.0.1';
}
return $data;
}
function da_mysql_phpmyadmin_blowfish_secret(): string
{
if (is_readable(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE)) {
$secret = trim((string)file_get_contents(DA_MYSQL_PMA_BLOWFISH_SECRET_FILE));
if ($secret !== '') {
return $secret;
}
}
return 'alt-mysql-phpmyadmin-secret-change-me';
}
function da_mysql_phpmyadmin_url_path(array $runtimeConfig): string
{
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
if ($path === '') {
return '/alt-mysql-phpmyadmin';
}
if ($path[0] !== '/') {
$path = '/' . $path;
}
$path = rtrim($path, '/');
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
}
function da_mysql_phpmyadmin_public_base_url(array $runtimeConfig): string
{
$configured = trim((string)($runtimeConfig['phpmyadmin_public_base_url'] ?? ''));
if (preg_match('#^https?://#i', $configured) === 1) {
return rtrim($configured, '/');
}
$scheme = 'http';
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
if ($https !== '' && $https !== 'off' && $https !== '0') {
$scheme = 'https';
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
$scheme = 'https';
} elseif (strtolower((string)($_SERVER['REQUEST_SCHEME'] ?? '')) === 'https') {
$scheme = 'https';
}
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
if ($host === '') {
$host = trim((string)($_SERVER['SERVER_NAME'] ?? 'localhost'));
}
if ($host === '') {
$host = 'localhost';
}
return $scheme . '://' . $host;
}
function da_mysql_phpmyadmin_signon_url(array $runtimeConfig): string
{
return da_mysql_phpmyadmin_public_base_url($runtimeConfig)
. da_mysql_phpmyadmin_url_path($runtimeConfig)
. '/da_login.php';
}
$runtimeConfig = da_mysql_phpmyadmin_runtime_config();
$credentialsFile = (string)($runtimeConfig['credentials_file'] ?? DA_MYSQL_PMA_DEFAULT_CONF);
if ($credentialsFile === '') {
$credentialsFile = DA_MYSQL_PMA_DEFAULT_CONF;
}
$daMysqlServer = da_mysql_phpmyadmin_parse_conf($credentialsFile);
$cfg = [];
$cfg['blowfish_secret'] = da_mysql_phpmyadmin_blowfish_secret();
$cfg['TempDir'] = __DIR__ . '/runtime/tmp';
$cfg['LoginCookieValidity'] = 900;
$i = 0;
$i++;
$cfg['Servers'][$i]['host'] = $daMysqlServer['host'];
$cfg['Servers'][$i]['port'] = $daMysqlServer['port'];
$cfg['Servers'][$i]['socket'] = $daMysqlServer['host'] === 'localhost' ? $daMysqlServer['socket'] : '';
$cfg['Servers'][$i]['auth_type'] = 'signon';
$cfg['Servers'][$i]['SignonScript'] = __DIR__ . '/da_signon.php';
$cfg['Servers'][$i]['SignonSession'] = 'DA_MYSQL_PHPMYADMIN';
$cfg['Servers'][$i]['SignonURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
$cfg['Servers'][$i]['LogoutURL'] = da_mysql_phpmyadmin_signon_url($runtimeConfig);
$cfg['Servers'][$i]['AllowNoPassword'] = false;
$cfg['Servers'][$i]['verbose'] = 'DirectAdmin alt-mysql';
$cfg['Servers'][$i]['only_db'] = '';
$cfg['ServerDefault'] = 1;
$cfg['AllowArbitraryServer'] = false;
PHP
cat > "$SIGNON_URL_PAGE" <<'PHP'
<?php
declare(strict_types=1);
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
function da_mysql_login_runtime_config(): array
{
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
return [];
}
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
if (!is_string($raw) || $raw === '') {
return [];
}
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
}
function da_mysql_login_url_path(array $runtimeConfig): string
{
$path = trim((string)($runtimeConfig['phpmyadmin_url_path'] ?? '/alt-mysql-phpmyadmin'));
if ($path === '') {
return '/alt-mysql-phpmyadmin';
}
if ($path[0] !== '/') {
$path = '/' . $path;
}
$path = rtrim($path, '/');
return $path !== '' ? $path : '/alt-mysql-phpmyadmin';
}
function da_mysql_login_ticket_dir(array $runtimeConfig): string
{
$dir = trim((string)($runtimeConfig['ticket_dir'] ?? ''));
if ($dir !== '') {
return rtrim($dir, '/');
}
return __DIR__ . '/runtime/tickets';
}
function da_mysql_login_error(string $message): void
{
http_response_code(400);
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
$safe = htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>alt-mysql phpMyAdmin</title></head><body>';
echo '<h1>alt-mysql phpMyAdmin</h1><p>' . $safe . '</p>';
echo '</body></html>';
exit;
}
function da_mysql_login_consume_ticket(array $runtimeConfig): array
{
$token = strtolower((string)($_GET['ticket'] ?? ''));
if (preg_match('/\A[a-f0-9]{64}\z/', $token) !== 1) {
da_mysql_login_error('Missing or invalid phpMyAdmin login ticket.');
}
$path = da_mysql_login_ticket_dir($runtimeConfig) . '/' . $token . '.json';
if (!is_readable($path)) {
da_mysql_login_error('The phpMyAdmin login ticket is not available or has already been used.');
}
$raw = file_get_contents($path);
@unlink($path);
$ticket = is_string($raw) ? json_decode($raw, true) : null;
if (!is_array($ticket)) {
da_mysql_login_error('The phpMyAdmin login ticket is invalid.');
}
if ((int)($ticket['expires_at'] ?? 0) < time()) {
da_mysql_login_error('The phpMyAdmin login ticket has expired.');
}
$auth = $ticket['auth'] ?? [];
if (!is_array($auth) || (string)($auth['user'] ?? '') === '' || (string)($auth['password'] ?? '') === '') {
da_mysql_login_error('The phpMyAdmin login ticket does not contain credentials.');
}
return $ticket;
}
function da_mysql_login_redirect_target(array $ticket): string
{
$route = (string)($ticket['route'] ?? '/database/structure');
if (preg_match('#^/[A-Za-z0-9_./-]+$#', $route) !== 1 || strpos($route, '//') !== false) {
$route = '/';
}
$query = ['route' => $route];
$db = (string)($ticket['db'] ?? ($ticket['auth']['db'] ?? ''));
if ($db !== '') {
$query['db'] = $db;
}
return 'index.php?' . http_build_query($query);
}
$runtimeConfig = da_mysql_login_runtime_config();
$ticket = da_mysql_login_consume_ticket($runtimeConfig);
$secure = false;
$https = strtolower((string)($_SERVER['HTTPS'] ?? ''));
if ($https !== '' && $https !== 'off' && $https !== '0') {
$secure = true;
} elseif (strtolower((string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https') {
$secure = true;
}
session_name(DA_MYSQL_SESSION);
session_set_cookie_params([
'lifetime' => 0,
'path' => da_mysql_login_url_path($runtimeConfig),
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
$_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] = $ticket['auth'];
session_write_close();
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
header('Location: ' . da_mysql_login_redirect_target($ticket), true, 302);
exit;
PHP
cat > "$SIGNON_SCRIPT" <<'PHP'
<?php
declare(strict_types=1);
const DA_MYSQL_SESSION = 'DA_MYSQL_PHPMYADMIN';
const DA_MYSQL_DEFAULT_CONF = '/usr/local/directadmin/conf/alt-mysql.conf';
const DA_MYSQL_RUNTIME_CONFIG = __DIR__ . '/runtime/config.json';
function da_mysql_runtime_config(): array
{
if (!is_readable(DA_MYSQL_RUNTIME_CONFIG)) {
return [];
}
$raw = file_get_contents(DA_MYSQL_RUNTIME_CONFIG);
if (!is_string($raw) || $raw === '') {
return [];
}
$decoded = json_decode($raw, true);
return is_array($decoded) ? $decoded : [];
}
function da_mysql_signon_session_auth(): array
{
$payload = $_SESSION['DA_MYSQL_PHPMYADMIN_AUTH'] ?? [];
return is_array($payload) ? $payload : [];
}
function da_mysql_signon_read_auth(): array
{
$previousActive = session_status() === PHP_SESSION_ACTIVE;
$previousName = session_name();
$previousId = $previousActive ? session_id() : '';
if ($previousActive && $previousName === DA_MYSQL_SESSION) {
return da_mysql_signon_session_auth();
}
if ($previousActive) {
session_write_close();
}
session_name(DA_MYSQL_SESSION);
session_id('');
$ssoSessionId = (string)($_COOKIE[DA_MYSQL_SESSION] ?? '');
if ($ssoSessionId !== '' && preg_match('/\A[A-Za-z0-9,-]+\z/', $ssoSessionId) === 1) {
session_id($ssoSessionId);
}
session_start();
$payload = da_mysql_signon_session_auth();
session_write_close();
if ($previousActive) {
session_name($previousName);
if ($previousId !== '') {
session_id($previousId);
}
session_start();
} elseif ($previousName !== '') {
session_name($previousName);
}
return $payload;
}
function get_login_credentials($user = ''): array
{
$payload = da_mysql_signon_read_auth();
$username = (string)($payload['user'] ?? '');
$password = (string)($payload['password'] ?? '');
return [$username, $password];
}
PHP
mkdir -p "$RUNTIME_DIR/tmp"
chown -R diradmin:diradmin "$RUNTIME_DIR" "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" 2>/dev/null || true
chmod 755 "$RUNTIME_DIR" "$TICKETS_DIR" "$RUNTIME_DIR/tmp" 2>/dev/null || true
chmod 644 "$CONFIG_INC" "$SIGNON_SCRIPT" "$SIGNON_URL_PAGE" "$RUNTIME_DIR/.htaccess" "$TICKETS_DIR/.htaccess" "$RUNTIME_DIR/index.html" "$TICKETS_DIR/index.html" 2>/dev/null || true
}
ensure_private_copy
write_private_config
log "Prywatna kopia phpMyAdmin dla alt-mysql jest gotowa w ${PHPMYADMIN_PRIVATE_DIR}."