DevOps Toolkit:用 PySide6 打造一站式 Windows 运维桌面工具集

一个把「SSH 部署、Git 管理、Maven 构建、打包发布、JSON 处理」收敛到本地桌面的工具箱实践笔记。

一、为什么要做这个工具

日复一日,本地开发者的运维动作其实高度重复:

  • 打开终端连上服务器,备份、传 jar、重启 Docker,盯着日志等健康检查;
  • 维护七八个 Git 仓库,挨个 git pull 看有没有人推了代码;
  • 多个 Maven 工程批量 clean package
  • 前端工程 pnpm build 后压缩成 zip 发给别人。

这些动作散落在不同脚本和文档里,新同事上手要配一整套环境,老同事也经常记错远程目录或容器名。于是我决定把它们收敛到一个本地桌面应用里 —— 这就是 DevOps Toolkit

它选择桌面形态而非 Web,原因很直接:它是「本地工具箱」,直接调用本机的 git / maven / pnpm,免部署、配置落地本地、不上传任何数据,打开即用。

二、技术选型

能力选型
GUI 框架PySide6(Qt 官方 Python 绑定)
UI 风格qfluentwidgets(微软 Fluent 风格控件)
远程连接paramiko(SSH / SFTP)
密码加密cryptography(Fernet 对称加密)
打包PyInstaller

整个项目分层非常克制:

app/
├── core/        # 引擎层:部署/ Git/ Maven/ 打包/ 配置/ 加密
├── models/      # 数据模型:Environment / Project / PipelineStep
├── pages/       # 页面:仪表盘 / 部署 / Git / Maven / 打包 / 设置
├── widgets/     # 通用组件:流水线、日志、环境选择、项目卡片
├── styles.py    # 主题引擎(设计令牌 + QSS + QPalette)
└── main_window.py

三、架构亮点:三种异步模型并存

这是整个项目里最值得分享的设计决策。桌面应用的命门是「不能卡 UI」,而不同的运维动作天然适合不同的并发模型,所以核心层没有强行统一,而是按场景各取所长:

  1. DeployEngine —— threading.Thread
    部署要管理「多项目并行 + 每项目独立 SSH 连接 + 可随时取消」,用线程最直观。关键是每个项目一份隔离上下文 _ProjectCtx

    @dataclass
    class _ProjectCtx:
        project: Project
        steps: List[PipelineStep] = field(default_factory=list)
        cancelled: threading.Event = field(default_factory=threading.Event)
        ssh_client: Optional[paramiko.SSHClient] = None   # paramiko 非线程安全,必须每项目独立
        sftp_client: Optional[paramiko.SFTPClient] = None
        run_id: int = 0   # 用于取消后过滤「迟到信号」
    

    多项目通过 threading.Semaphore 控制并发度(默认按 CPU 核数推导),单项目失败不影响其他项目。run_id 的设计很巧妙:取消后立刻重新部署会启动新线程,旧线程发出的「完成」信号带旧 run_id,UI 据此直接丢弃,避免界面被过期状态污染。

  2. GitEngine / MavenEngine / ProcessRunner —— QProcess
    这类动作本质是「启动一个长命令、实时捕获 stdout」,用 QProcess 比自己管理线程更自然,也天然契合 Qt 事件循环。

  3. 信号路由
    后台线程通过 Signal 把日志和步骤状态推给 UI,信号里携带 project_key,UI 再路由到对应的 PipelineWidget / LogWidget,多个项目并行时互不串台。

四、响应式配置:单例 + Qt Signal 桥

ConfigManager 是经典的单例(__new__ + 线程锁),但它的巧思在于:它是一个纯 Python 对象,却能把配置变更「广播」成 Qt 信号,驱动所有页面自动刷新。

class _ConfigNotifier(QObject):
    environments_changed = Signal()
    projects_changed = Signal()
    config_changed = Signal(str)   # "environments" / "projects" / "settings"

class ConfigManager:
    _instance = None
    _lock = threading.Lock()
    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
            return cls._instance

当用户在「设置页」增删环境或项目,只需:

ConfigManager().notifier.config_changed.connect(_on_config_changed)

部署页、Git 页、仪表盘就会自动同步,无需手动刷新。配置落盘用原子写入tempfile + os.replace),即使写入中途崩溃也不会损坏 JSON。

五、流水线状态机与可视化

部署被抽象成一组 PipelineStep,状态机清晰:

PENDING → RUNNING → SUCCESS / FAILURE / SKIPPED / CANCELLED

后端流水线:git pull → Maven clean → Maven package → 建立 SSH → 备份 → 上传 jar → 重启容器 + 健康检查
前端流水线:git pull → pnpm 构建 → 建立 SSH → 备份 → 上传 dist

任一步骤失败会自动把后续步骤标记为 SKIPPED 并终止,UI 上的 PipelineWidget 用颜色 + 图标实时反映每个步骤的状态。

六、安全设计:机器特征绑定的密码加密

environments.json 里存的是 SSH 密码,明文落盘显然不安全。项目用 cryptography 做了与机器绑定的对称加密

def _derive_key() -> bytes:
    user = getpass.getuser()
    host = socket.gethostname()
    install_path = os.path.dirname(...)   # 安装路径
    machine_id = f"{user}@{host}:{install_path}"
    kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                     salt=_SALT, iterations=100_000)
    return base64.urlsafe_b64encode(kdf.derive(machine_id.encode()))

密钥从「用户名 + 主机名 + 安装路径」派生,所以配置文件被复制到别的机器就无法解密 —— 这本身就是一项安全特性。同时兼容旧版明文(无 enc: 前缀原样返回),平滑升级。

七、部署流水线的健壮性细节

这些细节决定了工具「能不能真的日用」:

  • 远程备份tar 打包时排除 config / logs / backups,保留最近 N 份并自动清理旧的;
  • 上传校验:jar 上传后对比本地 / 远程文件大小,不一致给黄色告警;
  • 前端保护:扁平解压 dist 内容(不创建 dist 层),且备份并恢复服务器上的 platform-config.json,避免被覆盖;
  • Docker 健康检查:重启后等待启动,再检查容器状态、进程列表、最近日志,并在日志里扫描 Exception / Error 等关键词给出潜在告警;
  • 取消机制threading.Event 置位 + 关闭 SSH 连接 + terminate() 子进程,三管齐下保证能干净停下。

八、主题系统

主题用「设计令牌 + QSS 模板 + QPalette」实现,默认暗色。页面只要实现 apply_theme 方法,就能被主窗口以鸭子类型自动接入主题切换:

for _page in pages.values():
    _apply = getattr(_page, "apply_theme", None)
    if callable(_apply):
        window.theme_changed.connect(_apply)

九、打包与分发

PyInstaller 打包最大的坑是 qfluentwidgets / paramiko 有大量动态 / 延迟导入,必须显式收集子模块,否则 exe 启动即 ModuleNotFoundError

hiddenimports = (
    collect_submodules('qfluentwidgets') +
    collect_submodules('paramiko') +
    collect_submodules('cryptography') + [...]
)
exe = EXE(..., name='DevOpsToolkit', console=False, upx=True)

打包后配置文件落到 %APPDATA%/DevOpsToolkit/configs,首次运行自动从内置资源复制默认配置(且不覆盖已有配置)。一个 build_exe.py 脚本封装了清理旧目录、依赖自检、可选 UPX 压缩,一行 python build_exe.py 即可产出独立 exe。

十、写在最后

DevOps Toolkit 不是一个试图替代 CI/CD 的大型系统,它解决的是**「开发者自己机器上那一摊重复运维活」**。它的价值在于:

  • 把散落的动作收敛成可视化流水线,错误一眼可见;
  • 多项目并行部署,省下大量等待时间;
  • 配置本地、密码加密、不上云,安全边界清晰。

如果你也厌倦了在终端里复制粘贴远程目录和容器名,不妨照着这个架构思路,给自己也攒一个顺手的小工具箱。


项目已开源(MIT License),技术栈:PySide6 + qfluentwidgets + paramiko + cryptography。

源码:
– https://gitee.com/gtnotgod/local-toolbox.git

项目截图:首页
在这里插入图片描述

项目截图:部署批处理发布到服务器
在这里插入图片描述
项目截图:本地多项目的GIT管理,批量拉取,分支合并
在这里插入图片描述
项目截图:本地多项目的Maven后端管理;多项目的常见Maven批处理Clean/Package/Deploy/Reload/强制更新依赖
在这里插入图片描述

在这里插入图片描述
项目截图:本地多项目打包管理;批量打包前后端

在这里插入图片描述
项目截图:常见工具JSON和Base64
在这里插入图片描述

项目截图:项目基础设置
在这里插入图片描述
项目截图:批处理项目部署服务器环境设置
在这里插入图片描述
项目截图:多项目本地管理
在这里插入图片描述

项目截图:项目使用的本地基础设施环境
在这里插入图片描述
项目截图:项目的关于
在这里插入图片描述

主题切换::
在这里插入图片描述

更多推荐