# Sketch Mark 完整服务器包部署说明

适用于本仓库源码与 `SketchMark-server-<版本号>.zip` 完整部署包。完整包包含 Web、API、Sketch 插件和升级管理器，不包含生产数据库、账号、设计数据或 `.env`。首次安装，以及升级管理器或 Compose 配置发生变化时，使用完整包；网页「平台升级」使用的是另一个 `SketchMark-update-版本号.zip`，不能代替完整包。

> 修改本仓库的文件不会自动更新已生成的 ZIP。部署前应确认 ZIP 中的程序、插件和 `DEPLOY.md` 属于同一次打包。网页升级包只替换应用程序；如果现有服务器仍使用旧版升级管理器，还须用完整包更新管理器，不能只上传网页升级包。

## 部署前准备

- Linux 服务器已安装并启动 Docker Engine，且 `docker compose version` 可用。目标 ARM64/麒麟服务器需要单独验证镜像构建和运行，不能以本机 Docker Desktop 测试代替。
- 确定一个固定的部署目录，并预留数据库、上传暂存、镜像、升级预览及备份所需磁盘空间。完整包不会自动安装 Docker、配置域名、HTTPS、云安全组或异机备份。
- 部署前限制应用端口的访问来源：只允许内网/VPN，或由 HTTPS 反向代理访问。不要把未加密的 HTTP 服务直接暴露到公网。
- 仅使用自己构建且核实来源的包。ZIP 的完整性校验不等于发布者身份验证。

## 首次部署

将完整包上传到服务器，在准备使用的目录执行：

```sh
unzip SketchMark-server-版本号.zip -d sketch-mark
cd sketch-mark
docker compose version
docker compose config --quiet
chmod +x deploy.sh
./deploy.sh
docker compose ps
```

上面的 `版本号` 请替换为实际版本；直接使用仓库源码时跳过解压步骤，在仓库目录执行其余命令。

首次部署可复制 `.env.example` 为 `.env`，按自己的部署环境调整访问配置；已有部署保留原 `.env`，不要覆盖。使用实际配置的访问地址进行检查。`compose.yaml` 当前将主机端口映射到所有网络接口，必须配合防火墙或反向代理限制访问。

容器应包含 `sketchmark` 和 `updater`，前者提供网页/API，后者负责网页升级和程序回滚。首次打开实际访问地址时设置管理员账号和强密码。管理员登录后检查「插件连接」中的配置地址，并从该入口下载、安装与服务器匹配的 Sketch 插件；再进入账号菜单的「平台升级」，确认管理器就绪。

## 更新已有部署

优先在**原部署目录**覆盖解压，以保留原 `.env`、Compose 项目名及其数据卷。更新前记录运行状态并备份数据库：

```sh
cd /实际路径/sketch-mark
docker compose ps
docker compose exec -T sketchmark python -c 'import sqlite3; s=sqlite3.connect("/data/store.db"); d=sqlite3.connect("/tmp/store-backup.db"); s.backup(d); d.close(); s.close()'
docker compose cp sketchmark:/tmp/store-backup.db ./store-backup.db
docker compose exec -T sketchmark rm -f /tmp/store-backup.db
```

确认备份文件存在，并复制到服务器之外，再执行覆盖和启动：

```sh
unzip -o /上传路径/SketchMark-server-版本号.zip -d .
docker compose config --quiet
chmod +x deploy.sh
./deploy.sh
docker compose ps
docker compose logs --tail=100 sketchmark updater
```

完整包不包含 `.env` 和生产数据，但会覆盖同名程序、Compose 与说明文件；如果曾直接修改这些文件，先另存修改并检查差异。**不要运行** `docker compose down -v`、`docker volume prune` 或删除 `sketchmark-data`、`sketchmark-upgrades` 卷。前者保存数据库和上传暂存，后者保存升级记录、镜像归档及操作前备份。

如果必须换部署目录，先在旧目录执行 `docker compose ls` 记下项目名，并在新目录的 `.env` 中设置相同的 `COMPOSE_PROJECT_NAME`；否则 Compose 可能创建一套新数据卷，让页面看起来像全新安装。确认生产数据卷归属后再启动。

网页升级只更新应用代码；升级管理器或 Compose 配置有变化时仍需按本节使用完整包。已经通过网页升级到更高版本的实例，不要再运行**旧版**完整包的 `./deploy.sh`，否则可能把应用镜像降级。

## 部署后验收

在部署目录执行以下命令；`SITE_URL` 请填写实际访问地址，末尾不带斜杠：

```sh
docker compose ps
read -r -p '实际访问地址：' SITE_URL
curl -fsS "${SITE_URL}/api/health"
curl -fsS "${SITE_URL}/api/version"
docker compose logs --tail=100 sketchmark updater
```

健康接口应返回 `ok: true`，版本接口应与本次部署包一致。随后强制刷新浏览器，至少实际检查：

1. 管理员登录、已有账号及设计/历史版本仍在，画板图像、切图和底部工具栏可见；左侧栏和右侧详情能正常收起、展开。
2. 「插件连接」显示可用的配置地址，下载的 Sketch 插件版本与服务器匹配；在真实 Sketch 中打开插件菜单并做一次上传或更新已有设计，确认文件夹和版本正确。
3. 「平台升级」显示管理器就绪。涉及网页升级或程序回滚的发布，应先用隔离测试环境完成上传、预览、确认、故障恢复和数据保留演练，再操作生产。

页面版本正确但仍显示旧样式时，先强制刷新并检查反向代理/浏览器缓存；不要仅凭页脚文字判断容器是否更新。端口无法访问时检查 `docker compose ps`、日志、主机防火墙、云安全组及实际端口映射。

## 网页升级与回滚

管理员从账号菜单进入「平台升级」，上传匹配版本的 `SketchMark-update-版本号.zip`。管理器先用生产数据的隔离快照创建随机端口的预览环境，监听服务器所有网卡；页面按当前访问主机显示测试地址，可直接用 `http://服务器IP:测试端口/` 访问，无来源 IP 限制。如果管理页通过域名、反向代理或 `localhost` 打开，请将测试地址中的主机名替换为服务器 IP；同时放行该随机端口的主机防火墙与云安全组。预览包含生产设计及账号数据副本，设计读取接口可匿名访问，普通 HTTP 登录不加密；请只在可信网络下使用，并在验证后及时关闭测试环境。预览中的上传和修改**不会**同步回生产。验收通过后再点击确认升级。确认时会暂停写入、备份生产 SQLite、替换应用镜像并检查健康；失败时尝试恢复。

「历史程序版本」可对已归档的旧程序先创建预览，再确认回滚。回滚程序时保留操作时的生产账号、设计和设计版本，**不会**用旧版数据库覆盖生产；若数据库结构不兼容、归档缺失或空间不足，操作会被拒绝。即使结构兼容，旧程序也可能不理解新数据内容，必须通过真实数据快照预览验证。若页面显示「回滚失败」，不要反复操作，应保持停写并检查 `docker compose logs updater`，再由管理员人工排障。

升级/回滚归档和操作前备份仍位于生产服务器的 `sketchmark-upgrades` 卷，不是异地灾备。管理器按备份文件时间自动保留最近 100 份已结束任务的操作前数据库备份；正在执行或等待人工恢复的任务不清理，操作记录保留但已删除备份的路径不再显示。手工备份不受此策略影响；仍需单独制定异地导出和恢复演练计划。`updater` 持有 Docker socket，管理员账号及其 HTTPS 入口应按高权限系统保护。

## 数据库恢复与网络配置

仅在明确需要**把账号、设计和历史版本一并退回到备份时刻**时，才执行数据库恢复：

```sh
docker compose stop sketchmark
docker compose cp ./store-backup.db sketchmark:/data/store.db
docker compose up -d
docker compose logs --tail=100 sketchmark
```

正式对外访问应使用受信任的 HTTPS 域名，通过 Nginx/Caddy/Traefik 等反向代理转发到应用端口。Nginx 至少设置 `client_max_body_size 2m`，并为上传配置足够的读写超时；不要把 `/data`、备份或 `.env` 作为静态文件暴露。使用普通 HTTP 内网 IP 时，浏览器可能阻止自动复制到剪贴板并改为手动复制弹窗；更换域名后应重新核对「插件连接」里的配置地址。
