WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
# WeKnora 云镜像打包脚本(Cloud-Agnostic)
|
||||
|
||||
> **本文档面向「想把 WeKnora 打包成云镜像(AMI / 自定义镜像 / Snapshot)分发给其他人」的用户。**
|
||||
> **如果你只是想自己用 WeKnora,请直接看主仓 [README](../../README.md),`docker compose up -d` 即可。**
|
||||
|
||||
## 这套脚本能做什么
|
||||
|
||||
帮你把任意一台「能跑 Docker 的 Linux 实例」变成一份**可分发的云镜像模板**:
|
||||
|
||||
- 别人基于这份镜像创建新实例后,**首次开机会自动**:
|
||||
- 生成全新的随机密钥(DB / Redis / JWT / AES)
|
||||
- 启动 WeKnora 全部默认容器
|
||||
- 把生成的凭证写到 `/root/weknora-credentials.txt`
|
||||
- 自删除一次性初始化脚本
|
||||
- 实现「**开机即用、零私密泄漏、每实例独立密钥**」
|
||||
|
||||
适用平台(任意 systemd + Docker 的 Linux 都行):
|
||||
|
||||
- 腾讯云轻量应用服务器(Lighthouse) / 云服务器 CVM
|
||||
- AWS EC2 AMI
|
||||
- 阿里云 ECS 自定义镜像
|
||||
- 火山引擎 / 华为云 / Vultr Snapshot
|
||||
- 本地 KVM / Proxmox 模板
|
||||
|
||||
各平台具体的「制作镜像 / 共享 / 上架」操作步骤,请参考 [`docs/cloud-image/`](../../docs/cloud-image/) 下对应文档。
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
scripts/cloud-image/
|
||||
├── README.md # 本文档
|
||||
├── prepare.sh # 步骤一: 装 Docker + 拉运行时 + 装 firstboot
|
||||
├── cleanup.sh # 步骤二: 制作镜像前清理(执行后将锁定 SSH)
|
||||
├── firstboot.sh # 新实例首次开机自动执行(用户无感)
|
||||
└── systemd/
|
||||
├── weknora.service # 开机自启 docker compose
|
||||
└── weknora-firstboot.service # 首次启动 init(执行后自删)
|
||||
```
|
||||
|
||||
## 不需要 clone 整个 WeKnora 仓库
|
||||
|
||||
WeKnora 所有容器都从 Docker Hub 拉镜像(`wechatopenai/weknora-*`),Go / Python / 前端源码都不需要带到宿主机。
|
||||
|
||||
`docker-compose.yml` 实际从宿主机挂载到容器的只有:
|
||||
|
||||
```
|
||||
- ./config/config.yaml (单文件)
|
||||
- ./skills/preloaded/ (目录)
|
||||
```
|
||||
|
||||
所以镜像里需要的运行时文件**总共不到 100KB**:
|
||||
|
||||
| 文件 | 大小 | 用途 |
|
||||
|---|---|---|
|
||||
| `docker-compose.yml` | 12K | 容器编排 |
|
||||
| `.env` | 12K | 环境变量 |
|
||||
| `config/config.yaml` | 8K | 后端业务配置 |
|
||||
| `skills/preloaded/` | 56K | Agent 预置技能 |
|
||||
|
||||
`prepare.sh` 用 `curl + tar` 只下载这 4 项,不 `git clone`。
|
||||
|
||||
## 镜像里启动哪些容器
|
||||
|
||||
WeKnora `docker-compose.yml` 大量服务是 **profile 限定**,本镜像只默认启动核心 5 个。
|
||||
|
||||
**默认启动(5 个常驻容器,开机自启):**
|
||||
|
||||
| 容器 | 角色 |
|
||||
|---|---|
|
||||
| `frontend` | Vue UI / NGINX 反代 |
|
||||
| `app` | WeKnora Go 后端 |
|
||||
| `docreader` | Python 文档解析 (gRPC) |
|
||||
| `postgres` (ParadeDB) | 主库 + pgvector 向量检索 + BM25 |
|
||||
| `redis` | 流式输出 / 缓存 / 异步队列 |
|
||||
|
||||
> ParadeDB 自带 pgvector,默认场景下不需额外起向量库。
|
||||
|
||||
**额外预拉但不常驻:**
|
||||
|
||||
- `sandbox` 镜像:Agent Skills 由 app 按需 `docker run`。提前 `pull` 避免新实例首次执行 Skill 卡在下载。
|
||||
|
||||
**Profile 限定,不预装(用户需要时自己 `pull`):**
|
||||
|
||||
| profile | 用途 |
|
||||
|---|---|
|
||||
| `minio` | 对象存储替代本地文件 |
|
||||
| `qdrant` / `milvus` / `weaviate` / `doris` | 替代 pgvector |
|
||||
| `neo4j` | GraphRAG 知识图谱 |
|
||||
| `langfuse` | 自建 Langfuse 可观测平台 |
|
||||
| `dex` | OIDC 登录 |
|
||||
| `odl-hybrid` | OpenDataLoader Docling hybrid(体积大,无预发布镜像,需 `--build`) |
|
||||
|
||||
启用方式:
|
||||
|
||||
```bash
|
||||
cd /opt/WeKnora
|
||||
docker compose --profile neo4j up -d # 启用 GraphRAG
|
||||
docker compose --profile langfuse up -d # 启用自建 Langfuse
|
||||
docker compose --profile qdrant up -d # 切换到 Qdrant
|
||||
docker compose --profile odl-hybrid up -d --build odl-hybrid # Docling hybrid(按需)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整流程(云无关)
|
||||
|
||||
```
|
||||
1) 在目标云上买/装一台干净 Linux 实例(建议 4C8G+,Ubuntu 22.04)
|
||||
2) SSH 进去, 拷入本目录, 执行 prepare.sh
|
||||
3) 浏览器验证功能
|
||||
4) 执行 cleanup.sh (清掉私密 + SSH key, 自动关机)
|
||||
5) 在云控制台「制作镜像 / 创建快照 / 创建 AMI」
|
||||
6) 用新镜像创建测试实例, 验证 firstboot 工作正常
|
||||
7) 共享 / 公开镜像(参考各平台文档)
|
||||
```
|
||||
|
||||
### 步骤一:在干净实例上部署
|
||||
|
||||
要求:systemd + 联网 + sudo 权限。推荐 Ubuntu 22.04 / Debian 12 / CentOS Stream 9。
|
||||
|
||||
**1. 拷入脚本(任选一种,都不用 clone 整个 WeKnora 仓库)。**
|
||||
|
||||
> 命令需要写入 `/opt/`,最省心的做法是先 `sudo -i` 切到 root 再粘贴。
|
||||
> 如果坚持每行加 `sudo`,注意 `>>` 重定向是在你当前 shell 执行的,必须改用 `sudo tee -a`。
|
||||
|
||||
> **中国大陆云主机请直接看方式 C (scp)**。实测腾讯云 / 阿里云轻量服务器经常连 `github.com`、`raw.githubusercontent.com`、`gh-proxy.com` 这类境外 / 公益代理都连不上 (TLS RST 或超时),方式 A/B 会全军覆没。本机已经有这份仓库,scp 上去最稳。
|
||||
|
||||
```bash
|
||||
sudo -i # 切到 root, 后续命令直接执行
|
||||
|
||||
# === 方式 A: sparse checkout (~60KB) ===
|
||||
# 不通时设 GH_PROXY=https://gh-proxy.com/ 或 https://ghfast.top/, 注意末尾斜杠。
|
||||
GH_PROXY="${GH_PROXY:-}"
|
||||
mkdir -p /opt/weknora-tools && cd /opt/weknora-tools
|
||||
git init -q && git remote add origin "${GH_PROXY}https://github.com/Tencent/WeKnora.git"
|
||||
git config core.sparseCheckout true
|
||||
echo "scripts/cloud-image/" >> .git/info/sparse-checkout
|
||||
git pull -q --depth=1 origin main
|
||||
|
||||
# === 方式 B: 直接 curl (无 git 时用这个) ===
|
||||
# 不通时设 GH_PROXY=https://gh-proxy.com/ 或 https://ghfast.top/, 注意末尾斜杠。
|
||||
GH_PROXY="${GH_PROXY:-}"
|
||||
mkdir -p /opt/weknora-tools/scripts/cloud-image/systemd && cd /opt/weknora-tools
|
||||
base="${GH_PROXY}https://raw.githubusercontent.com/Tencent/WeKnora/main/scripts/cloud-image"
|
||||
for f in prepare.sh cleanup.sh firstboot.sh README.md; do
|
||||
curl -fsSL "$base/$f" -o "scripts/cloud-image/$f"
|
||||
done
|
||||
for f in weknora.service weknora-firstboot.service; do
|
||||
curl -fsSL "$base/systemd/$f" -o "scripts/cloud-image/systemd/$f"
|
||||
done
|
||||
chmod +x scripts/cloud-image/*.sh
|
||||
|
||||
# === 方式 C: 从本地 scp 上来 (推荐: 中国大陆云主机直接走这条) ===
|
||||
# 在本机 (能正常访问 GitHub 的机器) 执行:
|
||||
# scp -r scripts/cloud-image root@<实例IP>:/opt/weknora-tools/scripts/
|
||||
```
|
||||
|
||||
> 不确定 VM 能不能访问代理时,先探一下:
|
||||
> `for h in gh-proxy.com ghfast.top mirror.ghproxy.com github.moeyy.xyz kkgithub.com; do printf '%-25s' "$h"; curl -sS -o /dev/null -m 5 -w 'http=%{http_code} t=%{time_total}s\n' "https://$h/" 2>&1 || echo FAIL; done`
|
||||
> 哪个返回 `http=200/301/302` 就把 `GH_PROXY` 设成它 (后面加 `/`)。一个都不通的话,认命走方式 C。
|
||||
|
||||
**2. 执行部署:**
|
||||
|
||||
```bash
|
||||
sudo bash /opt/weknora-tools/scripts/cloud-image/prepare.sh
|
||||
|
||||
# 想 pin 特定版本(推荐, 保证镜像可复现)
|
||||
sudo WEKNORA_REF=v0.5.0 bash /opt/weknora-tools/scripts/cloud-image/prepare.sh
|
||||
|
||||
# 中国大陆机器三件套: 同时绕开 GitHub / get.docker.com / Docker Hub 的境外 CDN
|
||||
# (以腾讯云为例, 阿里云 / 华为云换对应镜像即可)
|
||||
sudo \
|
||||
WEKNORA_REF=v0.5.0 \
|
||||
WEKNORA_GH_PROXY=https://gh-proxy.com/ \
|
||||
DOCKER_INSTALL_MIRROR=https://mirrors.tencent.com/docker-ce/linux/ubuntu \
|
||||
DOCKER_REGISTRY_MIRROR=https://mirror.ccs.tencentyun.com \
|
||||
bash /opt/weknora-tools/scripts/cloud-image/prepare.sh
|
||||
```
|
||||
|
||||
> 三个变量分别解决三个不同的境外 CDN 不可达问题:
|
||||
> - `WEKNORA_GH_PROXY`:加速 **GitHub tarball** 下载(`prepare.sh` 步骤 2,运行时文件)
|
||||
> - `DOCKER_INSTALL_MIRROR`:绕开 **`get.docker.com`**,改用 apt + docker-ce 镜像源装 Docker(步骤 1)
|
||||
> - `DOCKER_REGISTRY_MIRROR`:加速 **Docker Hub** 镜像拉取(步骤 4,`wechatopenai/weknora-*`)
|
||||
>
|
||||
> 不同云厂商对应地址(按需替换 ubuntu/debian 部分以匹配实际发行版):
|
||||
> | 厂商 | `DOCKER_INSTALL_MIRROR` | `DOCKER_REGISTRY_MIRROR` |
|
||||
> |---|---|---|
|
||||
> | 腾讯云 | `https://mirrors.tencent.com/docker-ce/linux/ubuntu` | `https://mirror.ccs.tencentyun.com` |
|
||||
> | 阿里云 | `https://mirrors.aliyun.com/docker-ce/linux/ubuntu` | `https://<your-id>.mirror.aliyuncs.com` |
|
||||
> | 华为云 | `https://mirrors.huaweicloud.com/docker-ce/linux/ubuntu` | `https://<id>.mirror.swr.myhuaweicloud.com` |
|
||||
>
|
||||
> `DOCKER_INSTALL_MIRROR` 目前仅支持 apt 系(Ubuntu / Debian / TencentOS-apt)。
|
||||
> CentOS / Rocky 等 yum 系发行版 `get.docker.com` 一般能直连,没碰到再说。
|
||||
|
||||
`prepare.sh` 会:
|
||||
|
||||
1. 安装 Docker / Docker Compose plugin(已装则跳过)
|
||||
2. 用 `curl + tar` 下载 4 个运行时文件到 `/opt/WeKnora`
|
||||
3. 拉取并启动默认 5 个容器 + 预拉 sandbox 镜像
|
||||
4. 安装 `weknora.service`(开机自启)+ `weknora-firstboot.service`(首启 init)
|
||||
|
||||
完成后访问 `http://<公网IP>`。
|
||||
|
||||
### 步骤二:验证
|
||||
|
||||
至少验证:
|
||||
|
||||
- 能注册管理员、能登录
|
||||
- 能创建一个知识库
|
||||
- 能上传一个文档并完成解析
|
||||
- 能进行一次问答
|
||||
|
||||
```bash
|
||||
sudo docker compose -f /opt/WeKnora/docker-compose.yml ps
|
||||
curl -f http://localhost:8080/health
|
||||
```
|
||||
|
||||
### 步骤三:清理并制作镜像
|
||||
|
||||
> **重要**:`cleanup.sh` 会删除所有 SSH 公钥、清空日志、清空数据库与 docker volume。执行后**不要再 SSH 进来**,直接去云控制台关机制作镜像。
|
||||
|
||||
```bash
|
||||
sudo bash /opt/weknora-tools/scripts/cloud-image/cleanup.sh
|
||||
```
|
||||
|
||||
执行完会自动 `poweroff`。然后到对应云控制台按其文档制作镜像。
|
||||
|
||||
### 新实例首次开机行为
|
||||
|
||||
用户用你的镜像创建实例后,第一次开机时 `weknora-firstboot.service` 会:
|
||||
|
||||
1. 生成随机的 `DB_PASSWORD` / `REDIS_PASSWORD` / `JWT_SECRET` / `SYSTEM_AES_KEY`
|
||||
2. 写回 `/opt/WeKnora/.env`
|
||||
3. `docker compose up -d` 启动全部服务
|
||||
4. 把生成的凭证写到 `/root/weknora-credentials.txt`(仅 root 可读)
|
||||
5. 把自己 disable + 删除自己(确保只跑一次)
|
||||
|
||||
之后每次开机都由 `weknora.service` 接管。
|
||||
|
||||
> **注意**:`firstboot.sh` 默认**不**禁用注册(`DISABLE_REGISTRATION=false`),第一个注册的人会成为管理员。
|
||||
> 凭证文件里有「请尽快注册以防被抢注」的醒目提示。需要更严格控制可在 `firstboot.sh` 的 `replace` 调用列表中追加一行 `replace DISABLE_REGISTRATION true` 后再重制镜像。
|
||||
|
||||
---
|
||||
|
||||
## 升级镜像版本
|
||||
|
||||
镜像里没有 git 仓库,升级直接重跑 `prepare.sh`(会覆盖 4 个运行时文件,**不动 `.env` 和 docker volume 数据**):
|
||||
|
||||
```bash
|
||||
sudo WEKNORA_REF=v0.6.0 bash /opt/weknora-tools/scripts/cloud-image/prepare.sh
|
||||
sudo bash /opt/weknora-tools/scripts/cloud-image/cleanup.sh # 制作新镜像前
|
||||
```
|
||||
|
||||
> **打新镜像时想顺便清掉旧版本镜像层**(每个旧 weknora-* tag 几百 MB,4 个镜像 ~2-4GB):
|
||||
> ```bash
|
||||
> sudo PRUNE_OLD_IMAGES=true WEKNORA_REF=v0.6.0 \
|
||||
> bash /opt/weknora-tools/scripts/cloud-image/prepare.sh
|
||||
> ```
|
||||
> 默认 `false` 是为了保留回滚路径。打镜像前确认新版本稳定后再开。
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
- 镜像里**不要**预置任何 LLM API Key、Langfuse Key、个人 SSH key
|
||||
- 数据库 / Redis / MinIO 端口默认仅对 docker 网络可见,不要在云防火墙里对外开放
|
||||
- `/root/weknora-credentials.txt` 用 `umask 077` 创建,仅 root 可读
|
||||
- 每次重制镜像前必须执行 `cleanup.sh`,避免泄漏上一份测试数据 / SSH key / machine-id
|
||||
|
||||
## 各云平台具体操作
|
||||
|
||||
| 平台 | 文档 |
|
||||
|---|---|
|
||||
| 腾讯云轻量应用服务器 / CVM | [`docs/cloud-image/tencent-lighthouse.md`](../../docs/cloud-image/tencent-lighthouse.md) |
|
||||
| AWS EC2 AMI | (欢迎贡献) |
|
||||
| 阿里云 ECS | (欢迎贡献) |
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
# cleanup.sh - 在制作云镜像前清理私密数据。
|
||||
# 警告: 本脚本会删除 SSH 公钥、清空数据库与日志,最后会自动关机。
|
||||
# 执行后请直接在云控制台「制作镜像 / 创建快照 / 创建 AMI」,不要再 SSH 进来。
|
||||
set -euo pipefail
|
||||
|
||||
WEKNORA_DIR="${WEKNORA_DIR:-/opt/WeKnora}"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "[cleanup] 请使用 sudo 或 root 运行" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read -r -p "[cleanup] 该操作不可逆,确认继续? 输入 YES 继续: " ans
|
||||
if [[ "${ans}" != "YES" ]]; then
|
||||
echo "[cleanup] 已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[cleanup] 1/8 停止 WeKnora 容器"
|
||||
COMPOSE_PROJECT=""
|
||||
if [[ -d "${WEKNORA_DIR}" ]]; then
|
||||
cd "${WEKNORA_DIR}"
|
||||
# 优先用 compose ls 拿到真实 project 名 (默认是目录名小写, 如 weknora)
|
||||
COMPOSE_PROJECT="$(docker compose ls --format json 2>/dev/null \
|
||||
| grep -oE '"Name":"[^"]+"' | head -1 | cut -d'"' -f4 || true)"
|
||||
docker compose down -v --remove-orphans || true
|
||||
fi
|
||||
|
||||
echo "[cleanup] 2/8 清空 WeKnora 业务数据 + 首启 marker / 日志"
|
||||
if [[ -d "${WEKNORA_DIR}" ]]; then
|
||||
rm -rf "${WEKNORA_DIR}/data"/* "${WEKNORA_DIR}/logs"/* 2>/dev/null || true
|
||||
# 故意不在这里重建 .env: 让镜像里 .env 缺失, 任何在 firstboot 之前
|
||||
# 起来的 docker compose 都会因找不到 .env 而失败, 避免明文默认密码
|
||||
# (postgres123!@# 之类) 把 postgres 数据卷初始化坏。
|
||||
# firstboot.sh 自己会从 .env.example 拷贝并替换密钥。
|
||||
rm -f "${WEKNORA_DIR}/.env" "${WEKNORA_DIR}/.firstboot.done"
|
||||
fi
|
||||
rm -f /root/weknora-credentials.txt /var/log/weknora-firstboot.log
|
||||
|
||||
echo "[cleanup] 3/8 清理残留 docker 卷与构建缓存"
|
||||
# 严格按 compose project 名前缀匹配, 避免误伤同宿主上其它 postgres/redis 卷。
|
||||
if [[ -n "${COMPOSE_PROJECT}" ]]; then
|
||||
docker volume ls -q --filter "label=com.docker.compose.project=${COMPOSE_PROJECT}" \
|
||||
| xargs -r docker volume rm -f || true
|
||||
fi
|
||||
# 注意: 这里只清"卷 / 已停容器 / 构建缓存", 绝对不能清镜像。
|
||||
# 此前用过 `docker system prune -af --volumes`, 会把 prepare.sh 预拉的
|
||||
# wechatopenai/weknora-* 等镜像一并清掉, 导致基于镜像建出来的新实例
|
||||
# 在 firstboot 时还要重新从 Docker Hub 拉数 GB 镜像, 完全违背预装初衷。
|
||||
docker container prune -f || true
|
||||
docker builder prune -af || true
|
||||
# 只清未挂载的 dangling 卷 (此时 compose down -v 已清掉业务卷)
|
||||
docker volume prune -f || true
|
||||
|
||||
echo "[cleanup] 4/8 清空系统日志"
|
||||
journalctl --rotate || true
|
||||
journalctl --vacuum-time=1s || true
|
||||
find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.[0-9]' \) -print0 \
|
||||
| xargs -0 -r truncate -s 0 || true
|
||||
find /var/log -type f \( -name '*.gz' -o -name '*.[0-9]' \) -print0 \
|
||||
| xargs -0 -r rm -f || true
|
||||
|
||||
echo "[cleanup] 5/8 清理 SSH 历史与授权 key(执行后将无法 SSH 进来)"
|
||||
rm -f /root/.ssh/authorized_keys /root/.ssh/known_hosts /root/.bash_history
|
||||
for d in /home/*; do
|
||||
[[ -d "$d" ]] || continue
|
||||
rm -f "$d/.ssh/authorized_keys" "$d/.ssh/known_hosts" "$d/.bash_history"
|
||||
done
|
||||
find / -xdev -type f \( -name 'id_rsa*' -o -name '*.pem' -o -name '*.key' \) \
|
||||
-not -path '/etc/ssl/*' -not -path '/usr/*' -not -path '/var/lib/docker/*' 2>/dev/null \
|
||||
| tee /tmp/cleanup-secrets-found.txt || true
|
||||
echo "[cleanup] ↑ 上面是疑似遗留的密钥文件,必要时人工再核对"
|
||||
|
||||
echo "[cleanup] 6/8 重置 cloud-init / machine-id(让新实例拿到新 ID)"
|
||||
cloud-init clean --logs --seed 2>/dev/null || true
|
||||
truncate -s 0 /etc/machine-id || true
|
||||
rm -f /var/lib/dbus/machine-id || true
|
||||
|
||||
echo "[cleanup] 7/8 清理 apt / tmp"
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
fi
|
||||
rm -rf /tmp/* /var/tmp/* /root/.cache /home/*/.cache 2>/dev/null || true
|
||||
|
||||
echo "[cleanup] 8/8 同步磁盘并关机"
|
||||
history -c || true
|
||||
sync
|
||||
echo
|
||||
echo " 即将关机。关机完成后请到云控制台执行「制作镜像 / 创建快照 / 创建 AMI」。"
|
||||
echo
|
||||
sleep 3
|
||||
poweroff
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
# firstboot.sh - 由 weknora-firstboot.service 在新实例首次开机时自动执行。
|
||||
# 任务: 生成随机密钥写入 .env -> 启动容器 -> 输出凭证 -> 标记完成 + 自禁用。
|
||||
#
|
||||
# 幂等性策略:
|
||||
# 1) 一上来先写 ${MARKER} 标记 "已生成过密钥",
|
||||
# 之后即便 docker compose 失败导致脚本中断, 重启后由 unit 的
|
||||
# ConditionPathExists=!${MARKER} 拦住, 不会再生成新密钥覆盖 .env。
|
||||
# (旧密钥已经写进 postgres 数据卷, 二次生成会导致数据库永久无法登录)
|
||||
# 2) 失败时 unit 标记 failed, 用户可手动 docker compose up -d 恢复;
|
||||
# 凭证仍可在 ${ENV_FILE} 中查到。
|
||||
set -euo pipefail
|
||||
|
||||
WEKNORA_DIR="${WEKNORA_DIR:-/opt/WeKnora}"
|
||||
ENV_FILE="${WEKNORA_DIR}/.env"
|
||||
ENV_TEMPLATE="${WEKNORA_DIR}/.env.example"
|
||||
CRED_FILE="/root/weknora-credentials.txt"
|
||||
LOG_FILE="/var/log/weknora-firstboot.log"
|
||||
MARKER="${WEKNORA_DIR}/.firstboot.done"
|
||||
|
||||
# 提前打开 LOG_FILE, 同时把 stderr 也复制一份到 systemd journal 方便调试
|
||||
# (单纯 exec >> LOG_FILE 时, 脚本一开始的失败会因为 stderr 已被吞掉而看不到)
|
||||
mkdir -p "$(dirname "${LOG_FILE}")"
|
||||
exec > >(tee -a "${LOG_FILE}") 2>&1
|
||||
echo "==== firstboot started at $(date -Iseconds) ===="
|
||||
|
||||
if [[ -f "${MARKER}" ]]; then
|
||||
echo "marker ${MARKER} exists, skip (already initialized)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# cleanup.sh 不再保留 .env, 这里从 .env.example 拷贝模板再做替换。
|
||||
# 这样保证 firstboot 之前不会有任何含明文默认密码的 .env 让 weknora.service
|
||||
# 抢先把 postgres 数据卷用错的密码初始化掉。
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
if [[ -f "${ENV_TEMPLATE}" ]]; then
|
||||
echo "creating ${ENV_FILE} from ${ENV_TEMPLATE}"
|
||||
cp "${ENV_TEMPLATE}" "${ENV_FILE}"
|
||||
else
|
||||
echo "ERROR: neither ${ENV_FILE} nor ${ENV_TEMPLATE} found"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
DOCKER_BIN="$(command -v docker || true)"
|
||||
if [[ -z "${DOCKER_BIN}" ]]; then
|
||||
echo "ERROR: docker binary not found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 生成 32 字节强随机字符串(用于 AES-256 key, 必须刚好 32 字节)
|
||||
# 用 `() ... ()` 启子 shell 并关闭 pipefail: head 只读 N 字节后关闭 stdin,
|
||||
# tr 会收到 SIGPIPE (退出码 141), 在 `set -o pipefail` 下整条管道被判失败,
|
||||
# 触发顶层 `set -e` 把 firstboot.sh 8ms exit 1 干掉。
|
||||
gen32() ( set +o pipefail; LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32 )
|
||||
# 通用密码: 24 字符, 不含 / + = (避免出现在 URL / sed 替换时出问题)
|
||||
genpw() ( set +o pipefail; LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 24 )
|
||||
|
||||
DB_PWD=$(genpw)
|
||||
REDIS_PWD=$(genpw)
|
||||
JWT=$(genpw)$(genpw)
|
||||
SYS_AES=$(gen32)
|
||||
|
||||
# 用 | 作 sed 分隔符避免冲突; 仅替换以 KEY= 开头的行
|
||||
replace() {
|
||||
local key="$1" val="$2"
|
||||
if grep -qE "^${key}=" "${ENV_FILE}"; then
|
||||
sed -i "s|^${key}=.*|${key}=${val}|" "${ENV_FILE}"
|
||||
else
|
||||
echo "${key}=${val}" >>"${ENV_FILE}"
|
||||
fi
|
||||
}
|
||||
|
||||
replace DB_PASSWORD "${DB_PWD}"
|
||||
replace REDIS_PASSWORD "${REDIS_PWD}"
|
||||
replace JWT_SECRET "${JWT}"
|
||||
replace SYSTEM_AES_KEY "${SYS_AES}"
|
||||
replace GIN_MODE "release"
|
||||
|
||||
# 把 prepare.sh 阶段记录在 .cloud-image-meta 里的 WEKNORA_REF 还原为 .env 的
|
||||
# WEKNORA_VERSION, 否则 docker compose 会落回 :latest 默认值, 导致镜像版本
|
||||
# 与 prepare 时拉取的版本不一致。
|
||||
META_FILE="${WEKNORA_DIR}/.cloud-image-meta"
|
||||
if [[ -f "${META_FILE}" ]]; then
|
||||
META_REF=$(grep -E '^WEKNORA_REF=' "${META_FILE}" | tail -1 | cut -d= -f2- || true)
|
||||
if [[ -n "${META_REF}" ]]; then
|
||||
replace WEKNORA_VERSION "${META_REF}"
|
||||
echo "restored WEKNORA_VERSION=${META_REF} from ${META_FILE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 关键: .env 改完立刻写 marker。
|
||||
# 这之后即便 docker compose up 失败, 重启也不会再次重写 .env,
|
||||
# 防止与 postgres 已经持久化的密码不一致。
|
||||
umask 077
|
||||
touch "${MARKER}"
|
||||
chmod 0600 "${MARKER}"
|
||||
|
||||
echo "env updated, marker written, starting docker compose..."
|
||||
cd "${WEKNORA_DIR}"
|
||||
"${DOCKER_BIN}" compose up -d
|
||||
|
||||
# 尝试拿公网 IP, 失败就用内网
|
||||
PUB_IP=$(curl -fsS --max-time 5 https://ifconfig.me 2>/dev/null \
|
||||
|| curl -fsS --max-time 5 https://api.ipify.org 2>/dev/null \
|
||||
|| hostname -I | awk '{print $1}')
|
||||
|
||||
cat >"${CRED_FILE}" <<INFO
|
||||
========================================
|
||||
WeKnora 实例初始化完成
|
||||
生成时间: $(date -Iseconds)
|
||||
========================================
|
||||
|
||||
访问地址 : http://${PUB_IP}
|
||||
|
||||
注册后如需关闭后续注册, 编辑 ${ENV_FILE}:
|
||||
DISABLE_REGISTRATION=true
|
||||
然后执行: cd ${WEKNORA_DIR} && docker compose up -d
|
||||
|
||||
下列随机凭证已写入 ${ENV_FILE}, 请妥善保存(仅 root 可读):
|
||||
DB_PASSWORD = ${DB_PWD}
|
||||
REDIS_PASSWORD = ${REDIS_PWD}
|
||||
JWT_SECRET = ${JWT}
|
||||
SYSTEM_AES_KEY = ${SYS_AES}
|
||||
|
||||
注意:
|
||||
- 本文件只反映首启时刻的密钥, 后续以 ${ENV_FILE} 为准。
|
||||
- 切勿直接对外暴露 5432 / 6379 / 9000 等基础设施端口。
|
||||
- 仅用 80 / 443 对外服务, 必要时配置反向代理 + HTTPS。
|
||||
|
||||
INFO
|
||||
|
||||
echo "credentials written to ${CRED_FILE}"
|
||||
|
||||
# 仅停掉 unit, 不删 unit 文件 (否则当前正在运行的 oneshot 可能被 systemd 标记 failed)。
|
||||
# 下次重启时, weknora-firstboot.service 通过 ConditionPathExists=!${MARKER} 自动跳过。
|
||||
systemctl disable weknora-firstboot.service || true
|
||||
|
||||
echo "==== firstboot finished at $(date -Iseconds) ===="
|
||||
Executable
+237
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env bash
|
||||
# prepare.sh - 在干净的 Linux 实例上部署 WeKnora 运行时, 用于制作云镜像模板。
|
||||
# 不需要 clone 整个 WeKnora 仓库, 只下载 4 个运行时文件 (~100KB)。
|
||||
# 兼容: Ubuntu / Debian / CentOS / Rocky / TencentOS 等带 systemd + Docker 的发行版。
|
||||
# 使用方式: sudo bash prepare.sh
|
||||
# 可调环境变量:
|
||||
# WEKNORA_REF 要拉取的 git ref (tag / branch / commit), 默认 main
|
||||
# WEKNORA_DIR 部署目录, 默认 /opt/WeKnora
|
||||
# WEKNORA_REPO 仓库地址, 默认 https://github.com/Tencent/WeKnora
|
||||
# WEKNORA_GH_PROXY GitHub 加速前缀, 默认空。中国大陆机器可设
|
||||
# https://gh-proxy.com/ 或 https://ghfast.top/
|
||||
# (实际下载地址变成 ${WEKNORA_GH_PROXY}${WEKNORA_REPO}/archive/...)
|
||||
# DOCKER_INSTALL_MIRROR Docker 安装包镜像源, 默认空 (走 get.docker.com)。
|
||||
# 中国大陆机器境外 CDN 不通时设为, 例如:
|
||||
# https://mirrors.tencent.com/docker-ce/linux/ubuntu
|
||||
# https://mirrors.aliyun.com/docker-ce/linux/ubuntu
|
||||
# 会改用 apt + docker-ce 官方仓库镜像安装,
|
||||
# 含 docker-ce / containerd.io / docker-compose-plugin,
|
||||
# 完全不访问 get.docker.com。仅支持 apt 系发行版。
|
||||
# DOCKER_REGISTRY_MIRROR Docker Hub 加速器, 默认空。腾讯云内网可设
|
||||
# https://mirror.ccs.tencentyun.com
|
||||
# (会写入 /etc/docker/daemon.json 并重启 docker)
|
||||
# PRUNE_OLD_IMAGES 升级场景下是否清理 dangling / 旧版本 tag 镜像,
|
||||
# 默认 false。设为 true 时在拉新镜像之后执行
|
||||
# `docker image prune -af`, 把没有容器引用的镜像
|
||||
# (含旧 WEKNORA_VERSION 的 wechatopenai/weknora-*)
|
||||
# 一次性删掉, 减少要打进云镜像的体积。
|
||||
set -euo pipefail
|
||||
|
||||
WEKNORA_REF="${WEKNORA_REF:-main}"
|
||||
WEKNORA_DIR="${WEKNORA_DIR:-/opt/WeKnora}"
|
||||
WEKNORA_REPO="${WEKNORA_REPO:-https://github.com/Tencent/WeKnora}"
|
||||
WEKNORA_GH_PROXY="${WEKNORA_GH_PROXY:-}"
|
||||
DOCKER_INSTALL_MIRROR="${DOCKER_INSTALL_MIRROR:-}"
|
||||
DOCKER_REGISTRY_MIRROR="${DOCKER_REGISTRY_MIRROR:-}"
|
||||
PRUNE_OLD_IMAGES="${PRUNE_OLD_IMAGES:-false}"
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "[prepare] 请使用 sudo 或 root 运行" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 通过镜像源 apt 安装 docker-ce 全家桶 (含 compose-plugin)。
|
||||
# 用于中国大陆云主机直连 get.docker.com 被 RST 的场景。
|
||||
install_docker_via_apt_mirror() {
|
||||
local mirror="$1"
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
echo "[prepare] DOCKER_INSTALL_MIRROR 目前仅支持 apt 系发行版 (Ubuntu/Debian)" >&2
|
||||
return 1
|
||||
fi
|
||||
apt-get update -y
|
||||
apt-get install -y ca-certificates curl gnupg lsb-release
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL "${mirror%/}/gpg" | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
local arch codename
|
||||
arch="$(dpkg --print-architecture)"
|
||||
codename="$(lsb_release -cs)"
|
||||
echo "deb [arch=${arch} signed-by=/etc/apt/keyrings/docker.gpg] ${mirror%/} ${codename} stable" \
|
||||
> /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -y
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io \
|
||||
docker-buildx-plugin docker-compose-plugin curl tar
|
||||
}
|
||||
|
||||
echo "[prepare] 1/6 安装 Docker 与依赖"
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
if [[ -n "${DOCKER_INSTALL_MIRROR}" ]]; then
|
||||
echo "[prepare] 通过 ${DOCKER_INSTALL_MIRROR} 走 apt 安装 docker-ce (跳过 get.docker.com)"
|
||||
install_docker_via_apt_mirror "${DOCKER_INSTALL_MIRROR}"
|
||||
else
|
||||
curl -fsSL https://get.docker.com | bash
|
||||
fi
|
||||
fi
|
||||
systemctl enable --now docker
|
||||
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update -y
|
||||
apt-get install -y docker-compose-plugin curl tar
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
yum install -y docker-compose-plugin curl tar
|
||||
fi
|
||||
fi
|
||||
|
||||
# 可选: 配置 Docker Hub 加速器, 解决直连 registry-1.docker.io 超时的场景
|
||||
# (典型: 中国大陆云主机 / 内网受限环境)。仅在用户显式传入时才动 daemon.json。
|
||||
if [[ -n "${DOCKER_REGISTRY_MIRROR}" ]]; then
|
||||
echo "[prepare] 1.5/6 配置 Docker Hub 加速器: ${DOCKER_REGISTRY_MIRROR}"
|
||||
mkdir -p /etc/docker
|
||||
# 已存在的 daemon.json 走 python 合并, 避免覆盖用户其它配置
|
||||
if [[ -s /etc/docker/daemon.json ]] && command -v python3 >/dev/null 2>&1; then
|
||||
python3 - "$DOCKER_REGISTRY_MIRROR" <<'PY'
|
||||
import json, sys, pathlib
|
||||
p = pathlib.Path("/etc/docker/daemon.json")
|
||||
mirror = sys.argv[1]
|
||||
try:
|
||||
cfg = json.loads(p.read_text())
|
||||
except Exception:
|
||||
cfg = {}
|
||||
mirrors = cfg.get("registry-mirrors") or []
|
||||
if mirror not in mirrors:
|
||||
mirrors.insert(0, mirror)
|
||||
cfg["registry-mirrors"] = mirrors
|
||||
p.write_text(json.dumps(cfg, indent=2) + "\n")
|
||||
PY
|
||||
else
|
||||
cat >/etc/docker/daemon.json <<EOF
|
||||
{
|
||||
"registry-mirrors": ["${DOCKER_REGISTRY_MIRROR}"]
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
systemctl restart docker
|
||||
# 给 docker daemon 一点重启时间, 避免下面 docker compose pull 立刻拿到 EOF
|
||||
for _ in 1 2 3 4 5; do
|
||||
docker info >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
echo "[prepare] 2/6 拉取 WeKnora 运行时文件 (ref=${WEKNORA_REF})"
|
||||
# 只下载实际需要的 4 个文件, 不 clone 整个仓库 (~MB 级 -> ~KB 级)
|
||||
mkdir -p "${WEKNORA_DIR}/config" "${WEKNORA_DIR}/skills"
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "${tmp}"' EXIT
|
||||
|
||||
tarball_url="${WEKNORA_GH_PROXY}${WEKNORA_REPO}/archive/${WEKNORA_REF}.tar.gz"
|
||||
echo "[prepare] tarball: ${tarball_url}"
|
||||
curl -fsSL "${tarball_url}" -o "${tmp}/repo.tar.gz"
|
||||
# 仅解压需要的路径, 显著加速且省空间
|
||||
tar -xzf "${tmp}/repo.tar.gz" -C "${tmp}" \
|
||||
--wildcards \
|
||||
'*/docker-compose.yml' \
|
||||
'*/.env.example' \
|
||||
'*/config/config.yaml' \
|
||||
'*/skills/preloaded'
|
||||
src=$(find "${tmp}" -maxdepth 1 -mindepth 1 -type d -name 'WeKnora-*' | head -1)
|
||||
if [[ -z "${src}" ]]; then
|
||||
echo "[prepare] 解压失败, 未找到 WeKnora-* 目录" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${src}/docker-compose.yml" "${WEKNORA_DIR}/"
|
||||
cp "${src}/.env.example" "${WEKNORA_DIR}/"
|
||||
cp "${src}/config/config.yaml" "${WEKNORA_DIR}/config/"
|
||||
rm -rf "${WEKNORA_DIR}/skills/preloaded"
|
||||
cp -r "${src}/skills/preloaded" "${WEKNORA_DIR}/skills/"
|
||||
|
||||
# 记录元信息, 供 firstboot / 升级时参考
|
||||
cat >"${WEKNORA_DIR}/.cloud-image-meta" <<EOF
|
||||
WEKNORA_REF=${WEKNORA_REF}
|
||||
WEKNORA_REPO=${WEKNORA_REPO}
|
||||
PREPARED_AT=$(date -Iseconds)
|
||||
EOF
|
||||
|
||||
echo "[prepare] 3/6 准备 .env (默认值, firstboot 会替换为随机密钥)"
|
||||
cd "${WEKNORA_DIR}"
|
||||
[[ -f .env ]] || cp .env.example .env
|
||||
sed -i 's/^GIN_MODE=.*/GIN_MODE=release/' .env || true
|
||||
|
||||
# 把 WEKNORA_VERSION 与 WEKNORA_REF 对齐, 让 docker compose 拉取与 ref 一致的
|
||||
# 镜像 tag。无条件覆盖, 避免 .env 残留上一次 prepare 留下的旧版本号。
|
||||
# Docker Hub 上 wechatopenai/weknora-* 的 tag 命名约定:
|
||||
# - 浮动 tag:main(持续指向最新构建)
|
||||
# - 固定 release tag:v 前缀 + semver(如 v0.7.2、v0.5.2)
|
||||
# 因此这里不剥 v、也不映射到 latest。
|
||||
WEKNORA_VERSION_VAL="${WEKNORA_REF}"
|
||||
if grep -qE '^WEKNORA_VERSION=' .env; then
|
||||
sed -i "s|^WEKNORA_VERSION=.*|WEKNORA_VERSION=${WEKNORA_VERSION_VAL}|" .env
|
||||
else
|
||||
echo "WEKNORA_VERSION=${WEKNORA_VERSION_VAL}" >>.env
|
||||
fi
|
||||
echo "[prepare] -> WEKNORA_VERSION=${WEKNORA_VERSION_VAL}"
|
||||
|
||||
echo "[prepare] 4/6 拉取并启动默认 5 个常驻容器 (frontend/app/docreader/postgres/redis)"
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
|
||||
# 提前 pull sandbox 镜像 (Agent Skills 运行时由 app 按需 docker run, 非常驻)
|
||||
# 不预拉的话, 用户首次跑 Skill 会卡在下载
|
||||
echo "[prepare] 4.5/6 预拉 sandbox 镜像 (Agent Skills 用, 非常驻)"
|
||||
docker compose --profile full pull sandbox || true
|
||||
|
||||
# 其他向量库 / 可观测组件 (qdrant, milvus, weaviate, doris, neo4j, langfuse-*, minio, dex)
|
||||
# 不预拉, 体积可省 5-15GB. 用户如需启用:
|
||||
# cd /opt/WeKnora && docker compose --profile <name> up -d
|
||||
|
||||
# 升级场景: 清理旧版本 tag 的 wechatopenai/weknora-* 镜像。
|
||||
# 默认关闭, 保留回滚路径; 制作镜像前显式打开以减小体积。
|
||||
#
|
||||
# 注意: 不用 `docker image prune -af`!
|
||||
# sandbox 镜像在 compose 里只 pull 不 up (Agent Skills 由 app 按需 docker run),
|
||||
# 没有任何容器引用它, 一旦 `prune -a` 会把当前版本的 sandbox 一起删掉,
|
||||
# 反而违背 prepare.sh 4.5 步预拉 sandbox 的目的。
|
||||
# 这里精确按 tag 比对, 只删 wechatopenai/weknora-* 仓库下、tag 不等于当前
|
||||
# WEKNORA_VERSION 的镜像, 基础设施镜像 (paradedb / redis) 不动。
|
||||
if [[ "${PRUNE_OLD_IMAGES,,}" == "true" || "${PRUNE_OLD_IMAGES}" == "1" ]]; then
|
||||
echo "[prepare] 4.6/6 清理 wechatopenai/weknora-* 仓库下旧版本镜像 (PRUNE_OLD_IMAGES=true, keep=${WEKNORA_VERSION_VAL})"
|
||||
docker image ls --format '{{.Repository}}:{{.Tag}}' \
|
||||
| grep -E '^wechatopenai/weknora-' \
|
||||
| grep -vE ":${WEKNORA_VERSION_VAL}\$" \
|
||||
| xargs -r docker rmi -f 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "[prepare] 5/6 安装 systemd 单元"
|
||||
# 探测 docker 二进制路径, 不同发行版可能在 /usr/bin 或 /usr/local/bin
|
||||
DOCKER_BIN="$(command -v docker)"
|
||||
if [[ -z "${DOCKER_BIN}" ]]; then
|
||||
echo "[prepare] 未找到 docker 二进制" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[prepare] docker binary: ${DOCKER_BIN}"
|
||||
|
||||
install -m 0644 "${SCRIPT_DIR}/systemd/weknora.service" /etc/systemd/system/weknora.service
|
||||
install -m 0644 "${SCRIPT_DIR}/systemd/weknora-firstboot.service" /etc/systemd/system/weknora-firstboot.service
|
||||
install -m 0755 "${SCRIPT_DIR}/firstboot.sh" /usr/local/sbin/weknora-firstboot.sh
|
||||
|
||||
# 把 systemd 单元里的 docker 路径模板替换为实际路径
|
||||
sed -i "s|@DOCKER_BIN@|${DOCKER_BIN}|g" /etc/systemd/system/weknora.service
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable weknora.service
|
||||
systemctl enable weknora-firstboot.service
|
||||
|
||||
echo "[prepare] 6/6 完成"
|
||||
echo
|
||||
echo " WeKnora 运行时已部署到 ${WEKNORA_DIR}"
|
||||
echo " docker-compose.yml / config/config.yaml / skills/preloaded / .env"
|
||||
echo " 版本: ${WEKNORA_REF} (见 ${WEKNORA_DIR}/.cloud-image-meta)"
|
||||
echo
|
||||
echo " 打开浏览器访问 http://<本机公网IP> 验证功能"
|
||||
echo
|
||||
echo " 验证通过后执行清理并制作镜像:"
|
||||
echo " sudo bash ${SCRIPT_DIR}/cleanup.sh"
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=WeKnora (docker compose)
|
||||
Requires=docker.service weknora-firstboot.service
|
||||
After=docker.service network-online.target weknora-firstboot.service
|
||||
Wants=network-online.target
|
||||
# 关键: Requires=weknora-firstboot.service 让首启 init 失败时本服务也不启动,
|
||||
# 避免 firstboot 没成功生成随机密钥就用 .env.example 默认明文密码把 postgres
|
||||
# 数据卷初始化掉。weknora-firstboot.service 在已初始化的实例上由
|
||||
# ConditionPathExists=!/opt/WeKnora/.firstboot.done 跳过, 跳过 = 成功,
|
||||
# 不会阻塞本服务后续重启。
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory=/opt/WeKnora
|
||||
ExecStart=@DOCKER_BIN@ compose up -d
|
||||
ExecStop=@DOCKER_BIN@ compose down
|
||||
TimeoutStartSec=600
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user