feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
ip link set lo up
|
||||
echo DEVER_ARM_GUEST_BEGIN
|
||||
sh /payload/run.sh
|
||||
result=$?
|
||||
echo DEVER_ARM_GUEST_EXIT=$result
|
||||
sync
|
||||
poweroff -f
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
uname -m
|
||||
test "$(uname -m)" = aarch64
|
||||
test -e /proc/self/ns/user
|
||||
echo ARM_KERNEL_READY
|
||||
check_cleanup() {
|
||||
for process in /proc/[0-9]*/exe; do
|
||||
executable=$(readlink "$process" || true)
|
||||
case "$executable" in
|
||||
/worker/*|*/workers/*|*/sandbox/bin/*|*/sandbox/lib/*)
|
||||
echo "ERROR: Worker process leaked: $executable"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
mkdir -p /tmp/apps
|
||||
for ecosystem in pip npm go; do
|
||||
cp -a "/payload/apps/$ecosystem" "/tmp/apps/$ecosystem"
|
||||
program="/tmp/apps/$ecosystem/program"
|
||||
if ! "$program" sample.worker.probe '{}' > "/tmp/$ecosystem-first.json" 2> "/tmp/$ecosystem-first.log"; then
|
||||
cat "/tmp/$ecosystem-first.json" "/tmp/$ecosystem-first.log"
|
||||
exit 1
|
||||
fi
|
||||
grep -q '"data":true' "/tmp/$ecosystem-first.json"
|
||||
check_cleanup
|
||||
echo "ARM_WORKER_CALL_PASS=$ecosystem"
|
||||
if "$program" sample.worker.reject '{}' > "/tmp/$ecosystem-reject.json" 2> "/tmp/$ecosystem-reject.log"; then
|
||||
echo "ERROR: $ecosystem Worker failure unexpectedly succeeded"
|
||||
exit 1
|
||||
fi
|
||||
grep -Fq '"level":"error"' "/tmp/$ecosystem-reject.log"
|
||||
grep -Fq 'sample/worker/app.dever:3:' "/tmp/$ecosystem-reject.log"
|
||||
grep -Fq 'cannot read component protocol frame header: early eof' "/tmp/$ecosystem-reject.log"
|
||||
check_cleanup
|
||||
echo "ARM_WORKER_ERROR_PASS=$ecosystem"
|
||||
"$program" sample.worker.probe '{}' > "/tmp/$ecosystem-repeat.json"
|
||||
grep -q '"data":true' "/tmp/$ecosystem-repeat.json"
|
||||
check_cleanup
|
||||
cat "/tmp/$ecosystem-first.json"
|
||||
echo "ARM_WORKER_PASS=$ecosystem"
|
||||
# 只清理当前 guest 用例的可写副本和缓存;原产物保留在只读 payload。
|
||||
rm -r /tmp/apps
|
||||
mkdir -p /tmp/apps
|
||||
done
|
||||
cp -a /payload/apps/http /tmp/apps/http
|
||||
/tmp/apps/http/application > /tmp/http.log 2>&1 &
|
||||
server=$!
|
||||
trap 'kill "$server" 2>/dev/null || true; wait "$server" 2>/dev/null || true' EXIT
|
||||
attempt=0
|
||||
until wget -q -O /tmp/http.json http://127.0.0.1:18080/sample/http/hello; do
|
||||
attempt=$((attempt + 1))
|
||||
if test "$attempt" -ge 20; then
|
||||
cat /tmp/http.log
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
grep -q '"data":"arm-http"' /tmp/http.json
|
||||
cat /tmp/http.json
|
||||
kill "$server"
|
||||
wait "$server"
|
||||
trap - EXIT
|
||||
echo ARM_HTTP_PASS
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/busybox sh
|
||||
set -eu
|
||||
/bin/busybox --install -s /bin
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mount -t devtmpfs devtmpfs /dev
|
||||
mkdir -p /dev/pts /dev/shm /tmp /payload /run
|
||||
mount -t devpts devpts /dev/pts
|
||||
mount -t tmpfs tmpfs /tmp
|
||||
mount -t tmpfs tmpfs /dev/shm
|
||||
chmod 1777 /tmp /dev/shm
|
||||
modprobe virtio_mmio
|
||||
modprobe virtio_blk
|
||||
modprobe ext4
|
||||
mount -o ro /dev/vda /payload
|
||||
# initramfs 的 rootfs 不支持 pivot_root;先进入正常根挂载再验收嵌套沙箱。
|
||||
mkdir -p /newroot
|
||||
mount -t tmpfs tmpfs /newroot
|
||||
cp -a /bin /sbin /lib /etc /newroot/
|
||||
cp /init-stage /newroot/init-stage
|
||||
mkdir -p /newroot/proc /newroot/sys /newroot/dev /newroot/tmp /newroot/payload /newroot/run
|
||||
mount -o move /proc /newroot/proc
|
||||
mount -o move /sys /newroot/sys
|
||||
mount -o move /dev /newroot/dev
|
||||
mount -o move /tmp /newroot/tmp
|
||||
mount -o move /payload /newroot/payload
|
||||
exec /bin/busybox switch_root /newroot /bin/sh /init-stage
|
||||
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func Probe(_ context.Context, _ any, _ any) (any, error) {
|
||||
identity, err := uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if runtime.GOARCH != "arm64" || identity.Version() != 1 {
|
||||
return nil, errors.New("ARM target or locked UUID library differs")
|
||||
}
|
||||
message := []byte("ARM 独立依赖")
|
||||
var encoded bytes.Buffer
|
||||
writer := gzip.NewWriter(&encoded)
|
||||
if _, err := writer.Write(message); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader, err := gzip.NewReader(&encoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decoded, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := reader.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digest := sha256.Sum256(message)
|
||||
return map[string]any{"okay": bytes.Equal(decoded, message) && len(digest) == 32}, nil
|
||||
}
|
||||
|
||||
func Reject(_ context.Context, _ any, _ any) (any, error) {
|
||||
return nil, errors.New("expected ARM Worker failure")
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { gzipSync, gunzipSync } from 'node:zlib';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const isNumber = require('is-number');
|
||||
|
||||
export async function probe() {
|
||||
assert.equal(process.arch, 'arm64');
|
||||
const bundle = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
assert.equal(process.execPath, resolve(bundle, 'runtime/bin/node'));
|
||||
assert.equal(require('is-number/package.json').version, '7.0.0');
|
||||
assert.equal(isNumber('42'), true);
|
||||
assert.equal(isNumber('invalid'), false);
|
||||
const message = Buffer.from('ARM 独立依赖');
|
||||
assert.deepEqual(gunzipSync(gzipSync(message)), message);
|
||||
assert.equal(createHash('sha256').update(message).digest().length, 32);
|
||||
await Promise.resolve();
|
||||
return { okay: true };
|
||||
}
|
||||
|
||||
export async function reject() {
|
||||
throw new Error('expected ARM Worker failure');
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import asyncio
|
||||
import gzip
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
|
||||
async def probe(payload, setting):
|
||||
assert platform.machine() == "aarch64"
|
||||
assert six.__version__ == "1.17.0" and six.PY3
|
||||
assert list(six.iteritems({"value": 42})) == [("value", 42)]
|
||||
runtime = Path(sys.executable).resolve().parents[1]
|
||||
bundle = runtime.parent
|
||||
assert Path(sys.prefix).resolve() == runtime
|
||||
assert all(Path(path).resolve().is_relative_to(bundle) for path in sys.path)
|
||||
assert Path(six.__file__).resolve().is_relative_to(bundle)
|
||||
message = "ARM 独立依赖".encode()
|
||||
assert gzip.decompress(gzip.compress(message)) == message
|
||||
assert len(hashlib.sha256(message).digest()) == 32
|
||||
with sqlite3.connect(":memory:") as connection:
|
||||
assert connection.execute("select 6 * 7").fetchone() == (42,)
|
||||
await asyncio.sleep(0)
|
||||
return {"okay": True}
|
||||
|
||||
|
||||
async def reject(payload, setting):
|
||||
raise RuntimeError("expected ARM Worker failure")
|
||||
@@ -0,0 +1,88 @@
|
||||
"""用显式准备的私有 ARM 内核运行独立应用,不改主机服务或沙箱策略。"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
SOURCE = Path(__file__).resolve().parent
|
||||
ROOT = SOURCE.parents[2] / "target/arm64-cross"
|
||||
|
||||
|
||||
def prepare_images():
|
||||
init = ROOT / "initramfs"
|
||||
for source, destination in [
|
||||
("init.sh", init / "init-arm"),
|
||||
("bootstrap.sh", init / "init-stage"),
|
||||
("guest.sh", ROOT / "guest/run.sh"),
|
||||
]:
|
||||
shutil.copyfile(SOURCE / source, destination)
|
||||
destination.chmod(0o755)
|
||||
archive = ROOT / "guest-initramfs.gz"
|
||||
with archive.open("wb") as output:
|
||||
with subprocess.Popen(
|
||||
["/usr/bin/find", ".", "-print0"], cwd=init, stdout=subprocess.PIPE
|
||||
) as find:
|
||||
try:
|
||||
with subprocess.Popen(
|
||||
["/usr/bin/cpio", "--null", "-o", "--format=newc", "--quiet"],
|
||||
cwd=init, stdin=find.stdout, stdout=subprocess.PIPE,
|
||||
) as cpio:
|
||||
find.stdout.close()
|
||||
try:
|
||||
subprocess.run(["/usr/bin/gzip", "-1"], stdin=cpio.stdout, stdout=output, check=True)
|
||||
finally:
|
||||
cpio.stdout.close()
|
||||
finally:
|
||||
find.stdout.close()
|
||||
if find.returncode or cpio.returncode:
|
||||
raise RuntimeError("initramfs creation failed")
|
||||
disk = ROOT / "guest.ext4"
|
||||
with disk.open("wb") as output:
|
||||
output.truncate(1024 * 1024 * 1024)
|
||||
subprocess.run(
|
||||
["/usr/sbin/mke2fs", "-q", "-t", "ext4", "-F", "-d", str(ROOT / "guest"), str(disk)],
|
||||
check=True,
|
||||
)
|
||||
return archive, disk
|
||||
|
||||
|
||||
def main():
|
||||
archive, disk = prepare_images()
|
||||
command = [
|
||||
"/lib64/ld-linux-x86-64.so.2", "--library-path",
|
||||
str(ROOT / "tools/usr/lib/x86_64-linux-gnu"),
|
||||
str(ROOT / "tools/usr/bin/qemu-system-aarch64"),
|
||||
"-machine", "virt", "-cpu", "cortex-a72", "-smp", "2", "-m", "768",
|
||||
"-nographic", "-no-reboot", "-nic", "none",
|
||||
"-kernel", str(ROOT / "iso/boot/vmlinuz-virt"), "-initrd", str(archive),
|
||||
"-append", "console=ttyAMA0 rdinit=/init-arm panic=-1",
|
||||
"-drive", f"file={disk},format=raw,if=none,readonly=on,id=payload",
|
||||
"-device", "virtio-blk-device,drive=payload",
|
||||
]
|
||||
log = ROOT / "logs" / f"guest-{time.time_ns()}.log"
|
||||
started = time.monotonic()
|
||||
timed_out = False
|
||||
with log.open("xb") as output:
|
||||
try:
|
||||
result = subprocess.run(command, stdout=output, stderr=subprocess.STDOUT, timeout=600)
|
||||
exit_code = result.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
timed_out = True
|
||||
exit_code = None
|
||||
transcript = log.read_text(errors="replace")
|
||||
required = ["DEVER_ARM_GUEST_EXIT=0", "ARM_HTTP_PASS"]
|
||||
required.extend(f"ARM_WORKER_PASS={ecosystem}" for ecosystem in ("pip", "npm", "go"))
|
||||
passed = exit_code == 0 and all(marker in transcript for marker in required)
|
||||
record = {
|
||||
"command": command, "exit_code": exit_code, "timed_out": timed_out, "passed": passed,
|
||||
"seconds": time.monotonic() - started, "log": str(log),
|
||||
}
|
||||
log.with_suffix(".json").write_text(json.dumps(record, indent=2) + "\n")
|
||||
print(json.dumps(record), flush=True)
|
||||
if not passed:
|
||||
raise RuntimeError("ARM guest acceptance failed; see serial log")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user