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
16 changes: 16 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// RustKVM devkit: frontend + aarch64 device build, no host toolchains needed.
// The image is built locally on first open (scripts/devkit.sh image); set
// RUSTKVM_DEVKIT_IMAGE to use a registry copy instead.
"name": "RustKVM devkit",
"image": "${localEnv:RUSTKVM_DEVKIT_IMAGE:ghcr.io/rust-kvm/rustkvm-devkit:nightly-2026-06-14}",
"initializeCommand": "scripts/devkit.sh image --if-missing",
"runArgs": ["--network=host"],
"remoteUser": "ubuntu",
"updateRemoteUserUID": true,
"customizations": {
"vscode": {
"extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"]
}
}
}
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SDK patches are applied byte-for-byte; keep their whitespace and line endings.
os/**/*.patch -text -whitespace
3 changes: 2 additions & 1 deletion .github/workflows/buildkit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
run: |
host="$RUNNER_TEMP/sdk/buildroot/output/rockchip_rk3588/host"
test -x "$host/bin/aarch64-buildroot-linux-gnu-gcc"
cp -a "$host" ci/buildkit/rk3588-buildkit
cp -a "$host" "$RUNNER_TEMP/rk3588-buildkit"
- name: Upload system images
uses: actions/upload-artifact@v4
with:
Expand All @@ -81,6 +81,7 @@ jobs:
- uses: docker/build-push-action@v7
with:
context: ci/buildkit
build-contexts: rk3588-buildkit=${{ runner.temp }}/rk3588-buildkit
push: true
tags: |
${{ env.IMAGE }}:${{ inputs.sdk_tag }}
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
env:
RUSTUP_TOOLCHAIN: nightly
# Same pin as ci/devkit/Dockerfile.
RUSTUP_TOOLCHAIN: nightly-2026-06-14
CC_aarch64_unknown_linux_gnu: /opt/rk3588-buildkit/bin/aarch64-buildroot-linux-gnu-gcc
steps:
- uses: actions/checkout@v7
Expand All @@ -65,8 +66,9 @@ jobs:
echo "::error::tag ${GITHUB_REF_NAME} does not match workspace version ${version}"
exit 1
}
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-14
components: rust-src
targets: aarch64-unknown-linux-gnu
- uses: actions/download-artifact@v8
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
/out
23 changes: 21 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,30 @@ trunk serve # local dev server on :9000 (Trunk.toml)
## CI

- **`.github/workflows/ci.yml`** — workspace `rustfmt` (nightly) + `cargo deny` + host `clippy --workspace --release --all-targets -D warnings` + aarch64 `clippy --target aarch64-unknown-linux-gnu` (Ubuntu cross gcc, no link) + `cargo test --workspace` (GStreamer dev libs, stub `rkvm-web/dist`) + `actionlint` over every workflow, gated by a `CI Success` summary check.
- **`.github/workflows/buildkit.yml`** — manual: downloads the Buildroot SDK tar (Google Drive file ID), runs `./build.sh rk3588.mk`, publishes `/opt/rk3588-buildkit` as `ghcr.io/<owner>/rk3588-buildkit:{sdk_tag,latest}` and uploads the system images as an artifact.
- **`.github/workflows/release.yml`** — tag `vX.Y.Z` (must equal the workspace version): Trunk frontend → `cargo +nightly build -Z build-std` in the buildkit image → GitHub Release with `rustkvm_app-aarch64-unknown-linux-gnu` + `.sha256` (the assets OTA installs).
- **`.github/workflows/buildkit.yml`** — manual: downloads the Buildroot SDK tar (Google Drive file ID), runs `./build.sh rk3588.mk`, publishes `/opt/rk3588-buildkit` as `ghcr.io/<owner>/rk3588-buildkit:{sdk_tag,latest}` (via the `rk3588-buildkit` named build context of `ci/buildkit/Dockerfile`) and uploads the system images as an artifact.
- **`.github/workflows/release.yml`** — tag `vX.Y.Z` (must equal the workspace version): Trunk frontend → `cargo +nightly-2026-06-14 build -Z build-std` in the buildkit image → GitHub Release with `rustkvm_app-aarch64-unknown-linux-gnu` + `.sha256` (the assets OTA installs).
- **`.github/workflows/device-health.yml`** — every 6h and on demand: runs `app/tests/device_live.rs` against a live device (`RKVM_DEVICE_URL` variable, `RKVM_DEVICE_TOKEN` secret; optional `RKVM_PROBE_RUNNER` self-hosted runner label); a no-op when no device is configured. The same test runs locally with those env vars and is skipped when `RKVM_DEVICE_URL` is unset.
- **`.github/workflows/web.yml`** — `rkvm-web` only (triggers on `crates/rkvm-web/**`, `crates/rkvm-proto/**`): `rustfmt` (nightly) → `clippy` (stable, `--target wasm32-unknown-unknown -D warnings`) → `trunk build --locked` → `cargo deny`, gated by a `Web CI Success` summary check.

## Reproducible builds (devkit and firmware)

Only Docker is needed on a host; full guide in [`docs/building.md`](docs/building.md), shipping flow in [`docs/firmware-release.md`](docs/firmware-release.md).

```
ci/buildkit/Dockerfile # RK3588 cross gcc + sysroot image (Buildroot host dir as named context, docs/target binaries pruned)
ci/devkit/Dockerfile # buildkit + one pinned nightly (aarch64 + wasm32) + trunk; RUSTUP_TOOLCHAIN overrides `stage2`
ci/firmware/Dockerfile # Ubuntu 22.04 SDK host + repo; runs --privileged (loop devices)
.devcontainer/ # VS Code devcontainer on the devkit image
scripts/devkit.sh # buildkit | image | shell | run CMD | firmware-image | firmware STEPS
scripts/build-app.sh # trunk + device binary → out/app/ (binary, .sha256, BUILD_INFO)
scripts/build-firmware.sh # sync | patch | build | collect → out/firmware/ (sd + eflasher images)
os/rk3588/manifest.xml # FriendlyElec SDK projects pinned to exact commits
os/rk3588/patches/<project>/ # every local SDK change, applied with git apply after a reset
os/rk3588/rootfs-overlay/ # S99rustkvm, dhcpcd/network config, dropbear devmode wrapper
```

Toolchain pins live in `ci/devkit/Dockerfile` (and the nightly in `release.yml`); SDK changes go into `os/rk3588/patches`, never only into a local SDK checkout.

## Deployment

```bash
Expand Down
23 changes: 18 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["json", "env-filter"] }
uuid = { version = "1.23.3", default-features = false, features = ["v4"] }
webrtc = "0.17.1"
zip = { version = "8.0", default-features = false }
zip = { version = "9.0", default-features = false }

[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "warn"
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ rustkvm/
│ ├── rkvm-proto/ # protocol types shared with the frontend (HID-RPC, JSON-RPC, keymaps)
│ └── rkvm-web/ # Leptos 0.8 CSR frontend, built with Trunk (not a workspace member)
├── assets/images/ # built-in virtual-media images
├── ci/buildkit/ # Docker image for the RK3588 cross-build kit
├── ci/ # Docker images: cross toolchain, devkit, firmware builder
├── os/rk3588/ # firmware layer: pinned SDK manifest, patches, rootfs overlay
├── scripts/ # devkit.sh, build-app.sh, build-firmware.sh
├── rust/ # rust-lang/rust submodule for the self-built stage2 toolchain
└── dev_deploy.sh # build and deploy to a device over SSH
```
Expand Down Expand Up @@ -223,6 +225,17 @@ RustKVM has two build products: the **WASM frontend** (any host, stable Rust) an
**device binary** (cross-compiled for `aarch64-unknown-linux-gnu`). The frontend is
embedded into the binary at compile time, so build it first.

**Quickest path, any Linux host with Docker:**

```bash
scripts/devkit.sh run scripts/build-app.sh # → out/app/rustkvm_app-aarch64-unknown-linux-gnu
scripts/devkit.sh firmware # → out/firmware/ SD and eMMC images
```

Pinned versions, caches, proxies and the firmware build are covered in
[`docs/building.md`](docs/building.md). The manual setup below is what the containers
automate.

### 1. Frontend (any host)

```bash
Expand Down
27 changes: 26 additions & 1 deletion ci/buildkit/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
# RK3588 cross toolchain + sysroot (the Buildroot `host/` directory) at the
# path .cargo/config.toml expects. The directory comes in as the named build
# context `rk3588-buildkit`:
#
# docker buildx build --build-context rk3588-buildkit=/opt/rk3588-buildkit \
# -t ghcr.io/rust-kvm/rk3588-buildkit:20250527 ci/buildkit
#
# scripts/devkit.sh buildkit does exactly this.
FROM ubuntu:24.04 AS prune

COPY --from=rk3588-buildkit . /opt/rk3588-buildkit

# Keep only what compiling and linking against the sysroot needs: drop
# documentation, translations, the target's own executables and its password
# database (the image is public).
RUN cd /opt/rk3588-buildkit \
&& rm -rf doc man share/doc share/man share/info share/locale share/gtk-doc \
&& cd aarch64-buildroot-linux-gnu/sysroot \
&& rm -rf usr/bin usr/sbin usr/libexec bin sbin \
usr/share/doc usr/share/man usr/share/info usr/share/locale \
etc/shadow etc/gshadow

FROM ubuntu:24.04

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential ca-certificates curl git pkg-config xz-utils \
&& rm -rf /var/lib/apt/lists/*

COPY rk3588-buildkit /opt/rk3588-buildkit
COPY --from=prune /opt/rk3588-buildkit /opt/rk3588-buildkit

ENV PATH=/opt/rk3588-buildkit/bin:$PATH

LABEL org.opencontainers.image.source="https://github.com/Rust-KVM/rustkvm" \
org.opencontainers.image.description="RK3588 Buildroot cross toolchain and sysroot for RustKVM"
49 changes: 49 additions & 0 deletions ci/devkit/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# RustKVM devkit: everything needed to build the frontend and the device
# binary, on top of the RK3588 buildkit image (cross gcc + sysroot).
#
# scripts/devkit.sh image # build it locally
# scripts/devkit.sh shell # use it
#
# Toolchain versions are pinned here and nowhere else in the container path.
ARG BUILDKIT_IMAGE=ghcr.io/rust-kvm/rk3588-buildkit:20250527
FROM ${BUILDKIT_IMAGE}

# One pinned nightly builds both the device binary (-Z build-std) and the
# frontend (wasm32). Matches what release.yml uses.
ARG RUST_NIGHTLY=nightly-2026-06-14
ARG TRUNK_VERSION=0.21.14

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
openssh-client rsync sudo \
&& rm -rf /var/lib/apt/lists/*

ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:/opt/rk3588-buildkit/bin:$PATH

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile minimal --default-toolchain none \
&& rustup toolchain install "${RUST_NIGHTLY}" --profile minimal \
--component rust-src,rustfmt,clippy \
--target aarch64-unknown-linux-gnu,wasm32-unknown-unknown \
&& rustup default "${RUST_NIGHTLY}" \
&& curl -sSfL "https://github.com/trunk-rs/trunk/releases/download/v${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" \
| tar -xz -C /usr/local/cargo/bin trunk \
&& chmod -R a+w "${RUSTUP_HOME}" "${CARGO_HOME}"

# RUSTUP_TOOLCHAIN overrides the repo's rust-toolchain.toml (`stage2`, a
# toolchain only the reference workbench has), and crates/rkvm-web's `stable`.
ENV RUSTUP_TOOLCHAIN=${RUST_NIGHTLY} \
RKVM_WEB_TOOLCHAIN=${RUST_NIGHTLY} \
CC_aarch64_unknown_linux_gnu=/opt/rk3588-buildkit/bin/aarch64-buildroot-linux-gnu-gcc

# build.rs reads git metadata from a bind-mounted checkout owned by another uid.
# The devcontainer runs as `ubuntu` (uid remapped to the host user).
RUN git config --system --add safe.directory '*' \
&& (id ubuntu >/dev/null 2>&1 || useradd -m -s /bin/bash -u 1000 ubuntu)

LABEL org.opencontainers.image.source="https://github.com/Rust-KVM/rustkvm" \
org.opencontainers.image.description="RustKVM devkit: pinned Rust nightly, trunk and the RK3588 cross toolchain" \
org.opencontainers.image.licenses="GPL-2.0-only"
39 changes: 39 additions & 0 deletions ci/firmware/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Host environment for the FriendlyElec RK3588 Buildroot SDK (kernel, u-boot,
# Buildroot rootfs, SD / eMMC image packing). Used by scripts/build-firmware.sh
# through scripts/devkit.sh firmware.
#
# Image packing uses loop devices, so the container runs --privileged.
FROM ubuntu:22.04

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bc bison build-essential ca-certificates ccache clang cmake cpio curl \
device-tree-compiler dosfstools dwarves e2fsprogs exfatprogs fakeroot file flex \
gcc-arm-linux-gnueabihf git git-lfs gperf kmod libclang-dev \
libdbus-1-dev libelf-dev libgmp-dev libmpc-dev libncurses-dev \
libnl-3-dev libssl-dev llvm-dev lz4 openssl parted patch \
pkg-config python-is-python3 python3 python3-pip rsync sudo texinfo \
u-boot-tools udev unzip uuid-dev wget xz-utils zstd \
&& rm -rf /var/lib/apt/lists/*

# Google's `repo` launcher (v2.69), pinned by content hash.
ARG REPO_SHA256=6ed2f6325705b6ea0af99466d6ebd04adec387a5b6a906cd77bc8c6da65123e5
RUN curl -sSfL https://storage.googleapis.com/git-repo-downloads/repo -o /usr/local/bin/repo \
&& echo "${REPO_SHA256} /usr/local/bin/repo" | sha256sum -c - \
&& chmod a+rx /usr/local/bin/repo

# Buildroot refuses some packages as root; the build runs as `builder`, which
# may sudo for the SDK's image-packing steps. devkit.sh maps it to the host uid.
ARG UID=1000
ARG GID=1000
RUN groupadd -g "${GID}" builder \
&& useradd -m -s /bin/bash -u "${UID}" -g "${GID}" builder \
&& echo 'builder ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/builder \
&& git config --system --add safe.directory '*' \
&& git config --system user.name "RustKVM firmware build" \
&& git config --system user.email "build@localhost"

USER builder
WORKDIR /home/builder
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
| --- | --- |
| [Getting started on RK3588](getting-started.md) | Flashing, deploying, wiring and first login |
| [API reference](api.md) | HTTP routes, JSON-RPC, MCP tools, HID-RPC, MQTT, Prometheus metrics |
| [Building from source](../README.md#building-from-source) | Frontend, x86_64 host build and the aarch64 cross toolchain |
| [Building](building.md) | Docker devkit, application package and firmware image, pinned versions |
| [Releasing and shipping firmware](firmware-release.md) | Versioning, release checklist, flashing, factory state, acceptance test |
| [Building from source manually](../README.md#building-from-source) | Frontend, x86_64 host build and the aarch64 cross toolchain without containers |
| [Architecture and conventions](../AGENTS.md) | Module tree, coding rules, gotchas |
| [Contributing](../CONTRIBUTING.md) | Workflow, CI gates, PR expectations, release process |
| [Changelog](../CHANGELOG.md) | What changed between versions |
Expand Down
Loading
Loading