Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 109 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,20 @@ jobs:
path: dist/${{ matrix.asset_name }}

build-server:
name: Build server (linux-x86_64)
name: Build server (linux-${{ matrix.arch }})
needs: validate-version
runs-on: ubuntu-24.04
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: x86_64
modes: kvm pvm
- os: ubuntu-24.04-arm
arch: aarch64
modes: kvm
timeout-minutes: 30
steps:
- uses: actions/checkout@v6

Expand All @@ -124,7 +135,7 @@ jobs:
run: |
set -euo pipefail

for MODE in kvm pvm; do
for MODE in ${{ matrix.modes }}; do
BUNDLE="dist/bundle-$MODE"
mkdir -p "$BUNDLE/ublk"
cp target/release/server "$BUNDLE/server"
Expand Down Expand Up @@ -163,18 +174,17 @@ jobs:
mkdir -p "$BUNDLE/etc/overlaybd"
cp "$STAGE_DEPS/overlaybd/etc/overlaybd/overlaybd.json" "$BUNDLE/etc/overlaybd/overlaybd.json"
rm -rf "$STAGE_HOME"
if [[ "$MODE" == "kvm" ]]; then
ARCHIVE="dist/aenv-server-linux-x86_64.tar.gz"
else
ARCHIVE="dist/aenv-server-linux-x86_64-pvm.tar.gz"
ARCHIVE="dist/aenv-server-linux-${{ matrix.arch }}.tar.gz"
if [[ "$MODE" == "pvm" ]]; then
ARCHIVE="dist/aenv-server-linux-${{ matrix.arch }}-pvm.tar.gz"
fi
tar -czf "$ARCHIVE" -C "$BUNDLE" .
done

- uses: actions/upload-artifact@v7
with:
name: server-bundles
path: dist/aenv-server-linux-x86_64*.tar.gz
name: server-bundles-${{ matrix.arch }}
path: dist/aenv-server-linux-${{ matrix.arch }}*.tar.gz

release:
name: Create GitHub Release
Expand Down Expand Up @@ -215,32 +225,36 @@ jobs:
dist/aenv-darwin-aarch64
dist/aenv-server-linux-x86_64-pvm.tar.gz
dist/aenv-server-linux-x86_64.tar.gz
dist/aenv-server-linux-aarch64.tar.gz

- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/

docker-publish:
name: Build and push Docker image (${{ matrix.mode }})
docker-publish-kvm-images:
name: Build KVM Docker image (${{ matrix.arch }})
needs: release
runs-on: ubuntu-24.04
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- mode: kvm
version_suffix: ""
- mode: pvm
version_suffix: "-pvm"
- os: ubuntu-24.04
arch: amd64
platform: linux/amd64
- os: ubuntu-24.04-arm
arch: arm64
platform: linux/arm64
permissions:
contents: read
packages: write
timeout-minutes: 30
steps:
- uses: actions/checkout@v6

- name: Set lowercase owner
run: echo "OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_ENV
run: echo "OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_ENV"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[security · high]
This newly added publishing job runs Docker-maintained third-party actions by mutable tags while holding packages: write; the following login action also receives GITHUB_TOKEN. If either tag is retargeted or compromised, its code can steal the token or publish a tampered manifest. Pin both docker/setup-buildx-action and docker/login-action in this job to audited full commit SHAs.

Expand All @@ -257,10 +271,84 @@ jobs:
with:
context: .
file: deploy/docker/Dockerfile.agentenv
platforms: ${{ matrix.platform }}
push: true
Comment on lines +274 to 275

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[performance · medium]
The previous external cache-from was removed while this change adds a second native architecture build. BuildKit cache mounts are local to each ephemeral runner, so both legs now rebuild the Rust dependency layers from scratch on every release and may exceed the new 30-minute timeout, especially on ARM. Configure cache-from and cache-to with architecture-specific GHA scopes to avoid cross-architecture cache collisions.

Suggestion:

Suggested change
platforms: ${{ matrix.platform }}
push: true
platforms: ${{ matrix.platform }}
push: true
cache-from: type=gha,scope=agentenv-runtime-${{ matrix.arch }}
cache-to: type=gha,scope=agentenv-runtime-${{ matrix.arch }},mode=max

Comment on lines +274 to 275

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[performance · medium]
The previous cache-from was removed while image publication now builds two architectures on fresh hosted runners. The Dockerfile performs apt installation and a full Cargo build, so both matrix legs will rebuild all layers and may exceed the new 30-minute timeout, especially ARM64. Configure cache-from and cache-to using architecture-specific GHA scopes (for example, include matrix.arch in the scope).

build-args: |
AENV_VIRTUALIZATION_MODE=kvm
tags: |
ghcr.io/${{ env.OWNER }}/aenv-server:${{ github.ref_name }}-${{ matrix.arch }}

docker-publish-kvm-manifest:
name: Publish multi-architecture KVM Docker image
needs: docker-publish-kvm-images
runs-on: ubuntu-24.04
permissions:
packages: write
timeout-minutes: 30
steps:
- name: Set lowercase owner
Comment on lines +285 to +289

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[security · low]
This manifest-only job does not check out or otherwise read repository contents, so contents: read is unnecessary. Set contents: none (or omit it, since declaring job permissions makes unspecified scopes none) to reduce the token privileges exposed to the publishing steps.

Suggestion:

Suggested change
permissions:
contents: read
packages: write
steps:
- name: Set lowercase owner
permissions:
packages: write
steps:
- name: Set lowercase owner

run: echo "OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_ENV"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create multi-architecture manifest
run: |
set -euo pipefail
image="ghcr.io/${OWNER}/aenv-server"
version="${GITHUB_REF_NAME}"

docker buildx imagetools create \
--tag "${image}:${version}" \
--tag "${image}:latest" \
Comment on lines +309 to +310

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · low]
Every tag-triggered workflow unconditionally rewrites the shared latest tag, and this workflow has no concurrency or version-order guard. Overlapping or retried releases can therefore let an older release finish last and point latest backward. Serialize publication and/or update latest only after verifying this tag is the newest release; alternatively publish only the immutable version tag.

Suggestion:

Suggested change
--tag "${image}:${version}" \
--tag "${image}:latest" \
--tag "${image}:${version}" \

Comment on lines +309 to +310

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · medium]
Concurrent tag-release runs can publish latest out of order because this workflow has no concurrency/promotion guard: an older release finishing after a newer one will overwrite latest with the older manifest. Prefer publishing only immutable version tags, or update latest through a serialized promotion job that verifies this is still the newest release before writing it.

"${image}:${version}-amd64" \
"${image}:${version}-arm64"
Comment on lines +309 to +312

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[other · low]
This workflow has no release concurrency control, so overlapping tag runs can complete out of order and an older release can overwrite latest after a newer one. Since the documentation actively directs users to this mutable tag, serialize manifest publication (for example, with a shared release concurrency group and cancel-in-progress: false) or avoid publishing latest.

Comment on lines +308 to +312

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[other · medium]
Release runs are not serialized, so two tag pushes can publish concurrently and an older run that finishes last will move latest back to the older manifest. The new latest-pvm publication has the same race. Add workflow concurrency that serializes image publication (without cancelling an in-progress release), or update floating tags in a separately serialized job; immutable version tags can continue to publish concurrently.


- name: Inspect published image
run: |
docker buildx imagetools inspect \
"ghcr.io/${OWNER}/aenv-server:${GITHUB_REF_NAME}"

docker-publish-pvm:
name: Build and push PVM Docker image (amd64)
needs: release
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
timeout-minutes: 30
steps:
- uses: actions/checkout@v6

- name: Set lowercase owner
run: echo "OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_ENV"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push PVM image
uses: docker/build-push-action@v7
with:
context: .
file: deploy/docker/Dockerfile.agentenv
platforms: linux/amd64
push: true
Comment on lines +343 to 349

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[other · medium]
This replacement drops the previous cache-from, and neither this build nor the new per-architecture KVM builds exports/imports a Buildx cache. The Dockerfile compiles the Rust workspace and installs cargo-chef, while each release runs on fresh runners with a 30-minute timeout, so clean builds—especially ARM—can time out intermittently. Add architecture/mode-specific cache-from: type=gha,... and cache-to: type=gha,...,mode=max entries to both build steps to avoid cross-architecture cache collisions.

cache-from: type=gha,scope=agentenv-runtime
build-args: |
AENV_VIRTUALIZATION_MODE=${{ matrix.mode }}
AENV_VIRTUALIZATION_MODE=pvm
tags: |
ghcr.io/${{ env.OWNER }}/aenv-server:${{ github.ref_name }}${{ matrix.version_suffix }}
ghcr.io/${{ env.OWNER }}/aenv-server:latest${{ matrix.version_suffix }}
ghcr.io/${{ env.OWNER }}/aenv-server:${{ github.ref_name }}-pvm
ghcr.io/${{ env.OWNER }}/aenv-server:latest-pvm
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ AENV_INSTALL_SUDO ?= sudo
TEST_SCRIPTS_DIR := ./scripts/tests

# Runner for tests that require AENV's network and namespace capabilities.
CAPABILITY_RUNNER := CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="$(CURDIR)/scripts/run-with-capabilities.sh"
CARGO_HOST_TARGET_ENV = $(shell $(CARGO) -vV | sed -n 's/^host: //p' | tr '[:lower:]-' '[:upper:]_')
CAPABILITY_RUNNER = CARGO_TARGET_$(CARGO_HOST_TARGET_ENV)_RUNNER="$(CURDIR)/scripts/run-with-capabilities.sh"
AENV_TEST_STATE_ID ?= $(if $(GITHUB_RUN_ID),$(GITHUB_RUN_ID)-$(GITHUB_RUN_ATTEMPT),local-$$(id -u))
AENV_TEST_STATE_DIR ?= /tmp/aenv-test-$(AENV_TEST_STATE_ID)
AENV_TEST_DEPS_PATH ?= $(if $(AENV_DEPS_PATH),$(AENV_DEPS_PATH),/var/lib/aenv/deps)
Expand Down
6 changes: 3 additions & 3 deletions config/deps_manifest.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
[firecracker.kvm]
version = "1.15.1-patch-v1"
url = "https://pub-4ee15c400f554ab7a9eac3f5bc8f53de.r2.dev/firecracker-{version}-{arch}.tgz"
url = "https://github.com/kvcache-ai/firecracker/releases/download/aenv-deps/firecracker-{version}-{arch}.tgz"

[firecracker.pvm]
version = "v1.17.0-next.1"
url = "https://github.com/kvcache-ai/firecracker-next/releases/download/{version}/firecracker-next-{version}-{arch}.tgz"

[kernel.kvm]
version = "vmlinux-6.1.175"
url = "https://pub-4ee15c400f554ab7a9eac3f5bc8f53de.r2.dev/{version}"
url = "https://github.com/kvcache-ai/firecracker/releases/download/aenv-deps/{version}-{arch}"

[kernel.pvm]
version = "6.12.33-pvm"
url = "https://github.com/kvcache-ai/linux/releases/download/pvm-kernel-6.12.33/vmlinux-guest-6.12.33-pvm"

[tools]
version = "0.1.0"
url = "ghcr.io/zlzgithub-0801/agentenv-tools:{version}"
url = "ghcr.io/kvcache-ai/agentenv-tools:{version}"

[overlaybd]
version = "v1.0.18"
Expand Down
2 changes: 1 addition & 1 deletion deploy/docker/Dockerfile.agentenv
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \

FROM ubuntu:24.04 AS runtime-base
ARG APT_MIRROR_BASE
ARG TARGETARCH=amd64
ARG TARGETARCH
# Optional extra runtime packages, specified as a whitespace- or comma-separated build arg.
ARG EXTRA_RUNTIME_PACKAGES=""
COPY deploy/docker/apply_apt_mirror.sh /usr/local/bin/apply_apt_mirror.sh
Expand Down
2 changes: 1 addition & 1 deletion deploy/docker/Dockerfile.gateway
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM golang:1.25-bookworm AS build
ARG TARGETOS=linux
ARG TARGETARCH=amd64
ARG TARGETARCH
WORKDIR /src
COPY services/go.mod services/go.sum ./
RUN go mod download
Expand Down
4 changes: 2 additions & 2 deletions deploy/docker/Dockerfile.scheduler
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
FROM golang:1.25-bookworm AS build
ARG TARGETOS=linux
ARG TARGETARCH=amd64
ARG TARGETARCH
WORKDIR /src
COPY services/go.mod services/go.sum ./
RUN go mod download
COPY services/ .
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/scheduler ./scheduler/cmd

FROM alpine:3.21 AS grpc-health-probe
ARG TARGETARCH=amd64
ARG TARGETARCH
RUN apk add --no-cache curl \
&& case "${TARGETARCH}" in \
amd64|arm64) ARCH="${TARGETARCH}" ;; \
Expand Down
2 changes: 1 addition & 1 deletion docs/src/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ uses a dedicated `aenv` system account with `CAP_NET_ADMIN` and

### 1. Install and start the server

**Option A — Install Script (Linux x86_64)**
**Option A — Install Script (Ubuntu 24.04)**

The script installs both the server and the `aenv` CLI. Set `AENV_HOME_PATH` to
choose the data directory; if it is not set, AENV stores runtime dependencies
Expand Down
10 changes: 8 additions & 2 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,19 @@ esac

ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH_TAG="x86_64" ;;
x86_64|amd64) ARCH_TAG="x86_64" ;;
aarch64|arm64) ARCH_TAG="aarch64" ;;
*)
echo "error: unsupported architecture: $ARCH (server requires x86_64)" >&2
echo "error: unsupported architecture: $ARCH (supported: x86_64/amd64, aarch64/arm64)" >&2
exit 1
;;
esac

if [[ "$VIRTUALIZATION_MODE" == "pvm" && "$ARCH_TAG" != "x86_64" ]]; then
echo "error: PVM virtualization mode is only supported on x86_64 hosts" >&2
exit 1
fi

OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
if [[ "$OS" != "linux" ]]; then
echo "error: AgentENV server only supports Linux" >&2
Expand Down
15 changes: 11 additions & 4 deletions src/setup/deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub async fn ensure(config: &AppConfig, deps_path: &Path) -> Result<()> {
std::fs::create_dir_all(deps_path)?;

ensure_firecracker(config, manifest, &arch).await?;
ensure_kernel(config, manifest).await?;
ensure_kernel(config, manifest, &arch).await?;

// regctl is a runtime dependency for all registry access, not just tools
// drive extraction, so it remains provisioned for explicit tools drives.
Expand Down Expand Up @@ -149,7 +149,11 @@ async fn ensure_firecracker(
Ok(())
}

async fn ensure_kernel(config: &AppConfig, manifest: &SetupDependencyManifest) -> Result<()> {
async fn ensure_kernel(
config: &AppConfig,
manifest: &SetupDependencyManifest,
arch: &str,
) -> Result<()> {
if let Some(kernel_path) = config.kernel.image_path.as_deref() {
return validate_explicit_file("kernel.image_path", kernel_path, false);
}
Expand All @@ -167,7 +171,10 @@ async fn ensure_kernel(config: &AppConfig, manifest: &SetupDependencyManifest) -
.as_deref()
.unwrap_or(&mode_manifest.version);
let kernel_url_template = config.kernel.url.as_deref().unwrap_or(&mode_manifest.url);
let kernel_url = resolve_url(kernel_url_template, &[("version", kernel_version)]);
let kernel_url = resolve_url(
kernel_url_template,
&[("version", kernel_version), ("arch", arch)],
);
download_file(&kernel_url, &kernel_path).await
}

Expand Down Expand Up @@ -865,7 +872,7 @@ mod tests {
ensure_firecracker(&config, bundled_manifest(), "x86_64")
.await
.expect("accept explicit firecracker");
ensure_kernel(&config, bundled_manifest())
ensure_kernel(&config, bundled_manifest(), "x86_64")
.await
.expect("accept explicit kernel");
ensure_tools(&config, &deps_path, bundled_manifest()).expect("import explicit tools");
Expand Down
Loading