Debian/Ubuntu 安装 MariaDB 完整指南

80 阅读11分钟

版本: v1.1.0 | 更新日期: 2024年11月

🆕 版本更新说明

v1.1.0 (2024-11-06)

  • 安全改进:仅在提供远程用户参数时才启用远程访问
  • 更清晰的提示:明确告知用户是否启用远程访问功能
  • 问题修复:解决用户遇到的 "Host is not allowed to connect" 错误
  • 文档增强:新增常见问题解答和故障排除指南

v1.0.0 (初始版本)

  • 一键安装 MariaDB
  • 自动配置安全选项
  • 可选的远程访问配置

📋 目录


简介

MariaDB 是 MySQL 的一个开源分支,由 MySQL 原始开发者创建,完全兼容 MySQL,并提供了更好的性能和更多的功能。本指南提供了一个一键安装脚本,可以快速在 Debian/Ubuntu 系统上部署 MariaDB 数据库服务。

为什么选择 MariaDB?

  • 完全开源:真正的开源项目,社区活跃
  • 高性能:相比 MySQL 有更好的性能优化
  • 完全兼容:与 MySQL 命令和语法完全兼容
  • 功能丰富:提供更多存储引擎和功能
  • 积极维护:更新频繁,安全补丁及时

功能特性

本安装脚本提供以下功能:

🚀 一键安装

  • 自动安装 MariaDB Server 和 Client
  • 自动配置系统服务
  • 自动设置开机自启动

🔐 安全配置

  • 设置强密码策略
  • 删除匿名用户
  • 删除测试数据库
  • 密码强度验证

🌐 远程访问

  • 配置远程访问权限
  • 创建远程管理用户
  • 自动配置防火墙规则

✅ 自动验证

  • 验证服务运行状态
  • 测试数据库连接
  • 检查端口监听
  • 显示详细配置信息

🎨 友好界面

  • 彩色输出,清晰易读
  • 实时进度显示
  • 详细的错误提示
  • 完整的安装摘要

系统要求

操作系统

  • Debian 10/11/12
  • Ubuntu 18.04/20.04/22.04/24.04
  • 其他基于 Debian 的发行版

硬件要求

  • 最低配置:1 核 CPU,512 MB RAM,5 GB 硬盘
  • 推荐配置:2 核 CPU,2 GB RAM,20 GB 硬盘

软件要求

  • Python 3.6 或更高版本
  • root 或 sudo 权限
  • 网络连接(用于下载软件包)

快速开始

方法一:仅设置 root 密码

# 下载脚本
wget https://your-url/install_mariadb.py

# 或者直接创建脚本文件(参见文末完整脚本)

# 添加执行权限
chmod +x install_mariadb.py

# 运行安装(仅设置 root 密码)
sudo python3 install_mariadb.py "MyRootPass123!"

方法二:设置 root 和远程用户

# 运行安装(设置 root 密码和远程访问用户)
sudo python3 install_mariadb.py "MyRootPass123!" "appuser" "AppUserPass123!"

方法三:使用默认远程用户名

# 默认创建名为 appuser 的远程用户,密码与 root 相同
sudo python3 install_mariadb.py "MyRootPass123!" "appuser"

详细使用说明

命令格式

sudo python3 install_mariadb.py <root密码> [远程用户名] [远程用户密码]

参数说明

参数必需说明示例
root密码✅ 是MariaDB root 用户的密码MyRootPass123!
远程用户名❌ 否远程访问的用户名(默认:appuser)appuser
远程用户密码❌ 否远程用户的密码(默认:与 root 密码相同)AppUserPass123!

密码强度要求

为了保证数据库安全,建议密码满足以下条件:

  • ✅ 长度至少 8 位
  • ✅ 包含大写字母
  • ✅ 包含小写字母
  • ✅ 包含数字
  • ✅ 包含特殊字符(如 !@#$%^&*)

示例强密码

  • MyDatabase@2024
  • Secure#Pass123
  • MariaDB$trongPwd!

安装步骤详解

脚本会自动执行以下步骤:

步骤 1:更新系统软件包

apt update

更新软件包列表,确保安装最新版本的 MariaDB。

步骤 2:安装 MariaDB

apt install -y mariadb-server mariadb-client

安装 MariaDB 服务器和客户端程序。

步骤 3:启动服务

systemctl start mariadb
systemctl enable mariadb

启动 MariaDB 服务并设置开机自启动。

步骤 4:配置 root 密码

执行以下安全配置:

  • 设置 root 用户密码
  • 删除匿名用户
  • 禁用 root 远程登录(仅允许本地)
  • 删除测试数据库
  • 刷新权限

步骤 5:创建远程访问用户(可选)

如果提供了远程用户信息,脚本会:

  • 创建新用户
  • 授予所有权限
  • 允许从任何主机连接

步骤 6:配置远程访问

修改配置文件 /etc/mysql/mariadb.conf.d/50-server.cnf

  • bind-address127.0.0.1 改为 0.0.0.0
  • 允许外部网络访问

步骤 7:配置防火墙

如果系统安装了 UFW 防火墙:

ufw allow 3306/tcp

步骤 8:重启服务

systemctl restart mariadb

使配置生效。

步骤 9:验证安装

验证以下内容:

  • ✅ 服务运行状态
  • ✅ 端口监听(3306)
  • ✅ root 用户登录
  • ✅ 远程用户配置

配置说明

主要配置文件

文件路径说明
/etc/mysql/mariadb.conf.d/50-server.cnfMariaDB 服务器配置文件
/etc/mysql/mariadb.conf.d/50-client.cnf客户端配置文件
/etc/mysql/my.cnf主配置文件

重要目录

目录路径说明
/var/lib/mysql/数据库数据存储目录
/var/log/mysql/日志文件目录
/var/run/mysqld/运行时文件(如 socket 文件)

连接方式

本地连接

# 使用 root 用户连接
mysql -u root -p

# 连接后输入密码

远程连接

# 从其他机器连接
mysql -h <服务器IP> -u appuser -p

# 示例
mysql -h 192.168.1.100 -u appuser -p

应用程序连接

Python(PyMySQL)

import pymysql

connection = pymysql.connect(
    host='服务器IP',
    user='appuser',
    password='AppUserPass123!',
    database='your_database',
    charset='utf8mb4'
)

Java(JDBC)

String url = "jdbc:mariadb://服务器IP:3306/your_database";
String user = "appuser";
String password = "AppUserPass123!";

Connection conn = DriverManager.getConnection(url, user, password);

Node.js(mysql2)

const mysql = require('mysql2');

const connection = mysql.createConnection({
    host: '服务器IP',
    user: 'appuser',
    password: 'AppUserPass123!',
    database: 'your_database'
});

PHP(MySQLi)

$conn = new mysqli("服务器IP", "appuser", "AppUserPass123!", "your_database");

常见问题

1. 远程连接错误:"Host 'xxx' is not allowed to connect"

问题:执行脚本后,使用客户端远程连接数据库时报错:

null, message from server: "Host '121.225.142.53' is not allowed to connect to this MariaDB server"

原因分析

  • 您在运行脚本时只提供了 root 密码,没有创建远程访问用户
  • root 用户被脚本限制为仅本地访问(安全考虑)
  • 没有指定远程用户参数时,脚本不会配置远程访问功能

解决方案

方案 A:创建远程访问用户(推荐)

# 登录服务器,执行以下命令
mysql -u root -p
-- 创建远程用户
CREATE USER 'appuser'@'%' IDENTIFIED BY 'YourPassword123!';

-- 授予权限
GRANT ALL PRIVILEGES ON *.* TO 'appuser'@'%' WITH GRANT OPTION;

-- 刷新权限
FLUSH PRIVILEGES;

-- 退出
EXIT;

然后使用 appuser 连接:

mysql -h 服务器IP -u appuser -p

方案 B:重新运行脚本(推荐)

# 使用完整参数重新配置
sudo python3 install_mariadb.py "YourRootPassword" "appuser" "YourUserPassword"

脚本检测到已安装会询问是否继续配置,输入 y 即可。

重要提示

  • ⚠️ 从 1.1.0 版本开始,脚本必须提供远程用户名参数才会启用远程访问
  • ✅ 这样的设计更安全,避免无意中暴露数据库给外网
  • ✅ 使用专用远程用户而非 root 用户是最佳实践

2. 安装失败:权限不足

问题:执行脚本时提示权限错误

解决方案

# 确保使用 sudo 运行
sudo python3 install_mariadb.py "YourPassword123!"

2. 需要配置 bind-address 才能远程连接

重要说明:如果您只运行了 sudo python3 install_mariadb.py "密码",脚本会:

  • ✅ 配置 root 用户(仅本地访问)
  • 不会配置远程访问
  • 不会修改 bind-address
  • 不会创建远程用户

要启用远程访问,必须提供远程用户参数:

sudo python3 install_mariadb.py "RootPass123!" "appuser" "UserPass123!"

3. 端口被占用

问题:3306 端口已被其他服务占用

解决方案

# 查看占用端口的进程
sudo netstat -tulpn | grep 3306
# 或
sudo ss -tulpn | grep 3306

# 停止占用的服务
sudo systemctl stop mysql  # 如果是 MySQL

4. 远程连接失败(进阶排查)

问题:已创建远程用户,但仍无法从远程主机连接到 MariaDB

可能原因和解决方案

原因 1:防火墙阻止

# 检查防火墙状态
sudo ufw status

# 允许 3306 端口
sudo ufw allow 3306/tcp

# 或允许特定 IP
sudo ufw allow from 192.168.1.0/24 to any port 3306

原因 2:bind-address 配置错误

# 检查配置
sudo cat /etc/mysql/mariadb.conf.d/50-server.cnf | grep bind-address

# 应该显示:bind-address = 0.0.0.0
# 如果不是,手动修改后重启
sudo systemctl restart mariadb

原因 3:用户权限不足

# 登录 MariaDB
mysql -u root -p

# 检查用户权限
SELECT User, Host FROM mysql.user;

# 如果没有 '%' 的 Host,需要创建
CREATE USER 'appuser'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'appuser'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;

原因 4:云服务器安全组未开放

如果使用云服务器(阿里云、腾讯云、AWS 等),需要在控制台的安全组中开放 3306 端口。

5. 忘记 root 密码

解决方案

# 1. 停止 MariaDB 服务
sudo systemctl stop mariadb

# 2. 以安全模式启动(跳过权限检查)
sudo mysqld_safe --skip-grant-tables &

# 3. 登录(无需密码)
mysql -u root

# 4. 重置密码
USE mysql;
UPDATE user SET password=PASSWORD('新密码') WHERE User='root';
FLUSH PRIVILEGES;
EXIT;

# 5. 停止安全模式进程
sudo killall mysqld_safe
sudo killall mysqld

# 6. 正常启动服务
sudo systemctl start mariadb

6. 服务无法启动

解决方案

# 查看服务状态
sudo systemctl status mariadb

# 查看错误日志
sudo tail -f /var/log/mysql/error.log

# 检查配置文件语法
sudo mysqld --help --verbose

# 常见问题:磁盘空间不足
df -h

# 常见问题:配置文件错误
sudo cp /etc/mysql/mariadb.conf.d/50-server.cnf.backup /etc/mysql/mariadb.conf.d/50-server.cnf

7. 中文乱码问题

解决方案

编辑配置文件 /etc/mysql/mariadb.conf.d/50-server.cnf

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

重启服务:

sudo systemctl restart mariadb

安全建议

1. 限制远程访问 IP

不要将数据库暴露给整个互联网,只允许特定 IP 访问:

# 删除所有 IP 的访问规则
sudo ufw delete allow 3306/tcp

# 只允许特定 IP 访问
sudo ufw allow from 192.168.1.100 to any port 3306

# 允许 IP 段访问
sudo ufw allow from 192.168.1.0/24 to any port 3306

2. 定期更新密码

# 登录 MariaDB
mysql -u root -p

# 修改密码
ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
FLUSH PRIVILEGES;

3. 最小权限原则

不要给所有用户 ALL PRIVILEGES,根据需要分配权限:

-- 创建只读用户
CREATE USER 'readonly'@'%' IDENTIFIED BY 'password';
GRANT SELECT ON database_name.* TO 'readonly'@'%';

-- 创建特定数据库的管理员
CREATE USER 'dbadmin'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON specific_db.* TO 'dbadmin'@'%';

FLUSH PRIVILEGES;

4. 启用 SSL/TLS 加密连接

# 检查 SSL 是否启用
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"

# 如果未启用,编辑配置文件
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

# 添加以下内容
[mysqld]
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem

5. 定期备份数据库

# 备份所有数据库
sudo mysqldump -u root -p --all-databases > backup_$(date +%Y%m%d).sql

# 备份特定数据库
sudo mysqldump -u root -p database_name > database_backup.sql

# 自动备份脚本(添加到 crontab)
# 每天凌晨 2 点备份
0 2 * * * /usr/bin/mysqldump -u root -p'密码' --all-databases | gzip > /backup/mysql_$(date +\%Y\%m\%d).sql.gz

6. 监控和日志

# 启用慢查询日志
[mysqld]
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow-query.log
long_query_time=2

# 查看日志
sudo tail -f /var/log/mysql/error.log
sudo tail -f /var/log/mysql/slow-query.log

7. 禁用不必要的功能

-- 禁用 LOCAL INFILE(防止本地文件读取漏洞)
[mysqld]
local-infile=0

-- 禁用远程 root 登录(如果不需要)
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
FLUSH PRIVILEGES;

服务管理

基本命令

# 启动服务
sudo systemctl start mariadb

# 停止服务
sudo systemctl stop mariadb

# 重启服务
sudo systemctl restart mariadb

# 重新加载配置(无需重启)
sudo systemctl reload mariadb

# 查看服务状态
sudo systemctl status mariadb

# 启用开机自启动
sudo systemctl enable mariadb

# 禁用开机自启动
sudo systemctl disable mariadb

# 查看是否开机自启动
sudo systemctl is-enabled mariadb

查看运行信息

# 查看进程
ps aux | grep mysql

# 查看监听端口
sudo netstat -tulpn | grep 3306
sudo ss -tulpn | grep 3306

# 查看连接数
mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected';"

# 查看数据库列表
mysql -u root -p -e "SHOW DATABASES;"

# 查看用户列表
mysql -u root -p -e "SELECT User, Host FROM mysql.user;"

性能优化

编辑 /etc/mysql/mariadb.conf.d/50-server.cnf

[mysqld]
# 基本优化
max_connections=200
thread_cache_size=100
table_open_cache=2000

# InnoDB 优化(根据可用内存调整)
innodb_buffer_pool_size=1G  # 建议设置为可用内存的 50-70%
innodb_log_file_size=256M
innodb_flush_log_at_trx_commit=2
innodb_flush_method=O_DIRECT

# 查询缓存(MariaDB 10.3+)
query_cache_type=1
query_cache_size=64M
query_cache_limit=2M

# 临时表
tmp_table_size=64M
max_heap_table_size=64M

# 日志
slow_query_log=1
long_query_time=2

故障排除

诊断命令

# 1. 检查服务状态
sudo systemctl status mariadb

# 2. 查看错误日志
sudo tail -100 /var/log/mysql/error.log

# 3. 检查端口监听
sudo ss -tulpn | grep 3306

# 4. 测试连接
mysql -u root -p -e "SELECT 1;"

# 5. 检查磁盘空间
df -h

# 6. 检查进程
ps aux | grep mysql

# 7. 检查配置文件语法
sudo mysqld --help --verbose | grep -A 1 'Default options'

常见错误及解决方案

错误 1:Can't connect to local MySQL server through socket

# 检查 socket 文件
ls -la /var/run/mysqld/mysqld.sock

# 如果不存在,服务可能未启动
sudo systemctl start mariadb

# 检查配置文件中的 socket 路径
cat /etc/mysql/mariadb.conf.d/50-server.cnf | grep socket

错误 2:Access denied for user

# 确认用户名和密码正确
# 检查用户权限
mysql -u root -p -e "SELECT User, Host FROM mysql.user;"

# 重新授权
GRANT ALL PRIVILEGES ON *.* TO 'username'@'host' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;

错误 3:Too many connections

# 临时增加连接数
mysql -u root -p -e "SET GLOBAL max_connections=500;"

# 永久修改:编辑配置文件
[mysqld]
max_connections=500

# 重启服务
sudo systemctl restart mariadb

错误 4:Table is marked as crashed

# 修复表
mysql -u root -p
USE database_name;
REPAIR TABLE table_name;

# 或使用 mysqlcheck
mysqlcheck -u root -p --auto-repair --all-databases

完全卸载重装

如果问题无法解决,可以完全卸载后重新安装:

# 1. 停止服务
sudo systemctl stop mariadb

# 2. 卸载软件包
sudo apt remove --purge mariadb-server mariadb-client -y

# 3. 删除数据和配置(注意:会丢失所有数据)
sudo rm -rf /var/lib/mysql
sudo rm -rf /etc/mysql
sudo rm -rf /var/log/mysql

# 4. 清理残留
sudo apt autoremove -y
sudo apt autoclean

# 5. 重新安装
sudo python3 install_mariadb.py "YourPassword123!"

性能监控

使用 MySQL 命令监控

-- 查看当前连接
SHOW PROCESSLIST;

-- 查看状态变量
SHOW STATUS;
SHOW STATUS LIKE 'Threads%';
SHOW STATUS LIKE 'Connections';

-- 查看系统变量
SHOW VARIABLES;
SHOW VARIABLES LIKE 'max_connections';

-- 查看表状态
SHOW TABLE STATUS FROM database_name;

-- 查看 InnoDB 状态
SHOW ENGINE INNODB STATUS\G

使用 mytop 监控

# 安装 mytop
sudo apt install mytop -y

# 运行监控
mytop -u root -p

使用 mysqltuner

# 下载并运行
wget http://mysqltuner.pl/ -O mysqltuner.pl
chmod +x mysqltuner.pl
sudo ./mysqltuner.pl --user root --pass '密码'

数据库操作示例

创建数据库和用户

-- 创建数据库
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建用户
CREATE USER 'myapp_user'@'%' IDENTIFIED BY 'SecurePass123!';

-- 授权
GRANT ALL PRIVILEGES ON myapp.* TO 'myapp_user'@'%';
FLUSH PRIVILEGES;

-- 验证
SHOW GRANTS FOR 'myapp_user'@'%';

导入和导出数据

# 导出数据库
mysqldump -u root -p database_name > backup.sql

# 导出数据库结构(不含数据)
mysqldump -u root -p --no-data database_name > structure.sql

# 导出特定表
mysqldump -u root -p database_name table_name > table_backup.sql

# 导入数据库
mysql -u root -p database_name < backup.sql

# 或在 MySQL 中导入
mysql -u root -p
USE database_name;
SOURCE /path/to/backup.sql;

升级 MariaDB

# 1. 备份数据
sudo mysqldump -u root -p --all-databases > backup_before_upgrade.sql

# 2. 查看当前版本
mysql --version

# 3. 更新软件包列表
sudo apt update

# 4. 升级 MariaDB
sudo apt upgrade mariadb-server mariadb-client

# 5. 运行升级脚本
sudo mysql_upgrade -u root -p

# 6. 重启服务
sudo systemctl restart mariadb

卸载说明

如果需要卸载 MariaDB:

# 1. 备份数据(重要!)
sudo mysqldump -u root -p --all-databases > final_backup.sql

# 2. 停止服务
sudo systemctl stop mariadb

# 3. 卸载软件包
sudo apt remove --purge mariadb-server mariadb-client -y

# 4. 删除数据目录(如果需要)
sudo rm -rf /var/lib/mysql

# 5. 删除配置文件(如果需要)
sudo rm -rf /etc/mysql

# 6. 清理
sudo apt autoremove -y
sudo apt autoclean

总结

本安装脚本提供了一个快速、安全、可靠的方式来部署 MariaDB 数据库服务。通过自动化安装和配置过程,您可以在几分钟内拥有一个生产就绪的数据库环境。

关键要点

  • ✅ 使用强密码保护数据库
  • ✅ 限制远程访问 IP 范围
  • ✅ 定期备份数据库
  • ✅ 监控数据库性能
  • ✅ 及时更新安全补丁
  • ✅ 遵循最小权限原则

相关资源


完整安装脚本

将以下脚本保存为 install_mariadb.py,然后按照上述说明运行。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MariaDB 一键安装脚本
使用方法:
    sudo python3 install_mariadb.py <root密码> [远程用户名] [远程用户密码]
    
示例:
    sudo python3 install_mariadb.py MyRootPass123! appuser AppUserPass123!
    sudo python3 install_mariadb.py MyRootPass123!  # 只设置 root 密码
"""

import os
import sys
import subprocess
import time
import re

# 颜色输出
class Colors:
    GREEN = '\033[0;32m'
    YELLOW = '\033[1;33m'
    RED = '\033[0;31m'
    BLUE = '\033[0;34m'
    CYAN = '\033[0;36m'
    NC = '\033[0m'  # No Color
    BOLD = '\033[1m'

def print_step(message):
    """打印步骤信息"""
    print(f"\n{Colors.CYAN}{'='*60}{Colors.NC}")
    print(f"{Colors.GREEN}{Colors.BOLD}{message}{Colors.NC}")
    print(f"{Colors.CYAN}{'='*60}{Colors.NC}\n")

def print_success(message):
    """打印成功信息"""
    print(f"{Colors.GREEN}{message}{Colors.NC}")

def print_error(message):
    """打印错误信息"""
    print(f"{Colors.RED}{message}{Colors.NC}")

def print_warning(message):
    """打印警告信息"""
    print(f"{Colors.YELLOW}{message}{Colors.NC}")

def print_info(message):
    """打印信息"""
    print(f"{Colors.BLUE}{message}{Colors.NC}")

def run_command(command, show_output=True, check=True):
    """执行系统命令"""
    try:
        if show_output:
            result = subprocess.run(command, shell=True, check=check, 
                                   stdout=subprocess.PIPE, stderr=subprocess.PIPE, 
                                   text=True, encoding='utf-8')
            if result.stdout:
                print(result.stdout)
            if result.stderr and result.returncode != 0:
                print_error(result.stderr)
            return result.returncode == 0
        else:
            result = subprocess.run(command, shell=True, check=check,
                                   stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            return result.returncode == 0
    except subprocess.CalledProcessError as e:
        print_error(f"命令执行失败: {command}")
        print_error(f"错误: {e}")
        return False

def check_root():
    """检查是否为 root 用户"""
    if os.geteuid() != 0:
        print_error("此脚本需要 root 权限运行!")
        print_info("请使用: sudo python3 install_mariadb.py <密码>")
        sys.exit(1)

def validate_password(password):
    """验证密码强度"""
    if len(password) < 8:
        print_warning("警告: 密码长度少于 8 位,建议使用更强的密码")
        return False
    
    has_upper = bool(re.search(r'[A-Z]', password))
    has_lower = bool(re.search(r'[a-z]', password))
    has_digit = bool(re.search(r'\d', password))
    has_special = bool(re.search(r'[!@#$%^&*(),.?":{}|<>]', password))
    
    strength = sum([has_upper, has_lower, has_digit, has_special])
    
    if strength < 3:
        print_warning("警告: 密码强度较弱,建议包含大小写字母、数字和特殊字符")
        return False
    
    return True

def check_mariadb_installed():
    """检查 MariaDB 是否已安装"""
    result = subprocess.run("which mysql", shell=True, 
                          stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    return result.returncode == 0

def install_mariadb():
    """安装 MariaDB"""
    print_step("步骤 1: 更新系统软件包")
    if not run_command("apt update"):
        print_error("更新软件包列表失败!")
        return False
    print_success("软件包列表更新完成")
    
    print_step("步骤 2: 安装 MariaDB")
    print_info("正在安装 MariaDB Server 和 Client...")
    
    # 设置非交互式安装
    os.environ['DEBIAN_FRONTEND'] = 'noninteractive'
    
    if not run_command("apt install -y mariadb-server mariadb-client"):
        print_error("MariaDB 安装失败!")
        return False
    
    print_success("MariaDB 安装完成")
    
    # 查看安装版本
    result = subprocess.run("mysql --version", shell=True, 
                          capture_output=True, text=True)
    if result.returncode == 0:
        print_info(f"安装版本: {result.stdout.strip()}")
    
    return True

def start_mariadb():
    """启动 MariaDB 服务"""
    print_step("步骤 3: 启动 MariaDB 服务")
    
    # 启动服务
    if not run_command("systemctl start mariadb", show_output=False):
        print_error("启动 MariaDB 失败!")
        return False
    print_success("MariaDB 服务已启动")
    
    # 设置开机自启
    if not run_command("systemctl enable mariadb", show_output=False):
        print_warning("设置开机自启失败")
    else:
        print_success("已设置开机自启动")
    
    # 等待服务完全启动
    print_info("等待服务完全启动...")
    time.sleep(2)
    
    # 检查服务状态
    result = subprocess.run("systemctl is-active mariadb", shell=True, 
                          capture_output=True, text=True)
    if result.stdout.strip() == "active":
        print_success("MariaDB 服务运行正常")
        return True
    else:
        print_error("MariaDB 服务未正常运行")
        return False

def configure_root_password(password):
    """配置 root 密码"""
    print_step("步骤 4: 配置 root 密码")
    
    # 转义密码中的特殊字符
    escaped_password = password.replace("'", "\\'").replace('"', '\\"')
    
    sql_commands = f"""
ALTER USER 'root'@'localhost' IDENTIFIED BY '{escaped_password}';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
"""
    
    # 写入临时 SQL 文件
    sql_file = "/tmp/mariadb_setup.sql"
    with open(sql_file, 'w') as f:
        f.write(sql_commands)
    
    try:
        if run_command(f"mysql < {sql_file}", show_output=False):
            print_success("root 密码设置成功")
            print_success("已删除匿名用户")
            print_success("已删除测试数据库")
            return True
        else:
            print_error("配置失败")
            return False
    finally:
        # 删除临时文件
        if os.path.exists(sql_file):
            os.remove(sql_file)

def create_remote_user(root_password, username, user_password):
    """创建远程访问用户"""
    print_step("步骤 5: 创建远程访问用户")
    
    escaped_root_pass = root_password.replace("'", "\\'").replace('"', '\\"')
    escaped_user_pass = user_password.replace("'", "\\'").replace('"', '\\"')
    
    sql_commands = f"""
CREATE USER IF NOT EXISTS '{username}'@'%' IDENTIFIED BY '{escaped_user_pass}';
GRANT ALL PRIVILEGES ON *.* TO '{username}'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
"""
    
    sql_file = "/tmp/create_user.sql"
    with open(sql_file, 'w') as f:
        f.write(sql_commands)
    
    try:
        if run_command(f"mysql -u root -p'{escaped_root_pass}' < {sql_file}", show_output=False):
            print_success(f"远程用户 '{username}' 创建成功")
            print_success(f"用户 '{username}' 已授予所有权限")
            return True
        else:
            print_error("创建远程用户失败")
            return False
    finally:
        if os.path.exists(sql_file):
            os.remove(sql_file)

def configure_remote_access():
    """配置远程访问"""
    print_step("步骤 6: 配置远程访问")
    
    config_file = "/etc/mysql/mariadb.conf.d/50-server.cnf"
    
    # 备份原配置文件
    backup_file = f"{config_file}.backup"
    if not os.path.exists(backup_file):
        run_command(f"cp {config_file} {backup_file}", show_output=False)
        print_info(f"已备份配置文件到: {backup_file}")
    
    # 读取配置文件
    with open(config_file, 'r') as f:
        content = f.read()
    
    # 修改 bind-address
    if 'bind-address' in content:
        # 注释掉原来的 bind-address
        content = re.sub(r'^bind-address\s*=.*$', '# bind-address = 127.0.0.1', 
                        content, flags=re.MULTILINE)
        # 添加新的配置
        if '[mysqld]' in content:
            content = content.replace('[mysqld]', 
                                    '[mysqld]\n# 允许远程访问\nbind-address = 0.0.0.0')
        print_success("已修改 bind-address 为 0.0.0.0")
    else:
        # 如果没有 bind-address,添加一个
        if '[mysqld]' in content:
            content = content.replace('[mysqld]', 
                                    '[mysqld]\nbind-address = 0.0.0.0')
        print_success("已添加 bind-address = 0.0.0.0")
    
    # 写回配置文件
    with open(config_file, 'w') as f:
        f.write(content)
    
    print_success("远程访问配置完成")
    return True

def configure_firewall():
    """配置防火墙"""
    print_step("步骤 7: 配置防火墙")
    
    # 检查 UFW 是否安装
    result = subprocess.run("which ufw", shell=True, 
                          stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    if result.returncode == 0:
        # UFW 已安装
        print_info("检测到 UFW 防火墙")
        
        # 检查 UFW 状态
        result = subprocess.run("ufw status", shell=True, 
                              capture_output=True, text=True)
        
        if "inactive" in result.stdout.lower():
            print_warning("UFW 防火墙未启用")
            print_info("跳过防火墙配置(您可以稍后手动配置)")
        else:
            # 添加规则
            if run_command("ufw allow 3306/tcp", show_output=False):
                print_success("已添加防火墙规则:允许 3306 端口")
            else:
                print_warning("添加防火墙规则失败(可能已存在)")
    else:
        print_warning("未检测到 UFW 防火墙")
        print_info("如果您使用其他防火墙,请手动开放 3306 端口")
    
    return True

def restart_mariadb():
    """重启 MariaDB 服务"""
    print_step("步骤 8: 重启 MariaDB 服务")
    
    if run_command("systemctl restart mariadb", show_output=False):
        print_success("MariaDB 服务重启成功")
        time.sleep(2)
        
        # 检查服务状态
        result = subprocess.run("systemctl is-active mariadb", shell=True, 
                              capture_output=True, text=True)
        if result.stdout.strip() == "active":
            print_success("服务运行正常")
            return True
    
    print_error("MariaDB 服务重启失败")
    return False

def verify_installation(root_password, remote_user=None):
    """验证安装"""
    print_step("步骤 9: 验证安装")
    
    # 检查服务状态
    result = subprocess.run("systemctl is-active mariadb", shell=True, 
                          capture_output=True, text=True)
    if result.stdout.strip() == "active":
        print_success("✓ 服务运行正常")
    else:
        print_error("✗ 服务未运行")
        return False
    
    # 检查监听端口
    result = subprocess.run("netstat -tulpn | grep 3306 || ss -tulpn | grep 3306", 
                          shell=True, capture_output=True, text=True)
    if "3306" in result.stdout:
        if "0.0.0.0:3306" in result.stdout:
            print_success("✓ 正在监听所有接口 (0.0.0.0:3306)")
        else:
            print_success("✓ 正在监听 3306 端口")
    else:
        print_warning("⚠ 未检测到 3306 端口监听")
    
    # 测试 root 登录
    escaped_password = root_password.replace("'", "\\'").replace('"', '\\"')
    result = subprocess.run(f"mysql -u root -p'{escaped_password}' -e 'SELECT VERSION();'", 
                          shell=True, capture_output=True, text=True)
    if result.returncode == 0:
        print_success("✓ root 用户登录成功")
        version = result.stdout.strip().split('\n')[-1] if result.stdout else "未知"
        print_info(f"  MariaDB 版本: {version}")
    else:
        print_error("✗ root 用户登录失败")
        return False
    
    # 查看用户列表
    if remote_user:
        result = subprocess.run(
            f"mysql -u root -p'{escaped_password}' -e \"SELECT User, Host FROM mysql.user WHERE User IN ('root', '{remote_user}');\"",
            shell=True, capture_output=True, text=True)
        if result.returncode == 0:
            print_success(f"✓ 用户配置正确")
            print(result.stdout)
    
    return True

def print_summary(root_password, remote_user=None, remote_password=None):
    """打印安装摘要"""
    print(f"\n{Colors.GREEN}{'='*60}{Colors.NC}")
    print(f"{Colors.GREEN}{Colors.BOLD}{'  🎉 MariaDB 安装配置完成!':^60}{Colors.NC}")
    print(f"{Colors.GREEN}{'='*60}{Colors.NC}\n")
    
    # 获取服务器 IP
    result = subprocess.run("hostname -I | awk '{print $1}'", shell=True, 
                          capture_output=True, text=True)
    server_ip = result.stdout.strip() if result.returncode == 0 else "服务器IP"
    
    print(f"{Colors.BOLD}📋 配置信息:{Colors.NC}")
    print(f"  • MariaDB 版本: ", end="")
    result = subprocess.run("mysql --version", shell=True, capture_output=True, text=True)
    if result.returncode == 0:
        print(result.stdout.strip())
    
    print(f"\n{Colors.BOLD}👤 用户信息:{Colors.NC}")
    print(f"  • root 用户密码: {Colors.YELLOW}{root_password}{Colors.NC}")
    if remote_user and remote_password:
        print(f"  • 远程用户名: {Colors.YELLOW}{remote_user}{Colors.NC}")
        print(f"  • 远程用户密码: {Colors.YELLOW}{remote_password}{Colors.NC}")
    
    print(f"\n{Colors.BOLD}🔌 连接方式:{Colors.NC}")
    print(f"  • 本地连接:")
    print(f"    {Colors.CYAN}mysql -u root -p{Colors.NC}")
    
    if remote_user:
        print(f"\n  • 远程连接:")
        print(f"    {Colors.CYAN}mysql -h {server_ip} -u {remote_user} -p{Colors.NC}")
    
    print(f"\n{Colors.BOLD}🛠 服务管理:{Colors.NC}")
    print(f"  • 启动: {Colors.CYAN}systemctl start mariadb{Colors.NC}")
    print(f"  • 停止: {Colors.CYAN}systemctl stop mariadb{Colors.NC}")
    print(f"  • 重启: {Colors.CYAN}systemctl restart mariadb{Colors.NC}")
    print(f"  • 状态: {Colors.CYAN}systemctl status mariadb{Colors.NC}")
    
    print(f"\n{Colors.BOLD}📁 重要文件:{Colors.NC}")
    print(f"  • 配置文件: /etc/mysql/mariadb.conf.d/50-server.cnf")
    print(f"  • 数据目录: /var/lib/mysql")
    print(f"  • 日志文件: /var/log/mysql/error.log")
    
    print(f"\n{Colors.BOLD}⚠️  安全提醒:{Colors.NC}")
    print(f"  • 已允许外网访问 (0.0.0.0:3306)")
    print(f"  • 建议配置防火墙限制访问 IP")
    print(f"  • 定期备份数据库")
    print(f"  • 定期更新密码")
    
    if remote_user:
        print(f"\n{Colors.BOLD}🔒 防火墙建议:{Colors.NC}")
        print(f"  # 只允许特定 IP 访问")
        print(f"  {Colors.CYAN}ufw delete allow 3306/tcp{Colors.NC}")
        print(f"  {Colors.CYAN}ufw allow from 你的IP地址 to any port 3306{Colors.NC}")
    
    print(f"\n{Colors.GREEN}{'='*60}{Colors.NC}\n")

def main():
    """主函数"""
    # 检查 root 权限
    check_root()
    
    # 解析命令行参数
    if len(sys.argv) < 2:
        print_error("缺少必要参数!")
        print_info("\n使用方法:")
        print(f"  {Colors.CYAN}sudo python3 {sys.argv[0]} <root密码> [远程用户名] [远程用户密码]{Colors.NC}")
        print(f"\n示例:")
        print(f"  {Colors.CYAN}sudo python3 {sys.argv[0]} MyRootPass123! appuser AppUserPass123!{Colors.NC}")
        print(f"  {Colors.CYAN}sudo python3 {sys.argv[0]} MyRootPass123!{Colors.NC}")
        sys.exit(1)
    
    root_password = sys.argv[1]
    remote_user = sys.argv[2] if len(sys.argv) > 2 else None
    remote_password = sys.argv[3] if len(sys.argv) > 3 else None
    
    # 验证密码
    print_info("验证密码强度...")
    validate_password(root_password)
    if remote_password:
        validate_password(remote_password)
    
    print(f"\n{Colors.BLUE}{'='*60}{Colors.NC}")
    print(f"{Colors.BOLD}{Colors.BLUE}{'  MariaDB 一键安装脚本':^60}{Colors.NC}")
    print(f"{Colors.BLUE}{'='*60}{Colors.NC}\n")
    
    print_info("准备安装 MariaDB...")
    print_info(f"root 密码: {'*' * len(root_password)}")
    if remote_user:
        print_info(f"远程用户: {remote_user}")
        print_info(f"远程密码: {'*' * len(remote_password if remote_password else root_password)}")
    else:
        print_warning("未指定远程用户,将不启用远程访问功能")
        print_info("如需远程访问,请提供远程用户名和密码参数")
    
    # 检查是否已安装
    if check_mariadb_installed():
        print_warning("检测到 MariaDB 已安装")
        response = input("是否继续配置? (y/N): ")
        if response.lower() != 'y':
            print_info("安装已取消")
            sys.exit(0)
        skip_install = True
    else:
        skip_install = False
    
    try:
        # 安装 MariaDB
        if not skip_install:
            if not install_mariadb():
                print_error("安装失败!")
                sys.exit(1)
        
        # 启动服务
        if not start_mariadb():
            print_error("启动服务失败!")
            sys.exit(1)
        
        # 配置 root 密码
        if not configure_root_password(root_password):
            print_error("配置 root 密码失败!")
            sys.exit(1)
        
        # 创建远程用户
        if remote_user:
            # 如果没有指定远程密码,使用 root 密码
            final_remote_password = remote_password if remote_password else root_password
            if not create_remote_user(root_password, remote_user, final_remote_password):
                print_warning("创建远程用户失败,但可以继续")
            
            # 配置远程访问
            if not configure_remote_access():
                print_error("配置远程访问失败!")
                sys.exit(1)
            
            # 配置防火墙
            configure_firewall()
        else:
            print_warning("跳过远程访问配置(未指定远程用户)")
            print_info("仅允许本地连接,bind-address 保持默认配置")
        
        # 重启服务
        if not restart_mariadb():
            print_error("重启服务失败!")
            sys.exit(1)
        
        # 验证安装
        if not verify_installation(root_password, remote_user):
            print_error("验证安装失败!")
            sys.exit(1)
        
        # 打印摘要
        final_remote_password = remote_password if remote_password else root_password if remote_user else None
        print_summary(root_password, remote_user, final_remote_password)
        
    except KeyboardInterrupt:
        print_error("\n\n安装已被用户中断!")
        sys.exit(1)
    except Exception as e:
        print_error(f"\n发生错误: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == "__main__":
    main()


版权声明

本脚本和文档遵循 MIT 许可证,可自由使用和修改。

作者:[您的名字]
最后更新:2024年11月6日
版本:v1.1.0
许可证:MIT License


反馈与贡献

如果您在使用过程中遇到问题或有改进建议,欢迎反馈!


祝您使用愉快! 🎉