Skiff is a lightweight cross-compiled Linux OS focusing on creating a consistent and reproducible environment across any compute architecture.
Skiff loads a small ~30MB image containing the kernel and core system into RAM at boot-time. This ensures that the system will always boot up into a consistent state, ideal for embedded and mission-critical environments. Sudden failure of the storage drive does not break the system, as the core OS runs from memory.
As a modular configuration package manager for the industry-standard Buildroot embedded Linux tool, Skiff allows for a consistent developer experience and application execution environment across any compute platform. The compact nature of the system creates a minimal attack surface for security.
The "skiff/core" layer brings in the Docker container system, and introduces a user which is backed by a container. When logging into this user, the session is executed inside a container with a familiar operating system, such as Ubuntu. This decouples the core OS (kernel, init manager, container manager) from the userspace, allowing the userspace container to become portable between machines.
This repository includes configurations supporting a variety of embedded platforms, including Raspberry Pi and ODROID boards. Skiff can also run inside a Docker container, a qemu VM, as a typical x86_64 system, or a cloud VM. It's easy to add support for new boards and architectures, and the OS can be fully customized with Skiff's configuration package system.
You can now demo Skiff with a single command!
# Execute the latest Skiff release with Docker.
docker run -d --name=skiff \
--privileged \
--cap-add=NET_ADMIN \
--security-opt seccomp=unconfined \
--stop-signal=SIGRTMIN+3 \
--tmpfs /run \
--tmpfs /run/lock \
-t \
-v /sys/fs/cgroup:/sys/fs/cgroup:ro \
-v $(pwd)/skiff-persist:/mnt/persist \
paralin/skiffos:latest
# Run a shell in the container.
docker exec -it skiff sh
# Inside the container, switch to "Skiff core"
su - core
This will download and execute Skiff in a container.
Building a system with Skiff is easy! This example will build a basic OS for a Pi 3.
You can type make
at any time to see a status and help printout. Do this now,
and look at the list of configuration packages. Select which ones you want, and
set the comma-separated SKIFF_CONFIG
variable:
$ make # observe status output
$ SKIFF_CONFIG=pi/3 make configure # configure the system
$ make # check status again
$ make br/menuconfig # optionally explore config
$ make br/linux-menuconfig # optionally explore Linux config
$ make compile # build the system
After you run make configure
Skiff will remember what you selected in
SKIFF_CONFIG
. The compile command instructs Skiff to build the system.
You can also enable Docker or other packages in the target:
SKIFF_CONFIG=pi/3,apps/docker
Once the build is complete, it's time to flash the system to a SD card. You will
need to switch to sudo bash
for this on most systems.
$ sudo bash # switch to root
$ blkid # look for your SD card's device file
$ export PI_SD=/dev/sda # make sure this is right!
$ make cmd/pi/3/format # tell skiff to format the device
$ make cmd/pi/3/install # tell skiff to install the os
After you format a card, you do not need to do so again. You can call the install command as many times as you want to update the system. The persist partition is not touched in this step, so anything you save there, including Docker state and system configuration, will not be touched in the upgrade.
Skiff configurations are evaluated in the order they are specified. An example configuration might look like:
SKIFF_CONFIG=pi/3,apps/kodi make configure compile
Some operating systems are not compatible with the Skiff build system, due to the host not using glibc, or using some outdated or otherwise incompatible libraries for the fairly recent Skiff distribution.
If you encounter any errors related to host-* packages, you can try building Skiff inside Docker.
Workspaces allow you to configure and compile multiple systems in tandem.
Set SKIFF_WORKSPACE
to the name of the workspace you want to use. The
Buildroot setup will be constructed in workspaces/$SKIFF_WORKSPACE
. You can
also place configuration files in overrides/workspaces/$SKIFF_WORKSPACE/
to
override settings for that particular workspace locally.
The virt/ packages are designed for running Skiff in various virtualized environments.
Here is a minimal working example of running Skiff in Qemu:
$ SKIFF_CONFIG=virt/qemu make configure compile
$ make cmd/virt/qemu/run
Here is a minimal working example of running Skiff in Docker:
$ SKIFF_CONFIG=virt/docker make configure compile
$ make cmd/virt/docker/buildimage
$ make cmd/virt/docker/run
The build command compiles the image, and run executes it.
You can execute a shell inside the container with:
$ make cmd/virt/docker/exec
# alternatively
$ docker exec -it skiff sh
Skiff supports modular configuration packages. A configuration directory contains kernel configs, buildroot configs, system overlays, etc.
These packages are denoted as namespace/name
. For example, an ODROID XU4
configuration would be odroid/xu4
.
Configuration package directories should have a depth of 2, where the first directory is the category name and the second is the package name.
A configuration package is laid out into the following directories:
├── buildroot: buildroot configuration fragments
├── buildroot_ext: buildroot extensions (extra packages)
├── extensions: extra commands to add to the build system
│ └── Makefile
├── hooks: scripts hooking pre/post build steps
│ ├── post.sh
│ └── pre.sh
├── kernel: kernel configuration fragments
├── kernel_patches: kernel .patch files
├── root_overlay: root overlay files
├── metadata: metadata files
│ ├── commands
│ ├── dependencies
│ ├── description
│ └── unlisted
├── resources: files used by the configuration package
├── scripts: any scripts used by the extensions
├── uboot: u-boot configuration fragments
└── uboot_patches: u-boot .patch files
All files are optional.
You can set the following env variables to control this process:
SKIFF_CONFIG_PATH_ODROID_XU4
: Set the path for the ODROID_XU4 config package. You can set this to add new packages or override old ones.SKIFF_EXTRA_CONFIGS_PATH
: Colon separated list of paths to look for config packages.SKIFF_CONFIG
: Name of skiff config to use, or comma separated list to overlay, with the later options taking precedence
These packages will be available in the Skiff system.
It's often useful to be able to adjust the buildroot, kernel, or other configurations locally during development without actually creating a new configuration layer. This can be easily done with the overrides system.
The overrides
directory, as well as the
overrides/workspaces/$SKIFF_WORKSPACE
directory, are automatically used as
additional Skiff configuration packages. You can follow the Skiff configuration
package format as defined below to override any of the settings in Buildroot or
the Linux kernel, add extra Buildroot packages, add build hooks, etc.
SkiffOS is based on Buildroot, which can compile operating systems for virtually any machine. Therefore, SkiffOS also works on nearly any architecture or board.
Here are the boards/systems currently supported by Skiff:
Board | Config Package | Bootloader | Kernel | Notes |
---|---|---|---|---|
Docker Img | virt/docker | N/A | N/A | Run SkiffOS in Docker |
Qemu | virt/qemu | N/A | ✔ 5.4.13 | Run SkiffOS in QEmu |
Apple Macbook | apple/macbook | rEFInd | ✔ 5.4.13 | |
BananaPi M1 | bananapi/m1 | ✔ U-Boot 2019.01 Src | ✔ 5.4.13 | |
BananaPi M1+/Pro | bananapi/m1plus | ✔ U-Boot 2019.01 Src | ✔ 5.4.13 | |
BananaPi M2+ | bananapi/m2plus | ✔ U-Boot 2019.01 Src | ✔ 5.4.13 | ⚠ Untested |
BananaPi M3 | bananapi/m3 | ✔ U-Boot 2019.01 Src | ✔ 5.4.13 | |
Intel x86/64 | intel/x64 | Grub | ✔ 5.4.13 | |
Odroid C2 | odroid/c2 | ⚠ U-Boot 2015.01 Bin | ✔ 3.14.79 | |
Odroid HC1 | odroid/xu4 | ✔ U-Boot 2019.04 Src | ✔ 4.14.133 | |
Odroid U | odroid/u | ✔ U-Boot 2016.03 Src | ✔ mainline | ⚠ Discontinued |
Odroid XU3 | odroid/xu4 | ✔ U-Boot 2019.04 Src | ✔ 4.14.133 | ⚠ Discontinued |
Odroid XU4 | odroid/xu4 | ✔ U-Boot 2019.04 Src | ✔ 4.14.133 | |
OrangePi Lite | orangepi/lite | ✔ U-Boot 2018.05 Src | ✔ 4.17.15 | ⚠ Needs testing |
OrangePi Zero | orangepi/zero | ✔ U-Boot 2018.07 Src | ✔ 4.17.15 | ⚠ Needs testing |
PcDuino 3 | pcduino/3 | ✔ U-Boot 2019.07 Src | ✔ 4.19.97 | |
Pi 0 | pi/0 | N/A | ✔ 4.19.93 | |
Pi 1 | pi/1 | N/A | ✔ 4.19.93 | ⚠ Untested |
Pi 3 (and 1/2) | pi/3 | N/A | ✔ 4.19.93 | |
Pi 4 | pi/4 | N/A | ✔ 4.19.93 |
Adding support for a board involves creating a Skiff configuration package for the board, as described above.
If you have a board that is not yet supported by SkiffOS, please open an issue, and we will work with you to integrate and test the new platform.
Below are some common configuration tasks that may be necessary when configuring a new Skiff system.
Skiff uses NetworkManager to manage network connections.
Network configurations are loaded from /etc/NetworkManager/system-connections
and from skiff/connections
on the persist partition.
The configuration file format for these connections is documented here with examples.
You can use nmcli
on the device to manage NetworkManager
, and any connection
definitions written by nmcli device wifi connect
or similar will automatically
be written to the persist partition and persisted to future boots.
You can set the hostname by placing the desired hostname in the skiff/hostname
file on the persist partition. You could also set this in one of your config
packages by writing the desired hostname to /etc/hostname
.
The system on boot will generate the authorized_keys file for root.
It takes SSH public key files (*.pub
) from these locations:
/etc/skiff/authorized_keys
from inside the imageskiff/keys
from inside the persist partition
Users can work within a familiar, traditional, persistent OS environment if desired. This is called the "core" user within Skiff. If this feature is enabled:
- On first boot, the system will build the core container image.
- The correct base image for the architecture is selected.
- The default image contains Ubuntu bionic and systemd.
- SSH connections to the core user are dropped into the Docker container
This allows virtually any workflow to be migrated to Skiff. The config file structure is flexible, and allows for any number of containers, users, and images to be defined and built.
To enable, add the skiff/core
package to your SKIFF_CONFIG
comma-separated
list.
To customize the core environment, edit the file at skiff/core/config.yaml
on
the persist partition. The default config will be placed there on first boot.
The default config can be overridden with a file at
/opt/skiff/coreenv/defconfig.yaml
.
You can install Docker inside the core environment, and systemd is running, so you can enable it to correctly auto-start when you first connect.
ssh core@my-skiff-host
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
sudo systemctl enable --now docker
sudo docker ps
This is "docker inside docker!"
This section contains some example workloads you can use to get started. These examples are Docker based.
Alpine provides a lightweight environment with a package manager (apk) to install developer tools on-demand. This command will execute a persistent container named "work" which you can execute a shell inside to interact with. This workflow is similar to how Skiff Core drops SSH sessions into Docker containers as an optional feature.
# Replace arm32v6/alpine with alpine if on x86 or amd64 systems
docker run \
--name=work -d \
--pid=host --uts=host --net=host \
--privileged \
-v /:/root-fs -v /dev:/dev \
--privileged \
arm32v6/alpine:edge \
bin/sleep 99999
# Execute a shell in the container.
docker exec -it work sh
# Update the packages.
apk upgrade --update
# Add a package.
apk add vim
apk add alpine-sdk # adds compilers
Some useful tools to try:
- htop: interactive process manager similar to top
- atop: shows CPU statistics and process information as well as summaries of network interface load.
- bwm-ng: simple lightweight UI to show rx/tx and total bandwidth of all interfaces.
- bmon: detailed UI, shows all details of any network errors experienced and current bandwidth on all interfaces.
- nload: shows incoming and outgoing network load.
- nethogs: shows what processes are using network traffic.
System performance monitoring and benchmarking is easy with the glances tool.
The below command can be executed after sshing to the "root" user to start the performance monitoring UI on port 61208 on the device (for the ARM architecture):
docker run \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--pid=host --net=host \
--restart=always \
--name=glances \
--detach=true \
--privileged \
paralin/glances-arm:latest glances -w
System and container performance monitoring and benchmarking is easy with the cadvisor tool.
The below command can be executed after sshing to the "root" user to start the performance monitoring UI on port 8080 on the device:
docker run \
--volume=/var/run:/var/run:rw \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--publish=8080:8080 \
--detach=true \
--name=cadvisor \
braingamer/cadvisor-arm:latest
If you encounter issues or questions at any point when using Skiff, please file a GitHub issue.