Fork me on GitHub
April 12, 2016

by Tim Caswell

Luvi Apps in Alpine Linux

Recently I’ve discovered a new Linux distribution called Alpine Linux. It’s basically just Linux Kernel + musl libc + busybox. Or in other words, it’s super minimal and tiny.

Compile Luvi From Source

The pre-built luvi binaries won’t run here because they use glibc (the default libc for most linux systems.). But the good news is compiling a custom luvi is as simple as compiling from source.

Alpine has a simple package manager called apk. We’ll use it to install a few prerequisites needed to clone and build from source.

apk update
# Install git and perl (needed for git submodules).
apk add git perl
# Install build tools.
apk add build-base cmake linux-headers

Now we will build luvi from source in the normal way.

# Clone luvi source recursively to get all submodules.
git clone --recursive
# Build luvi (use tiny instead of reqular-asm if you don't need openssl)
cd luvi
make regular-asm test

If all goes well this will configure, build, and then test successfully.

Let’s install luvi to a new %HOME/bin folder…

mkdir ~/bin
install build/luvi ~/bin

…and add it to our path.

echo 'export PATH=$HOME/bin:$PATH' >> ~/.profile
. ~/.profile

Building the Luvi Invention Toolkit

Normally luvi based apps like lit, luvit, and wscat are built by downloading the appropriate luvi binary and concatenating it with a zip file containing the application itself. This works because executables ignore extra data on the end and zip files ignore extra data at the front. By combining them we get a file that is both a zip file and an executable.

Lit does a little more by reading package.lua and synthesizing the dependencies directly into the generated zip file. Also it attempts to compile all lua files into bytecode to save space and skip files not meant to be included. But in the end, it’s still generating a custom zip file and then appending it to the desired luvi binary.

Luvi can work on either a folder, a zip file, or detect a zip file appended to itself. Thus we can use lit to build itself once we have the luvi binary and the lit zip.


We can now run lit from the zip file by doing luvi -- args....
Everything before the -- in luvi is the source trees/zips, everything after is arguments to pass to the app itself.

Using the make source_app target_binary custom_luvi command we can built lit using our custom luvi.

luvi -- make ~/bin/lit ~/bin/luvi

Sharing Luvi Across Apps

But keeping with the spirit of Alpine and BusyBox, BusyBox is a single binary where commands like ls, cp, cat, etc are just symlinks pointing to the same file, we want to share the same luvi binary across all our luvi-based apps.

If you look at the built lit file in ~/bin, you’ll see it’s just slightly bigger than the luvi binary itself.

> du -h ~/bin/*
4.7M    /root/bin/lit
4.5M    /root/bin/luvi

It turns out this is quite possible. I added a feature to luvi a while back where if there are no arguments before the -- separator, it will consume one source path after it. This means you can replace the 4.5 megabyte header with a single line #!/root/bin/luvi --!

Let’s save that line to a file so that it can take the place of our custom luvi path.

echo '#!/root/bin/luvi --' > prefix # Make sure to use the path to *your* luvi.

Now let’s rebuild lit using this custom luvi. Also now that we have lit bootstrapped, we can build the normal way from the repository.

lit make lit://luvit/lit ~/bin/lit prefix

Now we have a much smaller lit.

244.0K  /root/bin/lit
4.5M    /root/bin/luvi

We can install any other luvi-based apps in the same way now.

lit make lit://luvit/luvit ~/bin/luvit prefix
lit make lit://creationix/wscat ~/bin/wscat prefix
lit make lit://creationix/simple-http-server ~/bin/simple-http-server prefix
lit make lit://virgo-agent-toolkit/fife ~/bin/fife prefix