Current File : //proc/thread-self/root/bin/alt-mysql-reconfigure
#!/usr/libexec/platform-python
# -*- mode:python; coding:utf-8; -*-
# author: Eugene Zamriy <ezamriy@cloudlinux.com>,
#         Sergey Fokin <sfokin@cloudlinux.com>
# editor: Eduard Chiganov <echiganov@cloudlinux.com>
# created: 29.07.2015 15:46
# edited: Eduard Chiganov 05.2025 16:20
# description: Selects correct alt-php MySQL binding according to the system
#              configuration.


import getopt
import glob
import logging
import os
import platform
import re
import subprocess
import sys
import traceback
from decimal import Decimal

try:
    import rpm
except:
    class rpm:
        RPMMIRE_REGEX = None
        class pattern:
            def __init__(self, packages):
                self.packages = packages
            def pattern(self, field, flag, pattern):
                regexp = re.compile(pattern)
                self.packages = list(filter(regexp.match, self.packages))
            def __getitem__(self, item):
                return self.packages[item]
        class TransactionSet:
            @staticmethod
            def dbMatch():
                return rpm.pattern(os.popen('rpm -qa').readlines())


VER_PATTERNS = {"18.1": "5.6", "18.1.0": "5.6", "18.0": "5.5", "18.0.0": "5.5",
                "18": "5.5", "16": "5.1", "15": "5.0", "20.1": "5.7",
                "20.2": "5.7", "20.3": "5.7", "21.0": "8.0", "21.2": "8.0", "24.0.5": "8.4"}


def is_debian():
    """
    Check if we running on Debian/Ubuntu

    @rtype  : bool
    @return  True or False
    """
    if os.path.exists("/etc/redhat-release"):
        return False
    return True


def is_plesk():
    """
    Check is it environment with installed plesk panel

    @rtype  : bool
    @return  True or False
    """
    if not os.path.exists("/usr/sbin/plesk"):
        return False
    return True


def configure_logging(verbose):
    """
    Logging configuration function.

    @type verbose:  bool
    @param verbose: Enable additional debug output if True, display only errors
        otherwise.
    """
    if verbose:
        level = logging.DEBUG
    else:
        level = logging.ERROR
    handler = logging.StreamHandler()
    handler.setLevel(level)
    log_format = "%(levelname)-8s: %(message)s"
    formatter = logging.Formatter(log_format, "%H:%M:%S %d.%m.%y")
    handler.setFormatter(formatter)
    logger = logging.getLogger()
    logger.addHandler(handler)
    logger.setLevel(level)
    return logger


def symlink_abs_path(path):
    """
    Recursively resolves symlink.

    @type path:  str
    @param path: Symlink path.

    @rtype:      str
    @return:     Resolved symlink absolute path.
    """
    processed_symlinks = set()
    if not isinstance(path, str):
        return None
    while os.path.islink(path):
        if path in processed_symlinks:
            return None
        path = os.path.join(os.path.dirname(path), os.readlink(path))
        processed_symlinks.add(path)
    return os.path.abspath(path)


def create_symlink_to_mysqli_ini(source_dir, target_dir):
    """
    Creates or updates a symbolic link to mysqli.ini.

    @type source_dir: str
    @param source_dir: Directory containing the actual mysqli.ini file.

    @type target_dir: str
    @param target_dir: Directory where the symlink will be created or updated.

    @rtype: bool
    @return: True if symlink is created or updated successfully, False otherwise.
    """
    source_path = os.path.join(source_dir, 'mysqli.ini')
    target_path = os.path.join(target_dir, 'mysqli.ini')

    if is_plesk():
        return False
    else:
        try:
            # Remove the target file/symlink if it exists (mimic --force)
            if os.path.exists(target_path) and not os.path.islink(target_path):
                os.remove(target_path)

            if source_path == symlink_abs_path(target_path):
                logging.debug(u"%s is already configured to %s" % (target_path, source_path))
                return True

            # Create the symlink
            os.symlink(source_path, target_path)
            logging.info(u"Symlink created or updated: %s -> %s" % (target_path, source_path))
            return True

        except Exception as e:
            logging.error(u"Error creating or updating symlink: %s" % str(e))
            return False


def find_interpreter_versions(interpreter="php"):
    """
    Returns list of installed alt-php versions and their base directories.

    @rtype:  list
    @return: List of version (e.g. 44, 55) and base directory tuples.
    """
    int_versions = []
    if interpreter == "ea-php":
        base_path_regex = "/opt/cpanel/ea-php[0-9][0-9]/root/"
    else:
        base_path_regex = "/opt/alt/%s[0-9][0-9]" % interpreter
    for int_dir in glob.glob(base_path_regex):
        int_versions.append((int_dir[-2:], int_dir))
    int_versions.sort()
    return int_versions


def find_mysql_executable(mysql="mysql"):
    """
    Detects MySQL binary full path.

    @type mysql:  str
    @param mysql: MySQL binary name (default is "mysql").

    @rtype:       str or None
    @return:      MySQL binary full path or None if nothing is found.
    """
    for path in os.environ["PATH"].split(os.pathsep):
        mysql_path = os.path.join(path, mysql)
        if os.path.exists(mysql_path) and os.access(mysql_path, os.X_OK):
            if os.path.islink(mysql_path):
                return os.readlink(mysql_path)
            else:
                return mysql_path


def is_percona(major, minor):
    """
    Check if Percona server is installed

    @type major:  str
    @param major: major version of sql server
    @type minor:  str
    @param minor: minor version of sql server

    @rtype:       bool
    @return:      True or False
    """
    if is_debian():
        if not os.system("dpkg -l | grep -i percona-server"):
            return True
    else:
        ts = rpm.TransactionSet()
        mi = ts.dbMatch()
        pattern = "Percona-Server-shared-{0}{1}|cl-Percona{0}{1}-shared".format(
            major, minor)
        mi.pattern('name', rpm.RPMMIRE_REGEX, pattern)
        for _ in mi:
            mysql_type = "percona"
            return True
    return False


def parse_mysql_version(version):
    """
    Extracts MySQL engine type and version from the version string
    (mysql -V output).

    @type version:  str
    @param version: MySQL version string (mysql -V output).

    @rtype:         tuple
    @return:        MySQL engine type (e.g. mariadb, mysql) and version (e.g.
        5.6, 10.0) tuple.
    """
    ver_rslt = ""
    if re.search("mysql", version, re.IGNORECASE):
        ver_rslt = re.search("mysql\s+Ver\s+.*?Distrib\s+((\d+)\.(\d+)\S*?),?"
                             "\s+for", version)
        if not ver_rslt:
            ver_rslt = re.search(r"mysql\s+Ver\s+((\d+)\.(\d+)\S*)", version)
    else:
        ver_rslt = re.search("mariadb\s+from\s+((\d+)\.(\d+)\S*?),?\s+", version)
        if not ver_rslt:
            ver_rslt = re.search("mariadb\s+Ver\s+.*?Distrib\s+((\d+)\.(\d+)\S*?),?"
                             "\s+for", version)
    if not ver_rslt:
        return None, None
    full_ver, major, minor = ver_rslt.groups()
    mysql_type = "mysql"
    mysql_ver = "%s.%s" % (major, minor)
    if re.search("mariadb", full_ver, re.IGNORECASE):
        mysql_type = "mariadb"
    # NOTE: there are no way to detect Percona by "mysql -V" output, so we
    #       are looking for Percona-Server-shared* or cl-Percona*-shared package installed
    if is_percona(major, minor):
        mysql_type = "percona"
    return mysql_type, mysql_ver


def get_mysql_version(mysql_path):
    """
    Returns MySQL engine type and version of specified MySQL executable.

    @type mysql_path:  str
    @param mysql_path: MySQL executable path.

    @rtype:            tuple
    @return:           MySQL engine type (mariadb or mysql) and version (e.g.
        5.6, 10.0) tuple.
    """
    proc = subprocess.Popen([mysql_path, "-V"], stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT, universal_newlines=True)
    out, _ = proc.communicate()
    if proc.returncode != 0:
        raise Exception(u"cannot execute \"%s -V\": %s" % (mysql_path, out))
    ver_string = out.strip()
    logging.debug(u"SQL version string is '%s'" % ver_string)
    return parse_mysql_version(ver_string)


def detect_so_version(so_path):
    """
    Parameters
    ----------
    so_path         : str or unicode
        Absolute path to .so library

    Returns
    -------
    tuple
        Tuple of MySQL type name and MySQL version

    """
    mysql_ver = None
    for ver_pattern in VER_PATTERNS:
        if re.search(re.escape(".so.%s" % ver_pattern), so_path):
            mysql_ver = VER_PATTERNS[ver_pattern]
            if is_debian():
                mysql_ver = mysql_ver.replace(".", "")
    # in some Percona builds .so was renamed to libperconaserverclient.so
    if "libperconaserverclient.so" in so_path:
        return "percona", mysql_ver
    # search for markers (mariadb/percona) in .so strings
    proc = subprocess.Popen(["strings", so_path], stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT, universal_newlines=True)
    out, _ = proc.communicate()
    if proc.returncode != 0:
        raise Exception(u"cannot execute \"strings %s\": %s" % (so_path, out))
    mysql_type = "mysql"
    for line in out.split("\n"):
        if re.search("percona", line, re.IGNORECASE):
            return "percona", mysql_ver
        maria_version = re.search("^(1[0-1]\.[0-9]+)\.[0-9]*(-MariaDB)?$", line,
                                  re.IGNORECASE)
        if maria_version is not None and len(maria_version.groups()) != 0:
            return "mariadb", maria_version.group(1)
        if re.search("5\.5.*?-MariaDB", line, re.IGNORECASE):
            return "mariadb", "5.5"
        if re.search("mariadb", line, re.IGNORECASE):
            mysql_type = "mariadb"
    return mysql_type, mysql_ver


def detect_lib_dir():
    """
    Returns
    -------
    str
        lib if running on 32-bit system, lib64 otherwise

    """
    if is_debian():
        return "lib/x86_64-linux-gnu"
    if platform.architecture()[0] == "64bit":
        return "lib64"
    else:
        return "lib"


def get_int_files_root_path(int_name, int_ver):
    """
    Parameters
    ----------
    int_name        : str or unicode
        Interpreter name (php, python)
    int_ver         : str or unicode
        Interpreter version (44, 70, 27, etc.)

    Returns
    -------
    str
        Absolute path to interpreter root

    """
    if int_name == "php":
        return "/opt/alt/php%s" % int_ver
    elif int_name == "ea-php":
        return "/opt/cpanel/ea-php%s/root/" % int_ver
    elif int_name == "python":
        return "/opt/alt/python%s" % int_ver
    else:
        raise NotImplementedError("Unknown interpreter")


def get_dst_so_path(int_name, int_ver, so_name):
    """
    Parameters
    ----------
    int_name        : str or unicode
        Interpreter name (php, python)
    int_ver         : str or unicode
        Interpreter version (44, 70, 27, etc.)
    so_name         : str or unicode
        MySQL shared library name

    Returns
    -------
    str
        Absolute path to MySQL binding destination point

    """
    lib_dir = detect_lib_dir()
    int_path = get_int_files_root_path(int_name, int_ver)
    int_dot_ver = "%s.%s" % (int_ver[0], int_ver[-1])
    if int_name in ["php", "ea-php"]:
        if re.match(r".*_ts.so", so_name):
            return os.path.join(int_path, "usr", lib_dir, "php-zts/modules", re.sub('_ts\.so', '.so', so_name))
        else:
            return os.path.join(int_path, "usr", lib_dir, "php/modules", so_name)
    elif int_name == "python":
        if os.path.exists("/opt/alt/python{0}/bin/python{1}".format(int_ver, int_ver[0])):
            proc = subprocess.Popen("/opt/alt/python{0}/bin/python{1} -c \"from distutils.sysconfig import get_python_lib; print(get_python_lib(True))\"".format(int_ver, int_ver[0]),
                                    shell=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT,
                                    universal_newlines=True)
            out, _ = proc.communicate()
            return os.path.join(out.strip(),so_name)
    else:
        raise NotImplementedError("Unknown interpreter")


def get_mysql_pkg_name(int_name, int_ver, mysql_type, mysql_ver, zts=False):
    """
    Parameters
    ----------
    int_name        : str or unicode
        Interpreter name (php, python)
    int_ver         : str or unicode
        Interpreter version (44, 27, 71, etc.)
    mysql_type      : str or unicode
        Mysql base type (mysql, mariadb, percona)
    mysql_ver       : str or unicode
        Mysql version (5.5, 10, 10.1)

    Returns
    -------

    """
    if int_name == "php":
        if not zts:
            return "alt-php%s-%s%s" % (int_ver, mysql_type, mysql_ver)
        else:
            return "alt-php%s-%s%s-zts" % (int_ver, mysql_type, mysql_ver)
    elif int_name == "ea-php":
        return "%s%s-php-%s%s" % (int_name, int_ver, mysql_type, mysql_ver)
    elif int_name == "python":
        return "alt-python%s-MySQL-%s%s" % (int_ver, mysql_type, mysql_ver)
    else:
        raise NotImplementedError("Unknown interpreter")


def get_so_list(int_name):
    """
    Parameters
    ----------
    int_name        : str
        Interpreter name (e.g. php, python, etc.)

    Returns
    -------

    """
    if int_name == "ea-php":
        return ["mysql.so", "mysqli.so", "pdo_mysql.so"]
    elif int_name == "php":
        if is_plesk():
            return ["mysqli.so", "pdo_mysql.so"]
        else:
            return ["mysql.so", "mysqli.so", "pdo_mysql.so", "mysql_ts.so",
                    "mysqli_ts.so", "pdo_mysql_ts.so"]
    elif int_name == "python":
        return ["_mysql.so"]
    else:
        raise NotImplementedError("Unknown interpreter")


def match_so_to_mysql():
    mysql = find_mysql_executable()
    # If we have no MySQL, then nothing should be done
    if not mysql:
        return
    possible_versions = list(VER_PATTERNS.values())
    possible_versions += ["10", "10.0", "10.1", "10.2", "10.3", "10.4", "10.5",
                          "10.6", "10.11", "11.4"]
    mysql_type, mysql_ver = get_mysql_version(mysql)
    if is_debian():
        mysql_ver = mysql_ver.replace(".", "")
    if mysql_type not in ["mysql", "mariadb", "percona"] or \
            mysql_ver not in possible_versions:
        return
    if mysql_ver == "5.0":
        search_pattern = re.compile(r"(\S*libmysqlclient\.so\.15\S*)")
    elif mysql_ver == "5.1":
        search_pattern = re.compile(r"(\S*libmysqlclient\.so\.16\S*)")
    elif mysql_ver in ("5.5", "10", "10.0", "10.1"):
        search_pattern = re.compile(r"(\S*libmysqlclient\.so\.18\.0\S*)")
    elif mysql_ver == "5.6":
        search_pattern = re.compile(r"(\S*libmysqlclient\.so\.18\.1\S*)")
    elif mysql_ver == "5.7":
        search_pattern = re.compile(r"(\S*libmysqlclient\.so\.20\S*)")
    elif mysql_ver == "8.0":
        search_pattern = re.compile(r"(\S*libmysqlclient\.so\.21\S*)")
    elif mysql_ver == "8.4":
        search_pattern = re.compile(r"(\S*libmysqlclient\.so\.24\S*)")
    elif mysql_ver in ("10.2", "10.3", "10.4", "10.5", "10.6", "10.11", "11.4"):
        search_pattern = re.compile(r"(\S*libmariadb\.so\.3\S*)")
    else:
        raise Exception(u"Cannot match MySQL library to any version")
    if mysql_type == "percona":
        search_path = ["/usr/%s" % detect_lib_dir()]
    else:
        search_path = ["/usr/local/mysql/lib/",            # Added path for Direect Admin
                       "/usr/%s/" % detect_lib_dir(),
                       "/usr/%s/mysql" % detect_lib_dir(),
                       "/usr/%s/mariadb" % detect_lib_dir()]
    files = []
    for libs_path in search_path:
        if os.path.exists(libs_path):
            for file in os.listdir(libs_path):
                files.append(os.path.join(libs_path, file))
    for one_file in files:
        if search_pattern.match(one_file):
            return (search_pattern.match(one_file).string,
                    mysql_type, mysql_ver)


def get_mysql_so_files():
    proc = subprocess.Popen(["/sbin/ldconfig", "-p"], stdout=subprocess.PIPE,
                            universal_newlines=True)
    out, _ = proc.communicate()
    if proc.returncode != 0:
        raise Exception(u"cannot execute \"ldconfig -p\": %s" % out)
    so_re = re.compile("^.*?=>\s*(\S*?(libmysqlclient|"
                       "libmariadb|"
                       "libperconaserverclient)\.so\S*)")
    forced_so_file = match_so_to_mysql()
    if forced_so_file:
        so_files = [forced_so_file]
    else:
        so_files = []
    for line in out.split("\n"):
        re_rslt = so_re.search(line)
        if not re_rslt:
            continue
        so_path = symlink_abs_path(re_rslt.group(1))
        if not so_path or not os.path.exists(so_path):
            continue
        mysql_type, mysql_ver = detect_so_version(so_path)
        so_rec = (so_path, mysql_type, mysql_ver)
        if so_rec not in so_files:
            so_files.append(so_rec)
    return so_files


def reconfigure_mysql(int_ver, mysql_type, mysql_ver, force=False,
                      int_name="php"):
    """
    Parameters
    ----------
    int_ver         : str or unicode
        Interpreter version (44, 70, 27, etc.)
    mysql_type      : str or unicode
        MySQL type (mysql, mariadb, percona)
    mysql_ver       : str or unicode
        MySQL version (5.5, 10.1, etc.)
    force           : bool
        Force symlink reconfiguration if True, do nothing otherwise
    int_name        : str or unicode
        Optional, defines interpreter name (php, python). Default is php

    Returns
    -------
    bool
        True if reconfiguration was successful, False otherwise

    """
    int_dir = get_int_files_root_path(int_name, int_ver)
    if is_plesk():
        so_list = get_so_list(int_name)
        for so_name in so_list:
            src_so = get_dst_so_path(int_name, int_ver, "nd_%s" % so_name)
            dst_so = get_dst_so_path(int_name, int_ver, so_name)
            if os.path.exists(src_so):
                try:
                    if os.path.exists(dst_so):
                        os.remove(dst_so)
                    os.symlink(src_so, dst_so)
                except Exception as e:
                    logging.error(u"Error creating symlink: %s" % str(e))
        return True
    if mysql_type == "mariadb":
        if mysql_ver in ("10", "10.0"):
            mysql_ver = "10"
        elif mysql_ver.startswith("10."):
            mysql_ver = mysql_ver.replace(".", "")
        elif mysql_ver.startswith("11."):
            mysql_ver = mysql_ver.replace(".", "0")
        elif mysql_ver == "5.5":
            # NOTE: there are no special bindings for MariaDB 5.5 in Cloud Linux
            #       so we are using the MySQL one
            mysql_type = "mysql"
    so_list = get_so_list(int_name)
    for so_name in so_list:
        src_so = os.path.join(int_dir, "etc",
                              "%s%s" % (mysql_type, mysql_ver), so_name)
        if not os.path.exists(src_so):
            if (so_name in ("mysqli.so", "pdo_mysql.so") and int_ver == "44") \
                    or (so_name == "mysql.so" and int_ver.startswith("7")) \
                    or (so_name == "mysql.so" and int_ver.startswith("8")) \
                    or (re.match(r".*_ts.so", so_name) and int_ver != 72):
                # NOTE: there are no mysql.so for alt-php7X and mysqli.so /
                #       pdo_mysql.so for alt-php44
                continue
            # TODO: maybe find an appropriate replacement for missing
            #       .so in other alt-php-(mysql|mariadb|percona) packages?
            mysql_pkg_name = get_mysql_pkg_name(int_name, int_ver, mysql_type,
                                                mysql_ver,
                                                bool(re.match(r".*_ts.so",
                                                     so_name)))
            logging.debug(u"%s is not found. Please install "
                          u"%s package" % (so_name, mysql_pkg_name))
            return False
        dst_so = get_dst_so_path(int_name, int_ver, so_name)
        dst_so_real = symlink_abs_path(dst_so)
        if src_so == dst_so_real:
            logging.debug(u"%s is already updated" % dst_so)
            continue
        else:
            force = True
        if not isinstance(dst_so, str):
            return False
        if os.access(dst_so, os.R_OK):
            # seems alt-php is already configured - don't touch without force
            # argument
            if not force:
                logging.debug(u"current %s configuration is ok (%s)" %
                              (dst_so, dst_so_real))
                continue
            os.remove(dst_so)
            os.symlink(src_so, dst_so)
            logging.info(u"%s was reconfigured to %s" % (dst_so, src_so))
        else:
            # seems current alt-php configuration is broken, reconfigure it
            try:
                os.remove(dst_so)
            except:
                pass
            os.symlink(src_so, dst_so)
            logging.info(u"%s was configured to %s" % (dst_so, src_so))
        continue
    return True


def check_alt_path_exists(int_path, int_name, int_ver):
    """
    Parameters
    ----------
    int_path        : str or unicode
        Interpreter directory on the disk (/opt/alt/php51, etc.)
    int_name        : str or unicode
        Interpreter name (php, python)
    int_ver         : str or unicode
        Interpreter version (44, 70, 27, etc.)

    Returns
    -------
    bool
        True if interpreter path exists, False otherwise

    """
    if not os.path.isdir(int_path):
        sys.stderr.write("unknown {0} version {1}".format(int_name, int_ver))
        return False
    return True


def main(sys_args):
    try:
        opts, args = getopt.getopt(sys_args, "p:P:e:v",
                                   ["php=", "python=", "ea-php=", "verbose"])
    except getopt.GetoptError as e:
        sys.stderr.write("cannot parse command line arguments: {0}".format(e))
        return 1
    verbose = False
    int_versions = []
    int_name = "php"
    for opt, arg in opts:
        if opt in ("-p", "--php"):
            int_name = "php"
            int_path = "/opt/alt/php%s" % arg
            if check_alt_path_exists(int_path, int_name, arg):
                int_versions.append((arg, int_path))
            else:
                return 1
        elif opt in ("-e", "--ea-php"):
            int_name = "ea-php"
            int_path = "/opt/cpanel/ea-php%s/root/" % arg
            if check_alt_path_exists(int_path, int_name, arg):
                int_versions.append((arg, int_path))
            else:
                return 1
        elif opt == "--python":
            int_name = "python"
            int_path = "/opt/alt/python%s" % arg
            if check_alt_path_exists(int_path, int_name, arg):
                int_versions.append((arg, int_path))
            else:
                return 1
        if opt in ("-v", "--verbose"):
            verbose = True
    log = configure_logging(verbose)
    if int_name == "ea-php":
        int_group = int_name
    else:
        int_group = "alt-%s" % int_name
    if not int_versions:
        int_versions = find_interpreter_versions()
    log.info(u"installed %s versions are\n%s" % (int_group,
             "\n".join(["\t %s: %s" % (int_group, i) for i in int_versions])))
    mysql_so_files = get_mysql_so_files()
    log.info(u"available SQL so files are\n%s" %
             "\n".join(["\t%s (%s-%s)" % i for i in mysql_so_files]))
    # skip reconfigure magick if file exists
    if os.path.exists("/opt/alt/alt-php-config/disable"):
        log.info(u"skip reconfiguration, because '/opt/alt/alt-php-config/disable' exists")
        return True
    try:
        mysql_path = find_mysql_executable()
        if not mysql_path:
            log.info(u"cannot find system SQL binary")
            for int_ver, int_dir in int_versions:
                status = False
                for so_name, so_type, so_ver in mysql_so_files:
                    if reconfigure_mysql(int_ver, so_type, so_ver,
                                         force=False, int_name=int_name):
                        status = True
                        break
                if not status:
                    log.debug(u"alt-%s%s reconfiguration is failed" %
                              (int_name, int_ver))
        else:
            log.debug(u"system SQL binary path is %s" % mysql_path)
            mysql_type, mysql_ver = get_mysql_version(mysql_path)
            log.debug(u"system SQL is %s-%s" % (mysql_type, mysql_ver))
            # check if we have .so for the system SQL version
            mysql_so_exists = False
            for so_name, so_type, so_ver in mysql_so_files:
                if so_type == mysql_type and so_ver == mysql_ver:
                    mysql_so_exists = True
                    break
            # reconfigure alt-php symlinks
            for int_ver, int_dir in int_versions:
                # system SQL was correctly detected and we found .so for it -
                # reconfigure alt-php to use it instead of previous
                # configuration
                if mysql_so_exists and \
                        reconfigure_mysql(int_ver, mysql_type, mysql_ver,
                                          force=True, int_name=int_name):
                    ini_src_path = "%s/etc/php.d.all" % get_int_files_root_path(int_name, int_ver)
                    ini_dst_path = "%s/etc/php.d" % get_int_files_root_path(int_name, int_ver)
                    if int_name == "php":
                        create_symlink_to_mysqli_ini(ini_src_path,ini_dst_path)
                    continue
                # we are unable to detect system SQL or it's .so is missing -
                # reconfigure alt-php to use .so that we have available, but
                # only if current configuration is broken
                status = False
                for so_name, so_type, so_ver in mysql_so_files:
                    if reconfigure_mysql(int_ver, so_type, so_ver, force=False,
                                         int_name=int_name):
                        status = True
                        ini_src_path = "%s/etc/php.d.all" % get_int_files_root_path(int_name, int_ver)
                        ini_dst_path = "%s/etc/php.d" % get_int_files_root_path(int_name, int_ver)
                        if int_name == "php":
                            create_symlink_to_mysqli_ini(ini_src_path,ini_dst_path)
                        break
                if not status:
                    log.debug(u"alt-%s%s reconfiguration is failed" %
                              (int_name, int_ver))
    except Exception as e:
        log.error(u"cannot reconfigure alt-%s SQL bindings: %s. "
                  u"Traceback:\n%s" % (int_name, e,
                                       traceback.format_exc()))
        return 1


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
Mười trang web sòng bạc và trò chơi dựa trên web tốt nhất của Web Cash Web chúng tôi 2025

Mười trang web sòng bạc và trò chơi dựa trên web tốt nhất của Web Cash Web chúng tôi 2025

Đối với nhiều người đang đánh giá các sòng bạc trực tuyến, việc kiểm tra thư mục sòng bạc trên internet được cung cấp ít hơn để xem trong số các tùy chọn tốt hơn có sẵn. Ưu điểm đề nghị kiểm game kingfun tra giới hạn của nhau và đặt cược thấp nhất bất cứ khi nào so sánh các trò chơi sòng bạc trực tuyến còn sống. Tổ chức đáng tin cậy đảm bảo chơi trò chơi dễ dàng và bạn có thể các nhà đầu tư hàng đầu, gây ra môi trường đánh bạc liền mạch. Dịch vụ hỗ trợ hợp pháp là rất quan trọng để sở hữu các vấn đề giải quyết thông qua các lớp chơi.

Game kingfun: Tiền thưởng sòng bạc và bạn có thể chiến dịch

Một cái gì đó khác nhau đã đăng ký sòng bạc dựa trên web thường là chúng cũng có với công nghệ mã hóa SSL hiện tại có sẵn với các tổ chức như Digicert và bạn có thể CloudFlare. Do đó, chi tiết cá nhân của riêng bạn và bạn có thể thông tin tiền tệ thực sự được bảo mật đúng cách và bạn sẽ xử lý. Và cuối cùng, tất cả các trang web cá cược được ủy quyền hiện cung cấp một cơ hội hợp lý về thu nhập tiềm năng trong suốt những năm qua. Để xác nhận độ tin cậy hoàn toàn mới của một sòng bạc trực tuyến khác, hãy xem hướng dẫn cấp phép của họ, hiểu xếp hạng của ưu đãi hàng đầu và bạn sẽ kiểm tra khả năng đáp ứng hoàn toàn mới của dịch vụ khách hàng.Khám phá các đánh giá ngoài hàng đầu cung cấp là một cách hiệu quả để xác định danh tiếng mới nhất của một sòng bạc internet thay thế.

Tùy thuộc vào đánh giá của người dùng trên cửa hàng trái cây và bạn có thể chơi yahoo, thỏa thuận giành chiến thắng của bạn với những người có ý nghĩa hoặc vấn đề. Sự pha trộn của chúng có lợi cho việc đảm bảo một ý nghĩa đánh bạc đặc biệt, và sau đó làm cho các sòng bạc trực tuyến mới trở thành một lựa chọn hấp dẫn cho những người tham gia tìm kiếm cuộc phiêu lưu và chi phí. Đảm bảo sòng bạc địa phương mới được ủy quyền bởi chính phủ chơi game được thừa nhận và bạn có thể dành các bước hoa hồng an toàn hơn là vô cùng quan trọng để có một an toàn và bạn sẽ thú vị trải nghiệm chơi game. Sòng bạc địa phương hoang dã được tổ chức cho các trò chơi đại lý thời gian thực, lợi nhuận đúng giờ và bạn sẽ tương thích di động. Mọi người cũng có thể thưởng thức các trò chơi chuyên gia còn sống phổ biến như Black-Jack, Alive Roulette, và bạn có thể Baccarat, được phát trực tiếp trong độ phân giải cao. Một khi bạn yêu cầu thanh toán từ một sòng bạc internet chính hãng, tất nhiên bạn cần phải nhận được các khoản thanh toán của mình càng sớm càng tốt.

game kingfun

Khi các cầu thủ đã ở các bang trong đó các sòng bạc dựa trên web không được đánh giá, họ sẽ chắc chắn bắt gặp các trang web xuất hiện bao gồm cả nó thử tòa án. Các trang web chơi game ngoài khơi này được thực hiện để hoạt động hoàn toàn trong luật pháp, dù sao chúng thực sự làm việc với thời trang bất hợp pháp khác. Một sòng bạc địa phương thời gian thực trực tuyến sẽ mang lại sự hồi hộp mới từ trò chơi truyền thống lên máy tính để bàn của bạn nếu không có điện thoại thông minh.Chơi roulette hoặc các trò chơi bài ví dụ Blackjack và Baccarat chống lại một người buôn bán của con người thông qua webcam.

Spinblitz – Lý tưởng cho phần thưởng hoàn toàn miễn phí và bạn sẽ giảm Cashout tối thiểu SC

Mua tiền điện tử cũng được an toàn và bạn sẽ đúng giờ với bảo vệ mật mã của họ. Đánh bạc trực tuyến hiện đang là phòng xử án bên trong Connecticut, Del biết, Michigan, Las Vegas, NJ, Pennsylvania, khu vực Rhode và bạn có thể West Virginia. Hầu như mọi người khác đều nói, ví dụ CA, Illinois, Indiana, Massachusetts và New York được yêu cầu thông qua thành công các luật và quy định tương tự trong tương lai.

Cảm giác của người dùng (UX) là điều cần thiết để có phần mềm chơi sòng bạc địa phương di động, bởi vì cá nhân nó có ảnh hưởng đến sự tham gia của người chơi và bạn có thể bảo trì. Một khung UX nhắm mục tiêu định tuyến liền mạch và bạn sẽ kết nối liên kết, vì vậy mọi người dễ dàng khám phá và say sưa trong một trò chơi video phổ biến. Các doanh nghiệp đánh bạc di động cần thực hiện trơn tru với một loạt các điện thoại di động, phục vụ để giúp bạn cả hồ sơ iOS và Android. Trò chơi video môi giới trực tiếp tái tạo cảm giác sòng bạc địa phương mới ở nhà từ sự pha trộn sự khéo léo của việc đặt cược trực tuyến đến bầu không khí nhập vai từ một doanh nghiệp đánh bạc thực tế.Những loại tương ứng thời gian trò chơi trò chơi video này với các nhà giao dịch, mang đến một yếu tố xã hội để tăng cường cảm giác cá cược tổng số.

game kingfun

Bạn sẽ cần một mật khẩu tuyệt vời để bạn có thể đăng nhập vào tài khoản ngân hàng của mình khi bạn cần chơi. Đó là điều đầu tiên mà bạn sẽ cần làm sau khi bạn tạo ra tư cách thành viên sòng bạc địa phương. Trên thực tế, các quy tắc và bạn sẽ cấu trúc từ Baccarat khá giống Blackjack. Dưới đây là lựa chọn tốt nhất để di chuyển số tiền lớn liên quan đến tài chính và một sòng bạc internet hàng đầu. Mặc dù nó có thể không phải là lựa chọn nhanh nhất, nhưng nó là một trong những lựa chọn thay thế tốt nhất cho các con lăn cao. Xin nhớ rằng đó không phải là một đánh giá toàn bộ về tất cả các trang web của cơ sở đánh bạc ngoài khơi.

Rất nhiều tiền Bigfoot, Phù thủy và bạn sẽ là Wizard, và Derby Bucks chỉ là một số vở kịch trao giải Jackpots có khoảng 97,5% RTP, do các tính năng bổ sung. Bạn sẽ không muốn để bạn có thể cáo buộc tiền thưởng và kết thúc chúng trước khi bạn sử dụng anh ấy hoặc cô ấy vì bạn không kiểm tra chính xác số tiền thưởng mới nhất cuối cùng. Trong các bản nháp của cơ sở đánh bạc chấp nhận bổ sung tiền thưởng, bạn có thể mua năm trăm phần thưởng xoay vòng ngay sau đó để thử 5 đô la. Mặc dù bạn cần ký gửi $ 5 và đặt cược $ Bước 1, bạn vẫn tiếp tục nhận được 100 đô la, đó là nhiều hơn gần như bất kỳ phần thưởng nào khác không có ý định khác. Mỗi một trong những trò chơi trực tuyến này có các biến thể mới lạ và bạn có thể quy định một điều đặt ra cho họ. Trò chơi sòng bạc cũng có thể nhận được một số số tiền khác, liên quan đến sòng bạc.

Không đặt cược 100 phần trăm các vòng quay miễn phí là một trong những ưu đãi tốt nhất được cung cấp tại các sòng bạc trực tuyến. Khi mọi người sử dụng các xoay chuyển này, mọi người sẽ thử được đưa ra làm tiền mặt thực sự, không có điều kiện cá cược nào. Có nghĩa là bạn có thể rút lại tiền thắng của mình một lần nữa thay vì đánh bạc một lần nữa. Những loại tiền thưởng này thường được liên kết với các chương trình khuyến mãi nhất định nếu không có bến cảng và bạn sẽ có thể có một vỏ bọc chiến thắng tối ưu.

Làm thế nào để chắc chắn rằng vị trí mới của một sòng bạc internet khác

game kingfun

Phần mềm di động trung thành đảm bảo lối chơi đơn giản, cho dù có quay các cổng hay thiết lập các sự kiện thể thao hay không. Toàn bộ năm 2025 được quyết định quan sát sự ra mắt hoàn toàn mới của nhiều sòng bạc mới nhất trên internet, ra mắt trải nghiệm đánh bạc sáng tạo và bạn có thể nâng cao các tính năng. Người ta ước tính rằng khoảng 15 sòng bạc dựa trên web mới đã được ra mắt mỗi tháng, làm nổi bật sự phổ biến ngày càng tăng của cờ bạc trực tuyến. SLOTSLV chắc chắn là một trong những sòng bạc dựa trên web tốt hơn trong trường hợp bạn đang cố gắng tìm các khe sòng bạc trực tuyến cụ thể. Sòng bạc trực tuyến cũng cung cấp các khoản thanh toán an toàn, các nhà đầu tư thời gian thực và bạn sẽ 31 vòng quay miễn phí sau khi bạn đăng ký.

Trò chơi đại lý thời gian thực: Đưa Vegas lên màn hình

Tiền mặt thực sự có lợi nhuận tại các sòng bạc trực tuyến trả tiền tốt nhất chủ yếu là một điểm cơ hội. Mặc dù các lựa chọn không kỹ lưỡng, bạn có thể cố gắng cơ hội của mình trong Roulette Baccarat, Blackjack, Mỹ hoặc Tây Âu và bạn có thể rất sáu. Các chuyên gia rất vui mừng được khám phá nhiều spin miễn phí 100 phần trăm đề xuất yêu cầu tại các sòng bạc trực tuyến tốt nhất của chúng tôi. Chúng tôi từ các lợi ích đã mô tả các phiên bản tiền thưởng được thêm vào các phiên bản thưởng thêm bên dưới liên quan đến những người đăng ký có giá trị của chúng tôi để trải nghiệm. Đối với những người đánh bạc một trăm đô la cũng như trò chơi trực tuyến có phía tài sản là 10%, doanh nghiệp đánh bạc mới nhất được dự đoán sẽ lưu trữ $ mười trong số bất kỳ đô la nào được đóng vai chính. Để có những người tham gia, nó chỉ đơn giản là anh ta có thể được dự đoán sẽ mất nhiều hơn một độ tuổi tuyệt vời để chơi.

Các phiên bản phổ biến ví dụ như Blackjack sống và bạn sẽ làm cho Roulette thực hiện trải nghiệm tiểu thuyết, thêm vào sự nổi bật liên tục của chúng.Chọn doanh nghiệp đánh bạc còn sống phù hợp nhất có thể tăng cảm giác đánh bạc của riêng bạn. Ưu tiên các doanh nghiệp đánh bạc có nhiều trò chơi video chuyên gia còn sống để lưu trữ trò chơi của bạn thú vị. Đánh giá các dịch vụ trò chơi trên trang web cho Variety và bạn có thể định vị với các lựa chọn của mình. Các ưu đãi chấp nhận đóng vai trò là một sự bao gồm nồng nhiệt cho các chuyên gia mới trong các sòng bạc dựa trên web, có xu hướng đến hình thức của một kế hoạch chào mừng pha trộn tiền thưởng có 100 % các xoay vòng miễn phí.

100 phần trăm các vòng quay miễn phí không có tiền thưởng tiền gửi là gì?

Nhà hàng Sòng bạc địa phương phục vụ như một khu bảo tồn để sở hữu những người đam mê trò chơi khe, các báo cáo xoay vòng từ phiêu lưu, phạm vi rộng và bạn có thể không ngừng phấn khích với mọi reel. Tự hào với một bộ sưu tập các tiêu đề vị trí độc quyền, cho mỗi lần quay là một nhiệm vụ cho thế giới đầy đủ của các bố cục độc đáo và bạn sẽ các tính năng sáng tạo. Duyệt các bản in đẹp và kiếm được giới hạn, giới hạn kích thước đặt cược và bạn có thể thêm các yêu cầu mật khẩu tiền thưởng khi so sánh các ưu đãi này. Thông tin Thông tin này có thể giúp bạn tận dụng các ưu đãi mới có sẵn. Tuy nhiên, không, phản hồi thành viên có xu hướng làm nổi bật sự cần thiết cho phạm vi trò chơi nâng cao và bạn có thể nhanh hơn các thời điểm hiệu ứng hỗ trợ khách hàng nhanh hơn làm tròn phần mềm cụ thể.

game kingfun

Vì vậy, nó tự lực cho phép người tham gia xác định phương tiện hoa hồng nổi tiếng, cũng như bitcoin, đô la bitcoin, litecoin, ethereum, v.v. Có bước 1,400+ Giải pháp thay thế trò chơi trực tuyến, cơ sở đánh bạc Stardust là một trong những doanh nghiệp đánh bạc quan trọng nhất. Điều này làm cho nó trở thành một sòng bạc địa phương rất linh hoạt để bạn sử dụng phần thưởng bổ sung không nhận được doanh nghiệp đánh bạc trực tuyến của mình từ.


Publicado

en

por

Etiquetas: