338 lines
12 KiB
Python
Executable File
338 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
平台兼容性检测模块
|
||
自动检测操作系统和可用库,提供统一的兼容性接口
|
||
支持 Arch Linux (PySide6) 和 CentOS 8 (Tkinter)
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import subprocess
|
||
import platform
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class PlatformCompat:
|
||
"""平台兼容性检测类"""
|
||
|
||
# GUI 后端类型
|
||
GUI_PYSIDE6 = "pyside6"
|
||
GUI_TKINTER = "tkinter"
|
||
GUI_NONE = "none"
|
||
|
||
# 操作系统类型
|
||
OS_ARCH = "arch"
|
||
OS_CENTOS = "centos"
|
||
OS_RHEL = "rhel"
|
||
OS_UBUNTU = "ubuntu"
|
||
OS_DEBIAN = "debian"
|
||
OS_UNKNOWN = "unknown"
|
||
|
||
def __init__(self):
|
||
self._gui_backend = None
|
||
self._os_type = None
|
||
self._os_version = None
|
||
self._python_version = sys.version_info
|
||
self._detected = False
|
||
|
||
def detect(self):
|
||
"""执行平台检测"""
|
||
if self._detected:
|
||
return
|
||
|
||
logger.info(f"Python版本: {self._python_version.major}.{self._python_version.minor}.{self._python_version.micro}")
|
||
logger.info(f"平台: {platform.platform()}")
|
||
logger.info(f"系统: {platform.system()}")
|
||
logger.info(f"发行版: {platform.release()}")
|
||
|
||
# 检测操作系统
|
||
self._detect_os()
|
||
|
||
# 检测GUI后端
|
||
self._detect_gui_backend()
|
||
|
||
self._detected = True
|
||
|
||
logger.info(f"检测结果 - OS: {self._os_type}, GUI后端: {self._gui_backend}")
|
||
|
||
def _detect_os(self):
|
||
"""检测操作系统类型"""
|
||
try:
|
||
# 尝试读取 /etc/os-release
|
||
if os.path.exists('/etc/os-release'):
|
||
with open('/etc/os-release', 'r') as f:
|
||
content = f.read().lower()
|
||
if 'arch' in content:
|
||
self._os_type = self.OS_ARCH
|
||
elif 'centos' in content:
|
||
self._os_type = self.OS_CENTOS
|
||
elif 'rhel' in content or 'red hat' in content:
|
||
self._os_type = self.OS_RHEL
|
||
elif 'ubuntu' in content:
|
||
self._os_type = self.OS_UBUNTU
|
||
elif 'debian' in content:
|
||
self._os_type = self.OS_DEBIAN
|
||
else:
|
||
self._os_type = self.OS_UNKNOWN
|
||
else:
|
||
# 尝试使用 platform.linux_distribution() (Python 3.6 可用)
|
||
distro = platform.dist() if hasattr(platform, 'dist') else (None, None, None)
|
||
if distro[0]:
|
||
distro_name = distro[0].lower()
|
||
if 'centos' in distro_name or 'rhel' in distro_name:
|
||
self._os_type = self.OS_CENTOS
|
||
elif 'arch' in distro_name:
|
||
self._os_type = self.OS_ARCH
|
||
else:
|
||
self._os_type = self.OS_UNKNOWN
|
||
else:
|
||
self._os_type = self.OS_UNKNOWN
|
||
except Exception as e:
|
||
logger.warning(f"检测操作系统失败: {e}")
|
||
self._os_type = self.OS_UNKNOWN
|
||
|
||
def _detect_gui_backend(self):
|
||
"""检测可用的GUI后端"""
|
||
# 首先尝试 PySide6 (需要 Python 3.7+)
|
||
if self._python_version >= (3, 7):
|
||
try:
|
||
import PySide6
|
||
self._gui_backend = self.GUI_PYSIDE6
|
||
logger.info("检测到 PySide6 可用")
|
||
return
|
||
except ImportError:
|
||
logger.debug("PySide6 不可用")
|
||
else:
|
||
logger.debug(f"Python {self._python_version.major}.{self._python_version.minor} 不支持 PySide6 (需要 3.7+)")
|
||
|
||
# 尝试 PySide2 (CentOS 8 可能可用)
|
||
try:
|
||
import PySide2
|
||
self._gui_backend = "pyside2"
|
||
logger.info("检测到 PySide2 可用")
|
||
return
|
||
except ImportError:
|
||
logger.debug("PySide2 不可用")
|
||
|
||
# 最后尝试 Tkinter (Python 内置)
|
||
try:
|
||
import tkinter
|
||
self._gui_backend = self.GUI_TKINTER
|
||
logger.info("检测到 Tkinter 可用")
|
||
return
|
||
except ImportError:
|
||
logger.debug("Tkinter 不可用")
|
||
|
||
self._gui_backend = self.GUI_NONE
|
||
logger.error("没有检测到可用的GUI后端")
|
||
|
||
def get_gui_backend(self):
|
||
"""获取检测到的GUI后端"""
|
||
if not self._detected:
|
||
self.detect()
|
||
return self._gui_backend
|
||
|
||
def get_os_type(self):
|
||
"""获取检测到的操作系统类型"""
|
||
if not self._detected:
|
||
self.detect()
|
||
return self._os_type
|
||
|
||
def is_pyside6_available(self):
|
||
"""检查 PySide6 是否可用"""
|
||
return self.get_gui_backend() == self.GUI_PYSIDE6
|
||
|
||
def is_tkinter_available(self):
|
||
"""检查 Tkinter 是否可用"""
|
||
return self.get_gui_backend() == self.GUI_TKINTER
|
||
|
||
def get_recommended_entry_point(self):
|
||
"""
|
||
获取推荐的入口点模块名
|
||
统一使用 Tkinter 版本
|
||
返回: (模块名, 类名)
|
||
"""
|
||
return ("mainwindow_tkinter", "MainWindow")
|
||
|
||
def get_system_command_compat(self):
|
||
"""
|
||
获取系统命令兼容性配置
|
||
不同发行版的命令参数可能有差异
|
||
"""
|
||
os_type = self.get_os_type()
|
||
|
||
# 默认配置
|
||
config = {
|
||
'lsblk_json_available': True, # lsblk -J 支持
|
||
'parted_script_mode': True, # parted -s 支持
|
||
'lvm_json_report': True, # LVM JSON 报告格式
|
||
'mdadm_conf_paths': [
|
||
'/etc/mdadm/mdadm.conf',
|
||
'/etc/mdadm.conf'
|
||
],
|
||
}
|
||
|
||
# CentOS/RHEL 8 特定配置
|
||
if os_type in (self.OS_CENTOS, self.OS_RHEL):
|
||
# CentOS 8 的 lsblk 支持 -J,但某些旧版本可能有问题
|
||
# 检查实际版本
|
||
try:
|
||
result = subprocess.run(['lsblk', '--version'],
|
||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
version_output = result.stdout.decode('utf-8', errors='ignore') + result.stderr.decode('utf-8', errors='ignore')
|
||
# util-linux 2.23+ 支持 JSON
|
||
if '2.23' in version_output or '2.3' in version_output:
|
||
config['lsblk_json_available'] = True
|
||
else:
|
||
# 尝试解析版本号
|
||
import re
|
||
version_match = re.search(r'(\d+)\.(\d+)', version_output)
|
||
if version_match:
|
||
major = int(version_match.group(1))
|
||
minor = int(version_match.group(2))
|
||
config['lsblk_json_available'] = (major > 2) or (major == 2 and minor >= 23)
|
||
except Exception:
|
||
pass
|
||
|
||
# CentOS 8 的 LVM 版本支持 JSON 报告
|
||
try:
|
||
result = subprocess.run(['lvs', '--version'],
|
||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||
# LVM 2.2.02.166+ 支持 JSON
|
||
version_output = result.stdout.decode('utf-8', errors='ignore') + result.stderr.decode('utf-8', errors='ignore')
|
||
import re
|
||
version_match = re.search(r'(\d+)\.(\d+)\.(\d+)\.(\d+)', version_output)
|
||
if version_match:
|
||
major = int(version_match.group(1))
|
||
minor = int(version_match.group(2))
|
||
config['lvm_json_report'] = (major >= 2) and (minor >= 2)
|
||
except Exception:
|
||
pass
|
||
|
||
return config
|
||
|
||
def get_installation_guide(self):
|
||
"""获取当前系统的安装指南"""
|
||
os_type = self.get_os_type()
|
||
backend = self.get_gui_backend()
|
||
|
||
guides = []
|
||
|
||
if os_type == self.OS_CENTOS:
|
||
guides.append("CentOS 8 安装命令:")
|
||
guides.append(" sudo yum install -y python3 python3-tkinter parted mdadm lvm2")
|
||
guides.append(" sudo yum install -y dosfstools e2fsprogs xfsprogs ntfs-3g")
|
||
if backend == self.GUI_TKINTER:
|
||
guides.append(" sudo pip3 install pexpect")
|
||
elif os_type == self.OS_ARCH:
|
||
guides.append("Arch Linux 安装命令:")
|
||
guides.append(" sudo pacman -S python python-pyside6 parted mdadm lvm2")
|
||
guides.append(" sudo pacman -S dosfstools e2fsprogs xfsprogs ntfs-3g")
|
||
guides.append(" pip install pexpect")
|
||
else:
|
||
guides.append("通用安装命令:")
|
||
guides.append(" 请安装: python3, tkinter, parted, mdadm, lvm2")
|
||
guides.append(" 以及文件系统工具: e2fsprogs, xfsprogs, dosfstools, ntfs-3g")
|
||
|
||
return "\n".join(guides)
|
||
|
||
|
||
# 全局实例
|
||
_platform_compat = None
|
||
|
||
def get_platform_compat():
|
||
"""获取全局平台兼容性实例"""
|
||
global _platform_compat
|
||
if _platform_compat is None:
|
||
_platform_compat = PlatformCompat()
|
||
_platform_compat.detect()
|
||
return _platform_compat
|
||
|
||
|
||
def check_prerequisites():
|
||
"""
|
||
检查系统前提条件
|
||
返回: (是否通过, 错误信息列表)
|
||
"""
|
||
compat = get_platform_compat()
|
||
errors = []
|
||
warnings = []
|
||
|
||
# 检查 GUI 后端
|
||
if compat.get_gui_backend() == PlatformCompat.GUI_NONE:
|
||
errors.append("没有检测到可用的GUI后端 (PySide6/PySide2/Tkinter)")
|
||
|
||
# 检查必要的系统命令
|
||
required_commands = [
|
||
('lsblk', '用于获取块设备信息'),
|
||
('parted', '用于分区操作'),
|
||
('mkfs.ext4', '用于创建ext4文件系统'),
|
||
('mount', '用于挂载操作'),
|
||
]
|
||
|
||
optional_commands = [
|
||
('mdadm', '用于RAID管理'),
|
||
('pvcreate', '用于LVM物理卷管理'),
|
||
('vgcreate', '用于LVM卷组管理'),
|
||
('lvcreate', '用于LVM逻辑卷管理'),
|
||
('mkfs.xfs', '用于创建XFS文件系统'),
|
||
('mkfs.ntfs', '用于创建NTFS文件系统'),
|
||
('mkfs.vfat', '用于创建FAT32文件系统'),
|
||
]
|
||
|
||
for cmd, desc in required_commands:
|
||
if not _command_exists(cmd):
|
||
errors.append(f"缺少必要命令: {cmd} ({desc})")
|
||
|
||
for cmd, desc in optional_commands:
|
||
if not _command_exists(cmd):
|
||
warnings.append(f"缺少可选命令: {cmd} ({desc})")
|
||
|
||
# 检查权限
|
||
if os.geteuid() != 0:
|
||
warnings.append("当前不是root用户,部分功能可能需要sudo权限")
|
||
|
||
return (len(errors) == 0, errors, warnings)
|
||
|
||
|
||
def _command_exists(cmd):
|
||
"""检查命令是否存在"""
|
||
try:
|
||
# Python 3.6 兼容: 使用 stdout/stderr 参数代替 capture_output
|
||
result = subprocess.run(
|
||
['which', cmd],
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.PIPE,
|
||
check=True
|
||
)
|
||
return True
|
||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||
return False
|
||
|
||
|
||
if __name__ == "__main__":
|
||
# 测试模式
|
||
logging.basicConfig(level=logging.DEBUG)
|
||
|
||
compat = get_platform_compat()
|
||
print(f"操作系统类型: {compat.get_os_type()}")
|
||
print(f"GUI后端: {compat.get_gui_backend()}")
|
||
print(f"推荐入口点: {compat.get_recommended_entry_point()}")
|
||
print(f"系统命令兼容配置: {compat.get_system_command_compat()}")
|
||
|
||
print("\n" + compat.get_installation_guide())
|
||
|
||
print("\n前提条件检查:")
|
||
passed, errors, warnings = check_prerequisites()
|
||
if passed:
|
||
print("✓ 基本前提条件满足")
|
||
else:
|
||
print("✗ 前提条件不满足:")
|
||
for e in errors:
|
||
print(f" 错误: {e}")
|
||
|
||
for w in warnings:
|
||
print(f" 警告: {w}")
|