Website: vinix-os.org
Vinix is an effort to write a modern, fast, and useful operating system in the V programming language.
Join the Discord chat.
- Keeping the code as simple and easy to understand as possible, while not sacrificing performance and prioritising code correctness.
- Making a usable OS which can run on real hardware, not just on emulators or virtual machines.
- Targeting modern 64-bit amd/arm architectures, CPU features, and multi-core computing.
- Maintaining good source-level compatibility with Linux to allow to easily port programs over. On arm64 Vinix runs Alpine binaries.
- Running on Apple Silicon Macbooks. Only M1 for now.
- Exploring V capabilities in bare metal programming and improving the compiler in response to the uncommon needs of bare metal programming.
- Having fun.
Note: Vinix is still pre-alpha software not meant for daily or production usage!
You can grab a pre-built nightly Vinix image at https://github.com/vlang/vinix/releases
Make sure to boot the ISO with enough memory (8+GiB) as, for now, Vinix loads its entire root filesystem in a ramdisk in order to be able to more easily boot on real hardware.
- Alpine Linux/musl userland
- bash
- zsh + Oh My Zsh
- gcc/g++
- V
- nano
- storage drivers
- ext2
- X.org
- X window manager
- Networking
- Wayland (Hyprland on aarch64)
- Hypervisor (Intel VT-x; see documentation)
- NUMA / multi-socket memory topology (see documentation)
- Real-time scheduling: SCHED_FIFO/RR/DEADLINE (see documentation)
- V-UI 2
- Intel HD graphics driver (Linux port)
The following is a distro-agnostic list of packages needed to build Vinix.
Skip to a paragraph for your host distro if there is any.
GNU make, findutils, curl, git, file, xz, rsync, xorriso,
qemu to test it, Python 3, Clang/LLVM/LLD, and a current V compiler need to
be present.
sudo apt install -y clang llvm lld make findutils curl git file xz-utils rsync xorriso qemu-system-x86 python3sudo pacman -S --needed clang llvm lld make findutils curl git file xz rsync xorriso qemu pythonsudo yum install -y clang llvm lld make findutils curl git file xz rsync xorriso qemu python3sudo xbps-install -Suv clang llvm lld make findutils curl git file xz rsync xorriso qemu python3The build downloads Alpine's pinned minirootfs, builds the kernel directly with the host compiler, and assembles a UEFI ISO. Nothing is bootstrapped from source -- there is no binutils, GCC, or mlibc toolchain to build first:
make all
# Or build the ARM64 image instead of the default AMD64 image:
make ARCHITECTURE=aarch64 allSet VINIX_ALPINE_DEVTOOLS=1 to include Alpine's prebuilt C/C++ toolchain in
the guest image:
VINIX_ALPINE_DEVTOOLS=1 make allBoth Alpine userland images include Zsh and a pinned, local Oh My Zsh
installation at /root/.oh-my-zsh. Vinix boots into a Zsh login shell, and
the native desktop Terminal and Hyprland's Foot terminal also launch Zsh by
default. The root .zshrc loads the bundled robbyrussell theme without guest
network access. Run the following inside Vinix to verify the shell setup:
/root/zsh-smoke.shThe framebuffer-native desktop has an amd64 build and QEMU launcher matching the aarch64 workflow. After checking out ui2, run:
git clone https://github.com/vlang/ui2 third_party/ui2
./run-desktop-amd64.shFor local development, a sibling ../ui2 checkout (for example
~/code/ui2 beside ~/code/vinix) is selected automatically and all of its
examples are installed in the desktop's ui2 Examples launcher. Set
VINIX_UI2_SOURCE to choose another checkout explicitly.
This stages Alpine's prebuilt musl development packages, compiles
vinix-desktop with Clang, and creates vinix-desktop-amd64.iso, whose init
starts the desktop directly. The runner uses KVM when available and otherwise
falls back to QEMU TCG; --no-build, --monitor, and --mem=MB are supported.
The same build works on Apple Silicon and cross-compiles the amd64 executable.
Build the languages, developer tools, X11 applications and alternate desktop into one image with a single command:
./build-all-aarch64.shThe resulting build-support/init-aarch64/initramfs-desktop.tar contains
Python, Ruby, Go, the current pinned V compiler, network and native developer
tools, X11, Firefox, Hyprland, x86 translation, Codex and Claude Code, in
addition to the native Vinix desktop. Java, Minecraft and Wine are deliberately
left out of this default image so users can install them on demand with pkg.
It is the image booted by:
./run-desktop-aarch64.sh --no-desktopThe aggregate builder rebuilds every owned layer and refuses to publish a
partial image. Use --reuse-layers to validate and reassemble existing layer
outputs during image work. Asahi Mesa and the native Blender backend require
their dedicated, mutually different ARM64 Linux build environments. The desktop
builder includes the native Blender backend automatically when its staging tree
is present, but Asahi Mesa only with --with-asahi-gpu, because that Mesa is
built for a real Apple GPU and carries no llvmpipe: on any other machine it
replaces a software renderer good for OpenGL 4.5 with one that stops at 3.3.
The M1 deployment scripts pass the flag; a QEMU image should not. Wi-Fi
firmware and proprietary Office media remain explicit inputs and are never
downloaded by the aggregate build.
The desktop runner splits the writable /root seed from the immutable image
and caches a compressed QEMU module. This keeps the boot payload small, leaves
headroom below FAT32's single-file limit, and lets Limine decompress the module
during boot.
The individual layer builders described below remain available for iterating on one component, but are not required for a normal default-image build.
The AArch64 desktop image contains the native V compiler and its matching
vlib, GCC, the editable staged desktop sources in /root/desktop, and the
headless ui2 module in /root/vmodules. QEMU also shares the checkout from
which it was launched at /mnt/host/vinix. The share is a read-only mirror
refreshed from macOS immediately before each build, so edits made after QEMU
started are included without rebuilding the image or restarting the VM.
Verify the compiler or rebuild and hot-reload the desktop from a Vinix Terminal with:
/root/v-smoke.sh
vinix-desktop-buildvinix-desktop-build refreshes the host mirror, stages the desktop and ui2
sources exactly as the image builder does, translates the desktop with V,
links a static AArch64
binary with GCC, keeps a copy at /root/vinix-desktop, atomically replaces
/usr/bin/vinix-desktop, and sends SIGHUP to PID 1. The supervisor lets the
old compositor close its applications and release the framebuffer, then starts
the new binary without rebooting the OS. Pass --no-reload to build only. The
editable source and module tree carries an image-generation marker. If an older
persistent home is missing either tree or belongs to another image generation,
the helper automatically builds the coherent copy in
/usr/share/vinix/desktop-dev. Pass both --source=DIR and --modules=DIR to
deliberately build a different source generation.
The runner shares its own checkout by default. Set
VINIX_QEMU_HOST_SOURCE=/path/to/vinix to select another checkout, or set it
to 0 to disable the host source service. vinix-host-sync can be run by
itself to refresh /mnt/host/vinix for inspection. The VirGL runner remains
offline and therefore uses the image's staged source copy.
The compiler layer is pinned to the newest V revision qualified by this tree.
Build it separately with ./build-v-aarch64.sh; set VINIX_V_SOURCE to a V
checkout when qualifying a newer revision without downloading another copy.
The layer includes the matching compiler sources, so v self rebuilds and
replaces /usr/lib/vlang/v inside Vinix. Ordinary V builds use the native TCC
package from Alpine Linux 3.24, and v self uses that same TCC toolchain. V's
worker passes run with -no-parallel until their threading is qualified on
Vinix. VJOBS=1 and synchronous compiler helper passes keep that contract
intact. It remains a Linux/musl program that runs on Vinix and continues to
target Vinix applications by default.
The aarch64 image can include Alpine's musl CPython 3.12 runtime and its native standard-library dependencies. Stage it before assembling the userland:
./build-python-aarch64.sh
./build-userland-aarch64.shBoth aarch64 userland builders automatically merge
build-aarch64-python/staging when it is present. Set
VINIX_PYTHON_STAGING=/path/to/staging to use another tree. The staging script
also installs /root/python3-smoke.py; the ARM64 VM image runs this test during
its boot suite and it can be rerun manually with python3.
Ruby 3.3, RubyGems, Bundler, Rake, and the native standard-library dependencies can be staged and merged into the same aarch64 image:
./build-ruby-aarch64.sh
./build-userland-aarch64.shThe userland builders merge build-aarch64-ruby/staging when present. Set
VINIX_RUBY_STAGING=/path/to/staging to override it. The runtime includes
/root/ruby-smoke.rb, which the ARM64 VM boot suite runs automatically and
which can also be invoked manually with ruby.
The Go compiler, linker, formatter and standard library can be staged for native development inside Vinix:
./build-go-aarch64.sh
./build-userland-aarch64.shBoth aarch64 userland builders merge build-aarch64-go/staging when present.
Set VINIX_GO_STAGING=/path/to/staging to use another tree. The ARM64 VM boot
suite compiles /root/go-smoke.go with the native Go compiler, then runs it to
exercise goroutines, filesystem operations, subprocesses, cryptography and TCP
loopback networking. It also builds a cgo program through the native GCC
toolchain when GCC is installed.
The aarch64 image supports the complete OpenJDK 25 JDK and JRE from Alpine Linux 3.24, the latest stable Alpine branch:
./build-java-aarch64.sh
./build-userland-aarch64.shBoth userland builders merge build-aarch64-java/staging when present. Set
VINIX_JAVA_STAGING=/path/to/staging to override it. The VM boot suite uses
javac and jar, then runs the compiled smoke program with java to exercise
the HotSpot runtime, threads, files, cryptography and loopback sockets.
JAVA_HOME is /usr/lib/jvm/java-25-openjdk.
The aarch64 image can include the official ARM64/musl Codex CLI together with
Alpine's musl builds of its rg and zsh helpers. Stage it before assembling
the userland:
./build-codex-aarch64.sh
./build-userland-aarch64.shBoth userland builders merge build-aarch64-codex/staging when present. Set
VINIX_CODEX_STAGING=/path/to/staging to use another tree. When Python is also
installed, the VM boot suite runs /root/codex-smoke.py, which drives
codex exec against a local Responses API server without requiring credentials
or Internet access.
Vinix does not yet implement Linux namespaces, so interactive and non-interactive Codex sessions must currently opt out of the upstream sandbox:
codex --dangerously-bypass-approvals-and-sandbox
codex exec --dangerously-bypass-approvals-and-sandbox "your task"The aarch64 image can also include Anthropic's native ARM64/musl Claude Code
CLI. The staging layer supplies the Alpine libgcc, libstdc++, and ripgrep
dependencies required by the musl build:
./build-claude-aarch64.sh
./build-userland-aarch64.shBoth userland builders merge build-aarch64-claude/staging when present. Set
VINIX_CLAUDE_STAGING=/path/to/staging to use another tree. When Python is
also installed, the VM boot suite runs /root/claude-smoke.py, which drives
claude --print against a local Messages API server without requiring
credentials or Internet access. Start an authenticated session with:
claude
claude --print "your task"Vinix uses Alpine 3.21's aarch64/musl repositories for optional software. Build the network-tools layer before the userland (the desktop's compact image already requires this layer):
./build-network-tools-aarch64.sh
./build-userland-aarch64.shThe desktop builder also overlays the current network-tools layer directly,
so rebuilding the desktop refreshes pkg even when its base userland archive
was created before package support was added.
Inside Vinix, use pkg to search, install, remove, and upgrade Alpine packages.
The friendly gtk name installs GTK 3, its two demonstration programs, the
Adwaita icons, and DejaVu fonts:
pkg update
pkg search gtk
pkg install gtk
./gtk-package-smoke.shGnumeric is also installed on demand with its GTK theme and fonts:
pkg install gnumeric
./gnumeric-package-smoke.shGIMP 2.10 runs through the desktop's private X11 window bridge. Install it on demand, then launch it from the wallpaper/Start menu or run its guest smoke test:
pkg install gimp
run-gimp
./gimp-package-smoke.shLibreOffice Writer and Calc run through the same private X11 window bridge, drawn by the GTK 3 VCL plugin. Install the suite on demand, then launch it from the wallpaper/Start menu or from a shell:
pkg install libreoffice
run-libreofficeA bootable image can carry it already installed, which is what the office regression test boots:
./build-libreoffice-aarch64.sh
./build-desktop-aarch64.sh --compact-initramfs --with-libreoffice
./tests/office/run.shAlpine builds the toolkit's runtime indexes from package triggers, which a
staged image never runs. run-libreoffice rebuilds the ones VCL needs on its
first start — the MIME database, the GDK-Pixbuf loader cache, the compiled
GSettings schemas and the icon-theme caches. Without them GTK cannot load a
single icon, and the suite exits before it maps a window rather than saying so.
It also seeds a profile: the hosted display has no window manager, so nothing
would size the document window to the surface, and a first run would open the
Tip of the Day dialog on top of the document.
The suite is much heavier on the kernel than the browsers are — its start-up
alone builds those indexes, then runs a UNO service manager over several
hundred shared objects — and the machine under it is not reliable there yet.
The same image reaches a drawn page in 15 s in one run and, in another, loses
the whole hosted process tree without a message, or stops in
scheduler_timer_handler with Attempted to get current CPU struct without
disabling ints: interrupts are enabled inside the scheduler's timer handler,
which is a pre-existing SMP fault this workload is simply the first to reach
often.
Blender's shared data and runtime libraries are installed directly from Alpine's aarch64 package. The desktop launcher uses a native Vinix GHOST build: it renders through surfaceless EGL into the Vinix compositor's shared-surface ABI, without Xorg or Wayland. Build that executable once on Alpine/aarch64 and then rebuild the desktop image:
./build-blender-native-aarch64.sh
./build-desktop-aarch64.sh
pkg install blender
./blender-package-smoke.shThe blender shell command remains useful for the background smoke test; the
desktop's Blender entry starts /usr/libexec/vinix-blender-native with audio
disabled until the remaining PulseAudio threading primitives are available.
Sublime Text is available through the same package frontend. This installs its
Alpine gcompat/GTK dependencies and a checksum-verified official ARM64 build,
including an application-menu entry:
pkg install sublime-text
subl
./sublime-package-smoke.shpkg disables Alpine maintainer scripts that assume a complete Alpine init
system.
When started with run-aarch64.sh (including through
run-desktop-aarch64.sh), successful package changes are saved in a fixed
archive under boot-image/ and layered over the initramfs on every later
launch. Thus pkg install gtk, shutting down QEMU, and starting it again keeps
GTK installed. The shell store is
boot-image/boot.img.packages.tar; the desktop store is
boot-image/boot-desktop-4096.img.packages.tar. Override its path with
VINIX_QEMU_PACKAGE_STORE, or delete it to reset installed packages.
Ephemeral runs use a private package store that is removed at shutdown unless
the variable explicitly selects a long-lived store.
Boot methods that do not use the QEMU runner retain package changes only in the
running root filesystem. Direct Alpine package names also work, for example
pkg install nano.
The QEMU runner keeps the base system in its initramfs-backed tmpfs and mounts
a separate persistent ext2 disk at /root by default:
./run-aarch64.shThe generic runner creates one fixed boot-image/boot.img.root.ext2 volume
(1 GiB by default). Use --persist=4096 for a 4 GiB new disk, --no-persist
for a disposable RAM-backed /root, or set
VINIX_QEMU_PERSIST_DISK and VINIX_QEMU_PERSIST_SIZE_MB to choose its path
and initial size. Existing disks are never reformatted. Creating a disk needs
mke2fs from e2fsprogs; on macOS, install it with brew install e2fsprogs.
The system files and package overlay continue to use their existing boot-image
paths; only /root is persistent. As with other writable ext2 experiments,
shut down the VM cleanly and use e2fsck from the host after an interrupted
run.
The desktop launcher enables persistence by default. It caches a QEMU-specific
base without /root, seeds boot-image/desktop-root.ext2 from the desktop
image once, and reuses both that volume and boot-image/boot-desktop-qemu.img.
Use --no-persist for a self-contained RAM-backed image that remains below
FAT32's 4 GiB file limit. --ephemeral creates a private boot disk, seeded
/root volume, and package store for a concurrent test, then removes all three
when QEMU exits. Other newly created boot images under the host temporary
directory are also removed unless VINIX_KEEP_TEMP_BOOT_DISK=1 is set.
tmux is included in the optional native developer-tools overlay. Build that
overlay before the userland to have tmux and its terminal definitions available
from first boot:
./build-developer-tools-aarch64.sh
./build-userland-aarch64.sh
tmuxVinix runs Mojang's own Minecraft client on AArch64 through its X11 and software-OpenGL stack. The game is not part of this repository. From the default Vinix desktop image, install it on demand:
pkg install minecraft
minecraft --checkThis installs Alpine's OpenJDK 21 and native runtime packages, then downloads the newest official Minecraft release compatible with that JVM from Mojang's distribution endpoints. Client, library and asset hashes are verified, and the large game data remains outside the base image. To stage the current release with OpenJDK 25 directly into a custom image instead, run:
./build-x11-aarch64.sh
./build-java-aarch64.sh
./build-minecraft-aarch64.sh
./build-desktop-aarch64.sh
./run-desktop-aarch64.sh --no-desktopVINIX_MINECRAFT_VERSION selects an exact version or release channel;
VINIX_MINECRAFT_ASSETS=none stages the code without the ~500 MiB of assets.
The package install constrains the release channel to Java 21, while the custom
image builder uses its staged Java 25 runtime and therefore takes the current
release without that constraint.
Open Minecraft from the desktop or run minecraft in a terminal. With no
account signed in it starts Mojang's free demo. minecraft --login signs in to
a Microsoft account that owns the game with the standard OAuth device-code
flow, after which minecraft plays the full game; --demo, --play,
--logout and a display-free --check are also accepted. Worlds and options
live under $HOME/.minecraft.
For the QEMU desktop, give the preinstalled game layer 10 GiB of RAM and use one virtual CPU while running Minecraft:
VINIX_QEMU_MEM=10240 VINIX_QEMU_SMP=1 ./run-desktop-aarch64.shThe launcher uses HotSpot's interpreter and reports one active processor. This avoids the generated-code and SMP races that Vinix still needs to resolve; the tradeoff is a slow first client startup.
Microsoft requires every launcher to use its own registered application, so set
VINIX_MINECRAFT_MSA_CLIENT_ID to the application id of an Azure registration
approved for Minecraft sign-in before using --login. The demo needs no
account and no application id.
Two things make the stock Linux build work on Vinix. Mojang ships no AArch64
Linux natives, so the LWJGL natives come from the same LWJGL release on Maven
Central; and those are glibc objects, so they load through gcompat, with
Alpine's native OpenAL and jemalloc substituted for the bundled copies that do
not survive that translation. Rendering uses Mesa's llvmpipe, the only software
rasteriser here that reaches the OpenGL 3.2 core profile the client requires.
Set VINIX_MINECRAFT_HARDWARE_GL=1 to experiment with hardware GL.
GTK and Gnumeric are deliberately not included in the base or network-tools
package layer. GTK is downloaded only when it or an application that needs it
is requested. The GTK smoke test first checks that the base image is GTK-free,
installs it, then opens both gtk3-demo and gtk3-widget-factory against the
Vinix Xorg server. Gnumeric is likewise absent until explicitly installed.
Firefox ESR can run as a stock Alpine musl application on Vinix's existing
framebuffer-backed Xorg server. Firefox draws its own interface with Gecko/XUL;
GTK 3 is staged as a userspace dependency for Linux window-system integration,
not implemented in the kernel or in vinix-desktop.
Build the X server and stage Firefox before assembling the full userland:
./build-x11-aarch64.sh
./build-firefox-aarch64.sh
./build-userland-aarch64.shBoot with at least 8 GiB of RAM, then launch the browser. The first command creates a 2 GiB sparse boot disk when one does not already exist. With no URL, Firefox opens the bundled smoke page; pass a URL to browse normally:
./run-aarch64.sh --mem=8192 --disk=2048
run-firefox
run-firefox https://example.comAfter rebuilding the desktop image, its wallpaper and Start menu contain a Firefox launcher. Clicking it opens Firefox in a normal movable Vinix window; the native desktop and taskbar remain visible around its private Xvfb display:
./build-desktop-aarch64.sh
./run-desktop-aarch64.sh --no-build --mem=8192The X11 session uses a small Vinix-specific input bridge for the native
absolute pointer packets and console keyboard. This keeps Linux evdev and udev
out of the system while giving Firefox normal X11 mouse and keyboard events.
The desktop builder overlays the current bridge and Firefox configuration onto
the full userland too, so an older base image cannot contain startx without
its required input bridge. If the compiled bridge is absent, rebuild X11 first.
build-firefox-aarch64.sh resolves and stages the complete Alpine runtime
dependency closure, including GTK/X11, fonts, TLS certificates, and media
libraries. It defaults to Alpine 3.22's Firefox 140 ESR: newer Alpine builds
currently link Scudo, whose virtual-memory contract Vinix does not yet provide.
Firefox uses the system GTK installation directly, without launcher-local GTK
backend or accessibility overrides. Running run-firefox directly still starts
the browser on the physical Xorg display for command-line debugging.
Set VINIX_FIREFOX_STAGING to merge a different completed staging tree, or
ALPINE_BRANCH/VINIX_FIREFOX_PACKAGE to select another compatible build.
The compact desktop image used by the default M1 deployment merges the Firefox
and X11 staging trees directly, alongside Python, Git and GCC, so its launcher
works without shipping the much larger complete userland image.
On M1 and in a VirGL VM, Firefox automatically enables WebRender over X11 EGL
when a render node and the matching Mesa runtime are present. Xorg imports the
GPU buffers through DRI3 and uses glamor. Other targets keep the software
renderer, as does VINIX_FORCE_SOFTWARE_GL=1. Firefox's Linux namespace and
seccomp sandboxes remain disabled because Vinix does not implement those
kernel facilities yet, so the browser displays its reduced-protection warning.
Chromium runs as a stock Alpine musl application on the same framebuffer-backed X11 stack Firefox uses. It is not in the image: the browser and its runtime are 232 MiB, so they are fetched on demand from the Alpine repositories.
pkg install chromium
run-chromium # the bundled start page
run-chromium https://example.comThe desktop's Start menu and wallpaper have a Chromium launcher beside the Firefox one. Until the package is installed the window says so rather than starting an X server for a browser that is not there.
A bootable image can carry the browser already installed, which is what the Chromium regression test boots:
./build-chromium-aarch64.sh
./build-desktop-aarch64.sh --compact-initramfs --with-chromiumChromium is a much heavier guest than Firefox, and four kernel facilities were added or repaired for it:
- procfs. Chromium finds its own program through
/proc/self/exeand re-executes it to start every child process, and each child checks that it is still single-threaded by reading the link count of/proc/self/taskthrough a descriptor it opened on/proc. Vinix now mounts a small procfs: a directory per live process withcmdline,comm,stat,statm,status, anexelink,task/andfd/, plusmeminfo,uptime,versionand the/proc/sysentries that are read at startup. The tree is rebuilt from the process table when a directory is looked up or read, so nothing in the clone or exit path has to take a filesystem lock and a directory can never describe a process that has already gone. SO_PASSCREDandSCM_CREDENTIALS. Chromium's crash handler sets up a socket pair that carries the peer's identity with each message, and treated a refusal as fatal. Unix sockets now accept the option and deliver the record.- Userspace faults end the process, not the machine. A
BRKinstruction — which is how__builtin_trap()and theCHECKmacros of large C++ programs abort — used to reach the kernel's fatal exception handler. It is now delivered asSIGTRAP, and any userspace fault with no handler terminates that process the way Linux does. - Sockets are open for writing. Every anonymous descriptor — socket,
socketpair, accepted connection, eventfd, timerfd, epoll, signalfd — was
created without an access mode, so it looked read-only and
write(2)on it was refused withEBADF. An X server answers its clients withwritev(2), so it accepted each connection and then dropped it: no application on the machine could open a window, Firefox included. They now carryO_RDWR, which is what Linux gives them.
Vinix implements neither user namespaces nor seccomp-bpf, so run-chromium
turns off both layers of Chromium's Linux sandbox and starts child processes
directly instead of through the zygote. With no GPU driver present, ANGLE falls
back to the CPU Vulkan device in chromium-swiftshader; VINIX_FORCE_SOFTWARE_GL=1
forces that path, and VINIX_CHROMIUM_SINGLE_PROCESS=1 collapses the browser
into one process, which is what separates an IPC failure from a rendering one.
Both browsers render their full interface on the desktop's hosted X11 display. The bring-up tests boot QEMU, start a browser through the same bridge the compositor uses, and wait for a viewable top-level window:
python3 tests/browsers/run_vm.py # Chromium
python3 tests/browsers/run_vm.py --firefox # Firefox
python3 tests/browsers/run_vm.py --desktop # Firefox inside a desktop window
python3 tests/browsers/run_vm.py --package # pkg install chromium, then run itThe Chromium, Firefox and package profiles run the browser through the X11
bridge with no compositor, which is not the arrangement anyone uses. --desktop starts the
real compositor, has it open the browser (vinix-desktop --open=Firefox), and
holds the result to the same deadline: a window in about 25 seconds with the
page drawn as soon as it appears.
Two things to know about the images. A persistent /root volume shadows the
copy of a file the image ships there, so the launchers take their start page
from /usr/share/vinix instead. And inspect a hosted surface by mapping it,
the way the compositor does: reading the framebuffer file with read(2) did
not reflect what the compositor was displaying, so a checker built on it calls
a working browser blank. The general case — a sparse file filled only through
a shared mapping, read back by another process — is covered by
tests/qemu-core, and passes, so whatever the surface hits is narrower than
that; until it is pinned down, map it.
pkg install chromium takes roughly half an hour in QEMU: 204 packages and
698 MiB through the emulated network, unpacked on an emulated CPU.
The ARM64 QEMU platform has a render-only VirtIO-GPU DRM driver for VirGL. It
uses the MMIO transport, so it does not depend on the still-unimplemented ARM64
PCI ECAM path. The firmware ramfb remains the visible framebuffer while Mesa
submits rendering to /dev/dri/renderD128 and copies completed frames to
/dev/fb0.
Build the shared Asahi/VirGL Mesa runtime in the Debian ARM64 build VM, copy
build-aarch64-asahi/staging back to this checkout, then assemble and boot the
desktop through KekVM's Metal-enabled QEMU:
./build-asahi-aarch64.sh
./build-desktop-aarch64.sh
./run-desktop-aarch64.sh --no-build --virglInside Vinix, the hardware smoke test prints the selected renderer and rejects software rasterizers:
run-gl-triangle-agx --rebuild
run-virgl-smoke
run-firefox--virgl selects KekVM's .tools/qemu-virgl binary and requests a GL-enabled
display. Override its location with VINIX_VIRGL_QEMU or select a QEMU display
backend with QEMU_DISPLAY_BACKEND. The simpler
--virtio-gpu option exposes the unaccelerated MMIO device and is useful for
transport probing, but it does not create a render node. KekVM's compact QEMU
currently omits libslirp, so this launch mode is offline; Firefox can exercise
its bundled local smoke page, while browsing needs a VirGL QEMU build with a
network backend.
Vinix can boot Hyprland 0.54.3 on QEMU and Apple Silicon. Aquamarine uses a
Vinix backend that presents rendered GBM buffers through /dev/fb0 and feeds
Hyprland from /dev/pointer plus the raw console keyboard. On M1 it renders
with AGX; QEMU uses Mesa's kms_swrast through Vinix's render-only dumb-buffer
DRM node. This does not pretend the firmware framebuffer is a KMS display.
Build the musl runtime and patched Aquamarine library on an ARM64 Linux or macOS host. If using the Debian ARM64 build VM, copy its staging directory back to the checkout used to assemble the desktop image:
./build-hyprland-aarch64.sh
# copy build-aarch64-hyprland/staging to the macOS checkout when needed
./build-desktop-aarch64.sh
./run-hyprland-aarch64.sh --no-buildrun-hyprland-aarch64.sh selects Hyprland for that boot; the ordinary desktop
launcher always starts the native Vinix desktop. In Hyprland, Super+Return
opens Foot, Super+Q closes a window, and Super+M exits to the native
Vinix desktop. The runtime smoke check is available inside the guest as
/root/hyprland-smoke.sh. Press Super+D for a full-screen, dependency-free
dashboard intended for live demonstrations and screenshots.
Vinix has an experimental native AGX path for the base M1 (t8103/G13G). It
uses the Mesa 25.0.5 Asahi Gallium driver for desktop OpenGL and GLES through
EGL, with surfaceless, GBM and X11 platform support. The native desktop uses a
surfaceless GPU presenter, while Xorg/Firefox share GPU buffers through PRIME
and DRI3. Both still copy the completed image to the Limine framebuffer because
Vinix does not yet have a native DCP/KMS scanout driver. The private GPU
firmware structures are currently pinned to Apple firmware ABI 12.3.0; the
driver refuses other firmware ABIs before touching GPU hardware.
Build Mesa in the Debian ARM64 VM (install clang, lld, meson, ninja,
pkg-config, bison, flex, python3-mako, python3-yaml,
libclang-19-dev, libclc-19-dev, libllvmspirvlib-19-dev, and
spirv-tools there):
./build-asahi-aarch64.shCopy build-aarch64-asahi/staging back to the same path in the macOS checkout,
then build the full ARM64 userland and kernel. The userland build needs the
Homebrew LLVM tools (brew install llvm):
./build-userland-aarch64.sh
make -C kernel ARCH=aarch64 CC=clangThe userland builder is host-independent: it extracts Alpine's AArch64
minirootfs and prebuilt build-base packages rather than compiling musl,
BusyBox, or GCC. The old VM entry point remains as a compatibility wrapper:
VINIX_ASAHI_STAGING="$PWD/build-aarch64-asahi/staging" \
./build-userland-aarch64-vm.shThis produces build-support/init-aarch64/initramfs.tar; copy that file and
kernel/bin/vinix back to the macOS checkout before deploying.
To put the accelerated native desktop and Firefox in the M1 image, rebuild the desktop after copying the Asahi staging tree, then select both the GPU and that image at deployment:
./build-desktop-aarch64.sh --compact-initramfs
./deploy-m1-efi.sh --apple-gpu --desktop-initramfs /Volumes/EFIThe compact build publishes both the uncompressed tar used by QEMU and a
deterministic .tar.gz used on Apple hardware. Limine expands the latter
before handing the module to Vinix. This is required because the Asahi EFI
System Partition is only 500 MiB; free space reported for the macOS APFS data
volume is unrelated to ESP capacity.
The installed M1 deployment helper enables the GPU in its default desktop mode, alongside Wi-Fi, so the normal hardware test is simply:
sudo ~/code/kek.shUse sudo ~/code/kek.sh gpu to isolate the driver with the shell test image,
or sudo ~/code/kek.sh desktop-wifi to boot the same desktop with AGX disabled
if the experimental probe resets before reaching userspace.
GPU deployments now run an archive preflight before touching the ESP. It
checks the native AArch64 kernel, Mesa 25.0.5 Asahi loader and Gallium library,
the in-guest test source, and—in desktop mode—the accelerated compositor and
Firefox/X11 closure. Use sudo ~/code/kek.sh gpu-probe only when deliberately
testing the kernel/RTKit probe without a complete Mesa image.
Deploy to an already-mounted M1 EFI system partition with the explicit GPU opt-in, then boot through m1n1 so Vinix receives the patched device tree:
./deploy-m1-efi.sh --apple-gpu /Volumes/EFIAfter the desktop starts, open Terminal and run the hardware-only demo below.
It rebuilds against the libraries in the booted image, rejects software,
validates a rendered pixel, and reserves this pass marker for Mesa's real
Apple M1 renderer (VirGL and fake G17 cannot produce it):
VINIX M1 AGX RENDER TEST: PASS
To run it or collect the renderer output again:
ls -l /dev/dri/renderD128
run-gl-triangle-agx --rebuildThe demo prints the EGL and GL renderer strings, rejects software renderers,
validates a rendered pixel, and reports when the frame reaches /dev/fb0.
The base M1 Air (t8103/G13G, including its 7-core fuse configuration) is the
first hardware target. The M5 Max (t6050/G17C) work remains separate and is
still fail-closed until its firmware command ABI is complete. The
fake G17 backend can already validate encoded
HAL300 3D register streams and drive synthetic workqueue/fence completions on
the host or in a VM, without claiming to emulate G17 firmware.
Vinix can preserve a Studio Display scanout that the Apple firmware and m1n1/U-Boot chain established before the kernel starts. This is deliberately a single-output framebuffer handoff. Both USB-C ports are monitored, so an inherited output reconnects live. If Vinix booted on the M1 Air panel, a first post-boot connection performs one ANS-ordered warm reboot so firmware can establish the external scanout.
./build-desktop-aarch64.sh
make -C kernel ARCH=aarch64 CC=clang
./deploy-m1-efi.sh --apple-studio-display --desktop-initramfs /Volumes/EFIThe deploy flag keeps the firmware's native mode, selects the largest GOP surface when multiple outputs are present, prevents the internal-panel DCP experiment from resetting the inherited external scanout, and enables the post-boot recovery. Vinix does not yet switch the output in place because it lacks native ATC/external-DCP modesetting. The Studio Display's audio/camera/USB devices are not included. See docs/apple-studio-display.md for the boot procedure, expected log lines, and failure diagnosis.
In Linux, if KVM is available, run with
make run-kvm
In macOS, if hvf is available, run with
make run-hvf
To run without any acceleration, run with
make run
=== Vinix aarch64 booting ===
vinit → exceptions → term → vmm → timer → gic → sched
→ scheduler spawns kmain_thread
→ polling mode timer fires, scheduler switches context via
eret
→ kmain_thread: framebuffer, socket, pipe, futex, fs,
initramfs
→ *** aarch64: Kernel initialisation complete ***


