uimage builds initramfs images composed of arbitrary Go commands and files.
uimage optimizes for space by utilizing gobusybox to compile many arbitrary Go commands into one binary.
uimage can be easily used with u-root, which contains many Go coreutils-like commands as well as bootloaders. However, uimage supports compilation of any Go command, and the use of u-root is not required.
Make sure your Go version is >= 1.21. If your Go version is lower,
$ go install golang.org/dl/go1.21.5@latest
$ go1.21.5 download
$ go1.21.5 version
# Now use go1.21.5 in place of go
Download and install mkuimage either via git:
git clone https://github.com/u-root/mkuimage
cd mkuimage/cmd/mkuimage
go install
Or install directly with go:
go install github.com/u-root/mkuimage/cmd/mkuimage@latest
Note
The mkuimage
command will end up in $GOPATH/bin/mkuimage
, so you may
need to add $GOPATH/bin
to your $PATH
.
Here are some examples of using the mkuimage
command to build an initramfs.
git clone https://github.com/u-root/u-root
Build gobusybox binaries of these two commands and add to initramfs:
$ cd ./u-root
$ mkuimage ./cmds/core/{init,gosh}
$ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
...
-rwxr-x--- 0 root root 4542464 Jan 1 1970 bbin/bb
lrwxrwxrwx 0 root root 2 Jan 1 1970 bbin/gosh -> bb
lrwxrwxrwx 0 root root 2 Jan 1 1970 bbin/init -> bb
...
Add symlinks for shell and init:
$ mkuimage -initcmd=init -defaultsh=gosh ./cmds/core/{init,gosh}
$ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
...
lrwxrwxrwx 0 root root 12 Jan 1 1970 bin/defaultsh -> ../bbin/gosh
lrwxrwxrwx 0 root root 12 Jan 1 1970 bin/sh -> ../bbin/gosh
...
lrwxrwxrwx 0 root root 9 Jan 1 1970 init -> bbin/init
...
Build everything from core without ls and losetup:
$ mkuimage ./cmds/core/* -./cmds/core/{ls,losetup}
Important
mkuimage
works when go build
and go list
work as well.
There are 2 ways to build multi-module command images using standard Go tooling.
$ mkdir workspace
$ cd workspace
$ git clone https://github.com/u-root/u-root
$ git clone https://github.com/u-root/cpu
$ go work init ./u-root
$ go work use ./cpu
$ mkuimage ./u-root/cmds/core/{init,gosh} ./cpu/cmds/cpud
$ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
...
-rwxr-x--- 0 root root 6365184 Jan 1 1970 bbin/bb
lrwxrwxrwx 0 root root 2 Jan 1 1970 bbin/cpud -> bb
lrwxrwxrwx 0 root root 2 Jan 1 1970 bbin/gosh -> bb
lrwxrwxrwx 0 root root 2 Jan 1 1970 bbin/init -> bb
...
# Works for offline vendored builds as well.
$ go work vendor # Go 1.22 feature.
$ mkuimage ./u-root/cmds/core/{init,gosh} ./cpu/cmds/cpud
GBB_PATH
is a place that mkuimage will look for commands. Each colon-separated
GBB_PATH
element is concatenated with patterns from the command-line and
checked for existence. For example:
GBB_PATH=$(pwd)/u-root:$(pwd)/cpu mkuimage \
cmds/core/{init,gosh} \
cmds/cpud
# Matches
# ./u-root/cmds/core/{init,gosh}
# ./cpu/cmds/cpud
To ease usability, the goanywhere
tool can create one Go workspaces the fly.
This works only with local file system paths:
$ go install github.com/u-root/gobusybox/src/cmd/goanywhere@latest
$ goanywhere ./u-root/cmds/core/{init,gosh} ./cpu/cmds/cpud -- go build -o $(pwd)
$ goanywhere ./u-root/cmds/core/{init,gosh} ./cpu/cmds/cpud -- mkuimage
goanywhere
creates a workspace in a temporary directory with the given
modules, and then execs u-root
in the workspace passing along the command
names.
goanywhere
supports GBB_PATH
, exclusions, globs, and curly brace expansions
as well.
Caution
While workspaces are good for local compilation, they are not meant to be checked in to version control systems. See below for the recommended way.
You may also create a go.mod with the commands you intend to compile.
To depend on commands outside of ones own repository, the easiest way to depend on Go commands is the following:
mkdir mydistro
cd mydistro
go mod init mydistro
Create a file with some unused build tag like this to create dependencies on commands:
//go:build tools
package something
import (
_ "github.com/u-root/u-root/cmds/core/ip"
_ "github.com/u-root/u-root/cmds/core/init"
_ "github.com/hugelgupf/p9/cmd/p9ufs"
)
You can generate this file for your repo with the gencmddeps
tool from
gobusybox:
go install github.com/u-root/gobusybox/src/cmd/gencmddeps@latest
gencmddeps -o deps.go -t tools -p something \
github.com/u-root/u-root/cmds/core/{ip,init} \
github.com/hugelgupf/p9/cmd/p9ufs
The unused build tag keeps it from being compiled, but its existence forces go mod tidy
to add these dependencies to go.mod
:
go mod tidy
mkuimage \
github.com/u-root/u-root/cmds/core/ip \
github.com/u-root/u-root/cmds/core/init \
github.com/hugelgupf/p9/cmd/p9ufs
# Works for offline vendored builds as well.
go mod vendor
mkuimage \
github.com/u-root/u-root/cmds/core/ip \
github.com/u-root/u-root/cmds/core/init \
github.com/hugelgupf/p9/cmd/p9ufs
You may also include additional files in the initramfs using the -files
flag.
If you add binaries with -files
are listed, their ldd dependencies will be
included as well.
$ mkuimage -files /bin/bash
$ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
...
-rwxr-xr-x 0 root root 1277936 Jan 1 1970 bin/bash
...
drwxr-xr-x 0 root root 0 Jan 1 1970 lib/x86_64-linux-gnu
-rwxr-xr-x 0 root root 210792 Jan 1 1970 lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-rwxr-xr-x 0 root root 1926256 Jan 1 1970 lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 0 root root 15 Jan 1 1970 lib/x86_64-linux-gnu/libtinfo.so.6 -> libtinfo.so.6.4
-rw-r--r-- 0 root root 216368 Jan 1 1970 lib/x86_64-linux-gnu/libtinfo.so.6.4
drwxr-xr-x 0 root root 0 Jan 1 1970 lib64
lrwxrwxrwx 0 root root 42 Jan 1 1970 lib64/ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
...
You can determine placement with colons:
$ mkuimage -files "/bin/bash:sbin/sh"
$ cpio -ivt < /tmp/initramfs.linux_amd64.cpio
...
-rwxr-xr-x 0 root root 1277936 Jan 1 1970 sbin/sh
...
For example on Debian, if you want to add two kernel modules for testing, executing your currently booted kernel:
$ mkuimage -files "$HOME/hello.ko:etc/hello.ko" -files "$HOME/hello2.ko:etc/hello2.ko" ./u-root/cmds/core/*
$ qemu-system-x86_64 -kernel /boot/vmlinuz-$(uname -r) -initrd /tmp/initramfs.linux_amd64.cpio
Just like standard Go tooling, cross compilation is easy and supported.
To cross compile for an ARM, on Linux:
GOARCH=arm mkuimage ./u-root/cmds/core/*
If you are on OS X, and wish to build for Linux on AMD64:
GOOS=linux GOARCH=amd64 ./u-root/cmds/core/*
A good way to test the initramfs generated by u-root is with qemu:
qemu-system-x86_64 -nographic -kernel path/to/kernel -initrd /tmp/initramfs.linux_amd64.cpio
Note that you do not have to build a special kernel on your own, it is
sufficient to use an existing one. Usually you can find one in /boot
.
If you don't have a kernel handy, you can also get the one we use for VM testing:
go install github.com/hugelgupf/vmtest/tools/runvmtest@latest
runvmtest -- bash -c "cp \$VMTEST_KERNEL ./kernel"
It may not have all features you require, however.
To automate testing, you may use the same vmtest framework that we use as well. It has native uimage support.
mkuimage can create an initramfs in two different modes, specified by -build
:
-
bb
mode: One busybox-like binary comprising all the Go tools you ask to include. See the gobusybox README for how it works. In this mode, mkuimage copies and rewrites the source of the tools you asked to include to be able to compile everything into one busybox-like binary. -
binary
mode: each specified binary is compiled separately and all binaries are added to the initramfs.