Files
endorphin-aster-vm/README.md
T
codex-bot 9648b9e8dd
Verify databases and deploy Aster VM / verify-and-deploy (push) Waiting to run
Initialize Aster VM and deploy PostgreSQL host on ces-control
2026-10-17 23:42:39 +08:00

11 KiB
Raw Blame History

Aster VM

Rust 编写的可嵌入语言运行时,当前为第一阶段:启动时引导根类 Object,并由对象工厂创建类和实例。这里的“顶级类”指根类;“生成器”指运行时引导与对象工厂。

本项目尚无词法/语法前端、字节码执行器、渲染器、GC 或完整元类体系,不承诺 Python 兼容。数据库只保存宿主元数据,不保存或恢复 VM 堆。

结构

crate 职责
aster-core 零外部依赖的 Value、安全句柄、堆、类、实例、VM 与工厂
aster-storage SQLx PostgreSQL/SQLite 适配、元数据表初始化、读写接口;不依赖内核
aster-cli aster bootstrap 演示,以及宿主元数据读写
aster-server 最小常驻宿主,仅提供健康检查与根类信息,供 Docker 等方式运行

依赖方向:CLI/Server → Core 和 Storage;Core 与 Storage 互不依赖。未来浏览器宿主的网络、DOM、渲染等能力应留在宿主一侧。

直接运行

使用 Rust 1.91 或更高兼容版本;当前实测版本为 1.91.1,依赖锁定在 Cargo.lock。不需要 Docker。首次构建需要访问 crates.io。

cargo build --workspace --locked
cargo run -p aster-cli -- bootstrap

预期输出含 root=Object parent=None、instances_distinct=true heap_objects=4。这条命令不读取数据库配置、不打开数据库;即使设置了错误的 DATABASE_URL 也不影响引导。

use aster_core::{Value, Vm};

fn main() -> Result<(), aster_core::VmError> {
    let mut vm = Vm::new()?;
    let point = vm.create_class("Point", vm.root_class())?;
    let instance = vm.create_instance(point)?;
    vm.set_field(instance, "x", Value::Integer(42))?;
    assert_eq!(vm.instance(instance)?.class(), point);
    Ok(())
}

嵌入时将 aster-core 加为 Cargo 路径依赖即可。上例与 crate 文档中的可运行示例语义相同。

宿主存储

不设置 DATABASE_URL 时,使用 SQLITE_PATH 指定的 SQLite 文件,默认 data/aster.db,相对于进程工作目录;自动创建父目录及文件。部署时必须把该目录放在持久卷上。SQLITE_PATH=:memory: 仅用于临时运行/测试,不持久化。

cargo run -p aster-cli -- metadata put project 'Aster VM'
cargo run -p aster-cli -- metadata get project
SQLITE_PATH=work/custom/aster.db cargo run -p aster-cli -- metadata put stage bootstrap

PostgreSQL 通过导出的 DATABASE_URL=postgres://... 或 postgresql://... 配置,优先于 SQLITE_PATH。显式空值、错误协议、连接失败都会返回错误并以非零状态退出,绝不静默降级。DATABASE_URL 不接受 SQLite URL;SQLite 使用独立路径配置。

连接成功后执行 schema.sql 中幂等的 v1 建表语句,提供 Storage::put/get/ping/close。读写使用参数绑定,重复键执行更新,缺失键返回 None。尚无复杂迁移版本管理。

Cargo 程序不自动加载 .env,请导出需要的环境变量。.env.example 是配置示例;Docker Compose 会自动读取复制后的 .env。连接 URL 可能含密码,不要贴到日志或提交到 Git。命令行只显示脱敏错误,底层诊断作为 Rust error source 保留给宿主。

常驻宿主

cargo run -p aster-server
curl --fail http://127.0.0.1:8080/health
curl --fail http://127.0.0.1:8080/bootstrap

ASTER_LISTEN 默认 127.0.0.1:8080。宿主启动时创建 VM、连接数据库、初始化表并写入 host.version=0.1.0;初始化失败即退出。/health 实际探测数据库,成功返回 200,失联返回 503;/bootstrap 返回 Object、无父类、堆对象数 1,多次读取不会重复引导。支持 Ctrl-C/Unix SIGTERM 优雅退出。

这两个端点不提供执行语言、对象写入或业务 API。服务没有认证层;示例 Compose 端口仅绑定宿主回环地址,外部访问由后续宿主部署决定。

Docker:默认 SQLite

cp .env.example .env
docker compose up --build -d
curl --fail http://127.0.0.1:8080/health
docker compose exec aster aster metadata put project 'Aster VM'
docker compose exec aster aster metadata get project
docker compose down
docker compose up -d
docker compose exec aster aster metadata get project

容器以 UID 10001 运行,aster-sqlite 命名卷挂载到 /data,保存数据库和 WAL 文件。down 保留卷;down -v 会删除持久数据。构建采用多阶段 Dockerfile,运行镜像同时含 aster-server 和 aster。Compose 的 SQLite 方案不会透传宿主 DATABASE_URL,以保证默认方案明确使用 SQLite。

Docker:选择 PostgreSQL

先将 .env 中的 POSTGRES_PASSWORD 换成随机十六进制字符串(可用 openssl rand -hex 24 生成)。该值同时用于数据库和连接 URL;使用其他字符时需要自行正确处理 URL 编码。

docker compose -f docker-compose.postgres.yml up --build -d
docker compose -f docker-compose.postgres.yml exec aster aster metadata put project 'Aster VM'
docker compose -f docker-compose.postgres.yml exec aster aster metadata get project
docker compose -f docker-compose.postgres.yml down

这是独立替代方案,只传入这一个 Compose 文件,不与默认文件叠加;两个方案不要同时占用同一个宿主端口。PostgreSQL 17 使用 aster-postgres 命名卷,数据库不向宿主公开端口。示例内部网络禁用数据库 TLS;跨不可信网络的部署应使用 PostgreSQL TLS 验证配置,例如 URL 参数 sslmode=verify-full。更改已有 PostgreSQL 卷的密码需要数据库管理操作,修改 .env 不会修改旧卷中的用户密码。

ces-control CI/CD 与持久化部署

正式部署选择 PostgreSQL;默认 SQLite Compose 仍保留作为独立运行选项。

.gitea/workflows/ci-deploy.yml 在 ces-control Runner 上响应 main push 和手动触发:先在远端 Docker 容器中执行 fmt、workspace 测试、严格 Clippy、真实 PostgreSQL 集成测试和 SQLite/HTTP 冒烟检查,通过后构建生产镜像,再部署。使用独立的临时 PostgreSQL 容器做测试,不连接生产数据库。测试容器与网络在退出时清理,编译缓存仅保留在远端专属 Docker 卷中。

正式服务由 docker-compose.ces-control.yml 管理,默认仅绑定 127.0.0.1:18081,内部连接 PostgreSQL 17。应用/数据库使用 unless-stopped 自动重启,日志轮换;数据库不发布端口。部署使用 commit SHA 标记镜像与 release 目录,健康检查或元数据读写失败时尝试恢复上一版应用,保留数据库。

服务器路径 用途
/opt/endorphin-aster-vm/releases/<sha>/ 固定版本的 Compose 与镜像配置
/opt/endorphin-aster-vm/current 最近健康版本的符号链接
/etc/endorphin-aster-vm/production.env 首次自动生成的数据库密码和端口,权限 0600
/var/lib/endorphin-aster-vm/postgres PostgreSQL 持久数据,替换应用/数据库容器后保留

无需在 Gitea 中设置生产数据库密码。不要删除或重新生成已有数据库对应的 production.env;修改密码需先通过 PostgreSQL 管理操作完成同步。首次部署没有可用的旧版可以回退;数据库 schema 的未来升级也需要另行设计兼容性,应用回退不等于回退数据库。

在服务器查看/停止当前部署(停止不删除数据):

docker compose -p endorphin-aster-vm \
  --env-file /etc/endorphin-aster-vm/production.env \
  --env-file /opt/endorphin-aster-vm/current/release.env \
  -f /opt/endorphin-aster-vm/current/compose.yml ps
curl --fail http://127.0.0.1:18081/health
curl --fail http://127.0.0.1:18081/bootstrap
# 同样参数接 stop 停服,接 up -d --wait 恢复。

本机不执行上述 CI 测试,也不为其安装 Rust/SQLite/PostgreSQL。正式部署与测试只运行在用户指定的 ces-control。该工作流不改动 autodl-gpu-runner-controller、rednote 或 TiktokUI。工作流语义依据 Gitea Actions 文档,环境文件加载依据 Docker Compose 文档。

验证(在用户指定的环境执行)

当前工作约定禁止在本地运行测试,也不为本地测试下载依赖。下面的脚本和示例在指定的 ces-control CI/部署环境执行,不代表每次修改后已全部执行。实际结果及此前清理情况见 验证记录。

sh scripts/check.sh
python3 scripts/smoke.py

检查脚本运行 fmt、workspace 测试和拒绝所有 warning 的 Clippy;冒烟脚本使用临时 SQLite 文件,验证独立引导、跨进程持久化、配置错误、HTTP 端点和 SIGTERM 退出,不需要外部数据库。

PostgreSQL 集成测试默认显式忽略,需要测试数据库。以下是指定验证环境中的 Docker 示例;仅操作专用测试容器,不使用生产库,当前不在本机执行:

docker run --rm -d --name aster-pg-test \
  -e POSTGRES_PASSWORD=aster-test -e POSTGRES_USER=aster -e POSTGRES_DB=aster \
  -p 127.0.0.1:55432:5432 postgres:17-bookworm
# 等待以下命令成功后再运行测试:
docker exec aster-pg-test pg_isready -U aster -d aster
ASTER_TEST_POSTGRES_URL='postgres://aster:aster-test@127.0.0.1:55432/aster?sslmode=disable' \
  cargo test -p aster-storage --test postgres --locked -- --ignored
docker stop aster-pg-test

测试会留下唯一键的元数据,专用测试库可随容器删除。缺少测试 URL 时显式运行该测试会失败,不会假装通过。真实 PostgreSQL 成功路径及容器结果以 验证记录 为准。

当前语义与后续开发

  • Object 是每个 VM 启动时唯一分配的根类,没有父类。类描述与类对象合为一个堆对象;实例保存类句柄。普通类必须指定已有父类,名称只是标签,无全局命名空间;保留精确名称 Object,其他同名类允许存在。
  • 对外只提供私有字段的 ObjectRef/ClassRef/InstanceRef。句柄包含进程内 VM 身份与槽位,所有访问检查归属、范围、种类;写入对象字段也检查引用归属。句柄不是持久 ID,不支持外部构造或反序列化。
  • 堆仅分配,不单独回收、不复用槽位;随 VM 销毁统一释放。宿主持有的句柄不延长 VM 生命周期。未来 GC 需要另行设计根集、代际槽位或稳定间接句柄,不能直接开始复用当前索引。
  • Nil/布尔/整数/浮点直接放入 Value,字符串由 Rust String 持有,类和实例使用堆句柄。尚未定义完整语言数值、方法查找、继承执行或异常语义。

下一阶段建议先确定字段/方法查找与宿主调用边界,再设计可测的最小指令执行循环;等对象可达性规则明确后引入 GC。详见 架构 与 开发交接。

依赖 API 依据 SQLx 0.8.6 官方文档 和 Axum 官方文档 实现。SQLx 0.9 要求更高 Rust 版本,本阶段使用与本机工具链兼容的 0.8.6;确切解析版本以锁文件为准。