# phdock **Repository Path**: ulthon/phdock ## Basic Information - **Project Name**: phdock - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-27 - **Last Updated**: 2026-06-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # PhDock — Ship PHP Apps as Native Desktop Apps > PhDock — The PHP Desktop Runtime > PhDock — Electron for PHP > PhDock — PHP 应用的桌面运行时 PhDock 是一个 PHP 桌面运行时,可以看作 Electron / Tauri 的 PHP 等价物。 它由一个 Go 编写的启动器和一个内置的 PHP-CGI 进程组成,让任何现有的 PHP Web 应用 (ThinkPHP、Laravel、WordPress、Flarum 等)无需改造即可打包成原生桌面应用。 Go 主进程负责窗口、系统托盘、进程管理与桌面能力 SDK,PHP-CGI 以 FastCGI 模式处理业务请求, 两者通过本机 HTTP 通信,最终用户拿到的是一个双击即用的桌面程序。 --- ## 特性 - **零改造打包**:现有 PHP Web 应用只需新增一个 `dock.json` 即可桌面化,无需改业务代码。 - **自带 PHP 运行时**:按 `dock.json` 声明的版本与扩展,首次启动时自动下载并缓存 PHP 二进制,最终用户无需安装 PHP。 - **桌面能力 SDK**:通知、文件对话框、剪贴板、窗口控制、系统托盘、守护进程等,PHP 通过 Composer 包或纯 curl 调用。 - **双运行模式**:同一二进制既可作为桌面应用 (`phdock run`) 弹窗运行,也可作为服务端 (`phdock serve`) 在 Linux 服务器上无 GUI 运行。 - **多应用隔离**:每个应用独立端口、独立进程组、独立数据目录,天然支持同时运行多个应用。 - **守护进程管理**:通过 `daemons` 字段声明 WebSocket、队列消费者等长驻进程,支持 `always` / `on-failure` / `never` 重启策略与指数退避。 - **内置 cron 调度**:在 `dock.json` 中声明定时任务,运行时按计划回调应用路由。 - **自动 HTTPS**:服务模式下绑定域名后,通过 certmagic 自动申请并续期 Let's Encrypt 证书。(规划中,第二阶段实现) --- ## 快速上手 1. **构建启动器**(需要本地已安装 Go 1.22+,桌面模式还需 CGO + gcc): ```bash # 桌面模式(需要 CGO + gcc,Wails 需要 -tags production) # -tags production: 启用 Wails 真实窗口(非 stub) # -ldflags "-H windowsgui": 编译为 GUI 子系统,不弹终端窗口 CGO_ENABLED=1 go build -tags production -ldflags "-H windowsgui" -o phdock.exe ./cmd # 纯服务模式(无 CGO,可交叉编译) CGO_ENABLED=0 go build -o phdock-serve ./cmd ``` 2. **在 PHP 应用根目录放置 `dock.json`**(最小示例): ```json { "id": "com.example.myapp", "name": "MyApp", "version": "1.0.0", "runtime": { "php": "8.2", "extensions": ["pdo_sqlite"] }, "web": { "port": 0, "document_root": "public", "rewrite": true } } ``` 3. **以桌面模式启动**: ```bash ./phdock run /path/to/your/php-app ``` PhDock 会自动下载 PHP 8.2、拉起 php-cgi、打开 WebView 窗口。 4. **以服务模式启动**(无 GUI,监听外网): ```bash ./phdock serve /path/to/your/php-app --bind 0.0.0.0 --port 8080 ``` 5. **在 PHP 中调用桌面能力**(可选): ```bash composer require phdock/sdk ``` ```php use PhDock\Runtime; $rt = Runtime::connect(); // 自动读取 PHDOCK_URL / PHDOCK_TOKEN $rt->notification()->send('欢迎', 'MyApp 已启动'); ``` --- ## dock.json 配置示例 下面是一份包含全部主流字段的完整示例,每个字段均已注释说明: ```jsonc { // 应用身份 "id": "com.ulthon.admin", // 反向域名,全局唯一 "name": "Ulthon Admin", // 人类可读名称,用于窗口标题 / 托盘 "version": "2.0.0", // 应用版本号 "description": "基于 ThinkPHP 的后台管理系统", "icon": "public/favicon.ico", // 应用图标(相对应用根目录) // PHP 运行时需求 "runtime": { "php": "8.2", // PHP 主版本,首次运行时按需下载 "extensions": [ // 所需扩展列表 "pdo_mysql", "gd", "zip", "fileinfo", "opcache" ], "ini": { // 注入到 php-cgi 的 php.ini 配置 "upload_max_filesize": "50M", "memory_limit": "256M" } }, // Web 层 "web": { "port": 0, // 0 = 自动从 [8100, 9000] 分配,避免多应用冲突 "document_root": "public", // PHP 文档根目录 "index": "index.php", // 默认入口文件 "rewrite": true, // 是否启用 URL 重写(ThinkPHP / WordPress 需要) "bind": "127.0.0.1" // 监听地址;桌面模式默认 127.0.0.1,服务模式 0.0.0.0 }, // 桌面窗口(仅桌面模式生效) "window": { "title": "Ulthon Admin", "width": 1280, "height": 800, "icon": "public/favicon.ico" }, // 服务模式相关 "server": { "domain": "", // 绑定域名;留空则用 IP 访问 "ssl": "auto", // auto = 有域名时自动 Let's Encrypt;none = 不启用 "workers": 4 // php-cgi worker 数量 }, // 数据库支持声明(仅供安装向导参考) "database": { "default": "sqlite", "supported": ["sqlite", "mysql"] }, // 首次启动引导 "setup": { "route": "/install", // 首次启动时打开的安装页路径 "detect": "/api/health" // GET 该路径返回 200 视为已安装,跳过引导 }, // 定时任务 "cron": [ { "schedule": "*/5 * * * *", // 标准 5 段 cron 表达式 "url": "/tools/timer/run" // 到点后由运行时对本机 Web 层发起的请求路径 } ], // 桌面能力权限声明 // true = 直接放行;"confirm" = 弹窗确认;false = 禁止 "permissions": { "notification": true, "clipboard": true, "dialog": true, "autostart": "confirm", "hotkey": "confirm", "exec": false }, // 生命周期钩子(命令在应用根目录执行) "hooks": { "pre_start": "php think migrate:run", // 启动前执行,如跑数据库迁移 "post_start": "", // 启动后执行 "pre_stop": "" // 停止前执行 }, // 守护进程(见下一节) "daemons": [ { "name": "websocket", "command": "php think websocket:start", "port": 0, // 0 = 自动从 [8200, 8300] 分配 "restart": "always", // always | on-failure | never "auto_start": true // 是否随应用启动 } ] } ``` --- ## SDK 使用示例 PhDock 通过环境变量 `PHDOCK_URL` 与 `PHDOCK_TOKEN` 把运行时地址注入 PHP 进程。 PHP 侧有两种调用方式。 ### 方式 1:Composer 包(推荐) ```bash composer require phdock/sdk ``` ```php use PhDock\Runtime; $rt = Runtime::connect(); // 自动读取 PHDOCK_URL / PHDOCK_TOKEN // 桌面通知 $rt->notification()->send('任务完成', '导出了 100 条数据'); // 文件对话框 $file = $rt->dialog()->openFile(['filters' => [['csv']]]); // 剪贴板 $rt->clipboard()->write('已复制到剪贴板'); // 窗口控制 $rt->window()->minimize(); $rt->window()->setTitle('新标题'); // 系统托盘 $rt->tray()->update('新文字'); // 查询守护进程端口 $wsPort = $rt->daemon('websocket')->getPort(); ``` ### 方式 2:纯 HTTP(零依赖) ```php $url = getenv('PHDOCK_URL'); // 如 http://127.0.0.1:19000 $token = getenv('PHDOCK_TOKEN'); $ch = curl_init("$url/api/notification.send"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', "X-Runtime-Token: $token", ], CURLOPT_POSTFIELDS => json_encode([ 'title' => '提示', 'body' => '操作完成', ]), CURLOPT_RETURNTRANSFER => true, ]); curl_exec($ch); ``` 运行时会校验 Token、检查 `dock.json` 声明的 `permissions`、对危险操作弹窗确认, 然后执行并返回结果。 --- ## 守护进程(daemons 字段) 很多 PHP 应用除了 Web 主进程,还需要长驻辅助进程:WebSocket 服务、队列消费者、 定时任务 worker 等。`daemons` 字段让这类进程由 PhDock 统一管理,而不必借助 systemd / supervisor。 ```json "daemons": [ { "name": "websocket", // 必填,应用内唯一 "command": "php think websocket:start", // 必填,任意 CLI 命令 "port": 0, // 0 = 自动从 [8200, 8300] 分配 "restart": "always", // always | on-failure | never "auto_start": true // 是否随应用启动,默认 true } ] ``` **字段语义:** | 字段 | 说明 | |------|------| | `name` | 守护进程名,应用内唯一,PHP SDK 通过它查询端口 | | `command` | 启动命令;首词为 `php` 时会被替换为 PhDock 管理的 PHP 可执行路径 | | `port` | `0` 表示自动分配(范围 8200-8300);`>0` 按指定值分配 | | `restart` | 重启策略,参考 systemd:`always` 总是重启 / `on-failure` 仅非零退出码重启 / `never` 不重启 | | `auto_start` | 是否随应用启动,默认 `true` | **重启与退避:** 失败后从 5s 起步指数退避,封顶 60s;连续失败 5 次后放弃,避免无限错误循环。 用户主动停止时通过信号通知 monitor goroutine 放弃重启。 **端口注入:** 守护进程启动时,PhDock 通过环境变量注入上下文: - `PHDOCK_DAEMON_PORT`:实际监听端口 - `PHDOCK_DAEMON_NAME`:守护进程名 - `PHDOCK_DAEMON_APP`:所属应用 ID PHP 侧可通过 `$rt->daemon('websocket')->getPort()` 查询,也可直接读 `PHDOCK_DAEMON_PORT`。 --- ## 架构概览 PhDock 是一个"两件套":Go 主进程 + PHP-CGI 子进程。 ``` PhDock(Go 启动器) ├── Go 主进程 → 系统托盘、进程管理、窗口管理、内置 cron、SDK API 服务 │ 内置 Web 层(HTTP 服务器、FastCGI 转发、自动 HTTPS) └── PHP-CGI → FastCGI 模式,处理 PHP 请求 ``` ### 桌面模式(`phdock run`) ``` Go 启动器进程 ├── 内置 Web 层(net/http + FastCGI 转发) 监听 127.0.0.1 ├── php-cgi master(2-4 个 worker,按需启动) ├── WebView 窗口(用户界面) ├── SDK HTTP API Server(供 PHP 调用桌面能力) ├── cron scheduler(定时任务调度) ├── DaemonManager(守护进程 supervisor) └── 系统托盘 ``` - 监听 `127.0.0.1`,只允许本机访问 - 弹出 WebView 窗口,用户通过窗口内界面使用应用 - 启用全部桌面能力 SDK(通知、对话框、剪贴板等) - 适合交付给非技术用户 ### 服务模式(`phdock serve`) ``` Go 启动器进程(无 GUI) ├── 内置 Web 层(net/http + certmagic 自动 HTTPS) ├── php-cgi master(worker 数量可配置) ├── cron scheduler(定时任务调度) └── DaemonManager(守护进程 supervisor) ``` - 监听 `0.0.0.0`,允许远程访问 - 不启动 WebView、系统托盘、桌面能力 SDK - 通过 CLI 管理应用(deploy / stop / start / logs) - 适合部署在 Linux 服务器上 两种模式共享同一套 `dock.json` 规范和进程管理逻辑,只是启用的组件不同。 每个应用启动一组独立进程,端口自动分配避免冲突。 ### 请求链路 ``` 用户浏览器 / WebView │ ▼ Go 内置 Web 层 (net/http) │ ├─ 静态资源 → 直接返回 │ └─ PHP 请求 → FastCGI 转发 ──→ php-cgi worker │ │ (需要桌面能力时) │ POST http://127.0.0.1:{sdk_port}/api/{cap} │ Header: X-Runtime-Token: {secret} │ ▼ Go SDK API Server │ Token 校验 → 权限校验 → 执行 ▼ 桌面能力(通知 / 对话框 / 剪贴板 ...) ``` ### 数据隔离 ``` ~/.phdock/ ├── php/ # PHP 运行时(按版本缓存) │ ├── 8.1/ │ ├── 8.2/ │ └── 8.3/ ├── extensions/ # 扩展池 ├── apps/ │ └── com.ulthon.admin/ # 应用实例 │ ├── app/ # 应用代码 │ ├── data/ # 持久化数据(storage、runtime) │ └── config/ # 用户配置(覆盖默认) └── harbor.json # 全局应用注册表 ``` --- ## 开发指南 ### 环境要求 - Go 1.22+ - PHP 8.1+(仅当本地调试 SDK 或跑 fixtures 时需要) - Windows / Linux / macOS(CGO 在桌面模式下用于原生窗口与托盘) ### 构建 ```bash # 桌面模式构建(需要 CGO + gcc,Wails 需要 -tags production 启用真实窗口) # Windows: $env:CGO_ENABLED=1; go build -tags production -ldflags "-H windowsgui" -o phdock.exe ./cmd # Linux/macOS: CGO_ENABLED=1 go build -tags production -o phdock ./cmd # 纯服务模式(无 CGO,便于交叉编译到 Linux 服务器) CGO_ENABLED=0 go build -o phdock-serve ./cmd ``` ### 运行测试 ```bash # Go 测试 go test ./... # 只跑某一包 go test ./internal/process/... # PHP SDK 测试(在 sdk-php/ 下) cd sdk-php && composer test ``` ### 目录结构 ``` uphp/ ├── cmd/ # CLI 入口(cobra 命令:run / serve / install / apps / logs ...) ├── internal/ │ ├── config/ # dock.json 解析与校验 │ ├── cron/ # 内置 cron 调度器 │ ├── installer/ # 应用安装 / 卸载逻辑 │ ├── logger/ # 结构化日志 │ ├── php/ # PHP 运行时下载、版本管理、php-cgi 启动 │ ├── process/ # 进程管理(PHP-CGI Manager + Daemon 守护进程) │ ├── sdk/ # 桌面能力 SDK 的 Go 端 HTTP API 服务 │ ├── security/ # Token 校验、权限模型 │ ├── service/ # 服务模式(无 GUI)相关逻辑 │ ├── setup/ # 首次启动引导 │ ├── tray/ # 系统托盘 │ ├── updater/ # 自动更新 │ ├── web/ # 内置 Web 层(net/http + FastCGI 转发) │ └── window/ # WebView 窗口管理 ├── sdk-php/ # PHP SDK(Composer 包 phdock/sdk) │ └── src/ │ ├── Runtime.php # SDK 入口 │ ├── Client.php # HTTP 客户端 │ ├── Daemon.php # 守护进程端口查询 │ └── Capabilities/ # notification / dialog / clipboard / window / tray ├── fixtures/ # 测试用 dock.json 等夹具 ├── spike/ # 原型验证代码 ├── phdock-brand.md # 品牌文档 ├── phruntime-design.md # 运行时设计文档 └── phruntime-products.md # 产品规划文档 ``` ### 新增一个 CLI 命令 1. 在 `cmd/` 下新建 `.go`。 2. 实现 `&cobra.Command{...}` 并在 `init()` 中用 `rootCmd.AddCommand(...)` 注册。 3. 业务逻辑放 `internal//`,保持 `cmd/` 只做参数解析与编排。 --- ## FAQ **Q1:PhDock 和 phpStudy / Laragon / XAMPP 有什么区别?** phpStudy 这类是"开发环境套件",面向开发者本地调试;PhDock 是"运行时分发", 面向最终用户。PhDock 自带 PHP、自动管理端口与守护进程、提供桌面能力 SDK, 最终产物是一个双击即用的桌面应用,用户不需要装任何东西。 **Q2:最终用户机器上没装 PHP 能跑吗?** 能。PhDock 按 `dock.json.runtime.php` 声明的版本,首次启动时从托管源下载 预编译的 PHP 二进制并缓存到 `~/.phdock/php//`,之后直接复用。 我们不自己编译 PHP,复用 php.net 官方预编译产物。 **Q3:已有的 ThinkPHP / Laravel / WordPress 项目要改代码吗?** 通常不用。只要项目有 `public/index.php` 这类单一入口并能开 URL 重写, 加一个 `dock.json` 就能跑。只有在需要调用桌面能力(通知、对话框等)时, 才需要 `composer require phdock/sdk` 并按需调用。 **Q4:桌面模式和服务模式必须用不同的二进制吗?** 不必。同一个 `phdock` 二进制既支持 `phdock run`(桌面模式)也支持 `phdock serve`(服务模式)。二者共享 `dock.json` 规范与进程管理逻辑, 只是启用的组件不同:服务模式不启动 WebView、托盘和桌面能力 SDK。 注意:Phase 1 中 `serve` 命令当前为骨架,仅打印 "service mode not implemented in phase 1" 后退出, 完整服务模式(含 PHP-FPM 管理、自动 SSL 等)在第二阶段实现。 若要交叉编译到无 CGO 环境的服务器,用 `CGO_ENABLED=0 go build` 即可。 **Q5:守护进程崩溃了怎么办?** PhDock 的 DaemonManager 参考 systemd 设计了重启策略:`always` 总是重启、 `on-failure` 仅在非零退出码时重启、`never` 不重启。失败后从 5s 起步指数退避, 封顶 60s;连续失败 5 次后放弃,避免无限错误循环。用户主动停止时通过 stop 信号 通知 monitor 放弃重启。端口和重启次数可通过 SDK 与 CLI 查询,便于诊断。 --- ## 许可证 MIT License