diff --git a/docs/cloud-image/README.md b/docs/cloud-image/README.md new file mode 100644 index 000000000..fb3f709cf --- /dev/null +++ b/docs/cloud-image/README.md @@ -0,0 +1,22 @@ +# WeKnora 云镜像打包指南 + +把 WeKnora 打包成可分发的云镜像(AMI / 自定义镜像 / Snapshot),用户基于镜像创建实例后开机即用、自动随机化密钥、零私密泄漏。 + +## 通用工具 + +云无关的脚本和详细说明:[`scripts/cloud-image/README.md`](../../scripts/cloud-image/README.md) + +包含 `prepare.sh` / `cleanup.sh` / `firstboot.sh` 三个脚本和两个 systemd 单元,已在多种发行版(Ubuntu / Debian / CentOS / TencentOS)上验证。 + +## 各平台具体操作 + +| 平台 | 文档 | 状态 | +|---|---|---| +| 腾讯云轻量应用服务器 / CVM | [tencent-lighthouse.md](./tencent-lighthouse.md) | ✅ | +| AWS EC2 (AMI) | _欢迎贡献_ | ⏳ | +| 阿里云 ECS | _欢迎贡献_ | ⏳ | +| 火山引擎 ECS | _欢迎贡献_ | ⏳ | +| 华为云 ECS | _欢迎贡献_ | ⏳ | +| 本地 KVM / Proxmox | _欢迎贡献_ | ⏳ | + +> 各平台文档结构尽量保持一致:实例规格建议 → 制作镜像操作 → 共享/公开方式 → 该平台独有的注意事项。 diff --git a/docs/cloud-image/tencent-lighthouse.md b/docs/cloud-image/tencent-lighthouse.md new file mode 100644 index 000000000..8c9216558 --- /dev/null +++ b/docs/cloud-image/tencent-lighthouse.md @@ -0,0 +1,100 @@ +# 腾讯云轻量应用服务器 / CVM 镜像制作指南 + +> **前置阅读**:通用脚本与流程见 [`scripts/cloud-image/README.md`](../../scripts/cloud-image/README.md)。本文只补充腾讯云平台专属的操作步骤。 + +## 实例规格建议 + +| 项 | 建议值 | +|---|---| +| 实例类型 | 轻量应用服务器(Lighthouse) / 云服务器 CVM 标准型 | +| CPU / 内存 | 至少 4 核 8G(推荐 4 核 16G,跑全功能 RAG + Agent) | +| 系统盘 | 至少 80G SSD | +| 镜像 | Ubuntu Server 22.04 LTS / TencentOS Server 3.1 | +| 地域 | 选你后续主要用户所在地域(同地域才能跨账号共享) | + +## 完整流程 + +1. 控制台买一台符合上述规格的实例 +2. SSH 进去,按 [scripts/cloud-image/README.md](../../scripts/cloud-image/README.md) 执行 `prepare.sh` +3. 浏览器访问 `http://<公网IP>` 验证功能 +4. 执行 `cleanup.sh`(会自动 `poweroff`) +5. 控制台「**制作镜像**」(见下) +6. 用新镜像创建一台测试实例,验证 firstboot 工作正常 +7. 「**共享镜像**」给其他账号 / 申请「**镜像市场**」上架(见下) + +## 制作镜像 + +**轻量应用服务器**: + +1. 控制台 → 轻量应用服务器 → 选中已关机的实例 +2. 「更多」→「**制作镜像**」 +3. 镜像名建议带版本号:`weknora-v0.5.0-ubuntu2204` +4. 等待 5–30 分钟(取决于系统盘大小) + +**云服务器 CVM**: + +1. 控制台 → 云服务器 → 选中已关机的实例 +2. 「更多」→「制作镜像」→ 选「整机镜像」 +3. 同样建议带版本号 + +> 同一账号下,自定义镜像有数量配额(默认 20 个),可在控制台查看。 + +## 验证镜像 + +强烈建议用新镜像创建一台测试实例,至少验证: + +- [ ] 能 SSH 进去(用控制台的默认密码 / 你导入的 key) +- [ ] `systemctl status weknora-firstboot` 显示已成功执行(或已 disable + 文件被删) +- [ ] `cat /root/weknora-credentials.txt` 里有随机密码 +- [ ] 浏览器打开公网 IP 能访问 WeKnora,能注册管理员 +- [ ] `docker compose -f /opt/WeKnora/docker-compose.yml ps` 全部 healthy +- [ ] `cat /opt/WeKnora/.cloud-image-meta` 显示正确的版本 + +## 共享给其他用户 + +按「覆盖范围」递增有 3 种方式: + +### 方式 A:跨账号共享(私下分享) + +控制台 → 自定义镜像 → 选中镜像 → 「**共享**」→ 输入对方腾讯云账号 ID(UIN)。 + +- 对方在自己的「共享镜像」列表能看到,可直接基于它创建实例 +- **限制**:必须同地域;对方账号必须已开通对应产品(Lighthouse / CVM) +- 适合小范围、合作伙伴、内测用户 + +### 方式 B:跨地域使用 + +Lighthouse 的镜像可以「共享给 CVM」,转成 CVM 自定义镜像后即可: + +- 跨地域复制 +- 导出为 `qcow2` 文件下载到本地(更通用,可用于 KVM / 其他云) + +### 方式 C:通过腾讯云市场上架镜像商品(公开一键部署) + +这是真正「任何用户都能在云市场搜到并一键购买/部署」的形态。流程比较重,适合长期运营。 + +参考文档(请以官方为准): + +- [云市场 - 镜像服务上架流程](https://cloud.tencent.com/document/product/306/3019) +- [云市场 - 镜像商品制作说明](https://cloud.tencent.com/document/product/306/30128) +- [Lighthouse - 应用镜像使用说明](https://cloud.tencent.com/document/product/1207/72665) +- [Lighthouse - 管理镜像(操作指南)](https://cloud.tencent.com/document/product/1207/63263) + +大致步骤: + +1. 登录 [腾讯云市场服务商管理控制台](https://console.cloud.tencent.com/serviceprovider),注册成为服务商 +2. 「商品管理 → 商品列表 → 新建商品」,接入类型选「**镜像**」 +3. 选择已制作好的自定义镜像(即上面方式 A/B 制作的那份) +4. **完成主机安全(专业版)检测**——这是镜像上架的硬性前置条件,需要自付 CVM + 主机安全专业版费用,建议用按量付费跑完即释放 +5. 填写商品名称、版本、亮点、详情、使用指南(必传 PDF/Word/PPT/ZIP/RAR,≤2MB)、售后说明 +6. 选择售卖方式(按量计费 / 按周期计费),按量计费目前仅支持 0 元规格 +7. 提交审核(云市场运营人员审核约 7 个工作日) +8. 审核通过后,用户在云市场或购买 CVM 时即可选到你的镜像 + +> WeKnora 是腾讯系开源项目(`Tencent/WeKnora`),如果想推动官方上架,建议在 [WeKnora GitHub Issues](https://github.com/Tencent/WeKnora/issues) 联系维护团队,而不是个人单独申请。 + +## 注意事项 + +- 腾讯云的 cloud-init 兼容性良好,`cleanup.sh` 里的 `cloud-init clean` 能正常工作 +- Lighthouse 的镜像默认大小限制为系统盘大小,请提前预估 +- 若使用域名 + HTTPS,建议在 firstboot 之外通过 [acme.sh](https://acme.sh) / certbot 单独配置,不要把证书烤进镜像 diff --git a/scripts/cloud-image/README.md b/scripts/cloud-image/README.md new file mode 100644 index 000000000..999872086 --- /dev/null +++ b/scripts/cloud-image/README.md @@ -0,0 +1,225 @@ +# 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 知识图谱 | +| `jaeger` | OpenTelemetry trace UI | +| `langfuse` | 自建 Langfuse 可观测平台 | +| `dex` | OIDC 登录 | + +启用方式: + +```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 +``` + +--- + +## 完整流程(云无关) + +``` +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 仓库):** + +```bash +# 方式 A: sparse checkout (~60KB) +mkdir -p /opt/weknora-tools && cd /opt/weknora-tools +git init -q && git remote add origin 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 +mkdir -p /opt/weknora-tools/scripts/cloud-image/systemd && cd /opt/weknora-tools +base=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 +``` + +**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 +``` + +`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` / `TENANT_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 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 # 制作新镜像前 +``` + +## 安全注意事项 + +- 镜像里**不要**预置任何 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 | (欢迎贡献) | diff --git a/scripts/cloud-image/cleanup.sh b/scripts/cloud-image/cleanup.sh new file mode 100755 index 000000000..6211e35db --- /dev/null +++ b/scripts/cloud-image/cleanup.sh @@ -0,0 +1,76 @@ +#!/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 容器" +if [[ -d "${WEKNORA_DIR}" ]]; then + cd "${WEKNORA_DIR}" + docker compose down -v --remove-orphans || true +fi + +echo "[cleanup] 2/8 清空 WeKnora 业务数据" +if [[ -d "${WEKNORA_DIR}" ]]; then + rm -rf "${WEKNORA_DIR}/data"/* "${WEKNORA_DIR}/logs"/* 2>/dev/null || true + rm -f "${WEKNORA_DIR}/.env" + cp "${WEKNORA_DIR}/.env.example" "${WEKNORA_DIR}/.env" +fi + +echo "[cleanup] 3/8 清理残留 docker 卷与构建缓存" +docker volume ls -q | grep -Ei 'weknora|postgres|redis|minio|qdrant|elasticsearch|milvus|weaviate' \ + | xargs -r docker volume rm -f || true +docker system prune -af --volumes || 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 diff --git a/scripts/cloud-image/firstboot.sh b/scripts/cloud-image/firstboot.sh new file mode 100755 index 000000000..5aa85c307 --- /dev/null +++ b/scripts/cloud-image/firstboot.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# firstboot.sh - 由 weknora-firstboot.service 在新实例首次开机时自动执行。 +# 任务: 生成随机密钥写入 .env -> 启动容器 -> 输出凭证 -> 自删除。 +set -euo pipefail + +WEKNORA_DIR="${WEKNORA_DIR:-/opt/WeKnora}" +ENV_FILE="${WEKNORA_DIR}/.env" +CRED_FILE="/root/weknora-credentials.txt" +LOG_FILE="/var/log/weknora-firstboot.log" + +exec >>"${LOG_FILE}" 2>&1 +echo "==== firstboot started at $(date -Iseconds) ====" + +if [[ ! -f "${ENV_FILE}" ]]; then + echo "ERROR: ${ENV_FILE} not found" + exit 1 +fi + +# 生成 32 字节强随机字符串(用于 AES-256 key, 必须刚好 32 字节) +gen32() { LC_ALL=C tr -dc 'A-Za-z0-9' >"${ENV_FILE}" + fi +} + +replace DB_PASSWORD "${DB_PWD}" +replace REDIS_PASSWORD "${REDIS_PWD}" +replace JWT_SECRET "${JWT}" +replace SYSTEM_AES_KEY "${SYS_AES}" +replace TENANT_AES_KEY "${TENANT_AES}" +replace GIN_MODE "release" + +echo "env updated, starting docker compose..." +cd "${WEKNORA_DIR}" +/usr/bin/docker 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}') + +umask 077 +cat >"${CRED_FILE}" <&2 + exit 1 +fi + +echo "[prepare] 1/6 安装 Docker 与依赖" +if ! command -v docker >/dev/null 2>&1; then + curl -fsSL https://get.docker.com | bash +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 + +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 + +curl -fsSL "${WEKNORA_REPO}/archive/${WEKNORA_REF}.tar.gz" -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" < up -d + +echo "[prepare] 5/6 安装 systemd 单元" +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 + +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" diff --git a/scripts/cloud-image/systemd/weknora-firstboot.service b/scripts/cloud-image/systemd/weknora-firstboot.service new file mode 100644 index 000000000..eff4bd997 --- /dev/null +++ b/scripts/cloud-image/systemd/weknora-firstboot.service @@ -0,0 +1,16 @@ +[Unit] +Description=WeKnora first-boot initialization (runs once) +After=docker.service network-online.target +Requires=docker.service +Wants=network-online.target +Before=weknora.service +ConditionPathExists=/usr/local/sbin/weknora-firstboot.sh + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/weknora-firstboot.sh +RemainAfterExit=no +TimeoutStartSec=900 + +[Install] +WantedBy=multi-user.target diff --git a/scripts/cloud-image/systemd/weknora.service b/scripts/cloud-image/systemd/weknora.service new file mode 100644 index 000000000..b42c73195 --- /dev/null +++ b/scripts/cloud-image/systemd/weknora.service @@ -0,0 +1,16 @@ +[Unit] +Description=WeKnora (docker compose) +Requires=docker.service +After=docker.service network-online.target weknora-firstboot.service +Wants=network-online.target + +[Service] +Type=oneshot +RemainAfterExit=yes +WorkingDirectory=/opt/WeKnora +ExecStart=/usr/bin/docker compose up -d +ExecStop=/usr/bin/docker compose down +TimeoutStartSec=600 + +[Install] +WantedBy=multi-user.target