Skip to content

Feat: About to Introduce Service Mode#430

Draft
Kiramei wants to merge 30 commits into
pur1fying:masterfrom
Kiramei:master
Draft

Feat: About to Introduce Service Mode#430
Kiramei wants to merge 30 commits into
pur1fying:masterfrom
Kiramei:master

Conversation

@Kiramei
Copy link
Copy Markdown
Collaborator

@Kiramei Kiramei commented Dec 18, 2025

  1. 引入 Service 模式,为了后续能够和 baas-tauri 以及 baas-webui 进行对接。该模式不会影响目前的UI,但仍然需要经过测试,重点观察是否会在Git上出BUG;
  2. 重构了 installer.py,但不确定是否有功能遗失,旧版保存于 _installer.py 中,需要 @pur1fying 仔细核对 ;
  3. 该PR多数由打包后的 dist 前端文件组成,后期该部分更新方法考虑使用 Github Action 进行PR推送。即该文件夹会与 https://github.com/Kiramei/baas-webui/tree/gh-pages 保持同步;
  4. 有页面更新功能的接口,但是目前前端方面仍未书写,但可以先进行测试;
  5. 对于 OCRserver_installer 进行了对Git CLI的支持,默认情况下会自动选择系统级Git(该模式无进度条),若不存在,则回滚PyGit2,此举是为了适应Docker部署;

Kiramei and others added 21 commits November 21, 2025 18:37
This branch was previously covered by a revert for misoperation of PR. Now it should be restored for the following developing. - Sincere apology from Kiramei.
Feat: Log Beautification with Rich
Replaces custom logging and console output with a centralized rich-based logging format. Moves log handler and formatter setup to service/__init__.py via set_log_format(), and updates main.service.py and core/utils.py to use standard logging. Adds logging for config changes in config_manager.py and suppresses specific warnings for cleaner output.
Introduces a new update_to_latest command to the websocket API and implements a robust update mechanism in service/utils/_update.py, supporting both MirrorC and Git-based updates. Refactors update logic in service/utils/update.py to use a new GitOperationHandler abstraction, improving reliability and maintainability. Adds PID file management to main.service.py for process tracking, and updates service/runtime.py and service/app.py to support the new update command.
This Track made the docker available for building and deployment.
Introduces deploy/installer/installer.py with comprehensive installer logic, including environment setup, package installation, update mechanisms, and error handling. Updates build.bat to include --hidden-import=_cffi_backend for PyInstaller. Refactors deploy/installer/installer.py to streamline imports, configuration management, and utility classes, improving maintainability and modularity.
Refactor: installer logic refactor
@Kiramei Kiramei added this to the service-mode milestone Dec 18, 2025
@Kiramei Kiramei requested a review from pur1fying December 18, 2025 13:52
@Kiramei Kiramei self-assigned this Dec 18, 2025
@Kiramei Kiramei added 请求帮助 需要首要更加关注的问题 新的议题 新的议题,用于后续提交PR GUI相关 与UI相关的问题,提案等 功能改进 需要改进的地方 labels Dec 18, 2025
This was linked to issues Dec 18, 2025
@Kiramei Kiramei linked an issue Dec 18, 2025 that may be closed by this pull request
@Kiramei Kiramei marked this pull request as ready for review December 18, 2025 14:11
@Kiramei Kiramei requested a review from Daodanfd5 December 18, 2025 14:19
Kiramei and others added 3 commits December 18, 2025 23:56
Introduces the DotPrinter class to provide visual progress indication during git operations. Updates all usages of TemporaryDirectory to specify the parent directory for better file management. Adds calls to gc.collect() after major file operations to improve resource cleanup.
Remove the useless APT packages and Delete some cached files.
@MC-ALL
Copy link
Copy Markdown
Contributor

MC-ALL commented Dec 29, 2025

WoW, excited to see that Service Mode has been integrated. Thanks for your amazing work!

After reading the PR description and checking the code, I have a few questions and suggestions regarding engineering best practices, especially since I'm a newcomer to this project:

  1. Regarding Tests: I noticed the description mentions the need for manual verification and potential bugs. Since there are currently no automated test cases for the new backend, adding some CI tests would be much safer than relying solely on manual checks to prevent regression.
  2. Version Control: The description notes that this PR includes built dist frontend files. It is generally not recommended to store built artifacts/binaries in the Git repository as it bloats the history. A better approach might be to add the frontend repo as a Git Submodule, or simply build the frontend assets during the CI/Docker build process, rather than committing them here.
  3. Docker Support: regarding the system-wide git adaptation for Docker: drawing from my experience with projects like LibreChat, updating the application logic inside a running container via cli is usually considered an anti-pattern. The best practice is to treat containers as immutable and updates should be done by pulling a new image, not by running updates inside the container. We should ideally only mount minimal config files, not the source code.

哇,很高兴看到 Service Mode 已经集成进来了,感谢你的出色工作!

在阅读了 PR 描述和代码后,作为新手,我有几点关于工程最佳实践的疑问和建议:

  1. 关于测试:我注意到描述中提到了需要人工核对以及可能存在的 Bug。鉴于目前新后端还没有自动化测试用例,增加一些 CI 测试会比单纯依赖人工检查更稳妥,也能有效防止功能倒退。
  2. 关于版本控制: 描述中提到这个 PR 包含了构建好的 dist 前端文件。通常不建议将构建产物/二进制文件存入 Git 仓库,因为这会使提交历史变得臃肿。更好的做法可能是将前端仓库作为 Git Submodule 添加进来,或者直接在 CI/Docker 构建流程中编译前端资源,而不是把它们直接提交到这里。
  3. 关于 Docker 支持:描述中提到的针对 Docker 的 system-wide git 适配,根据我使用类似 LibreChat 这类项目的经验,通过 CLI 在运行中的容器内部更新应用逻辑通常被视为是不合适的。 最佳实践是将容器视为不可变的,更新应该通过拉取新镜像来完成,而不是在容器内部运行更新命令。理想情况下,我们应该只挂载最小化的配置文件,而不是挂载源代码。

@Kiramei
Copy link
Copy Markdown
Collaborator Author

Kiramei commented Jan 1, 2026

WoW, excited to see that Service Mode has been integrated. Thanks for your amazing work!

After reading the PR description and checking the code, I have a few questions and suggestions regarding engineering best practices, especially since I'm a newcomer to this project:

  1. Regarding Tests: I noticed the description mentions the need for manual verification and potential bugs. Since there are currently no automated test cases for the new backend, adding some CI tests would be much safer than relying solely on manual checks to prevent regression.
  2. Version Control: The description notes that this PR includes built dist frontend files. It is generally not recommended to store built artifacts/binaries in the Git repository as it bloats the history. A better approach might be to add the frontend repo as a Git Submodule, or simply build the frontend assets during the CI/Docker build process, rather than committing them here.
  3. Docker Support: regarding the system-wide git adaptation for Docker: drawing from my experience with projects like LibreChat, updating the application logic inside a running container via cli is usually considered an anti-pattern. The best practice is to treat containers as immutable and updates should be done by pulling a new image, not by running updates inside the container. We should ideally only mount minimal config files, not the source code.

哇,很高兴看到 Service Mode 已经集成进来了,感谢你的出色工作!

在阅读了 PR 描述和代码后,作为新手,我有几点关于工程最佳实践的疑问和建议:

  1. 关于测试:我注意到描述中提到了需要人工核对以及可能存在的 Bug。鉴于目前新后端还没有自动化测试用例,增加一些 CI 测试会比单纯依赖人工检查更稳妥,也能有效防止功能倒退。
  2. 关于版本控制: 描述中提到这个 PR 包含了构建好的 dist 前端文件。通常不建议将构建产物/二进制文件存入 Git 仓库,因为这会使提交历史变得臃肿。更好的做法可能是将前端仓库作为 Git Submodule 添加进来,或者直接在 CI/Docker 构建流程中编译前端资源,而不是把它们直接提交到这里。
  3. 关于 Docker 支持:描述中提到的针对 Docker 的 system-wide git 适配,根据我使用类似 LibreChat 这类项目的经验,通过 CLI 在运行中的容器内部更新应用逻辑通常被视为是不合适的。 最佳实践是将容器视为不可变的,更新应该通过拉取新镜像来完成,而不是在容器内部运行更新命令。理想情况下,我们应该只挂载最小化的配置文件,而不是挂载源代码。

感谢您的回复,关于您提到的几点:

  1. 确实自动化CI测试是比较有必要的,鉴于目前我对测试这方面不了解,我后续会进行自动测试的不足。由于我确实这方面是小白,后续如果可能还请您多多指教。
  2. 对,我的原意确实不是将前端的Built放进仓库,也考虑过使用Git Submodule的做法,但是我担心的是主仓库和辅仓库的脱钩问题。在辅仓库 Kiramei/baas-webui 中,作为主分支的 master 包含前端build工作流,其触发工作流会将built的内容存放在 gh-pages分支下,那么如果 master 发生更改后, gh-pages也会对应生成,我担心的是利用git-submodule主仓库仍旧保持原本的版本,即无法检测前端相关改变,如果要改变需要手动触发一个拉取事件,这样可能会使得任务变得更加复杂?目前我不太能够确定git-submodule,以及git-subtree带来的副作用,我可能需要您给出一个最为合理的做法,并且您提到直接在CI/Docker的构建流程中编译前端资源,我的目标不仅仅是Docker能跑,还需要保证大部分用户能使用,也能够对过往版本进行兼容,他们进行代码的拉取也能够顺利。还有,本项目存在镜像源依赖,您可能需要考虑submodule对于镜像源的支持,如果submodule绑定一个github仓库,他被mirror到了比如说gitee上,如果还是将绑定指针指向github,那就是错误的,用户可能无法正常拉取代码,用户拉取的资源我个人认为必然是能够直接使用的构建完成的文件。
  3. 这个我有考虑,我的难点在于BAAS是热更新频繁的,用Git更新是项目更新的核心,并且我之前在4.中提出过,这个docker提供的服务是可以通过页面点击按钮进行更新的,这个原理您从代码里可以得出,就是使用Git进行源码更新后,然后进行服务的自重启进行更新,并且你也可以看到我提供的 deploy/service/docker-compose.yaml 是将BAAS源码持久化的,思想来源于 dockurr/windows 等镜像,即进行数据持久化,保持在系统中的更改能够保存下来。如果按照您所说的,那么BAAS的热更是极为频繁的,如果需要改变目前的处境,就需要pur1fy在开发范式上进行重大改变,我认为是不太可能的,毕竟一个活动马上就一个更新,docker的构建也是很频繁,用户拉取也是频繁。我理想是构建一个可以长期的Docker(仅是运行环境的提供,如果运行环境变化即更新docker),这是与ALAS(似乎是需要用户自行构建,不清楚,可能需要调查)不同的点。鉴于上述理由,我认为应该谨慎讨论此案。

@MC-ALL
Copy link
Copy Markdown
Contributor

MC-ALL commented Jan 2, 2026

感谢您的回复,关于您提到的几点:

  1. 确实自动化CI测试是比较有必要的,鉴于目前我对测试这方面不了解,我后续会进行自动测试的不足。由于我确实这方面是小白,后续如果可能还请您多多指教。

  2. 对,我的原意确实不是将前端的Built放进仓库,也考虑过使用Git Submodule的做法,但是我担心的是主仓库和辅仓库的脱钩问题。在辅仓库 Kiramei/baas-webui 中,作为主分支的 master 包含前端build工作流,其触发工作流会将built的内容存放在 gh-pages分支下,那么如果 master 发生更改后, gh-pages也会对应生成,我担心的是利用git-submodule主仓库仍旧保持原本的版本,即无法检测前端相关改变,如果要改变需要手动触发一个拉取事件,这样可能会使得任务变得更加复杂?目前我不太能够确定git-submodule,以及git-subtree带来的副作用,我可能需要您给出一个最为合理的做法,并且您提到直接在CI/Docker的构建流程中编译前端资源,我的目标不仅仅是Docker能跑,还需要保证大部分用户能使用,也能够对过往版本进行兼容,他们进行代码的拉取也能够顺利。还有,本项目存在镜像源依赖,您可能需要考虑submodule对于镜像源的支持,如果submodule绑定一个github仓库,他被mirror到了比如说gitee上,如果还是将绑定指针指向github,那就是错误的,用户可能无法正常拉取代码,用户拉取的资源我个人认为必然是能够直接使用的构建完成的文件。

  3. 这个我有考虑,我的难点在于BAAS是热更新频繁的,用Git更新是项目更新的核心,并且我之前在4.中提出过,这个docker提供的服务是可以通过页面点击按钮进行更新的,这个原理您从代码里可以得出,就是使用Git进行源码更新后,然后进行服务的自重启进行更新,并且你也可以看到我提供的 deploy/service/docker-compose.yaml 是将BAAS源码持久化的,思想来源于 dockurr/windows 等镜像,即进行数据持久化,保持在系统中的更改能够保存下来。如果按照您所说的,那么BAAS的热更是极为频繁的,如果需要改变目前的处境,就需要pur1fy在开发范式上进行重大改变,我认为是不太可能的,毕竟一个活动马上就一个更新,docker的构建也是很频繁,用户拉取也是频繁。我理想是构建一个可以长期的Docker(仅是运行环境的提供,如果运行环境变化即更新docker),这是与ALAS(似乎是需要用户自行构建,不清楚,可能需要调查)不同的点。鉴于上述理由,我认为应该谨慎讨论此案。

感谢你的详细说明。对于你提到的国内镜像源分发和用户体验的难点,我非常理解。不过,关于将构建产物存入 Git 以及 Docker 的使用方式,为了项目的长期健康,我还是想进一步阐述一下我的观点。

  1. 关于 Git 仓库体积膨胀:我认为使用 git 来直接分发构建产物是不可持续的。Git 擅长处理文本差异,但并不擅长处理构建产物。每次前端代码微小的改动,生成的混淆 js 文件在 git 看来都是全新的二进制文件,Git 会永久保存每一个历史版本的副本。如下图所示,目前的 baas-webui 中,.git 文件夹的大小已经远远超过了代码本身。对于普通用户来说,这些膨胀的历史记录不仅无用,还会导致 git clone 越来越慢。如果按照高频热更的节奏,这个仓库很快就会变得巨大,这是我们应该极力避免的。

    图2之1: baas-webui 的空间占用分布图片

    图2之2: baas-ocr 的空间占用分布图片

  2. 关于多组件分发方案:对于你担心的 Submodule 难用问题,业界其实有更成熟的模式,类似于 AndroidrepoZephyrwest 工具:基于 Manifest(清单) 的分发。我们可以实现一个轻量级的 syncupdate 脚本,它读取一份清单文件 manifest.yaml ,可以是由安装程序生成的也可以是用户修改后的。里面定义了:Baas_python 主仓库的地址 & 版本OCR 二进制文件的 Release URLWebUI 前端资源的 Release URL 。脚本负责去特定的地方,可以是 GitHub Release,也可以是配置好的国内镜像源,下载特定的版本。这比 Git Submodule 更灵活,也完美解决了构建产物不进 Git 但用户依然开箱即用的问题。

  3. 关于 Docker 镜像策略:考虑到 Docker 用户大多是 Linux 用户,我依然认为不可变镜像是最佳实践。一、开箱即用。二、回滚安全:如果热更失败,用户重新 docker pull 即可恢复,这比在容器内修复杂的 git 状态要简单得多。三、层级复用:Docker 镜像是分层的。只要依赖库不常变,我们可以构建一个通用的运行库层,用户每次 pull 其实只下载了最后的代码层,速度非常快,这比在容器内运行 git 更新逻辑更安全稳定。

  4. 我想就 docker 部分作一些补充,我个人觉得使用 docker 的可能有相当大一部分是 nas 用户或是新手用户,他们往往意识不到即使容器被销毁,程序代码仍可能在本地持久化卷中残留。基于此,我个人认为应回归 docker 的标准体验,用户无需在面板上点击更新按钮,或是在创建容器时等待代码拉取。体验应与其他标准镜像保持一致,假如填写的是 latest 标签,docker 就应解决一切更新问题;假如需要回滚或锁定版本,只需修改标签即可。这种方式既符合用户习惯,也能避免因残留代码覆盖新镜像而导致的潜在问题。

综合考虑工程规范、国内网络环境以及目前现状,我个人的建议是:

  • 一期: service 功能上线(此PR)

    • 以现有的方式分发 webui 构建产物,或者是实现从 Release 直接下载
    • 提供基于 3 的分层 docker 镜像
  • 二期:实现基于清单的分发(留待以后实现)

    • 将选择镜像的功能从 installer 中解耦
    • 实现 sync 解析资源清单
    • 同步下游的资源镜像

@Kiramei
Copy link
Copy Markdown
Collaborator Author

Kiramei commented Jan 2, 2026

感谢你的详细说明。对于你提到的国内镜像源分发和用户体验的难点,我非常理解。不过,关于将构建产物存入 Git 以及 Docker 的使用方式,为了项目的长期健康,我还是想进一步阐述一下我的观点。

1. 关于 `Git` 仓库体积膨胀:我认为使用 `git` 来直接分发构建产物是不可持续的。`Git` 擅长处理文本差异,但并不擅长处理构建产物。每次前端代码微小的改动,生成的混淆 `js` 文件在 `git` 看来都是全新的二进制文件,`Git` 会永久保存每一个历史版本的副本。如下图所示,目前的 `baas-webui` 中,`.git` 文件夹的大小已经远远超过了代码本身。对于普通用户来说,这些膨胀的历史记录不仅无用,还会导致 `git clone` 越来越慢。如果按照高频热更的节奏,这个仓库很快就会变得巨大,这是我们应该极力避免的。
   图2之1: `baas-webui` 的空间占用分布<img alt="图片" width="1258" height="608" src="https://private-user-images.githubusercontent.com/80183456/531433320-d4986bba-fa03-4410-94c5-a8d8dc2373e6.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjczNTc2NDUsIm5iZiI6MTc2NzM1NzM0NSwicGF0aCI6Ii84MDE4MzQ1Ni81MzE0MzMzMjAtZDQ5ODZiYmEtZmEwMy00NDEwLTk0YzUtYThkOGRjMjM3M2U2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAxMDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMTAyVDEyMzU0NVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWVmYmY0NzJlODNjYTA3M2I2NmFkZTRiYmVhYjEzMWUxYjdmNzhlNTAxMmY5MGI3MGVhNjhjZmNjNWU3ZTk1MmUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.g1RcuGsFXbPVrWgsNs0NQgZtVFGTpeQoB2YNe_eJ2j4">
   图2之2: `baas-ocr` 的空间占用分布<img alt="图片" width="1487" height="516" src="https://private-user-images.githubusercontent.com/80183456/531434354-7d46287b-1c47-4a80-a3a9-e2e81d79d66b.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjczNTc2NDUsIm5iZiI6MTc2NzM1NzM0NSwicGF0aCI6Ii84MDE4MzQ1Ni81MzE0MzQzNTQtN2Q0NjI4N2ItMWM0Ny00YTgwLWEzYTktZTJlODFkNzlkNjZiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAxMDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMTAyVDEyMzU0NVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWY1MzI3MmEyNjE4YjUyZTI4YTRkZDFjMDU3NjQwOTU0ZDAyZjI5NjZlMmJkZDAyN2U0YzlhNmQ0ZDNhNGEyOTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.c4aCUUkdfSBS_71_hlfI6JF3r818DsfOfTxsjWvBRls">

2. 关于多组件分发方案:对于你担心的 `Submodule` 难用问题,业界其实有更成熟的模式,类似于 `Android` 的 `repo` 或 `Zephyr` 的 `west` 工具:基于 `Manifest`(清单) 的分发。我们可以实现一个轻量级的 `sync` 或 `update` 脚本,它读取一份清单文件 `manifest.yaml` ,可以是由安装程序生成的也可以是用户修改后的。里面定义了:`Baas_python 主仓库的地址 & 版本`,`OCR 二进制文件的 Release URL`,`WebUI 前端资源的 Release URL` 。脚本负责去特定的地方,可以是 GitHub Release,也可以是配置好的国内镜像源,下载特定的版本。这比 `Git Submodule` 更灵活,也完美解决了构建产物不进 `Git` 但用户依然开箱即用的问题。

3. 关于 `Docker` 镜像策略:考虑到 `Docker` 用户大多是 `Linux` 用户,我依然认为不可变镜像是最佳实践。一、开箱即用。二、回滚安全:如果热更失败,用户重新 `docker pull` 即可恢复,这比在容器内修复杂的 `git` 状态要简单得多。三、层级复用:`Docker` 镜像是分层的。只要依赖库不常变,我们可以构建一个通用的运行库层,用户每次 `pull` 其实只下载了最后的代码层,速度非常快,这比在容器内运行 `git` 更新逻辑更安全稳定。

4. 我想就 `docker` 部分作一些补充,我个人觉得使用 `docker` 的可能有相当大一部分是 `nas` 用户或是新手用户,他们往往意识不到即使容器被销毁,程序代码仍可能在本地持久化卷中残留。基于此,我个人认为应回归 `docker` 的标准体验,用户无需在面板上点击更新按钮,或是在创建容器时等待代码拉取。体验应与其他标准镜像保持一致,假如填写的是 `latest` 标签,`docker` 就应解决一切更新问题;假如需要回滚或锁定版本,只需修改标签即可。这种方式既符合用户习惯,也能避免因残留代码覆盖新镜像而导致的潜在问题。

综合考虑工程规范、国内网络环境以及目前现状,我个人的建议是:

* 一期: `service` 功能上线(此PR)
  
  * 以现有的方式分发 `webui` 构建产物,或者是实现从 `Release` 直接下载
  * 提供基于 3 的分层 `docker` 镜像

* 二期:实现基于清单的分发(留待以后实现)
  
  * 将选择镜像的功能从 `installer` 中解耦
  * 实现 `sync` 解析资源清单
  * 同步下游的资源镜像
  1. 关于Git Clone, 我在修改的installer里,我建议可以直接使用浅层克隆,即只克隆最新版本,从用户的角度来看,过往的更新他们往往是没有用的,他们不会去进行手动回滚更新,我们无需把用户当作开发者对待。所以我建议可以考虑加上 --dep 1 这个参数。并且,我在个人开发中也经常使用该方式克隆代码,简单快捷。而Pull的速度,我认为不会造成很大影响,如果频繁使用且更新的话;
  2. 这个模式我可以去了解一下,感谢您提供的思路;
  3. 你说的很对,docker一般是不会考虑让用户来进行git热更新的,并且您说的“可以构建一个环境层”的建议所言极是,我们对于环境这一方面是不会进行频繁的更新,我们引入新的依赖包也只是根据需求来,这个没问题。但关于代码层,我仍有隐忧,其中比较有代表性的就是用户需要手动进行 docker pull 拉取最新的代码,然后对旧版的layer也需要进行删除处理。其次,对于docker的频繁更新是否会产生大量的存储要求,从结果上来看,您的说法可能会耗费大量build的时间,1000个commit意味着1000个镜像,存储上我觉得应该和git类似大小。并且,dockerhub等平台是否会接受如此频繁的更新,是否会产生费用(我们开发需要保证0费用支出)也是比较值得商榷的。

@MC-ALL
Copy link
Copy Markdown
Contributor

MC-ALL commented Jan 2, 2026 via email

@Kiramei
Copy link
Copy Markdown
Collaborator Author

Kiramei commented Jan 3, 2026

  1. 即使是基于浅拷贝对于分发二进制或者构建产物来说依旧是“缓兵之计”,因为体积依旧会随着不断地拉取新代码而膨胀。这是一个长期的隐患,不过为了推进项目进度我觉得可以暂时采取这个折中的方案。
  2. 关于构建时间和费用的担忧是非常负责任的,不过在现代 CI/CD 流程中,这些其实都有成熟的解法。我们完全不需要依赖 Docker Hub。GitHub 自带的 GHCR (GitHub Container Registry) 是完全免费的(对于公开仓库),且与 GitHub Actions 无缝集成。没有任何费用支出,也没有严格的上传限制。依赖包几个月不变一次,CI 会直接复用缓存。Docker 只会重新打包代码层。即使有 1000 次 commit,也只是增加了 1000 个的 layer,对于存储和传输的压力微乎其微。
  3. 用户只需要无脑执行 docker pull :latest。Docker 守护进程会自动处理增量下载和指针切换。至于旧的 Layer,Docker 会自动管理,用户不需要去底层文件系统删 Layer。Docker 提供了标准命令 docker image prune,一条命令就能自动识别并清理掉所有不再被容器使用的悬空镜像。很多 NAS 系统(如群晖)的 Docker 管理面板里甚至直接有一个清理按钮。
  4. 仅从我个人的角度来看,Docker 镜像的工作完全可以同步进行,这本质上只是 CI/CD 的打包逻辑,与核心代码功能的无关。假如需要具体的实现方案(GitHub Actions)或者测试验证,我也乐意效劳协助完成这部分的搭建。

感谢您耐心的回复,我大概了解了。最后关于费用方面一个问题:

据我了解,Github Action对于普通用户来说每月应该有2000分钟限制和500MB Artifact存储,按照项目目前的开发更新频率,您评估一下是否可能触及该限制?Ref: https://docs.github.com/zh/actions/reference/limits

开发方面,我近期在推进其他的项目,本PR可能暂时无法处理,但在大概一两周后 pur1fy 会开始对该 PR 的功能性和兼容性进行验证和测试,届时我将优先考虑他所提出的建议。等处理完功能和兼容的问题、然后我个人推进的项目完成后,我将按照您提供思路进行具体实现,届时有劳您协助该 PR 的推进了。

@MC-ALL
Copy link
Copy Markdown
Contributor

MC-ALL commented Jan 3, 2026 via email

Copy link
Copy Markdown
Contributor

@MC-ALL MC-ALL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve done a quick sweep of the areas I’m familiar with. Apart from the download logic—which is critical—the other issues aren't blockers. However, I believe the last two points are quite important for the project's long-term engineering health.

Comment thread service/runtime.py
from core.device import emulator_manager
from main import Main
from .broadcast import BroadcastChannel
from .utils import *
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we change this to explicit imports? Using import * impacts readability because it's hard to tell where functions come from. It also messes up static analysis tools.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. As it is a initial version for introducing service mode, every module is designed to simplify the code structure. As you can see, there's potential for packaging every module for different function for clarification.

Comment thread service/app.py
Comment on lines +292 to +294
elif cmd.command == "update_to_latest":
result = await context.runtime.update_to_latest()
response_payload = {"status": "ok", "data": result}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed the underlying code for the update logic. It appears to work by spawning a new process to replace the current one. Do the backend and the scripts currently share the same interpreter?

I noticed the frontend instructs users to stop all tasks. Should we also implement a check on the backend to verify and stop any running tasks before executing this command?

Also, regarding the implementation in service/runtime.py, it seems the return statement in that function is unreachable. Could we modify the flow to send a 'restart initiated' response to the frontend before the actual restart? This would also allow us to introduce a delay window where the operation could be manually cancelled.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first question, I'd say no. As you can see from deploy/installer/installer.py, the virtual env is spawned by the installer script, where the installer script is running on the packaged one with Pyinstaller. You may have doubts in the updating process. For our project has already made use of pygit2 for OCR updates, we reuse this module for dynamic update, which needs all tasks users are running to be stopped, avoiding unexpected issues. For the final question, I'd say my implementation has not been prepared for it, so your proposal could be useful, thx.

Comment thread service/utils/_update.py
Comment thread service/utils/_update.py
Comment on lines +66 to +70
with open(file_path, "wb") as download_f:
for chunk in response.iter_content(chunk_size=1024 * 64):
if not chunk:
continue
download_f.write(chunk)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a network interruption occurs during the streaming phase (inside the for chunk in response.iter_content loop), requests will raise exceptions like ChunkedEncodingError or IncompleteRead. Since the loop is outside the try-block, these errors will be unhandled and cause the entire application to crash.

Below are the results of an ugly AI-generated black-box test for your reference.

[INFO] [BadServer] Serving on port 9991

==================================================
 >>> 开始黑盒测试 (Blackbox Test) <<<
==================================================

客户端正在请求下载: http://localhost:9991/incomplete_file.zip ...
[INFO] Prepare for downloading incomplete_file.zip
[INFO] [BadServer] Sent 10 bytes and stopped.
[INFO] Server claims file size is: 1000 bytes

==================================================
 >>> 测试结果:程序崩溃 (Crash) <<<
==================================================

程序抛出了未捕获的异常: ChunkedEncodingError: ('Connection broken: IncompleteRead(10 bytes read, 990 more expected)', IncompleteRead(10 bytes read, 990 more expected))

测试结束。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ..., I see it. May a try-catch block fix can resolve this problem?

Comment thread service/app.py
Comment on lines +237 to +257
try:
if cmd.command == "start_scheduler":
if not cmd.config_id:
raise ValueError("config_id is required for start_scheduler")
result = await context.runtime.start_scheduler(cmd.config_id,
set_log=context.ensure_runtime_logger_attached)
response_payload = {"status": "ok", "data": result}
elif cmd.command == "stop_scheduler":
if not cmd.config_id:
raise ValueError("config_id is required for stop_scheduler")
result = await context.runtime.stop_scheduler(cmd.config_id)
response_payload = {"status": "ok", "data": result}
elif cmd.command == "solve":
if not cmd.config_id:
raise ValueError("config_id is required for solve")
task = cmd.payload.get("task")
if not task:
raise ValueError("task is required for solve command")
result = await context.runtime.solve_task(cmd.config_id, task,
set_log=context.ensure_runtime_logger_attached)
response_payload = {"status": "ok", "data": result}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation uses a lengthy if-elif chain to handle different commands, which results in high cyclomatic complexity and violates the Open/Closed Principle. Adding new commands requires constant modification of this entry function, making it harder to maintain and test.

Consider refactoring this into a Dictionary-based Dispatcher or the Command Pattern. You could map command strings to specific handler functions. This would separate the routing logic from the business logic (validation & execution), significantly improving readability and extensibility.

For example,

async def _handle_start_scheduler(cmd: CommandMessage, context: ServiceContext) -> Any:
    if not cmd.config_id:
        raise ValueError("config_id is required")
    return await context.runtime.start_scheduler(
        cmd.config_id, set_log=context.ensure_runtime_logger_attached
    )

async def _handle_stop_scheduler(cmd: CommandMessage, context: ServiceContext) -> Any:
    if not cmd.config_id:
        raise ValueError("config_id is required")
    return await context.runtime.stop_scheduler(cmd.config_id)

async def _handle_solve(cmd: CommandMessage, context: ServiceContext) -> Any:
    if not cmd.config_id:
        raise ValueError("config_id is required")
    task = cmd.payload.get("task")
    if not task:
        raise ValueError("task is required")
    return await context.runtime.solve_task(
        cmd.config_id, task, set_log=context.ensure_runtime_logger_attached
    )

# ...

COMMAND_HANDLERS = {
    "start_scheduler": _handle_start_scheduler,
    "stop_scheduler": _handle_stop_scheduler,
    "solve": _handle_solve
}

# ...

@app.websocket("/ws/trigger")
async def websocket_trigger(websocket: WebSocket) -> None:

# ...

            try:
                handler = COMMAND_HANDLERS.get(cmd.command)
                
                if handler:
                    result = await handler(cmd, context)
                    response_payload = {"status": "ok", "data": data}
                else:
                    raise ValueError(f"Unsupported command '{cmd.command}'")

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fix is ok, I'll consider implementing this.

Comment thread service/app.py
Comment on lines +141 to +148
except (AuthenticationError, HTTPException) as exc:
await websocket.close(code=4401, reason=str(exc))
except WebSocketDisconnect:
pass
except Exception as exc:
import traceback
traceback.print_exc()
await websocket.close(code=1011, reason=str(exc))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every WebSocket endpoint repeats the same handshake logic, exception handling (AuthenticationError, WebSocketDisconnect), and cleanup routines.

Introduce a websocket_scope async context manager. This manager should handle the handshake on entry and standardize error handling/cleanup on exit. This will reduce boilerplate significantly and ensure consistent behavior across all endpoints.

For example,

@contextlib.asynccontextmanager
async def websocket_scope(websocket: WebSocket):
    """
    Handles the standard lifecycle of a BAAS WebSocket connection:
    1. Performs handshake on entry.
    2. Catches standard exceptions (Auth, Disconnect).
    3. Yields the cipher for encryption/decryption.
    """
    try:
        # standard handshake logic moved here
        if _SHARED_SECRET is None:
            raise RuntimeError("Shared secret not initialised")
        await websocket.accept()
        session = HandshakeSession(_SHARED_SECRET)
        challenge = session.issue_challenge()
        await websocket.send_json({"type": "handshake", ...})
        # ... verify response ...
        await websocket.send_json({"type": "handshake_ok"})
        cipher = session.build_cipher()
        
        yield cipher  # Yield cipher to the inner block
        
    except (AuthenticationError, HTTPException) as exc:
        await websocket.close(code=4401, reason=str(exc))
    except WebSocketDisconnect:
        pass  # Normal closure
    except Exception as exc:
        import traceback
        traceback.print_exc()
        await websocket.close(code=1011, reason=str(exc))

Before,

@app.websocket("/ws/trigger")
async def websocket_trigger(websocket: WebSocket):
    try:
        _, cipher = await _perform_handshake(websocket)
        # ... logic ...
    except Exception as e:
        # ... error handling ...

After,

@app.websocket("/ws/trigger")
async def websocket_trigger(websocket: WebSocket):
    async with websocket_scope(websocket) as cipher:
        # ... logic using cipher ...
        # No need to worry about handshake or top-level try-except

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fix is ok, I'll consider implementing this.

@pur1fying
Copy link
Copy Markdown
Owner

有关installer.py,此次pr我想尽可能解决包括 #454 在内的一些启动器报错

  1. 其中repository path 'xxx' is not owned by current user可以通过在u盘中运行installer.py进行复现,解决方式是在git的全局配置中加上safe.directory="/path/tp/BAAS_repo"
  2. 另外考虑到BAAS启动器的参数配置越来越多,在每次打包好exe后进行人工测试会很麻烦,我打算做一个installer的测试程序,以保证它在不同更新方式和网络状态都可以正常运行

目前问题:

  1. 安装 / 更新仓库的逻辑不正确,应该完全对照 新增Mirror酱更新方式 #309 中的更新逻辑图来写 @Kiramei 如果需要的话我可以协助修改,就是原installer中try_git_install_or_update函数的内容
  2. 另外我发现目前启动器在u盘中无法正确生成venv
image

.venv/Scripts中缺失了很多文件
image
正常是这样的
image

@Kiramei
Copy link
Copy Markdown
Collaborator Author

Kiramei commented Feb 4, 2026

有关installer.py,此次pr我想尽可能解决包括 #454 在内的一些启动器报错

1. 其中`repository path 'xxx' is not owned by current user`可以通过在u盘中运行installer.py进行复现,解决方式是在git的全局配置中加上`safe.directory="/path/tp/BAAS_repo"`

2. 另外考虑到BAAS启动器的参数配置越来越多,在每次打包好exe后进行人工测试会很麻烦,我打算做一个installer的测试程序,以保证它在不同更新方式和网络状态都可以正常运行

目前问题:

1. 安装 / 更新仓库的逻辑不正确,应该完全对照 [新增Mirror酱更新方式 #309](https://github.com/pur1fying/blue_archive_auto_script/issues/309) 中的更新逻辑图来写 @Kiramei 如果需要的话我可以协助修改,就是原installer中`try_git_install_or_update`函数的内容

2. 另外我发现目前启动器在u盘中无法正确生成venv
image

.venv/Scripts中缺失了很多文件 image 正常是这样的 image

关于问题第二点,先前版本正常吗

@pur1fying
Copy link
Copy Markdown
Owner

有关installer.py,此次pr我想尽可能解决包括 #454 在内的一些启动器报错

1. 其中`repository path 'xxx' is not owned by current user`可以通过在u盘中运行installer.py进行复现,解决方式是在git的全局配置中加上`safe.directory="/path/tp/BAAS_repo"`

2. 另外考虑到BAAS启动器的参数配置越来越多,在每次打包好exe后进行人工测试会很麻烦,我打算做一个installer的测试程序,以保证它在不同更新方式和网络状态都可以正常运行

目前问题:

1. 安装 / 更新仓库的逻辑不正确,应该完全对照 [新增Mirror酱更新方式 #309](https://github.com/pur1fying/blue_archive_auto_script/issues/309) 中的更新逻辑图来写 @Kiramei 如果需要的话我可以协助修改,就是原installer中`try_git_install_or_update`函数的内容

2. 另外我发现目前启动器在u盘中无法正确生成venv
image .venv/Scripts中缺失了很多文件 image 正常是这样的 image

关于问题第二点,先前版本正常吗

这是我首次遇到,之前也没有人上报过这种问题 (这也是我首次在u盘中运行启动器)

@Kiramei
Copy link
Copy Markdown
Collaborator Author

Kiramei commented Feb 8, 2026

近期在尝试重构installer为运行库,采用类似状态机的形式进行设计,预计导入uv作为包管理器,正在研究uv的便携化,目前检验下来大概率能够兼容之前创建的 virtual venv 的 environment。

@pur1fying
Copy link
Copy Markdown
Owner

感谢您的回复,关于您提到的几点:

  1. 确实自动化CI测试是比较有必要的,鉴于目前我对测试这方面不了解,我后续会进行自动测试的不足。由于我确实这方面是小白,后续如果可能还请您多多指教。
  2. 对,我的原意确实不是将前端的Built放进仓库,也考虑过使用Git Submodule的做法,但是我担心的是主仓库和辅仓库的脱钩问题。在辅仓库 Kiramei/baas-webui 中,作为主分支的 master 包含前端build工作流,其触发工作流会将built的内容存放在 gh-pages分支下,那么如果 master 发生更改后, gh-pages也会对应生成,我担心的是利用git-submodule主仓库仍旧保持原本的版本,即无法检测前端相关改变,如果要改变需要手动触发一个拉取事件,这样可能会使得任务变得更加复杂?目前我不太能够确定git-submodule,以及git-subtree带来的副作用,我可能需要您给出一个最为合理的做法,并且您提到直接在CI/Docker的构建流程中编译前端资源,我的目标不仅仅是Docker能跑,还需要保证大部分用户能使用,也能够对过往版本进行兼容,他们进行代码的拉取也能够顺利。还有,本项目存在镜像源依赖,您可能需要考虑submodule对于镜像源的支持,如果submodule绑定一个github仓库,他被mirror到了比如说gitee上,如果还是将绑定指针指向github,那就是错误的,用户可能无法正常拉取代码,用户拉取的资源我个人认为必然是能够直接使用的构建完成的文件。
  3. 这个我有考虑,我的难点在于BAAS是热更新频繁的,用Git更新是项目更新的核心,并且我之前在4.中提出过,这个docker提供的服务是可以通过页面点击按钮进行更新的,这个原理您从代码里可以得出,就是使用Git进行源码更新后,然后进行服务的自重启进行更新,并且你也可以看到我提供的 deploy/service/docker-compose.yaml 是将BAAS源码持久化的,思想来源于 dockurr/windows 等镜像,即进行数据持久化,保持在系统中的更改能够保存下来。如果按照您所说的,那么BAAS的热更是极为频繁的,如果需要改变目前的处境,就需要pur1fy在开发范式上进行重大改变,我认为是不太可能的,毕竟一个活动马上就一个更新,docker的构建也是很频繁,用户拉取也是频繁。我理想是构建一个可以长期的Docker(仅是运行环境的提供,如果运行环境变化即更新docker),这是与ALAS(似乎是需要用户自行构建,不清楚,可能需要调查)不同的点。鉴于上述理由,我认为应该谨慎讨论此案。

感谢你的详细说明。对于你提到的国内镜像源分发和用户体验的难点,我非常理解。不过,关于将构建产物存入 Git 以及 Docker 的使用方式,为了项目的长期健康,我还是想进一步阐述一下我的观点。

  1. 关于 Git 仓库体积膨胀:我认为使用 git 来直接分发构建产物是不可持续的。Git 擅长处理文本差异,但并不擅长处理构建产物。每次前端代码微小的改动,生成的混淆 js 文件在 git 看来都是全新的二进制文件,Git 会永久保存每一个历史版本的副本。如下图所示,目前的 baas-webui 中,.git 文件夹的大小已经远远超过了代码本身。对于普通用户来说,这些膨胀的历史记录不仅无用,还会导致 git clone 越来越慢。如果按照高频热更的节奏,这个仓库很快就会变得巨大,这是我们应该极力避免的。
    图2之1: baas-webui 的空间占用分布图片
    图2之2: baas-ocr 的空间占用分布图片
  2. 关于多组件分发方案:对于你担心的 Submodule 难用问题,业界其实有更成熟的模式,类似于 AndroidrepoZephyrwest 工具:基于 Manifest(清单) 的分发。我们可以实现一个轻量级的 syncupdate 脚本,它读取一份清单文件 manifest.yaml ,可以是由安装程序生成的也可以是用户修改后的。里面定义了:Baas_python 主仓库的地址 & 版本OCR 二进制文件的 Release URLWebUI 前端资源的 Release URL 。脚本负责去特定的地方,可以是 GitHub Release,也可以是配置好的国内镜像源,下载特定的版本。这比 Git Submodule 更灵活,也完美解决了构建产物不进 Git 但用户依然开箱即用的问题。
  3. 关于 Docker 镜像策略:考虑到 Docker 用户大多是 Linux 用户,我依然认为不可变镜像是最佳实践。一、开箱即用。二、回滚安全:如果热更失败,用户重新 docker pull 即可恢复,这比在容器内修复杂的 git 状态要简单得多。三、层级复用:Docker 镜像是分层的。只要依赖库不常变,我们可以构建一个通用的运行库层,用户每次 pull 其实只下载了最后的代码层,速度非常快,这比在容器内运行 git 更新逻辑更安全稳定。
  4. 我想就 docker 部分作一些补充,我个人觉得使用 docker 的可能有相当大一部分是 nas 用户或是新手用户,他们往往意识不到即使容器被销毁,程序代码仍可能在本地持久化卷中残留。基于此,我个人认为应回归 docker 的标准体验,用户无需在面板上点击更新按钮,或是在创建容器时等待代码拉取。体验应与其他标准镜像保持一致,假如填写的是 latest 标签,docker 就应解决一切更新问题;假如需要回滚或锁定版本,只需修改标签即可。这种方式既符合用户习惯,也能避免因残留代码覆盖新镜像而导致的潜在问题。

综合考虑工程规范、国内网络环境以及目前现状,我个人的建议是:

  • 一期: service 功能上线(此PR)

    • 以现有的方式分发 webui 构建产物,或者是实现从 Release 直接下载
    • 提供基于 3 的分层 docker 镜像
  • 二期:实现基于清单的分发(留待以后实现)

    • 将选择镜像的功能从 installer 中解耦
    • 实现 sync 解析资源清单
    • 同步下游的资源镜像

我也赞成从release下载

@pur1fying
Copy link
Copy Markdown
Owner

pur1fying commented Feb 22, 2026

目前我在规划着用vue写自动战斗相关的用户界面 ( 轴文件管理, 运行, 搜索 )

自动战斗的运行涉及和C++端代码交互
轴文件管理和搜索则要与另一个远程的后端交互

如果要把这些界面也在BAAS中展示应该注意什么?
@Kiramei 麻烦帮我看一下

@MC-ALL
Copy link
Copy Markdown
Contributor

MC-ALL commented Mar 28, 2026

近期在尝试重构installer为运行库,采用类似状态机的形式进行设计,预计导入uv作为包管理器,正在研究uv的便携化,目前检验下来大概率能够兼容之前创建的 virtual venv 的 environment。

其实可以考虑做成一个 bootstrap program / 启动引导程式?分发这个启动引导程式就行?uv 是用 rust 写的,直接用 rust 还顺带解决了跨平台的问题。做成 tui / entry / frontend + backend 的架构,也够简单和可扩展了。

@Kiramei Kiramei marked this pull request as draft April 16, 2026 08:26
@Kiramei
Copy link
Copy Markdown
Collaborator Author

Kiramei commented Apr 16, 2026

近期在尝试重构installer为运行库,采用类似状态机的形式进行设计,预计导入uv作为包管理器,正在研究uv的便携化,目前检验下来大概率能够兼容之前创建的 virtual venv 的 environment。

其实可以考虑做成一个 bootstrap program / 启动引导程式?分发这个启动引导程式就行?uv 是用 rust 写的,直接用 rust 还顺带解决了跨平台的问题。做成 tui / entry / frontend + backend 的架构,也够简单和可扩展了。

参考 BAAS-Tauri 项目。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

GUI相关 与UI相关的问题,提案等 功能改进 需要改进的地方 新的议题 新的议题,用于后续提交PR 请求帮助 需要首要更加关注的问题

Projects

None yet

3 participants