Run the official OpenHarmony emulator userland — byte-for-byte unmodified — inside a runc container on a small Alpine Linux guest under stock QEMU, and get the full OpenHarmony UI (lock screen, SystemUI, launcher) rendering to a VNC framebuffer.
host QEMU (any distro build, 2D virtio-gpu — no GL, no patches)
└─ Alpine 3.20 guest (PID 1 = /sbin/sshinit: cgroup v1 with six controllers,
│ bind-mounts /dev/{cpuset,memcg,pids}, sshd)
└─ runc container (rootfs = the OpenHarmony userland, PID 1 = OHOS /init)
→ composer_host / render_service / systemui / launcher all start
→ lock screen renders via DRM scanout through virtio-gpu
→ watch it on VNC, verify it with the colour-fingerprint check
Oniro (the OpenHarmony distribution by the Oniro Project) ships an x86_64 emulator image that normally boots under a patched QEMU with an OpenHarmony-tuned kernel. harmonypod takes a different route: the image's userland only is packed into an ext4 image, mounted inside a plain Alpine guest, and started with plain runc run. Nothing inside the OpenHarmony userland is patched, rebuilt or replaced — including the graphics HAL. The composer talks to the virtual GPU through the kernel DRM interface and scans out directly, which is why a 2D virtio-gpu-pci is enough.
The OHOS rootfs image (work/ohos-rootfs.img) is assembled by ohos-rootfs/build.sh purely by concatenating the two ext4 images from the official oniro_emulator.zip:
images/system.img— already a rootfs-style ext4 image (system/,vendor/,usr/,bin -> /system/bin, ... at the top level). It is copied to the new image as the whole root.images/vendor.img— overlays the root'svendor/directory.
The build then checks md5 anchors of the graphics stack (and fails if they mismatch or if libwayland-client.so.0 is present — it is not, in the pristine image):
| file (inside the rootfs) | md5 (first 8) |
|---|---|
vendor/lib64/libdisplay_composer_vendor.z.so |
483714b6 |
vendor/lib64/passthrough/libdisplay_buffer_vdi_impl.z.so |
5bccd321 |
system/etc/init/graphic.cfg |
1efe293d |
system/bin/render_service |
1e58c3c4 |
Re-check from a running guest any time:
$ md5sum /mnt/ohos/vendor/lib64/libdisplay_composer_vendor.z.so \
/mnt/ohos/vendor/lib64/passthrough/libdisplay_buffer_vdi_impl.z.so \
/mnt/ohos/system/etc/init/graphic.cfg /mnt/ohos/system/bin/render_service- Linux x86_64 host with
qemu-system-x86_64≥ 8 (distro package is fine — the 2D path needs no GL and no patched QEMU),python3,gcc+ libdrm headers,e2fsprogs(mkfs.ext4),curl,tar. - KVM strongly recommended (
/dev/kvmwritable); without it the guest falls back to TCG and everything is slow. - Root for loop-device mounts and
mkfs(or run inside a privileged container). oniro_emulator.zip— the Oniro Project x86_64 emulator bundle (containsimages/system.img+images/vendor.img). See the Oniro documentation for the current download location. Put the path inconfig/env.- The guest kernel bzImage — either download
bzImage-containerfrom this project's GitHub Releases and place it atartifacts/bzImage-container, or build your own (make kernel, see below). - ~12 GB of free disk (zip extraction + two 4 GB images).
$ git clone <this repo> && cd harmonypod
$ cp config/env.example config/env
$ editor config/env # set ONIRO_ZIP=...
$ ls artifacts/bzImage-container # download the Release bzImage here (or: make kernel)
$ make ohos-rootfs # assemble work/ohos-rootfs.img + anchor check
$ make guest-rootfs # build work/alpine-rootfs.img, generate work/guest-key(.pub)
$ make run # boot QEMU, wait for ssh, start the OHOS container
$ make accept # VNC frame grab + colour check → ACCEPT-PASS/FAILmake run boots the guest (2D virtio-gpu-pci, two virtio disks, ssh forwarded to 127.0.0.1:55557, VNC on 127.0.0.1:5902), waits for the guest sshd, mounts the OHOS disk at /mnt/ohos, runs the one-shot modeset, and starts runc run ohos. After ~60 s the lock screen is live; make accept prints e.g.
VNC 368x720 distinct=9053 samples=29520
top: [('bdbec2', 1054), ('000000', 720), ('bebdc2', 539), ('bfbec3', 435)]
bdbec2: 1054 (3.570%)
...
ACCEPT-PASS
Open a VNC viewer on 127.0.0.1:5902 (no password) to watch the UI. make bringup restarts the container in a running guest; make clean removes work/.
| capability | state | notes |
|---|---|---|
| display, 2D path (DRM direct scanout, zero-mod userland) | works | this repo's main line; verified with ACCEPT-PASS (distinct ≥ 9000, lock-screen fingerprint ~3.6%) on a stock distro QEMU 8.2.2 |
| display, GPU path (virgl + DRM direct scanout) | works | separate setup, see gpu/README.md; screenshot docs/screenshots/vdrm-srv11.png |
| display, GPU path (virgl + wayland + dmabuf zero-copy) | works | OHOS display HAL wayland backend with zwp_linux_dmabuf_v1; screenshot docs/screenshots/verify-dmabuf.png |
| hdc (host ↔ device tooling) | works | connect recipe in tools/build-hdc.md |
| input (touch / mouse / unlock) | known issue | events reach the OHOS input stack but are dropped; see docs/STATUS.md — this image can be watched, not operated |
| audio | untested | no CI coverage yet |
| cgroup v2 | not supported | the recipe mounts cgroup v1 with six controllers; v2 port is on the roadmap |
See docs/STATUS.md for the full matrix, environment details and known-issue analysis.
The guest's PID 1 is a small ash script (kernel cmdline init=/sbin/sshinit). It mounts proc/sys/devtmpfs/devpts, sets up slirp networking (10.0.2.15), mounts cgroup v1 with the six controllers the OpenHarmony init expects (pids cpuset memory freezer devices cpu,cpuacct) and bind-mounts them under /dev/{cpuset,memcg,pids} — the runc spec bind-mounts /config from /dev/cpuset, which the OHOS services read. Then it starts sshd (root, public-key only) so the host-side scripts can drive the guest.
root.path = /mnt/ohos— the mounted OHOS disk (the guest image only carries the spec, not the rootfs).args = ["/init"]— the OHOS init runs as container PID 1.- Full capability set,
maskedPaths/readonlyPathsemptied — the stock image expects a fairly bare-metal environment. - Bind mounts:
/tmp/wland/data/local/wlfrom the guest's/tmp/xdgare only used by the GPU/wayland branch; on the 2D path they stay empty. - cgroup v1 namespace layout is provided by the guest's init (above).
When the container starts, no one is DRM master yet, and with no master the kernel never issues SET_SCANOUT to QEMU's virtio-gpu — the host keeps showing whatever scanout state it booted with. fbmaster briefly claims DRM master, allocates a dumb framebuffer and performs one modeset on the virtual output; that "opens" the scanout path. It exits after 5 s, composer_host (inside the container) then takes over as DRM master and the UI appears.
A frame is pulled via QEMU's VNC (works for both the 2D and GL paths — the monitor's screendump fails with no surface under -display egl-headless,gl=on). The check requires:
- ≥ 1000 distinct colours over a 1/3-pixel sampling grid (a dead/black screen yields a handful), and
- one of the lock-screen fingerprint greys
bdbec2/bebdc2/bfbec3present at ≥ 0.05 %.
The released bzImage-container (md5 2966b7f4fad4c72ba93eaeddf138fe1c) is built from:
- base tree: OpenHarmony
kernel_linux_6_6(a vanilla 6.6 tree does not work — OHOS userland depends on the OHOS binder extensions), - board patches from the Oniro device board (
device_board_oniro/x86_general):hdf.patch+kernel_patch/*, - the board's
x86_general_linux_defconfigpluskernel/container-config.fragment(namespaces, cgroup v1 controllers, veth/bridge, ashmem, SELinux stub,DRM_FBDEV_EMULATION=n, HDF off — every entry commented), kernel/patches/0001-virtio-gpu-deduplicate-formats.patch:virtio_gpu_formats[]listsDRM_FORMAT_ARGB8888twice on little-endian (HOST_ARGB8888expands to the same fourcc); weston 12 asserts on the duplicate when building its format list from the driver's list. The patch is queued for upstream submission.
Set OHOS_KERNEL_SRC, BOARD_PATCH_DIR, BOARD_DEFCONFIG in config/env, then make kernel.
- Input does not work (biggest gap): pointer events reach MMI but get dropped (
Failed to obtain physical display,deviceId:4,MF:-1); injection viauitest/uinputis rejected byVerifySystemApp()because the caller has no OHOS bundle identity. Identical on the DRM path ⇒ an OpenHarmony image/input-stack issue, not a container issue. Needs an OHOS-side fix (display-group/device-id resolution). Details in docs/STATUS.md. - cgroup v2: the recipe hard-depends on v1 (
/configbind from/dev/cpuset, etc.). A v2 port needs OHOS init changes. - Bare-metal host variant: running the OHOS userland directly on a host (no VM) additionally needs ashmem (
CONFIG_ASHMEM) and binderfs on the host kernel — untested. - Upstreaming: the virtio-gpu format dedup patch; cgroup controller requirements documentation.
- Audio: untested (no audio device is passed through in the 2D recipe).
config/env.example build/run configuration (copy to config/env)
ohos-rootfs/build.sh oniro_emulator.zip -> work/ohos-rootfs.img (+ anchor check)
guest/build.sh alpine-minirootfs -> work/alpine-rootfs.img (+ ssh keypair)
guest/sshinit guest PID 1 (cgroups, sshd)
guest/runc-config.json runc OCI spec (root.path=/mnt/ohos)
host/run-2d.sh QEMU 2D launch + ssh wait
tools/bringup.sh mount OHOS disk, modeset, runc run
tools/accept.sh VNC frame grab + colour acceptance
tools/fbmaster.c one-shot DRM master/modeset helper
tools/vncsnap.py, see.py minimal RFB client / PPM colour statistics
kernel/ kernel fragment + patches + build script
gpu/README.md the GPU (virgl) branch: patches, gotchas, artifacts
docs/STATUS.md completion matrix + known issues
docs/screenshots/ proof screenshots (2D / virgl+DRM / dmabuf zero-copy)
artifacts/ gitignored; Release binaries (bzImage) live here locally
Nothing binary is committed: images land in work/ (gitignored), prebuilt kernels and the GPU-branch binaries are published as GitHub Release attachments.
Built on top of the Oniro Project (Eclipse Foundation) OpenHarmony distribution images and OpenHarmony (OpenAtom Foundation). The OpenHarmony userland is not redistributed by this repository — you fetch it from the official Oniro release infrastructure yourself. Alpine Linux provides the guest base.
This project is an independent experiment. It is not affiliated with, endorsed by, or supported by the OpenAtom Foundation, the Oniro Project, Huawei, or any OpenHarmony working group. "OpenHarmony", "HarmonyOS" and related names are trademarks of their respective owners; they are used here only to refer to the upstream projects.
Licensed under Apache-2.0.