diff --git a/compose.yaml b/compose.yaml index 962b6b0..86c64fe 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,7 +13,7 @@ services: security_opt: [no-new-privileges:true] depends_on: docker-gateway: - condition: service_started + condition: service_healthy postgres: condition: service_healthy networks: [control] @@ -51,6 +51,11 @@ services: - /var/run/docker.sock:/var/run/docker.sock:ro group_add: - "${DOCKER_GID:-999}" + healthcheck: + test: [CMD, wget, -q, -O, /dev/null, http://127.0.0.1:8081/healthz] + interval: 2s + timeout: 2s + retries: 15 read_only: true tmpfs: - /tmp:size=16m,noexec,nosuid,nodev diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..823f60a --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,236 @@ +# CreatorHub 部署 + +本文档适用于当前阶段 A:在一台可信 Linux 主机上通过 Docker Compose 部署,且仅允许本机单用户访问。当前控制面没有认证或 CSRF 防护,PostgreSQL 使用仅限内部网络的 `trust` 认证;不要将服务反向代理到公网或共享网络。 + +## 部署内容 + +`compose.yaml` 会启动以下服务: + +- `creator-hub`:Go 1.26 控制面,同时提供 React 19/Vite 8 构建的静态页面; +- `docker-gateway`:受限 Docker API 网关,是唯一挂载 `/var/run/docker.sock` 的服务; +- `postgres`:PostgreSQL 17,数据保存在 `creatorhub_postgres` 命名卷; +- 浏览器容器:由网关按需创建,使用固定摘要的指纹浏览器镜像,Profile 保存在 `creatorhub-profile-<运行时名称>` 命名卷。 + +控制面只发布到 `127.0.0.1`。Compose 的 `creatorhub_control` 网络和网关创建的 `creatorhub_browser` 网络均为内部网络;阶段 A 的浏览器容器默认不能访问外网,也不能互相通信。 +`creator-hub` 会等待 `docker-gateway` 健康检查通过后再启动。 + +## 前置条件 + +- Linux 主机; +- Docker Engine 26 或兼容版本; +- Docker Compose v2; +- 当前用户可访问 Docker daemon; +- 可访问 `git.ipao.vip`,并已完成私有镜像仓库登录(如仓库要求认证); +- `curl`,用于部署后检查。 + +在仓库根目录执行预检: + +```bash +test -S /var/run/docker.sock +docker info >/dev/null +docker compose version +docker compose config --quiet +``` + +## 首次部署 + +以下命令应在同一个 shell、仓库根目录执行。`DOCKER_GID` 必须与宿主机 Docker socket 的组一致;`CREATORHUB_PORT` 仅控制本机监听端口。 + +```bash +export DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)" +export CREATORHUB_PORT=8080 + +docker pull git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7 +docker compose config --quiet +docker compose up --detach --build +``` + +控制面启动时会连接 PostgreSQL,并在事务和 advisory lock 保护下自动执行前向迁移。迁移失败时控制面会退出,由 Compose 按 `restart: unless-stopped` 重启;先检查日志,不要删除数据卷。 + +## 部署验证 + +```bash +curl --fail --silent --show-error \ + --retry 30 --retry-delay 2 --retry-connrefused \ + --output /dev/null "http://127.0.0.1:${CREATORHUB_PORT}/healthz" + +curl --fail --silent --show-error \ + --retry 30 --retry-delay 2 --retry-connrefused \ + "http://127.0.0.1:${CREATORHUB_PORT}/api/browsers" >/dev/null + +docker compose exec -T postgres \ + psql -U creatorhub -d creatorhub -tAc \ + 'SELECT 1 FROM schema_migration WHERE version = 1;' \ + | grep -qx 1 + +docker compose ps +``` + +健康检查应成功,浏览器列表接口应返回 JSON,迁移查询当前应输出 `1`,三个 Compose 服务应为运行状态。然后访问 ;修改过 `CREATORHUB_PORT` 时使用对应端口。 + +排障时读取结构化服务日志: + +```bash +docker compose logs --tail=200 creator-hub docker-gateway postgres +``` + +## 配置 + +Compose 部署时通常只需设置以下宿主机变量: + +| 变量 | 默认值 | 说明 | +| --- | --- | --- | +| `CREATORHUB_PORT` | `8080` | 映射到 `127.0.0.1` 的控制面端口 | +| `DOCKER_GID` | `999` | Docker socket 的宿主机组 ID;必须按实际值设置 | + +服务本身支持并校验以下环境变量;`compose.yaml` 已提供当前部署所需的值: + +| 服务 | 变量 | 当前 Compose 值 | +| --- | --- | --- | +| `creator-hub` | `LISTEN_ADDR` | 默认 `:8080` | +| `creator-hub` | `DOCKER_GATEWAY_URL` | `http://docker-gateway:8081` | +| `creator-hub` | `WEB_DIR` | 镜像内固定为 `/app/web` | +| `creator-hub` | `DATABASE_URL` | `postgres://creatorhub@postgres/creatorhub?sslmode=disable` | +| `creator-hub` | `LOG_LEVEL` | 默认 `info` | +| `docker-gateway` | `LISTEN_ADDR` | 默认 `:8081` | +| `docker-gateway` | `DOCKER_SOCKET` | 默认值和 Compose 挂载均固定为 `/var/run/docker.sock`;不能只覆盖环境变量 | +| `docker-gateway` | `BROWSER_NETWORK` | `creatorhub_browser` | +| `docker-gateway` | `LOG_LEVEL` | 默认 `info` | + +不要把凭据写入仓库或 Compose 文件。共享或生产部署必须先补充认证、CSRF 防护和独立 Docker daemon/VM(或 Docker authorization plugin),并将 PostgreSQL 改为强认证。 + +## 更新与回滚 + +更新前记录当前版本并备份数据库: + +```bash +set -Eeuo pipefail +git rev-parse HEAD +umask 077 +export BACKUP_FILE="$(pwd)/creatorhub-$(date +%Y%m%d-%H%M%S).dump" +docker compose exec -T postgres \ + pg_dump -U creatorhub -d creatorhub --format=custom \ + > "$BACKUP_FILE" +test -s "$BACKUP_FILE" +docker compose exec -T postgres pg_restore --list \ + < "$BACKUP_FILE" >/dev/null +``` + +拉取已审核版本后,重新执行部署和验证: + +```bash +set -Eeuo pipefail +git pull --ff-only +export DOCKER_GID="$(stat -c '%g' /var/run/docker.sock)" +export CREATORHUB_PORT=8080 +docker pull git.ipao.vip/rogee/fingerprint-chromium@sha256:b9f23b8e3ac640174db0dfa49e9095fe7eb06f5db55a4e7550d979b35ff3a1b7 +docker compose config --quiet +docker compose up --detach --build +``` + +数据库迁移只支持安全前进,不提供自动破坏性回滚。需要同时恢复旧代码和更新前数据库时,修改下面两个变量后**整块执行一次**;不要逐行或拆块执行。预检、恢复演练、动态容器回收、停服、主库恢复、提交切换和启动都位于同一个 fail-fast subshell 中。 + +```bash +( + set -Eeuo pipefail + + BACKUP_FILE=/absolute/path/to/creatorhub-YYYYmmdd-HHMMSS.dump + RESTORE_REV=PREVIOUS_REVIEWED_COMMIT_SHA + : "${BACKUP_FILE:?set BACKUP_FILE to the absolute archive path}" + : "${RESTORE_REV:?set RESTORE_REV to the previous reviewed commit SHA}" + test -r "$BACKUP_FILE" + test -s "$BACKUP_FILE" + git cat-file -e "${RESTORE_REV}^{commit}" + docker compose exec -T postgres pg_restore --list \ + < "$BACKUP_FILE" >/dev/null + + docker compose exec -T postgres \ + dropdb --if-exists --force -U creatorhub creatorhub_restore_check + docker compose exec -T postgres \ + createdb -U creatorhub creatorhub_restore_check + docker compose exec -T postgres \ + pg_restore -U creatorhub -d creatorhub_restore_check \ + --exit-on-error --no-owner --no-privileges \ + < "$BACKUP_FILE" + docker compose exec -T postgres \ + psql -U creatorhub -d creatorhub_restore_check -v ON_ERROR_STOP=1 -tAc \ + 'SELECT 1 FROM schema_migration WHERE version = 1;' \ + | grep -qx 1 + docker compose exec -T postgres \ + dropdb --force -U creatorhub creatorhub_restore_check + + docker ps --all --quiet \ + --filter 'name=^creatorhub-browser-' \ + --filter 'label=io.creatorhub.managed=true' \ + --filter 'label=io.creatorhub.runtime-id' \ + | xargs --no-run-if-empty docker rm --force + docker compose stop creator-hub docker-gateway + + umask 077 + PRE_ROLLBACK_BACKUP="$(pwd)/creatorhub-pre-rollback-$(date +%Y%m%d-%H%M%S).dump" + docker compose exec -T postgres \ + pg_dump -U creatorhub -d creatorhub --format=custom \ + > "$PRE_ROLLBACK_BACKUP" + test -s "$PRE_ROLLBACK_BACKUP" + docker compose exec -T postgres pg_restore --list \ + < "$PRE_ROLLBACK_BACKUP" >/dev/null + + test -r "$BACKUP_FILE" + test -s "$BACKUP_FILE" + git cat-file -e "${RESTORE_REV}^{commit}" + docker compose exec -T postgres pg_restore --list \ + < "$BACKUP_FILE" >/dev/null + + docker compose exec -T postgres \ + dropdb --if-exists --force -U creatorhub creatorhub + docker compose exec -T postgres createdb -U creatorhub creatorhub + docker compose exec -T postgres \ + pg_restore -U creatorhub -d creatorhub \ + --exit-on-error --no-owner --no-privileges \ + < "$BACKUP_FILE" + docker compose exec -T postgres \ + psql -U creatorhub -d creatorhub -v ON_ERROR_STOP=1 -tAc \ + 'SELECT 1 FROM schema_migration WHERE version = 1;' \ + | grep -qx 1 + + git switch --detach "$RESTORE_REV" + if ! docker compose up --detach --build; then + docker compose stop creator-hub docker-gateway || true + exit 1 + fi +) +``` + +任何命令失败时 subshell 立即退出;若失败发生在主库 `dropdb` 之后,应用保持停止。`docker compose up` 自身失败时也会显式停回应用服务。成功后重新执行“部署验证”。不要直接删除数据卷或手工改写迁移记录。 + +## 停止与数据保留 + +`docker compose stop/down` 不管理网关动态创建的浏览器容器;直接执行会留下运行中的浏览器和活动会话。先严格按固定容器名前缀及两个管理标签筛选并回收,命令不带 `--volumes`,因此 Profile 卷仍会保留: + +```bash +docker ps --all --quiet \ + --filter 'name=^creatorhub-browser-' \ + --filter 'label=io.creatorhub.managed=true' \ + --filter 'label=io.creatorhub.runtime-id' \ + | xargs --no-run-if-empty docker rm --force +test -z "$(docker ps --all --quiet \ + --filter 'name=^creatorhub-browser-' \ + --filter 'label=io.creatorhub.managed=true' \ + --filter 'label=io.creatorhub.runtime-id')" + +docker compose stop +``` + +恢复服务使用 `docker compose up --detach`。需要删除服务容器和网络但保留数据时使用: + +```bash +docker compose down +``` + +不要执行 `docker compose down --volumes`:它会删除 PostgreSQL 数据卷。浏览器 Profile 卷不属于 Compose 声明卷,删除浏览器容器或执行 `docker compose down` 时仍会保留;可用以下命令核对: + +```bash +docker volume ls --filter name=creatorhub-profile- +``` + +`docker.sock` 即使以只读文件方式挂载,也仍允许 Docker API 写操作,等价于宿主机 root 权限。完整安全边界、API 契约和失败语义见[《浏览器容器控制面》](architecture/container-control.md)。