# 经白色机的固定文章发布流程 ## 设计 维护脚本:`tools/editorial/release.py`(本机传输)、`tools/editorial/ssh_askpass.py`(只向OpenSSH认证组件提供登记密码)、`editorial:release`(业务服务器命令)。现有文章API负责资产、草稿、版本、链接校验、原子发布、回执和搜索缓存同步,契约见[EDITORIAL_API.md](EDITORIAL_API.md)。本封装不另写文章SQL,不经ATOM。 固定路线:本机SSH→VPS-001白色机→VPS-003 `/www/wwwroot/clashfreenode-new.com`。SSH强制ProxyCommand、既有主机密钥核验,禁用配置文件和已有连接复用,不接受自定义目标/命令,也没有直连回退。白色机仅转发SSH,不保存稿件、凭据或运行定时器。 远端固定以nginx运行`php artisan editorial:release`,命令校验production环境、真实项目路径和base_url。JSON由stdin传递。服务器内通过Laravel HTTP Kernel调用原有API路由,经过原认证、校验和控制器;无需本机或公网HTTP。运行身份使用现有服务器权限,不改SSH策略或管理员凭据。 进程内临时API认证值只保存在内存,寿命10分钟,每次内部请求后恢复原配置,结束/断开杀进程即消失;不写.env或公用credential.json,不重载配置、不启用公网编辑入口。签名预览也在同一命令内渲染,公网API关闭时这些预览URL不可从外部打开。旧API的手动操作契约仍保留,周期发布改用本封装。 ## 调用 本机在项目目录执行,输出文件包含私有预览,权限600,不提交Git: ```sh python3 tools/editorial/release.py catalog --input /私有目录/empty.json --output /私有目录/catalog.json python3 tools/editorial/release.py source --input /私有目录/source.json --output /私有目录/source-result.json python3 tools/editorial/release.py prepare --input /私有目录/bundle.json --output /私有目录/prepared.json python3 tools/editorial/release.py review --input /私有目录/id.json --output /私有目录/review.json python3 tools/editorial/release.py publish --input /私有目录/publish.json --output /私有目录/published.json python3 tools/editorial/release.py status --input /私有目录/id.json --output /私有目录/status.json ``` - empty.json为`{}`;source.json为`{"article_id":真实整数ID}`。catalog/source只读,不创建release。 - bundle.json含`release_id`(UUID)、`articles`(原API字段)及可选`assets`。每个asset为`{"name":"diagram","base64":"实际PNG/JPEG/WebP/GIF字节的base64"}`,正文/封面用`file:diagram`占位;程序上传后替换为asset引用。单图≤8MiB、最多10图、总输入≤48MiB;不接收SVG,既有本站SVG可以按真实路径引用。新画的技术SVG可在本地渲染为PNG后通过资产API上传,无需为了配图部署程序。 - id.json仅`{"release_id":"原UUID"}`。服务器先保存稳定草稿ID/发布UUID,再调用API;相同bundle重复prepare不新建版本或重新抽取参数,相同release_id改正文拒绝409。改稿应显式创建新release并使用当前版本。 - review.json的`data.previews`含原模板渲染的HTML,`data.review`含hash和digest。先阅读及检查冻结版本;程序渲染通过不等于人类/浏览器视觉验收。正式HTML内含线上资源链接,禁止直接在本机浏览器打开加载;视觉检查在本地对应稿件完成。必须线上页面验收时仍按项目访问批准规则到Windows,不以取预览为由越界。 - publish.json为`{"release_id":"原UUID","expected_hash":"review中hash","review_digest":"review中digest"}`,不得盲填或以另一份稿件替换。程序先查回执;已发表则只重试后处理,草稿版本冲突返回失败。 - status会从API重新查询并核对公开正文,不只读本地台账。`phase=complete`要求内容/版本/上传图哈希和缓存/搜索/sitemap/IndexNow状态通过(无需通知的IndexNow skipped视为完成),且IndexNow通知为done或明确skipped;`published_pending`代表正文已提交但后续仍待处理,退出码2,不能据此另发正文。返回`published_at`来自服务器真实发布事务时间。 本机工具只输出结果文件路径及状态,不回显SSH认证、正文或预览。SSH断开、超时或非JSON输出都保存未知/失败状态,不自动重放写入;用原release_id查status。不存在草稿时沿原bundle恢复prepare。 ## 状态与恢复 服务器台账`storage/app/private/editorial-releases/UUID.json`,权限600;原子替换并fsync,文件锁防并发,系统会在进程退出时释放锁。保存IDs先于写入API。台账记录prepared/reviewed/publishing/complete或published_pending;唯一发表事实仍以API事务回执为准。服务器台账损坏/丢失时须从原请求UUID和API回执恢复,不能另起ID重发。 回读校验包括真实版本、标题、摘要、slug、栏目、封面、正文哈希及内容寻址图文件SHA;点赞参数随结果返回供确认。文章完整HTTP页面及外部链接状态按任务另外验证,不能把内部Kernel渲染说成外部网络/浏览器验收。没有真实新稿时不向正式站发布演练文章。 上线前仅执行`2026_10_31_150000_add_article_display_timestamp.php`迁移(新增可空`sDisplayPublishedAt`);旧文显示回退原时间。新文草稿冻结0~21600秒偏移,首次发布计算展示时间;保留created_at、实际回执、点赞起点,旧文改稿不改展示日期。回滚程序保留新增可空列和历史发布记录,不删业务数据。 验证与当前部署状态见[2026-10-31发布记录](EDITORIAL_RELEASE_2026-10-31.md)。周期调度和额度唯一见[WEEKLY_TUTORIALS.md](WEEKLY_TUTORIALS.md)。 IndexNow的结果、环境开关及服务器延时重试见[教程SEO通知](EDITORIAL_INDEXNOW.md)。通知pending只重试后处理,不再次发表正文。 2026-10-31编辑发布已接入[IndexNow通知与重试](EDITORIAL_INDEXNOW.md),复用发布回执effects;通知pending不表示正文未发布,禁止为通知重发正文。 ## 图片发布目录 2026-10-31首篇真实上传配图时发现正式 `public/images/editorial` 缺失。用户明确批准创建该专用目录并设为nginx所有、0755;已实施并完成真实文章发布。上级public/images权限未改变。以后部署预检该目录存在且执行身份nginx可写,禁止临时以root执行文章发布来绕过。回执见[首篇周期教程](ROUTING_MODES_2026-10-31.md)。