diff --git a/.gitignore b/.gitignore index 7d9203f0c22acc172f0a72890c66d308b5ceca56..8832cc689a32eb457eb0196d25e5ff8bd6d4f772 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ platform_data/openclaw/* !platform_data/openclaw/.gitkeep vendor/chrome/147.0.7727.57/*.zip vendor/qmd-models/*.gguf +vendor/aws/*.zip \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 49c798be3040302b99ad7a37780c7b9ee7fce41e..3a7ee23730fc7b0c8862541d47ad5c0f0a7d9599 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ARG TARGETARCH ARG NODE_VERSION=24.14.1 ARG OPENCLAW_VERSION=2026.4.15 -ARG IMAGE_VERSION=20260424.02 +ARG IMAGE_VERSION=dev ARG QMD_VERSION=2.1.0 ARG MQTT_CHANNEL_VERSION=2.2.0 ARG HZG_CLI_VERSION=2.0.0 @@ -98,7 +98,30 @@ RUN sed -i "s|http://archive.ubuntu.com/ubuntu|${UBUNTU_APT_MIRROR}|g" /etc/apt/ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Layer 2: install Node.js. +# Optional offline AWS CLI archives. +# If present, place them at vendor/aws/awscli-exe-linux-.zip. +COPY vendor/aws /tmp/vendor/aws + +# Layer 2: install AWS CLI for the Qiniu S3-compatible API. +RUN case "${TARGETARCH}" in \ + amd64) aws_arch="x86_64" ;; \ + arm64) aws_arch="aarch64" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ + esac \ + && aws_local_zip="/tmp/vendor/aws/awscli-exe-linux-${aws_arch}.zip" \ + && if [ -f "${aws_local_zip}" ]; then \ + echo "Using local AWS CLI archive: ${aws_local_zip}" \ + && cp "${aws_local_zip}" /tmp/awscliv2.zip; \ + else \ + echo "Local AWS CLI archive not found, falling back to upstream download" \ + && curl -fsSLo /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${aws_arch}.zip"; \ + fi \ + && unzip -q /tmp/awscliv2.zip -d /tmp \ + && /tmp/aws/install \ + && rm -rf /tmp/aws /tmp/awscliv2.zip \ + && aws --version + +# Layer 3: install Node.js. # Rebuild this layer only when the Node version or target architecture changes. RUN case "${TARGETARCH}" in \ amd64) node_arch="x64" ;; \ @@ -117,7 +140,7 @@ COPY vendor/chrome /tmp/vendor/chrome # If present, place them at vendor/qmd-models/.gguf. COPY vendor/qmd-models /tmp/vendor/qmd-models -# Layer 3: create the runtime user and persistent directory skeleton before bundled installs. +# Layer 4: create the runtime user and persistent directory skeleton before bundled installs. # The platform user's HOME lives on the persisted data volume so ~/.openclaw stays a real directory. # Runtime npm and pip roots stay writable for the platform user after the container starts. RUN useradd --home-dir ${HOME} --shell /bin/bash platform \ @@ -131,7 +154,7 @@ RUN useradd --home-dir ${HOME} --shell /bin/bash platform \ /var/platform_data/pip-packages/bin \ && chown -R platform:platform ${BUNDLED_FILES_ROOT} /var/platform_data ${HOME} ${PLATFORM_BUNDLED_QMD_CACHE_ROOT} -# Layer 4: install bundled Node CLIs. +# Layer 5: install bundled Node CLIs. # These bundled CLIs are pinned into /usr/local so later runtime installs can still use /var/platform_data. # For amd64 Chrome for Testing, also install the legacy setuid sandbox helper inside # the container so Ubuntu/AppArmor hosts do not require host-level sysctl changes. @@ -175,10 +198,10 @@ RUN npm config set registry ${NPM_REGISTRY} \ && qmd --version \ && ln -sf /usr/bin/pip3 /usr/local/bin/pip -# Layer 5: pre-download the default QMD GGUF models into an immutable image cache. +# Layer 6: pre-download the default QMD GGUF models into an immutable image cache. # Runtime bind mounts can replace /var/platform_data, so bundled models must live outside it. # Prefer repository-local GGUF files when present, otherwise fall back to Hugging Face. -RUN runuser -u platform --preserve-environment -- env HOME=${HOME} XDG_CACHE_HOME=${PLATFORM_BUNDLED_QMD_CACHE_ROOT} node --input-type=module <<'EOF' +RUN env HOME=${HOME} XDG_CACHE_HOME=${PLATFORM_BUNDLED_QMD_CACHE_ROOT} node --input-type=module <<'EOF' import { copyFileSync, existsSync, mkdirSync, statSync } from "fs"; import { join } from "path"; import { @@ -230,7 +253,9 @@ for (const result of results) { } EOF -# Layer 6: install the default MQTT channel into a build-only OpenClaw state dir, +RUN chown -R platform:platform ${PLATFORM_BUNDLED_QMD_CACHE_ROOT} + +# Layer 7: install the default MQTT channel into a build-only OpenClaw state dir, # then copy the resolved plugin payload into a read-only bundled path. RUN build_state_dir=/tmp/openclaw-build-state \ && build_home_dir=/tmp/openclaw-build-home \ @@ -254,7 +279,7 @@ RUN build_state_dir=/tmp/openclaw-build-state \ && chmod -R a=rX,u+w "${OPENCLAW_BUNDLED_PLUGIN_ROOT}/mqtt-channel" \ && rm -rf "${build_state_dir}" "${build_home_dir}" "${install_log}" -# Layer 7: install default ClawHub skills into a build workspace, then copy the +# Layer 8: install default ClawHub skills into a build workspace, then copy the # resolved skill folders into a read-only bundled path. These default skills # rely on Python and Chromium, both of which are already installed above. RUN build_state_dir=/tmp/openclaw-build-state \ @@ -293,14 +318,14 @@ RUN build_state_dir=/tmp/openclaw-build-state \ && chmod -R a=rX,u+w "${OPENCLAW_BUNDLED_SKILL_ROOT}" \ && rm -rf "${build_state_dir}" "${build_home_dir}" "${build_workspace_dir}" "${install_log}" -# Layer 8: warm up QMD search after model pre-download completes. +# Layer 9: warm up QMD search after model pre-download completes. # Use BM25-only search here so CI mode does not trigger LLM-backed query expansion. # This keeps later platform_home or platform_data changes from invalidating the warmup layer. RUN runuser -u platform --preserve-environment -- env TERM=dumb CI=1 XDG_CACHE_HOME=${PLATFORM_BUNDLED_QMD_CACHE_ROOT} qmd search "test" >/tmp/qmd-warmup.log 2>&1 \ || { cat /tmp/qmd-warmup.log; exit 1; } \ && rm -f /tmp/qmd-warmup.log -# Layer 9: copy platform templates, data skeleton, and management scripts. +# Layer 10: copy platform templates, data skeleton, and management scripts. COPY --chown=platform:platform platform_home ${BUNDLED_FILES_ROOT}/platform_home COPY --chown=platform:platform platform_data ${BUNDLED_FILES_ROOT}/platform_data COPY platform_home/scripts/platform-common.sh /usr/local/lib/platform/common.sh @@ -310,7 +335,7 @@ COPY platform_home/scripts/logs.sh /usr/local/bin/logs COPY platform_home/scripts/restart.sh /usr/local/bin/restart COPY platform_home/scripts/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -# Layer 10: generate image metadata for version-aware runtime upgrades. +# Layer 11: generate image metadata for version-aware runtime upgrades. RUN jq -n \ --arg image_version "${IMAGE_VERSION}" \ --arg openclaw_version "${OPENCLAW_VERSION}" \ @@ -338,18 +363,10 @@ RUN jq -n \ } \ }' > ${BUNDLED_FILES_ROOT}/platform-release.json -# Layer 11: normalize line endings, set execute bits, and verify the qshell binary for the target architecture. -RUN case "${TARGETARCH}" in \ - amd64) qshell_path="${BUNDLED_FILES_ROOT}/platform_home/bin/linux-x64/qshell" ;; \ - arm64) qshell_path="${BUNDLED_FILES_ROOT}/platform_home/bin/linux-arm64/qshell" ;; \ - *) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ - esac \ - && sed -i 's/\r$//' /usr/local/lib/platform/common.sh \ +# Layer 12: normalize line endings and set execute bits on bundled scripts. +RUN sed -i 's/\r$//' /usr/local/lib/platform/common.sh \ && sed -i 's/\r$//' /usr/local/bin/agents /usr/local/bin/doctor /usr/local/bin/logs /usr/local/bin/restart /usr/local/bin/docker-entrypoint.sh \ && find ${BUNDLED_FILES_ROOT}/platform_home/scripts -type f -name '*.sh' -exec sed -i 's/\r$//' {} + \ - && test -f "${qshell_path}" \ - && chmod +x "${qshell_path}" \ - && find ${BUNDLED_FILES_ROOT}/platform_home/bin -type f -name qshell -exec chmod +x {} \; \ && chmod +x /usr/local/lib/platform/common.sh \ && chmod +x /usr/local/bin/agents \ && chmod +x /usr/local/bin/doctor \ diff --git a/MANUAL_DEPLOY.md b/MANUAL_DEPLOY.md index 0809508e3078d875202c789c73a61c77a8a0a98f..bc75645a904971844305fa2200d4f1dedf70eb8b 100644 --- a/MANUAL_DEPLOY.md +++ b/MANUAL_DEPLOY.md @@ -218,6 +218,34 @@ sudo npm install huozige-web-app-cli -g huozige-web-app-cli status ``` +安装 AWS CLI v2,用于按照 `/opt/openclaw_enterprise_terminal/FILE-TRANSFER.md` 中的约定,通过 S3 协议传输文件。 + +Linux x64 示例: + +```bash +curl -fsSLo /tmp/awscliv2.zip https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip +cd /tmp +unzip -q awscliv2.zip +sudo ./aws/install +aws --version +``` + +Linux arm64 示例: + +```bash +curl -fsSLo /tmp/awscliv2.zip https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip +cd /tmp +unzip -q awscliv2.zip +sudo ./aws/install +aws --version +``` + +如果需要升级已安装版本,可改用: + +```bash +sudo ./aws/install --update +``` + 创建 `/opt/openclaw_enterprise_terminal/` 目录,并确保 OpenClaw 的运行身份有 read 权限。 ```bash @@ -226,15 +254,13 @@ sudo chown /opt/openclaw_enterprise_terminal/ -R sudo chmod 755 /opt/openclaw_enterprise_terminal/ ``` -将本项目中的 `FILE-TRANSFER.md` 、 `HZG-INVOKE.md` 和 `bin` 目录全部上传到该目录。 +将本项目中的 `FILE-TRANSFER.md` 和 `HZG-INVOKE.md` 上传到该目录。 修改 openclaw.json 的 env 根节点下 var 节点,配置环境变量。 ```json "env": { "vars": { - "QINIU_ACCESS_KEY": "<步骤3中记录的AK>", - "QINIU_SECRET_KEY": "<步骤3中记录的SK>", "HZG_CLI_MQTT_BROKER":"<步骤2中记录的访问地址和端口,mqtts://nc166001.ala.cn-hangzhou.emqxsl.cn:8883>", "HZG_CLI_USERNAME":"<步骤2中记录的的用户名>", "HZG_CLI_PASSWORD":"<步骤2中记录的的密码>", @@ -251,6 +277,41 @@ sudo chmod 755 /opt/openclaw_enterprise_terminal/ openclaw gateway restart ``` +然后使用 OpenClaw 的运行身份,写入 AWS CLI 配置。下面以 `platform` 用户为例,请按实际运行身份替换: + +```bash +sudo -u platform mkdir -p /var/platform_data/.aws + +sudo -u platform tee /var/platform_data/.aws/credentials >/dev/null <<'EOF' +[default] +aws_access_key_id = <步骤3中记录的AK> +aws_secret_access_key = <步骤3中记录的SK> +EOF + +sudo -u platform tee /var/platform_data/.aws/config >/dev/null <<'EOF' +[default] +region = <步骤3中记录的Region,如cn-east-1> +endpoint_url = <步骤3中记录的S3 Endpoint,如https://s3.cn-east-1.qiniucs.com> +EOF + +sudo chown -R platform:platform /var/platform_data/.aws +sudo chmod 700 /var/platform_data/.aws +sudo chmod 600 /var/platform_data/.aws/credentials /var/platform_data/.aws/config +``` + +完成后可以做一次连通性验证: + +```bash +sudo -u platform aws s3 ls --endpoint-url <步骤3中记录的S3 Endpoint,如https://s3.cn-east-1.qiniucs.com> +``` + +如果希望直接验证文件上传链路,也可以继续执行: + +```bash +echo "manual deploy smoke test" >/tmp/aws-upload-smoke.txt +sudo -u platform aws s3 cp /tmp/aws-upload-smoke.txt s3://agents-out/$(date +%s)_aws-upload-smoke.txt +``` + 这一步需要记录以下信息备用: - HZG_CLI_REQUEST_TOPIC @@ -282,7 +343,7 @@ openclaw gateway restart ## 文件传输 -接收到文件 Uri 或者需要将文件发送给用户时,需要先阅读 `/opt/openclaw_enterprise_terminal/FILE-TRANSFER.md` 采用 `Qshell` 完成文件传输。 +接收到文件 Uri 或者需要将文件发送给用户时,需要先阅读 `/opt/openclaw_enterprise_terminal/FILE-TRANSFER.md` 采用 AWS CLI 的 S3 协议完成文件传输。 ``` diff --git a/README.md b/README.md index eb72673ee00b04a518cb439b7d578410fb8d6a91..38a2b711d13f7d48773ad26c0c24dd760649a461 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ U --> I --> E --> B --> D ## 网络拓扑 -位于DMZ隔离网络的 `OC端`(安装有 Agent 执行器) --- Internet ---→ MQTT Broker(如EMQX Cloud)/ 七牛云 --- Internet ---→ 位于安全网络的`服务器端`(安装有活字格服务器端,含 AI 工作台和业务系统) +位于DMZ隔离网络的 `OC端`(安装有 Agent 执行器) --- Internet ---→ MQTT Broker(如EMQX Cloud)/ OSS 对象存储(兼容S3,如七牛云) --- Internet ---→ 位于安全网络的`服务器端`(安装有活字格服务器端,含 AI 工作台和业务系统) 推荐做法: @@ -37,7 +37,7 @@ U --> I --> E --> B --> D - 操作系统:Ubuntu 24.x 或更新版本 - 硬件:8 Core / 16 GB RAM(20+ 并发) - 数据库:MySQL 8 或更新版本 -- 网络:需要确保该服务器可以连接到互联网(包括 MQTT Broker 和七牛云) +- 网络:需要确保该服务器可以连接到互联网(包括 MQTT Broker 和 OSS) ## 2、准备 MQTT Broker @@ -58,21 +58,26 @@ EMQX Cloud 地址:`https://cloud.emqx.com/console/` - 用户名:`?` - 密码:`?` -## 3、准备七牛云 +## 3、准备 OSS(S3 兼容) -> 以七牛云 Kodo 对象存储为例,其他对象存储实现原理类似,但需要修改本项目中的 `FILE-TRANSFER.md` 以适配相应服务。 +> 以七牛云 Kodo 对象存储为例,可采用兼容AWS S3操作接口的其他OSS。执行器侧文件传输现已统一走 S3 协议,容器内使用 `aws` CLI。 七牛云对象存储官网:`https://www.qiniu.com/products/kodo` +七牛云 AWS CLI 示例:`https://developer.qiniu.com/kodo/12574/aws-cli-examples` + 1. 按照官方说明注册 七牛云 账号并登录 2. 按照页面提示完成实名认证(截止 2026年4月8日,七牛云为用户创建的桶提供少量的免费配额,可满足基本的验证测试要求) 3. 在控制台的密钥管理页面,启用密钥访问 4. 创建一个新的密钥,保存好 AK 和 SK +5. 确认所用空间所属 Region 和对应 S3 Endpoint,例如 `cn-east-1` 与 `https://s3.cn-east-1.qiniucs.com` 这一步需要记录以下信息备用: - AK:`?` - SK:`?` +- Region:`?` +- S3 Endpoint:`?` ## 4、准备 Agent 执行器 @@ -84,7 +89,7 @@ EMQX Cloud 地址:`https://cloud.emqx.com/console/` - 操作系统 : Ubuntu 24.x 或更新版本 - 硬件 : 2 Core / 8 GB RAM (10+并发) - LLM :公网的大模型服务,如 GPT5.4、Qwen3.5-Plus 等 -- 网络 :需要确保该服务器可以连接到互联网(包括 MQTT Broker 和七牛云) +- 网络 :需要确保该服务器可以连接到互联网(包括 MQTT Broker 和七牛云对象存储) ### 4.1 准备执行器的配置文件 @@ -178,15 +183,17 @@ sudo chmod 755 /var/platform_data "HZG_CLI_TIMEOUT": "500", "HZG_CLI_USERNAME": "xxx", "HZG_CLI_PASSWORD": "xxx", - "QINIU_ACCESS_KEY": "xxx", - "QINIU_SECRET_KEY": "xxx", "OC_OPENAI_API_KEY": "sk-xxx", "OC_ANTHROPIC_API_KEY": "", "OC_MQTT_CHANNEL_BROKER": "mqtts://example.ala.cn-hangzhou.emqxsl.cn:8883", "OC_MQTT_CHANNEL_USERNAME": "xxx", "OC_MQTT_CHANNEL_PASSWORD": "xxx", "OC_MQTT_CHANNEL_INBOUND_TOPIC_TPL": "agents/channel/{agent-name}/inbound", - "OC_MQTT_CHANNEL_OUTBOUND_TOPIC_TPL": "agents/channel/{agent-name}/outbound" + "OC_MQTT_CHANNEL_OUTBOUND_TOPIC_TPL": "agents/channel/{agent-name}/outbound", + "S3_ACCESS_KEY_ID": "xxx", + "S3_SECRET_ACCESS_KEY": "xxx", + "S3_ENDPOINT_URL": "https://s3.cn-east-1.qiniucs.com", + "S3_REGION": "cn-east-1" } ``` @@ -198,8 +205,10 @@ sudo chmod 755 /var/platform_data - HZG_CLI_PASSWORD、OC_MQTT_CHANNEL_PASSWORD:步骤2中记录的密码 - OC_OPENAI_API_KEY:如果你采用的大模型是 OpenAI 风格接口,将密钥配置到这里 - OC_ANTHROPIC_API_KEY:如果你采用的大模型是 Anthropic 风格接口,将密钥配置到这里 -- QINIU_ACCESS_KEY:步骤3中记录的 AK -- QINIU_SECRET_KEY:步骤3中记录的 SK +- S3_ACCESS_KEY_ID:步骤3中记录的 AK +- S3_SECRET_ACCESS_KEY:步骤3中记录的 SK +- S3_REGION:步骤3中记录的 Region +- S3_ENDPOINT_URL:步骤3中记录的 S3 Endpoint 其他项目直接采用默认值即可。 @@ -246,7 +255,7 @@ Agent 执行器的镜像保存在阿里云,您需要将其拉取到 `OC端`。 docker pull crpi-4auaoyyj6r36p6lb.cn-hangzhou.personal.cr.aliyuncs.com/huozige_lab/enterprise-agent-platform-oc-x64:{版本号} ``` -版本号请参考本方案的 release ,如 `20260424.02`。· +版本号请参考本方案的 release ,如 `20260424.02`。如需在 arm64 架构的服务器上部署,请发邮件联系:`will.ning@grapecity.com` 拉取完成后,您需要执行命令并启动名为 `enterprise-agent-platform-oc` 的容器。 @@ -296,7 +305,7 @@ Agent 执行器内置了一些创建的操作命令,均可通过 `docker exec` `doctor` 命令:用于执行配置自检,确保配置层面的完整性 -- doctor:检查配置,包含 `config.json`、`env.json`、目录权限、QMD/OpenClaw CLI 可用性、`openclaw config validate`,以及 gateway 是否处于运行状态;建议在首次部署、升级镜像、修改 `env.json` 或排查 agent 无响应问题时先执行一次。 +- doctor:检查配置,包含 `config.json`、`env.json`、目录权限、QMD/OpenClaw CLI/AWS CLI 可用性、S3 客户端配置、`openclaw config validate`,以及 gateway 是否处于运行状态;建议在首次部署、升级镜像、修改 `env.json` 或排查 agent 无响应问题时先执行一次。 `restart` 命令:手动重启 Agent 执行器 @@ -329,6 +338,8 @@ Agent 执行器基于 `OpenClaw`,内置了以下常用组件(CLI 程序 / AP 在管理控制台上,设置 `claw` 应用的全局变量: +说明:这里的 `QINIU_*` 是门户应用现有字段命名;执行器容器内的文件传输配置改为 `env.json` 中的 `S3_*`。 + - MQTT_BROKER_HOST : 步骤2中记录的访问地址 - MQTT_BROKER_PORT : 步骤2中记录的端口 - MQTT_BROKER_USER : 步骤2中记录的用户名 @@ -351,7 +362,7 @@ Agent 执行器基于 `OpenClaw`,内置了以下常用组件(CLI 程序 / AP 然后修改 `Template` 提示词模板,其中提供了三个用于替换的关键字: - `[Input]`:用户输入的问题,必须保留 -- `n_27691352`:`服务器端` 的会话 ID,“一个用户 + 一个 Agent 的组合”对应一个会话,这个信息会影响鉴权,必须保留 +- `[Session]`:`服务器端` 的会话 ID,“一个用户 + 一个 Agent 的组合”对应一个会话,这个信息会影响鉴权,必须保留 - `[UserName]`:当前使用的用户名,可选 最后修改 `Users` 字段,设置有权限使用该 Agent 的用户列表,多个用户间采用半角逗号分隔。 diff --git a/docker-build-arm64.sh b/docker-build-arm64.sh new file mode 100755 index 0000000000000000000000000000000000000000..341ca6586ac26df2560a272f36ee7890fb3e0c79 --- /dev/null +++ b/docker-build-arm64.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +set -euo pipefail + +context_dir="." +session_root=".build/docker-builds" +image_version="" +image_tag="" + +usage() { + cat <<'EOF' +Usage: + ./docker-build-arm64.sh --version [--tag ] [--context ] [--session-root ] + +Examples: + ./docker-build-arm64.sh --version 20260425.01 + ./docker-build-arm64.sh --version 20260425.01 --tag enterprise-agent-platform-oc-arm64:20260425.01 +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --version) + image_version="${2:-}" + shift 2 + ;; + --tag) + image_tag="${2:-}" + shift 2 + ;; + --context) + context_dir="${2:-}" + shift 2 + ;; + --session-root) + session_root="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "${image_version}" ]]; then + echo "Missing required --version argument." >&2 + usage >&2 + exit 1 +fi + +if [[ -z "${image_tag}" ]]; then + image_tag="enterprise-agent-platform-oc-arm64:${image_version}" +fi + +resolved_context_dir="$(cd "${context_dir}" && pwd)" +resolved_session_root="${resolved_context_dir}/${session_root}" +mkdir -p "${resolved_session_root}" + +session_id="$(date +"%Y%m%d-%H%M%S")" +session_dir="${resolved_session_root}/${session_id}" +mkdir -p "${session_dir}" + +log_path="${session_dir}/docker-build.timed.log" +meta_path="${session_dir}/meta.json" +exit_code_path="${session_dir}/exit-code.txt" + +started_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" +clawhub_token="${OPENCLAW_CLAWHUB_TOKEN:-${CLAWHUB_TOKEN:-${CLAWHUB_AUTH_TOKEN:-}}}" +clawhub_auth_detected="false" +if [[ -n "${clawhub_token}" ]]; then + clawhub_auth_detected="true" +fi + +cat > "${meta_path}" <&1 | tee "${log_path}" +exit_code=${PIPESTATUS[0]} +set -e + +printf '%s\n' "${exit_code}" > "${exit_code_path}" + +finished_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" +cat > "${meta_path}" <[^:]+)$') { + return $null + } + + $tagValue = $matches['tag'] + if ([string]::IsNullOrWhiteSpace($tagValue) -or $tagValue -eq "latest" -or $tagValue -eq "timed-build") { + return $null + } + + return $tagValue +} + function Get-FirstEnvironmentValue { param([string[]]$Names) @@ -208,6 +228,10 @@ if ([string]::IsNullOrWhiteSpace($ImageTag)) { $ImageTag = Get-DefaultImageTag -Arch $TargetArch } +if ([string]::IsNullOrWhiteSpace($ImageVersion)) { + $ImageVersion = Get-ImageVersionFromTag -Tag $ImageTag +} + $resolvedContextDir = (Resolve-Path -LiteralPath $ContextDir).Path $resolvedSessionRoot = Join-Path $resolvedContextDir $SessionRoot New-Item -ItemType Directory -Force -Path $resolvedSessionRoot | Out-Null @@ -229,6 +253,7 @@ $startedAt = Get-Date sessionDir = $sessionDir contextDir = $resolvedContextDir targetArch = $TargetArch + imageVersion = $ImageVersion imageTag = $ImageTag logPath = $logPath exitCodePath = $exitCodePath @@ -243,6 +268,11 @@ $startedAt = Get-Date Write-Host ("Session: {0}" -f $sessionDir) Write-Host ("Image tag: {0}" -f $ImageTag) +if (-not [string]::IsNullOrWhiteSpace($ImageVersion)) { + Write-Host ("Image version: {0}" -f $ImageVersion) +} else { + Write-Host "Image version: not set; Dockerfile default IMAGE_VERSION will be used" +} Write-Host ("Target arch: {0}" -f $TargetArch) Write-Host ("Context: {0}" -f $resolvedContextDir) if ($clawHubAuth.Detected) { @@ -260,11 +290,18 @@ $dockerArgs = @( "--build-arg", "TARGETARCH=$TargetArch" ) +if (-not [string]::IsNullOrWhiteSpace($ImageVersion)) { + $dockerArgs += @("--build-arg", "IMAGE_VERSION=$ImageVersion") +} + if ($clawHubAuth.Detected) { $dockerArgs += @("--build-arg", "CLAWHUB_TOKEN=$($clawHubAuth.Token)") } $displayBuildArgs = @("TARGETARCH=$TargetArch") +if (-not [string]::IsNullOrWhiteSpace($ImageVersion)) { + $displayBuildArgs += "IMAGE_VERSION=$ImageVersion" +} if ($clawHubAuth.Detected) { $displayBuildArgs += "CLAWHUB_TOKEN=" } @@ -299,6 +336,7 @@ $exitCode = if (Test-Path -LiteralPath $exitCodePath) { sessionDir = $sessionDir contextDir = $resolvedContextDir targetArch = $TargetArch + imageVersion = $ImageVersion imageTag = $ImageTag logPath = $logPath exitCodePath = $exitCodePath diff --git a/docker-internal-test.ps1 b/docker-internal-test.ps1 index b8863ee425998611770a50fcef1f3a26e538b9a9..8f5412526681651d2bda5cabdd84b61c82ba666d 100644 --- a/docker-internal-test.ps1 +++ b/docker-internal-test.ps1 @@ -227,6 +227,28 @@ function New-SyntheticCommandResult { } } +function Add-SkippedStep { + param( + [Parameter(Mandatory = $true)][string]$Name, + [Parameter(Mandatory = $true)][string]$CommandLine, + [string]$Notes = "" + ) + + $result = New-SyntheticCommandResult -CommandLine $CommandLine -Stdout "Skipped" -Stderr "" + Add-Result -Name $Name -Status "SKIP" -CommandResult $result -Notes $Notes +} + +function Get-ContainerArchitecture { + param([Parameter(Mandatory = $true)][string]$Container) + + $result = Invoke-ExternalCommand -FilePath "docker" -Arguments (New-DockerExecArgs -Container $Container -ExecArgs @("uname", "-m")) -TimeoutSeconds 30 + if ($result.ExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($result.Stdout)) { + return "" + } + + return $result.Stdout.Trim().ToLowerInvariant() +} + function Test-AgentListed { param( [string]$Output, @@ -744,6 +766,69 @@ function New-AgentExecCommand { return "openclaw agent --agent {0} --message 'You must use the exec tool to run the shell command pwd, then reply with only the absolute path output from that command.' --json --timeout {1}" -f $TargetAgent, $TimeoutSeconds } +function Get-S3UploadStatus { + param( + $CommandResult, + [string]$ExpectedKeyPrefix + ) + + if ($CommandResult.TimedOut) { + return "TIMEOUT" + } + + if ($CommandResult.ExitCode -ne 0) { + return "FAIL" + } + + $combinedText = @($CommandResult.Stdout, $CommandResult.Stderr) -join "`n" + if ($combinedText -match [regex]::Escape($ExpectedKeyPrefix)) { + return "PASS" + } + + return "FAIL" +} + +function New-S3UploadCommand { + param([string]$TargetAgent) + + return (@( + "set -euo pipefail" + ("agent_name={0}" -f (ConvertTo-Json -Compress $TargetAgent)) + 'bucket="agents-out"' + 'timestamp="$(date +%s)"' + 'local_root="/tmp/s3-upload-smoke/${agent_name}"' + 'mkdir -p "${local_root}"' + 'local_file="${local_root}/${timestamp}_s3-upload-smoke.txt"' + 'printf ''s3 upload smoke test for %s at %s\n'' "${agent_name}" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "${local_file}"' + 'aws s3 ls "s3://${bucket}" >/dev/null 2>&1 || aws s3 mb "s3://${bucket}"' + 'aws s3 cp "${local_file}" "s3://${bucket}/$(basename "${local_file}")"' + 'aws s3 ls "s3://${bucket}/$(basename "${local_file}")"' + ) -join "`n") +} + +function Invoke-S3UploadStep { + param( + [Parameter(Mandatory = $true)][string]$Name, + [Parameter(Mandatory = $true)][string]$Container, + [Parameter(Mandatory = $true)][string]$TargetAgent, + [int]$TimeoutSeconds = 120 + ) + + $expectedKeyPrefix = "s3://agents-out/" + $uploadCommand = New-S3UploadCommand -TargetAgent $TargetAgent + + return Invoke-TestStep ` + -Name $Name ` + -FilePath "docker" ` + -Arguments (New-DockerExecUserArgs -Container $Container -User "platform" -ExecArgs @("/bin/bash", "-lc", $uploadCommand)) ` + -TimeoutSeconds $TimeoutSeconds ` + -StatusEvaluator { + param($r) + return Get-S3UploadStatus -CommandResult $r -ExpectedKeyPrefix $expectedKeyPrefix + } ` + -Notes ("Expected uploaded object under {0}" -f $expectedKeyPrefix) +} + Append-Line ("Docker internal regression test") Append-Line ("Started At : {0}" -f (Get-Date).ToString("o")) Append-Line ("Container : {0}" -f $ContainerName) @@ -757,6 +842,8 @@ $DeleteProbeAgentName = "{0}_delete" -f $AgentName $PostCleanupAgentName = "codexprobe" $containerInspectBefore = $null $dockerRunArgs = @() +$containerArchitecture = "" +$skipAgentBrowserChecks = $false Append-Line ("Pre-Rerun Agent : {0}" -f $PreRerunAgentName) Append-Line ("Post-Rerun Agent : {0}" -f $PostRerunAgentName) @@ -784,6 +871,13 @@ $portCheck = Invoke-TestStep ` } ` -Notes "Expected {} or null, meaning no host ports are published." +$containerArchitecture = Get-ContainerArchitecture -Container $ContainerName +$skipAgentBrowserChecks = $containerArchitecture -in @("aarch64", "arm64") +if ($skipAgentBrowserChecks) { + Append-Line ("Agent browser checks are skipped for container architecture: {0}" -f $containerArchitecture) + Append-Line +} + Invoke-WaitForGatewayReadyStep -Name "gateway_ready_before" -Container $ContainerName -TimeoutSeconds 180 | Out-Null Invoke-TestStep -Name "agents_list_before" -FilePath "docker" -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("agents", "list")) -TimeoutSeconds 60 | Out-Null Invoke-TestStep -Name "doctor_before" -FilePath "docker" -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("doctor")) -TimeoutSeconds 120 | Out-Null @@ -823,23 +917,44 @@ Invoke-TestStep ` -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("/bin/bash", "-lc", $hzgStatusCommand)) ` -TimeoutSeconds 60 | Out-Null -Invoke-TestStep ` - -Name "agent_browser_open_pre_rerun" ` - -FilePath "docker" ` - -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("agent-browser", "--session", $AgentBrowserSession, "open", "http://127.0.0.1:18789/healthz")) ` - -TimeoutSeconds 90 | Out-Null +Invoke-S3UploadStep ` + -Name "s3_upload_pre_rerun" ` + -Container $ContainerName ` + -TargetAgent $PreRerunAgentName ` + -TimeoutSeconds 120 | Out-Null -Invoke-TestStep ` - -Name "agent_browser_get_text_pre_rerun" ` - -FilePath "docker" ` - -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("agent-browser", "--session", $AgentBrowserSession, "get", "text", "body")) ` - -TimeoutSeconds 60 | Out-Null +if ($skipAgentBrowserChecks) { + Add-SkippedStep ` + -Name "agent_browser_open_pre_rerun" ` + -CommandLine ("docker exec {0} agent-browser --session {1} open http://127.0.0.1:18789/healthz" -f $ContainerName, $AgentBrowserSession) ` + -Notes ("Skipped on arm64 container architecture: {0}" -f $containerArchitecture) + Add-SkippedStep ` + -Name "agent_browser_get_text_pre_rerun" ` + -CommandLine ("docker exec {0} agent-browser --session {1} get text body" -f $ContainerName, $AgentBrowserSession) ` + -Notes ("Skipped on arm64 container architecture: {0}" -f $containerArchitecture) + Add-SkippedStep ` + -Name "agent_browser_close_pre_rerun" ` + -CommandLine ("docker exec {0} agent-browser --session {1} close" -f $ContainerName, $AgentBrowserSession) ` + -Notes ("Skipped on arm64 container architecture: {0}" -f $containerArchitecture) +} else { + Invoke-TestStep ` + -Name "agent_browser_open_pre_rerun" ` + -FilePath "docker" ` + -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("agent-browser", "--session", $AgentBrowserSession, "open", "http://127.0.0.1:18789/healthz")) ` + -TimeoutSeconds 90 | Out-Null -Invoke-TestStep ` - -Name "agent_browser_close_pre_rerun" ` - -FilePath "docker" ` - -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("agent-browser", "--session", $AgentBrowserSession, "close")) ` - -TimeoutSeconds 60 | Out-Null + Invoke-TestStep ` + -Name "agent_browser_get_text_pre_rerun" ` + -FilePath "docker" ` + -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("agent-browser", "--session", $AgentBrowserSession, "get", "text", "body")) ` + -TimeoutSeconds 60 | Out-Null + + Invoke-TestStep ` + -Name "agent_browser_close_pre_rerun" ` + -FilePath "docker" ` + -Arguments (New-DockerExecArgs -Container $ContainerName -ExecArgs @("agent-browser", "--session", $AgentBrowserSession, "close")) ` + -TimeoutSeconds 60 | Out-Null +} try { $containerInspectBefore = Get-ContainerInspectObject -Container $ContainerName diff --git a/env.json b/env.json index ff5ab0d704c6e7d772065d86e9dfb87ce0cd80f2..b5a681429aac43463d9756b11c844b7e0a12f18e 100644 --- a/env.json +++ b/env.json @@ -5,13 +5,15 @@ "HZG_CLI_TIMEOUT": "500", "HZG_CLI_USERNAME": "xxx", "HZG_CLI_PASSWORD": "xxx", - "QINIU_ACCESS_KEY": "xxx", - "QINIU_SECRET_KEY": "xxx", "OC_OPENAI_API_KEY": "sk-xxx", "OC_ANTHROPIC_API_KEY": "", "OC_MQTT_CHANNEL_BROKER": "mqtts://example.ala.cn-hangzhou.emqxsl.cn:8883", "OC_MQTT_CHANNEL_USERNAME": "xxx", "OC_MQTT_CHANNEL_PASSWORD": "xxx", "OC_MQTT_CHANNEL_INBOUND_TOPIC_TPL": "agents/channel/{agent-name}/inbound", - "OC_MQTT_CHANNEL_OUTBOUND_TOPIC_TPL": "agents/channel/{agent-name}/outbound" + "OC_MQTT_CHANNEL_OUTBOUND_TOPIC_TPL": "agents/channel/{agent-name}/outbound", + "S3_ACCESS_KEY_ID": "xxx", + "S3_SECRET_ACCESS_KEY": "xxx", + "S3_ENDPOINT_URL": "https://s3.cn-east-1.qiniucs.com", + "S3_REGION": "cn-east-1" } diff --git a/platform_home/FILE-TRANSFER.md b/platform_home/FILE-TRANSFER.md index 63d180eea001b3b759c6588c4afa5fbdcda0fc28..e5007805a32c527179e55cba4c1f1df00412bf55 100644 --- a/platform_home/FILE-TRANSFER.md +++ b/platform_home/FILE-TRANSFER.md @@ -1,36 +1,25 @@ # File Transfer -平台智能体必须通过七牛云 `qshell` 传输文件,包含但不限于发送附件到聊天,不要用别的方式。 +平台智能体必须通过 `aws` CLI 的 S3 协议传输文件,包含但不限于发送附件到聊天,不要用别的方式。 -## 文件路径 +## 客户端与配置 -`qshell`的路径: - -- 优先使用 `command -v qshell` 查找当前实际可执行路径 -- 当前平台部署目录: `/opt/platform_home/bin/qshell` - -如果不存在,可在线下载。下载时请根据当前平台选择对应版本,且务必保留下载链接中的 `ref` 和 `s_path` 参数: - -- Linux x64: `https://kodo-toolbox-new.qiniu.com/qshell-v2.18.0-linux-amd64.tar.gz?ref=developer.qiniu.com&s_path=%2Fkodo%2F1302%2Fqshell` -- Linux arm64: `https://kodo-toolbox-new.qiniu.com/qshell-v2.18.0-linux-arm.tar.gz?ref=developer.qiniu.com&s_path=%2Fkodo%2F1302%2Fqshell` -- macOS arm64: `https://kodo-toolbox-new.qiniu.com/qshell-v2.18.0-darwin-arm64.tar.gz?ref=developer.qiniu.com&s_path=%2Fkodo%2F1302%2Fqshell` - -## 认证 - -在执行其他qshell指令前,需要使用AK和SK登录: `qshell account ` - -- : 从环境变量 `QINIU_ACCESS_KEY` 获取 -- : 从环境变量 `QINIU_SECRET_KEY` 获取 -- : 固定为 `platform` +- 优先使用 `command -v aws` 查找当前实际可执行路径。 +- 容器启动时会根据 `/var/platform_data/env.json` 自动生成 `~/.aws/credentials` 和 `~/.aws/config`。 +- 文件传输依赖以下环境变量: + - `S3_ACCESS_KEY_ID` + - `S3_SECRET_ACCESS_KEY` + - `S3_REGION` + - `S3_ENDPOINT_URL` ## 接收文件 当收到以 `file_input://` 开头的消息时: -1. 从消息中提取 `bucket` 和文件名。示例:`file_input://bname/fname.bin` 中,`bname` 是 bucket,`fname.bin` 是文件名。第一个空格后的内容一律忽略。 +1. 从消息中提取 `bucket` 和 `key`。示例:`file_input://bname/path/to/fname.bin` 中,`bname` 是 bucket,`path/to/fname.bin` 是 key。第一个空格后的内容一律忽略。 2. 下载到 Workspace 下的 `file_input` 目录。 3. 为避免冲突,保存名必须加时间戳前缀,例如 `123456_fname.bin`。如果原文件名已有时间戳前缀,只替换,不重复追加。 -4. 用 `qshell get -o ` 下载。 +4. 用 `aws s3 cp "s3:///" ""` 下载。 5. 下载完成后,把该文件的绝对路径加入上下文,再按我的后续要求读取和处理该文件。 ## 发送文件 @@ -38,20 +27,22 @@ 当你需要把文件发给我时: 1. 先给我回复解释该文件的内容,包括文件中有什么、该如何使用等,然后再做上传操作。 -2. 用 `qshell bucket agents-out` 检查名为 `agents-out` 的Bucket 是否存在;若不存在,用 `qshell mkbucket agents-out --region z0 --private` 创建。 +2. 固定使用 bucket `agents-out`。 3. 先把文件复制到 Workspace 下的 `file_output` 目录。 4. 复制后的文件名必须加时间戳前缀,例如 `123456_fname.bin`。如果原文件名已有时间戳前缀,只替换,不重复追加。 -5. 用 `qshell fput agents-out ` 上传,`` 是上传的文件名,`` 是需要上传的本地文件全路径。 -6. 上传成功后,立即给我回复这个格式的内容:`file_output://agents-out/`,不要再添加任何其他内容。 +5. 如果 bucket 不存在,先执行 `aws s3 ls "s3://agents-out" >/dev/null 2>&1 || aws s3 mb "s3://agents-out"`。 +6. 用 `aws s3 cp "" "s3://agents-out/"` 上传。 +7. 上传成功后,立即给我回复这个格式的内容:`file_output://agents-out/`,不要再添加任何其他内容。 ## 最小约束 -- 所有收发都必须走七牛云 `qshell`。 +- 所有收发都必须走 `aws` CLI 的 S3 协议。 - 发送文件需要用单独的回复,不允许和其他内容合并。 - `file_input` 和 `file_output` 目录不存在时先创建。 -- 不要改动 bucket/key 语义;下载时远端 key 是发来的文件名,上传时远端 key 是重命名后的文件名。 +- 下载时必须保留消息中的完整 key,不要只取最后一级文件名。 +- 上传时远端 key 固定为 bucket 根下的 `<时间戳文件名>`,不要再套目录。 - 不要声称“已做好”却同时又说上传失败。只有上传成功后才能返回 `file_output://...`。 ## 官方文档 -https://developer.qiniu.com/kodo/1302/qshell +https://developer.qiniu.com/kodo/12574/aws-cli-examples diff --git a/platform_home/bin/linux-arm64/qshell b/platform_home/bin/linux-arm64/qshell deleted file mode 100755 index 3dbc13be34753dbd37309f805cfa98a66261c606..0000000000000000000000000000000000000000 Binary files a/platform_home/bin/linux-arm64/qshell and /dev/null differ diff --git a/platform_home/bin/linux-x64/qshell b/platform_home/bin/linux-x64/qshell deleted file mode 100755 index 2d78ae864fbbc78497e6c0282b8a382bc415b7af..0000000000000000000000000000000000000000 Binary files a/platform_home/bin/linux-x64/qshell and /dev/null differ diff --git a/platform_home/scripts/docker-entrypoint.sh b/platform_home/scripts/docker-entrypoint.sh index 3dc05181e86e1301245667f910b221e36cc43e79..a0b97e6c038aa2f43f9d1883cec973feab3f02eb 100644 --- a/platform_home/scripts/docker-entrypoint.sh +++ b/platform_home/scripts/docker-entrypoint.sh @@ -80,7 +80,6 @@ if [[ "$(id -u)" -eq 0 && "${PLATFORM_ENTRYPOINT_REEXEC:-0}" != "1" ]]; then reconcile_platform_layout_with_image ensure_platform_runtime_permissions ensure_qmd_shared_models_cache - ln -sfn "${platform_home}/bin/qshell" /usr/local/bin/qshell install_runtime_env_shell_exports load_runtime_env_from_env_json export_openclaw_runtime_env @@ -102,13 +101,13 @@ if [[ ! -r "${env_json}" ]]; then fi ontology_dir="${ontology_root}" -qshell_path="${platform_home}/bin/qshell" mkdir -p "${platform_runtime_config_dir}" "${workspaces_root}" "${logs_root}" "${platform_home}" ensure_platform_runtime_home_symlink install_runtime_env_shell_exports load_runtime_env_from_env_json +install_s3_cli_config_from_env if [[ ! -d "${ontology_dir}" ]]; then echo "Missing ontology directory ${ontology_dir}." >&2 @@ -135,14 +134,6 @@ migrate_persisted_runtime_config mkdir -p "$(dirname "$(default_workspace_path)")" "$(default_workspace_path)" -if [[ ! -f "${qshell_path}" ]]; then - echo "Missing ${qshell_path}. Rebuild the image for the current architecture or restore the bundled qshell binary." >&2 - exit 1 -fi - -chmod +x "${qshell_path}" -export PATH="${platform_home}/bin:${PATH}" - if [[ $# -ge 3 && "$1" == "openclaw" && "$2" == "gateway" && ( "$3" == "run" || "$3" == "start" ) ]]; then exec > >(tee -a "${logs_root}/gateway.log") 2>&1 run_foreground_gateway_supervisor "$@" diff --git a/platform_home/scripts/doctor.sh b/platform_home/scripts/doctor.sh index 23f384957f06c9106116d021a524ecb14f1f374d..fc06b0f81cd0803f11936feadfc1c920a4f1bddd 100644 --- a/platform_home/scripts/doctor.sh +++ b/platform_home/scripts/doctor.sh @@ -145,8 +145,10 @@ main() { check_required_config_value '.OC_MQTT_CHANNEL_PASSWORD' 'OC_MQTT_CHANNEL_PASSWORD' "${env_json}" check_required_config_value '.OC_MQTT_CHANNEL_INBOUND_TOPIC_TPL' 'OC_MQTT_CHANNEL_INBOUND_TOPIC_TPL' "${env_json}" check_required_config_value '.OC_MQTT_CHANNEL_OUTBOUND_TOPIC_TPL' 'OC_MQTT_CHANNEL_OUTBOUND_TOPIC_TPL' "${env_json}" - check_required_config_value '.QINIU_ACCESS_KEY' 'QINIU_ACCESS_KEY' "${env_json}" - check_required_config_value '.QINIU_SECRET_KEY' 'QINIU_SECRET_KEY' "${env_json}" + check_required_config_value '.S3_ACCESS_KEY_ID' 'S3_ACCESS_KEY_ID' "${env_json}" + check_required_config_value '.S3_SECRET_ACCESS_KEY' 'S3_SECRET_ACCESS_KEY' "${env_json}" + check_required_config_value '.S3_ENDPOINT_URL' 'S3_ENDPOINT_URL' "${env_json}" + check_required_config_value '.S3_REGION' 'S3_REGION' "${env_json}" if jq -e '.HZG_CLI_MQTT_BROKER | test("^mqtts?://[^:]+:[0-9]+")' "${env_json}" >/dev/null; then pass "HZG_CLI_MQTT_BROKER includes host and port" @@ -171,6 +173,12 @@ main() { else warn "OC_MQTT_CHANNEL_OUTBOUND_TOPIC_TPL should contain {agent-name}" fi + + if jq -e '.S3_ENDPOINT_URL | test("^https?://[^[:space:]]+$")' "${env_json}" >/dev/null; then + pass "S3_ENDPOINT_URL is a valid URL" + else + warn "S3_ENDPOINT_URL should be a full URL like https://s3.cn-east-1.qiniucs.com" + fi fi if ensure_openclaw_cli; then @@ -185,8 +193,24 @@ main() { warn "qmd CLI is not available" fi + if command -v aws >/dev/null 2>&1; then + pass "aws CLI is available" + else + warn "aws CLI is not available" + fi + if [[ -r "${env_json}" ]]; then load_runtime_env_from_env_json + install_s3_cli_config_from_env + check_path_readable "${HOME}/.aws/config" "AWS config" + check_path_readable "${HOME}/.aws/credentials" "AWS credentials" + if command -v aws >/dev/null 2>&1; then + if [[ -n "$(aws configure get endpoint_url 2>/dev/null || true)" ]]; then + pass "AWS CLI endpoint configuration is available" + else + warn "AWS CLI endpoint configuration is missing" + fi + fi fi if [[ -r "${config_json}" ]] && jq -e '.memory.backend == "qmd"' "${config_json}" >/dev/null; then diff --git a/platform_home/scripts/platform-common.sh b/platform_home/scripts/platform-common.sh index 001b16ad40d66f14d632d1656dfcd09d6a21d8d8..655723ba92b0e4e8ec952888811b20c8b8b6860e 100644 --- a/platform_home/scripts/platform-common.sh +++ b/platform_home/scripts/platform-common.sh @@ -328,30 +328,11 @@ platform_home_has_managed_layout() { || [[ -e "${platform_home}/AGENTS.template.md" ]] \ || [[ -e "${platform_home}/SOUL.template.md" ]] \ || [[ -e "${platform_home}/USER.template.md" ]] \ - || [[ -d "${platform_home}/scripts" ]] \ - || [[ -f "${platform_home}/bin/qshell" ]] -} - -detect_linux_bin_subdir() { - local machine_arch - machine_arch="${PLATFORM_TARGET_ARCH:-$(uname -m)}" - - case "${machine_arch}" in - x86_64|amd64) - printf '%s' "linux-x64" - ;; - aarch64|arm64) - printf '%s' "linux-arm64" - ;; - *) - echo "Unsupported machine architecture: ${machine_arch}" >&2 - return 1 - ;; - esac + || [[ -d "${platform_home}/scripts" ]] } bootstrap_platform_layout_from_image() { - local bundled_root bundled_home bundled_data bundled_qshell + local bundled_root bundled_home bundled_data bundled_root="${bundled_files_root}" bundled_home="${bundled_root}/platform_home" @@ -359,7 +340,6 @@ bootstrap_platform_layout_from_image() { mkdir -p \ "${platform_home}" \ - "${platform_home}/bin" \ "${platform_home}/templates" \ "${platform_home}/scripts" \ "${ontology_root}" \ @@ -385,13 +365,12 @@ bootstrap_platform_layout_from_image() { merge_bundled_platform_data_skeleton "${bundled_data}" merge_directory_contents_if_present "${bundled_home}/ontology" "${ontology_root}" - bundled_qshell="${bundled_home}/bin/$(detect_linux_bin_subdir)/qshell" - copy_if_missing "${bundled_qshell}" "${platform_home}/bin/qshell" + rm -rf "${platform_home}/bin" ensure_platform_runtime_home_symlink } upgrade_platform_layout_from_image() { - local bundled_root bundled_home bundled_data bundled_qshell + local bundled_root bundled_home bundled_data bundled_root="${bundled_files_root}" bundled_home="${bundled_root}/platform_home" @@ -399,7 +378,6 @@ upgrade_platform_layout_from_image() { mkdir -p \ "${platform_home}" \ - "${platform_home}/bin" \ "${platform_home}/templates" \ "${ontology_root}" \ "${workspaces_root}" \ @@ -433,8 +411,7 @@ upgrade_platform_layout_from_image() { merge_bundled_platform_data_skeleton "${bundled_data}" merge_directory_contents_if_present "${bundled_home}/ontology" "${ontology_root}" - bundled_qshell="${bundled_home}/bin/$(detect_linux_bin_subdir)/qshell" - replace_path_with_copy "${bundled_qshell}" "${platform_home}/bin/qshell" + rm -rf "${platform_home}/bin" ensure_platform_runtime_home_symlink } @@ -509,9 +486,11 @@ ensure_platform_runtime_permissions() { "${platform_runtime_config_dir}" chown -R root:root "${platform_home}" - chown -R "${platform_runtime_user}:${platform_runtime_group}" \ - "${platform_data_root}" \ - "${platform_runtime_config_dir}" + # Skip sockets and other special files so a stale agent-browser socket + # in persisted data cannot crash container startup. + find "${platform_data_root}" \ + \( -type d -o -type f -o -type l \) \ + -exec chown "${platform_runtime_user}:${platform_runtime_group}" {} + ensure_directory_mode "${platform_home}" 755 644 ensure_directory_mode "${platform_data_root}" 755 644 @@ -528,10 +507,6 @@ ensure_platform_runtime_permissions() { find "${platform_home}/scripts" -type f -exec chmod 755 {} + fi - if [[ -f "${platform_home}/bin/qshell" ]]; then - chmod 755 "${platform_home}/bin/qshell" - fi - if [[ -d "${npm_global_prefix}/bin" ]]; then find "${npm_global_prefix}/bin" -type f -exec chmod 755 {} + fi @@ -783,6 +758,44 @@ install_runtime_env_shell_exports() { fi } +install_s3_cli_config_from_env() { + local aws_dir credentials_tmp config_tmp + + if [[ -z "${S3_ACCESS_KEY_ID:-}" && -z "${S3_SECRET_ACCESS_KEY:-}" \ + && -z "${S3_ENDPOINT_URL:-}" && -z "${S3_REGION:-}" ]]; then + return 0 + fi + + if [[ -z "${S3_ACCESS_KEY_ID:-}" || -z "${S3_SECRET_ACCESS_KEY:-}" \ + || -z "${S3_ENDPOINT_URL:-}" || -z "${S3_REGION:-}" ]]; then + echo "S3 CLI config skipped because S3_* values in env.json are incomplete." >&2 + return 0 + fi + + aws_dir="${HOME:-${platform_data_root}}/.aws" + mkdir -p "${aws_dir}" + chmod 700 "${aws_dir}" + + credentials_tmp="$(mktemp)" + config_tmp="$(mktemp)" + + { + printf '[default]\n' + printf 'aws_access_key_id = %s\n' "${S3_ACCESS_KEY_ID}" + printf 'aws_secret_access_key = %s\n' "${S3_SECRET_ACCESS_KEY}" + } > "${credentials_tmp}" + + { + printf '[default]\n' + printf 'region = %s\n' "${S3_REGION}" + printf 'endpoint_url = %s\n' "${S3_ENDPOINT_URL}" + } > "${config_tmp}" + + chmod 600 "${credentials_tmp}" "${config_tmp}" + mv "${credentials_tmp}" "${aws_dir}/credentials" + mv "${config_tmp}" "${aws_dir}/config" +} + ensure_openclaw_cli() { if ! command -v openclaw >/dev/null 2>&1; then echo "openclaw CLI is not installed in this environment." >&2 diff --git a/platform_home/templates/AGENTS.template.md b/platform_home/templates/AGENTS.template.md index 7eeb567ceee63b60f3ccf7c724dd3d7a539c31ea..0620d4be4951a4040b146e90f13f57a7ec1dc46b 100644 --- a/platform_home/templates/AGENTS.template.md +++ b/platform_home/templates/AGENTS.template.md @@ -20,7 +20,7 @@ ## 文件传输 -接收到文件 Uri 或需要将文件发送给用户时,需要先阅读 `/opt/platform_home/FILE-TRANSFER.md`,采用 Qshell 完成文件传输。 +接收到文件 Uri 或需要将文件发送给用户时,需要先阅读 `/opt/platform_home/FILE-TRANSFER.md`,采用 AWS CLI 的 S3 协议完成文件传输。 ## Session Startup diff --git a/vendor/aws/attention.md b/vendor/aws/attention.md new file mode 100644 index 0000000000000000000000000000000000000000..7d42126c9a2c6abc6740e94acb604fa46211b3b2 --- /dev/null +++ b/vendor/aws/attention.md @@ -0,0 +1,11 @@ +# 注意事项 + +本文件夹中存放的文件 zip 尺寸过大,不适合提交到Git,所以,你需要从以下地址手动下载,并放置到本目录下: + +- vendor\aws\awscli-exe-linux-x86_64.zip +- vendor\aws\awscli-exe-linux-aarch64.zip + +下载地址: + +- https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip +- https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip