# 私有云 API ## 3.0.0 更新 当前使用 PostgreSQL、双 API 副本、独立 Worker 和本机 1–4 副本自动扩缩容。新增共享空间、跨用户 DVM 文件接力、公开分享、AES-256-GCM 静态加密、定时加密备份与恢复工具。Web 控制台:https://awndawd.com/web/ 。完整用法与运维说明:[V3.md](https://awndawd.com/downloads/V3.md)。 原用户、Token、文件 ID 和权限保留;新功能的 `workspace:manage` / `share:manage` 需明确签发,不自动赋给旧 Token。DVM1/DVM2 协议不变,C 引擎版本为 3.0.0。 **当前仍为单宿主机:双 API 能应对进程故障,本机扩容不会新增云机器;PostgreSQL/网关/磁盘仍是单点,本地备份不是异地容灾。** 当前部署/恢复请以 V3.md 为准,下方 SQLite 初始化及早期部署段落只用于旧版独立环境,不要在本机生产目录执行。 ## 2.0.0 / DVM2 历史更新 服务端、SDK、CLI 和 C 引擎版本统一为 2.0.0。服务端执行器已编译为 C 扩展;DVM2 新增 26 条位运算、数学、数组和字符串指令,共 61 条,同时继续执行 DVM1。资源限制不提高,现有 Token 和数据无需迁移。完整语义、构建及兼容说明见 [DVM2.md](https://awndawd.com/downloads/DVM2.md)。 默认编译 DVM2;连接旧服务器可使用 `compile_assembly(source, version=1)` 或 `uds-client compile source.asm -o output.dvm --dvm-version 1`。反汇编保留 `.version`,显式参数与声明冲突会报错。下方 DVM1 章节描述兼容协议,其指令和限制仍有效。 ## 正式 Python SDK 已提供可独立安装的 `uds-client 3.0.0`,Python 3.10+,无第三方运行时依赖。下载 [wheel 安装包](https://awndawd.com/downloads/uds_client-3.0.0-py3-none-any.whl) 后执行: ```bash python -m pip install ./uds_client-3.0.0-py3-none-any.whl ``` ```python import os from uds_client import Client client = Client("https://awndawd.com", os.environ["UDS_TOKEN"]) job = client.submit_assembly("INPUT 0\nINPUT 1\nADD\nRETURN", inputs=[20, 22]) print(client.wait_job(job["id"])["result"]["value"]) # 42 ``` 支持文件上传/下载、分页、DVM 编译/提交、任务等待/取消、共享空间、分享链接和结构化异常。公网使用 HTTPS,无需 `allow_http=True`;不自动读取任何服务器凭据。完整说明见 [SDK.md](https://awndawd.com/downloads/SDK.md),另提供 [源码包](https://awndawd.com/downloads/uds_client-3.0.0.tar.gz)。尚未发布到 PyPI,请使用这里的 wheel 而非裸包名安装。 本文可直接访问,无需 Token,浏览器显示 Markdown 原文: - 公网:https://awndawd.com/README.md - 局域网:http://192.168.105.137:8088/README.md 这两个地址的 `/` 根路径同样显示本文。局域网入口提供本文和固定的客户端分发文件;私有 API 仍需通过原有入口携带 Token 访问。 ## 独立客户端(Linux x86_64) 无需安装 Python,下载解压后即可运行: - [Linux x86_64 完整包](https://awndawd.com/downloads/uds-client-linux-x86_64.zip):可执行文件、中文说明、汇编示例。 - [单个可执行文件](https://awndawd.com/downloads/uds-client-linux-x86_64) - [Python 通用包](https://awndawd.com/downloads/uds-client-portable.zip):需要 Python 3.10+,不需要第三方库。 - [SHA-256 校验值](https://awndawd.com/downloads/SHA256SUMS) / [客户端说明](https://awndawd.com/downloads/CLIENT.md) 局域网下载可将链接前缀替换为 `http://192.168.105.137:8088`。原生版本在 Ubuntu 24.04 x86_64 构建验证,要求兼容的 glibc 2.39+。分发文件不含 Token,接收者使用自己的 Token;不要分发个人登录后的配置文件。 ```bash chmod +x uds-client ./uds-client --server https://awndawd.com login ./uds-client submit examples/sum.asm --inputs '[[1,2,3,4,5]]' --wait ``` 当前 HTTPS 入口无需 `--allow-http`;登录时隐藏输入。安装与完整功能见包内 README。构建产物在项目 `dist/`,构建命令为 `.venv/bin/python scripts/build_client.py --native`。Docker 镜像默认不内置原生分发文件,如需提供下载,应额外挂载已构建 `dist` 到 `/app/dist`(只读)。 面向少量受邀用户的 API 和 Token Web 控制台,不开放注册。当前为 Python 3.12 + FastAPI + PostgreSQL,加密文件保存在本机磁盘;API/Worker 使用 systemd,PostgreSQL 与 TLS 代理使用独立容器。 本机已使用用户级 systemd 启动,实际入口、凭据位置及公网待办见 [DEPLOYMENT.md](DEPLOYMENT.md)。下面的初始化命令用于新部署,无需在已初始化的实例重复执行。 ## 已实现 - 管理员创建/禁用用户、修改容量、签发权限 Token;用户可查看与撤销自己的 Token。 - Token 使用 256 位随机值,数据库只存 SHA-256 摘要;明文仅在签发时返回,有效期 1–365 天。 - 文件上传、列表、下载、删除、SHA-256 校验、用户容量配额;用户间隔离,管理员也通过自己的空间接口访问自己的文件。 - 异步计算:`sha256`、UTF-8 `text_stats`、`numeric_stats`,以及执行自定义字节码的 `dvm`。支持查询、取消、清理任务记录。 - 持久化任务队列、崩溃后重排运行中的任务、清理未完成上传、管理与变更操作审计。 - 鉴权后每用户每分钟 120 次请求;最多 64 个连接/任务并发;单计算进程 CPU 时间 10 秒、总时间 20 秒、地址空间 256 MiB。 自定义计算采用 DVM1 汇编与字节码,支持循环、分支、数组、字符串、受限文件读写。它不执行用户提交的 Python、Shell 或 Docker 镜像;进程资源限制不是通用宿主代码沙箱。现有 Token 可按已有计算与存储权限使用 DVM,无需重新签发。 ## 启动 在实际部署服务器上进入项目目录: ```bash docker compose up -d --build docker compose exec api python -m cloud.cli init curl http://127.0.0.1:8088/healthz ``` `init` 只允许执行一次,会返回管理员 Token(30 天有效)。请保存到密码管理器。不要把 Token 写入代码或提交到 Git;API 通过 `Authorization: Bearer ...` 接收 Token,不接受 URL 中的 Token。 原生部署也可使用 `.venv/bin/python -m cloud.cli --token-file data/admin-token.json init`,将首次凭据写入仅当前用户可读写的文件。保存到密码管理器后移除这份明文副本;之后签发的 Token 不会自动写入此文件。 管理员 Token 丢失或过期时,在服务器本地执行: ```bash docker compose exec api python -m cloud.cli recover-admin ``` 此操作撤销该管理员原有的全部 Token,再签发一个新的。仅有服务器本地权限的操作者能运行它。普通用户没有自行注册或自行扩权接口。 默认只监听本机 `127.0.0.1:8088`。外部访问由 HTTPS 反向代理接入。文件与数据库在 Compose 的 `cloud_data` 命名卷中,重建容器不会丢失;不要执行 `docker compose down -v`,它会删除数据卷。 ### 不使用 Docker ```bash python3 -m venv .venv .venv/bin/python -m pip install -r requirements.lock .venv/bin/python -m cloud.cli init .venv/bin/uvicorn cloud.app:create_app --factory --host 127.0.0.1 --port 8088 --workers 1 --limit-concurrency 64 --no-access-log ``` 本机持久化路径默认为 `./data`,可用 `DATA_DIR` 修改。可将 `deploy/ubuntu-data-server.service` 安装为 systemd 系统服务;先确认其中的用户名、路径与实际机器一致,并初始化 `data` 目录: ```bash sudo install -m 644 deploy/ubuntu-data-server.service /etc/systemd/system/ubuntu-data-server.service sudo systemctl daemon-reload sudo systemctl enable --now ubuntu-data-server ``` Docker 与原生部署二选一。必须只运行一个 API worker;多实例使用相同目录时启动锁会拒绝第二个实例。 ## awndawd.com 的 DNS 和 HTTPS **当前公网 HTTPS:`https://awndawd.com`**,不带端口。Cloudflare 443 → 源站 `43.136.122.67:59104` → FRP → 本机 TLS 代理 `127.0.0.1:18443` → API `127.0.0.1:8088`。详细配置见 DEPLOYMENT.md。下方旧域名/直连 Caddy 方案仅为历史参考,不是当前部署步骤。 在 `awndawd.org` 的 DNS 服务商处创建: | 类型 | 主机记录 | 值 | TTL | | --- | --- | --- | --- | | A | data | 43.136.122.67 | 300 或默认 | 使用“仅 DNS”模式,记录值只填写 IP,不带端口。当前本机地址为 `192.168.105.137`,由 FRP 隧道接入公网服务器。没有正确可用的公网 IPv6 时不要配置 AAAA。 云服务器安全组与系统防火墙需允许 TCP 80/443。如果服务位于 NAT 后,公网入口必须将 80/443 转发至运行反向代理的机器;DNS 本身不会建立转发。域名首次配置可使用“仅 DNS”模式。 `deploy/Caddyfile` 是站点配置示例。宿主机运行的 Caddy 可以直接代理到 `127.0.0.1:8088`。将该站点块合并到现有配置,验证配置后再 reload,不要覆盖已有站点。Caddy 在域名解析和入站端口正确时自动申请/续期证书并将 HTTP 重定向至 HTTPS。参考:[Caddy 自动 HTTPS 文档](https://caddyserver.com/docs/automatic-https)。 **本环境的 80/443 已由 `webmc-caddy-1` 容器使用。** 不能再启动第二个占用这些端口的反向代理。Docker 中的 `127.0.0.1` 指向 Caddy 容器自身,不能直接使用示例中的本机 upstream;应将 API 与 Caddy 接入一个专用共享 Docker 网络,为 API 设置唯一别名 `data-api`,并在站点配置中写 `reverse_proxy data-api:8088`。共享网络应同时写入两个项目的 Compose 文件,以便重建后仍然有效。 当前 FRP TCP 59104 转发已经建立,不依赖现有的 80/443 反向代理。如要使用下面不带端口的 HTTPS URL,仍需单独配置公网 443 入口和证书。 DNS 和代理完成后,从另一台机器验证: ```bash curl https://data.awndawd.org/healthz curl https://data.awndawd.org/v1/me ``` 应分别得到 `{"status":"ok"}` 和 HTTP 401。后者说明未携带 Token 时无法访问私有接口。请直接使用 HTTPS 调用带 Token 的请求,避免 Token 在 HTTP 重定向之前明文传输。 ## 使用示例 以下为 Bash。用 `read -s` 输入管理员 Token,避免直接写入 shell 历史: ```bash API=https://data.awndawd.org read -rs -p 'Admin token: ' ADMIN_TOKEN curl -sS "$API/v1/admin/users" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"alice","quota_bytes":1073741824}' ``` 从响应中复制用户 `id`,签发普通用户 Token: ```bash USER_ID=替换为用户id curl -sS "$API/v1/admin/users/$USER_ID/tokens" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"scopes":["storage:read","storage:write","storage:delete","compute:submit","compute:read","compute:cancel"],"expires_days":30}' ``` 只读用户仅给 `storage:read`;计算文件还需 `compute:submit` 与 `storage:read`,读取结果还需 `compute:read`。`admin` 允许管理用户、配额和签发任意权限 Token,只应给可信管理员。 ```bash read -rs -p 'User token: ' TOKEN # 上传原始文件内容(不是 multipart);curl 自动发送文件长度 curl -sS -X PUT "$API/v1/objects?name=example.txt" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/octet-stream' --data-binary @example.txt # 列表与下载,OBJECT_ID 取自上传响应 curl -sS "$API/v1/objects" -H "Authorization: Bearer $TOKEN" OBJECT_ID=替换为文件id curl -fS "$API/v1/objects/$OBJECT_ID" -H "Authorization: Bearer $TOKEN" -o downloaded.txt # 提交数值计算 curl -sS "$API/v1/jobs" -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"kind":"numeric_stats","numbers":[1,2,3,4,5]}' # 查询后台结果,JOB_ID 取自提交响应 JOB_ID=替换为任务id curl -sS "$API/v1/jobs/$JOB_ID" -H "Authorization: Bearer $TOKEN" ``` 文件计算的请求体为 `{"kind":"text_stats","object_id":"文件id"}` 或 `{"kind":"sha256","object_id":"文件id"}`。文本必须为 UTF-8;词数按空白字符分隔,中文连续文本会计为一个词。作业状态为 `queued`、`running`、`succeeded`、`failed`、`cancelled`;客户端轮询建议至少间隔 2 秒。 ## 接口索引 DVM 使用方式、二进制格式及全部操作码见下文“DVM1 自定义字节码计算”。 除公开健康检查、文档和固定客户端分发文件外,所有私有 API 都需要 Bearer Token。无 `/docs` 或网页 UI;`GET /v1/openapi.json` 返回机器可读 API 说明。 | 方法和路径 | 功能 / 权限 | | --- | --- | | GET /v1/me | 自身信息、配额和用量 | | GET /v1/tokens | 自身 Token 元数据 | | DELETE /v1/tokens/{id} | 撤销自己的 Token;管理员可撤销任意 Token | | POST /v1/admin/users | 创建用户 / admin | | GET /v1/admin/users | 用户列表 / admin | | PATCH /v1/admin/users/{id} | enabled、quota_bytes / admin | | POST /v1/admin/users/{id}/tokens | 签发 Token / admin | | GET /v1/admin/audit | 变更审计 / admin | | PUT /v1/objects?name=名称 | 原始文件上传 / storage:write | | GET /v1/objects | 文件列表 / storage:read | | GET /v1/objects/{id} | 下载 / storage:read | | DELETE /v1/objects/{id} | 删除 / storage:delete | | GET /v1/compute/types | 支持的计算类型 | | POST /v1/jobs | 创建任务 / compute:submit | | GET /v1/jobs | 任务列表 / compute:read | | GET /v1/jobs/{id} | 状态与结果 / compute:read | | POST /v1/jobs/{id}/cancel | 取消 / compute:cancel | | DELETE /v1/jobs/{id} | 删除结束的任务记录 / compute:cancel | 列表支持 `limit`(默认 100,上限 1000)及 `offset`;审计分页用 `before`(上一页最小的审计 id)。时间为 Unix 秒,容量单位为字节。错误返回 `detail`,常用状态码:401 未授权、403 权限不足、404 不存在或属于他人、409 状态冲突、413 超限、422 输入错误、429 限流/队列满、503 存储暂不可用。 ## 默认限制与运维 - 普通用户默认配额 1 GiB;管理员初始配额 10 GiB。配额包含正在上传的预留空间,上传失败自动释放。 - 单文件默认 100 MiB,上传需 Content-Length,总上传时间 120 秒;通过 `MAX_UPLOAD_BYTES` 修改时同步调整反向代理限制。 - 每用户最多 10000 个文件、10000 条保留任务、10 个未结束任务;全局最多 100 个未结束任务,单计算并发。 - 数值计算最多 10000 个有限数,绝对值不超过 1e100。JSON 请求体最大 1 MiB。 - 文件按随机 ID 不可变保存,同名上传会生成新文件。暂不支持 S3 协议、目录语义、分片/断点续传、下载 Range 或公开分享链接。 - Token 撤销/用户禁用会阻止后续请求;已开始的下载可能完成。禁用用户会取消未结束计算,撤销 Token 不撤销已经提交的任务。 - 队列运行中的任务在崩溃重启后重试,因此内置任务必须保持无副作用。正常停机时正在运行的任务会取消。 - 单机没有副本容灾、磁盘静态加密或自动备份。应监控宿主机磁盘、配置实际可承受的用户配额,并定期将备份复制到另一台机器。审计表需要按运维保留策略归档。 - Docker 日志每文件 10 MiB、最多 3 个;应用变更审计不含 Token 明文和请求体。健康检查只检测进程与数据库可响应,不替代公网探测和磁盘监控。 查看状态:`docker compose ps`;日志:`docker compose logs --tail=100 api`;更新:`docker compose up -d --build`。 备份必须让数据库和文件保持一致。对 Docker 部署,先停止 API,再将容器 `/data` 的完整内容复制到一个新目录,最后重启: ```bash docker compose stop api backup_dir=$(mktemp -d ./backup-XXXXXXXX) docker compose cp api:/data/. "$backup_dir/" docker compose start api ``` 备份失败也要执行 `docker compose start api`。备份包含用户、Token 摘要、文件、队列和审计,请限制目录读取权限并离机保存。恢复时停止服务,将备份完整恢复至数据卷并确保容器 UID 10001 可读写,再启动;不要仅恢复 SQLite 文件或将运行中的数据库单独复制。原生部署同理,停服务后备份整个 `data` 目录。定期在独立目录验证恢复。 ## 开发验证 ```bash .venv/bin/python -m pip install -r requirements-dev.txt .venv/bin/python -m pytest -q ``` 测试使用临时数据目录,不修改实际用户数据。 ## DVM1 自定义字节码计算 工作流程为:编写 `.asm` 汇编 → 本地编译为 `.dvm` 二进制 → Base64 编码后提交 `kind=dvm` → 查询任务结果。服务端执行的是字节码,JSON 仅用于传输参数。 ```bash # 在本项目目录执行,不需要网络 .venv/bin/python -m cloud.assembler examples/sum.asm -o /tmp/sum.dvm # 可选:反汇编;再次汇编能还原相同字节码 .venv/bin/python -m cloud.assembler --disassemble /tmp/sum.dvm -o /tmp/sum.dis.asm ``` 下面命令在服务器本机读取管理员凭据,通过回环地址提交求和任务,不把凭据传到公网 HTTP: ```bash .venv/bin/python - <<'PY' import base64, json, urllib.request from pathlib import Path token = json.loads(Path('data/admin-token.json').read_text())['token'] body = {'kind': 'dvm', 'bytecode_base64': base64.b64encode(Path('/tmp/sum.dvm').read_bytes()).decode(), 'inputs': [[1, 2, 3, 4, 5]]} request = urllib.request.Request('http://127.0.0.1:8088/v1/jobs', data=json.dumps(body).encode(), headers={'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}) with urllib.request.urlopen(request) as response: print(response.read().decode()) PY ``` 响应仍为 `202 {"id":"任务id","state":"queued"}`。用原有 `GET /v1/jobs/{id}` 查询;求和成功时 `result.value` 为 `15`,`result.instructions_executed` 为实际执行指令数。该计数不包含额外的数据处理预算扣减。 ### 请求、权限及结果 `POST /v1/jobs` 新增字段如下;旧的三种计算任务请求保持兼容,不接受 DVM 专用字段。 | 字段 | 规则 | | --- | --- | | kind | 固定为 `dvm` | | bytecode_base64 | 必填,严格 Base64 编码的 DVM1 字节码 | | inputs | 可选 JSON 数组,默认 `[]`;元素为标量或一维标量数组 | | object_id | 可选,最多一个属于当前用户的输入文件 | | output_name | 可选,生成一个输出文件的显示名称,1–255 字符且不含控制字符 | 标量为 null、布尔、64 位有符号整数、有限双精度浮点数、字符串。字节串可通过 `.bytes` 常量、`ENCODE`、`READ` 产生。输入不支持 JSON 对象、嵌套数组或非有限数。字节串不能直接作为 JSON 返回值,需通过 `WRITE` 输出文件。 提交需 `compute:submit`;有输入文件时另需 `storage:read`;有 `output_name` 时另需 `storage:write`;查询结果需 `compute:read`。任务与文件只能由所属用户访问。已有管理员 Token 包含这些权限。 成功时 `result` 包含 `value`、`instructions_executed`,以及生成文件时的 `output_object_id`。通过普通文件下载接口读取该 ID。有 `output_name` 但未执行 `WRITE` 时会生成零字节文件;没有 `output_name` 却执行 `WRITE` 会失败。 程序运行期间输入文件不能删除;输出先写入临时文件,成功后检查用户启用状态、容量和文件数量配额,再发布输出及任务结果。配额包含预留空间;失败和取消不留下可见输出。正常停止服务取消正在运行的任务,崩溃恢复会重新执行未完成任务。撤销 Token 不会撤销已提交任务,禁用用户会取消任务。 无效字节码在提交时返回 422;运行错误使任务进入 `failed`,保留文本 `error`,并附加 `error_details`:`code`、`message`、`instruction_index`、`opcode`。资源监督和文件发布错误没有对应指令时,后两项为 null。错误不会返回宿主文件路径或 Python 堆栈。 ### 汇编语法与类型 每行一条指令,指令名不区分大小写,名称和标签区分大小写。分号在字符串外开始注释。`.const 名称 JSON标量` 声明常量,`.bytes 名称 十六进制` 声明字节串(允许空字节串),`标签:` 声明跳转位置。名称为字母或下划线开头、后跟字母数字下划线。常量和标签可前向引用。LOAD/STORE 使用 0–255 的十进制局部变量索引。 ```text .const one 1 .const greeting "你好; DVM" .bytes newline 0a INPUT 0 CONST one ADD RETURN ``` 局部变量初始为 null。数值运算接受整数与浮点数,布尔不作为数值;混合数值运算按浮点执行,DIV 始终返回浮点,MOD 使用向下取整除法对应的余数(余数与除数同号)。整数溢出、除零、NaN/Infinity、类型不匹配和索引越界都会终止。JZ/JNZ 只接受布尔,不把零或空字符串隐式转换为 false。 字符串按 Unicode 码点索引与切片,字节串 GET 返回 0–255 的整数。数组只能包含标量;数组 LOAD、STORE、DUP 共享引用,SET 原地修改,SLICE/CONCAT 创建新的浅拷贝数组,因此无法构造嵌套或循环引用。RETURN 消耗一个栈顶值,允许栈内还有其他值;未执行 RETURN 就到达程序末尾视为错误。 ### 全部操作码与栈效果 栈表示从左到右为从底到顶;表中的操作只影响所列栈顶部分。无操作数指令的二进制操作数字段必须为零。 | 编号 | 指令 | 栈效果与语义 | | --- | --- | --- | | 0 | CONST 名称 | `→ value`,载入常量 | | 1 | INPUT 索引 | `→ value`,载入 inputs 元素 | | 2 | LOAD 索引 | `→ value`,读取局部变量 | | 3 | STORE 索引 | `value →`,写入局部变量 | | 4 | POP | `value →` | | 5 | DUP | `value → value value` | | 6 | SWAP | `a b → b a` | | 7 | ADD | `a b → a+b`,仅数值 | | 8 | SUB | `a b → a-b` | | 9 | MUL | `a b → a*b` | | 10 | DIV | `a b → a/b` | | 11 | MOD | `a b → a%b` | | 12 | NEG | `a → -a` | | 13 | EQ | `a b → bool`,同类型标量或混合数值的相等比较 | | 14 | LT | `a b → ab` | | 17 | GE | `a b → a>=b` | | 18 | NOT | `bool → bool` | | 19 | AND | `a b → bool`,两个操作数均须为布尔 | | 20 | OR | `a b → bool`,两个操作数均须为布尔 | | 21 | JMP 标签 | 不改变栈,无条件跳转 | | 22 | JZ 标签 | `bool →`,false 时跳转 | | 23 | JNZ 标签 | `bool →`,true 时跳转 | | 24 | ARRAY | `length → array`,创建用 null 填充的数组 | | 25 | GET | `sequence index → value`,不允许负索引 | | 26 | SET | `array index scalar → array`,原地修改 | | 27 | LEN | `sequence → length` | | 28 | SLICE | `sequence start end → sequence`,半开区间,要求 `0 <= start <= end <= length` | | 29 | CONCAT | `a b → sequence`,两个序列必须同类型 | | 30 | ENCODE | `string → bytes`,严格 UTF-8 | | 31 | DECODE | `bytes → string`,严格 UTF-8 | | 32 | READ | `offset length → bytes`,输入文件绝对偏移读取;EOF 可返回不足 length 的数据 | | 33 | WRITE | `bytes →`,向本任务输出文件顺序追加 | | 34 | RETURN | `value →`,结束程序并返回 JSON 兼容值 | ### 二进制格式 所有多字节字段为小端。头部为 `4 字节 DVM1 魔数 + uint32 常量区字节长度 + uint32 指令数量`,之后依次为常量区、指令区。没有额外尾部。指令固定 5 字节:`uint8 操作码 + uint32 操作数`。CONST 操作数为从零开始的常量索引;跳转为从零开始的指令索引。 常量由 `uint8 类型 + uint32 数据字节长度 + 数据` 串联组成:类型 0 为 null(0 字节),1 为 bool(1 字节,只允许 0/1),2 为 int64(8 字节),3 为 IEEE754 float64(8 字节,必须有限),4 为 UTF-8 字符串,5 为原始字节串。数组由运行时创建。未知版本/操作码、截断、尾随数据、非法常量或跳转均被拒绝。 ### 资源限制 | 资源 | DVM1 上限 | | --- | --- | | 字节码 / 静态指令 | 256 KiB / 32768 条 | | 执行预算 | 100 万单位,每条指令至少 1 单位 | | 操作数栈 / 局部变量 | 4096 项 / 256 项 | | 管理数据累计分配预算 | 32 MiB;保守累计,不在释放对象后返还 | | 单字节串 / 字符串 | 8 MiB;字符串按每码点最多 4 字节保守估计 | | 单数组 | 100000 个标量 | | JSON 返回值 | 1 MiB,生成前按转义后大小上界检查 | | 累计输入读取 / 输出文件 | 各 16 MiB;读取按请求长度计量,包括 EOF 后的请求 | | CPU / 总时间 / 进程地址空间 | 10 秒 / 20 秒 / 256 MiB | 批量数据操作除指令成本外,每处理 64 字节(向上取整)扣减一单位预算;按保守对象大小计量的操作可能更早用尽预算。创建对象前检查大小。进程 stdout 最多 1 MiB + 4096 字节,stderr 最多 64 KiB,超限终止任务。文件通过专用文件描述符传递,不通过 stdout 返回;虚拟机没有路径、网络或系统调用指令。 JSON 请求体仍为最大 1 MiB,因此 Base64 程序和 inputs 的合计必须满足请求限制。用户并发任务数量、全局队列限制和存储配额沿用原有规则。 ### 示例程序 - `examples/sum.asm`:输入 `[[1,2,3,4,5]]`,返回 `15`,包含循环和累加。 - `examples/filter.asm`:输入 `[[2,0,-3,5]]`,返回 `[2,5]`,包含条件分支和数组操作。 - `examples/write_text.asm`:输入 `["你好"]`,提供 `output_name: "hello.txt"`,生成 UTF-8 文件。 - `examples/read_text.asm`:提供 `object_id`,读取前 4096 字节并严格解码;若截断位置落在 UTF-8 字符中间,会返回 INVALID_UTF8。 本机完整验证:`.venv/bin/python scripts/dvm_smoke.py`,使用回环 API,创建并清理自己的测试任务和文件,保留审计记录。 # Web 管理控制台 入口:`https://awndawd.com/web/`。输入现有 API Token,查看当前身份、权限、有效期、空间用量、文件元数据、Job 状态和完整结果/错误、同用户 Token 元数据及计算能力。具有 `admin` 权限时,还可分页查看用户列表和审计记录。点击记录的“查看”显示完整 API 字段。 所有操作沿用 API 鉴权,不提升权限。文件和 Job 属于用户,并非独立归属于某一枚 Token;缺少 `storage:read` 或 `compute:read` 时会提示无权查看。Token 仅保留在当前页面内存中,不写入 localStorage、Cookie 或 URL;刷新/离开页面或退出后需重新输入。仅 HTTPS 或本机 loopback HTTP 允许输入 Token。原有 `/README.md` 和下载接口不变,局域网文档专用服务不承载控制台。 - 文件:上传、下载、删除。下载在浏览器校验长度与 SHA-256 后保存;浏览器上传/下载单文件上限 100 MiB,还受 API、账户配额和 Cloudflare 限制。较大文件请使用 CLI/SDK。下载暂存在浏览器内存,不适合大文件。 - Job:通过 JSON 表单提交全部已支持的计算类型,取消排队/运行中任务,删除已结束任务记录。文件计算示例:`{"kind":"sha256","object_id":"文件ID"}`;默认表单提供数字统计示例,DVM 字段参见上方文档。 - Token:撤销当前用户的 Token;撤销当前登录 Token 会退出。 - 管理员:创建用户、调整字节配额、启用/禁用用户、选择权限和有效期签发 Token。禁用用户会取消其活动任务,重新启用会恢复未过期且未撤销的 Token。新 Token 只显示一次,请及时安全保存。 按钮按权限显示(`admin` 不隐含文件/计算权限)。删除、取消、撤销、用户启停需要确认;授予 `admin` 权限也需单独确认。写入不自动重试,遇到超时/断网先刷新核对是否已生效,再决定重试。退出或关闭页面不能撤销服务器已接收的操作。