Skip to content

Feat: Local dev for programs and runtimes #340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ venv/*
.env.local

.gitsigners

# Devtools
devtools/bin
devtools/programs
devtools/built_programs
69 changes: 69 additions & 0 deletions devtools/0_install_env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/bin/sh
set -euf

arch=$(uname -m)
image_bucket_url="https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/$arch"
bin_dir=bin
firecracker=firecracker
kernel=vmlinux.bin
mkdir -p ${bin_dir}

# Check architecture
if [ ${arch} = "x86_64" ]; then
kernel_url="${image_bucket_url}/kernels/vmlinux.bin"
elif [ ${arch} = "aarch64" ]; then
kernel_url="${image_bucket_url}/kernels/vmlinux.bin"
else
echo "Cannot run firecracker on $arch architecture."
exit 1
fi

# Dependencies
apt update -y
apt upgrade -y
apt install squashfs-tools debootstrap -y

# Get the Firecracker binary
if [ ! -f ${bin_dir}/${firecracker} ]; then
echo "Installing Firecracker..."
release_url="https://github.com/firecracker-microvm/firecracker/releases"
latest=$(basename $(curl -fsSLI -o /dev/null -w %{url_effective} ${release_url}/latest))
arch=$(uname -m)
if curl -L ${release_url}/download/${latest}/firecracker-${latest}-${arch}.tgz | tar -xz; then
bin_folder="release-${latest}-$(uname -m)"
mv "${bin_folder}/firecracker-${latest}-$(uname -m)" ${bin_dir}/${firecracker}
rm -r "${bin_folder}"
echo "Firecracker installation complete."
else
echo "Failed to install Firecracker. Please check your network connection and try again."
exit 1
fi
else
echo "Firecracker is already installed."
fi

# Get the vmlinux.bin
if [ ! -f ${bin_dir}/${kernel} ]; then
echo "Installing kernel..."
if curl -L -o ${bin_dir}/${kernel} $kernel_url; then
echo "${kernel} installation complete."
else
echo "Failed to install ${kernel}. Please check your network connection and try again."
exit 1
fi
else
echo "${kernel} is already installed."
fi

# Docker installation
if docker --version >/dev/null 2>&1; then
echo "Docker is already installed."
else
apt install docker.io -y
fi
# Docker without sudo
groupadd -f docker
usermod -aG docker $(whoami)
docker context use default

echo "Environment setup complete."
53 changes: 53 additions & 0 deletions devtools/1a_build_custom_runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/sh
set -euf

runtime=$1

# Utils
setup=utils/setup.sh
inittab=utils/inittab
init=utils/init0.sh
init_py=utils/init1.py
loading_html=utils/loading.html

# Temp Files
dest=/mnt/rootfs

# Target
rootfs_dir=runtimes
target=$rootfs_dir/$runtime.squashfs
mkdir -p $rootfs_dir

# Cleanup previous run
rm -rf $target
rm -rf $dest
mkdir $dest

echo "Downloading Debian Bullseye minimal"
debootstrap --variant=minbase bullseye $dest http://deb.debian.org/debian/

echo "Run setup script"
chmod +x $setup
cp $setup $dest/setup.sh
chroot $dest /bin/sh -c "./setup.sh && rm -f setup.sh"

# Reduce size
rm -fr $dest/root/.cache
rm -fr $dest/var/cache
mkdir -p $dest/var/cache/apt/archives/partial
rm -fr $dest/usr/share/doc
rm -fr $dest/usr/share/man
rm -fr $dest/var/lib/apt/lists

echo "Install init scripts"
cp $inittab $dest/etc/inittab
chmod +x $init
chmod +x $init_py
cp $init $dest/sbin/init
cp $init_py $dest/root/init1.py
cp $loading_html $dest/root/loading.html

echo "Creating squashfs image"
mksquashfs $dest $target -noappend -quiet
rm -rf $dest
echo "Done"
33 changes: 33 additions & 0 deletions devtools/1b_download_runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/sh
set -euf

default_runtime=63f07193e6ee9d207b7d1fcf8286f9aee34e6f12f101d2ec77c1229f92964696
get_message_url=https://api1.aleph.im/api/v0/messages/
ipfs_gateway=https://ipfs.aleph.im/ipfs/
rootfs_dir=runtimes
mkdir -p $rootfs_dir

# Ask for runtime item hash
read -p "Enter a runtime item hash (leave blank for default): " runtime
if [ -z "${runtime}" ]; then
runtime=$default_runtime
echo "Using default runtime: ${runtime}"
else
echo "Using provided runtime: ${runtime}"
fi

# Get item hash message
message=$(curl -s -X GET "$get_message_url$runtime")
ipfs_cid=$(echo "$message" | jq -r '.message.content.item_hash')
#echo "IPFS CID: $ipfs_cid"

# Get runtime file
runtime_file="${runtime}.squashfs"
download_url="$ipfs_gateway$ipfs_cid"
if [ ! -f "${rootfs_dir}/${runtime_file}" ]; then
echo "Downloading ${runtime_file}..."
curl -L $download_url -o ${rootfs_dir}/${runtime_file}
echo "Download complete."
else
echo "${runtime_file} is already available."
fi
16 changes: 16 additions & 0 deletions devtools/1b_edit_runtime.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh
set -euf

apt install squashfs-tools -y

rootfs_dir=runtimes
runtime=63f07193e6ee9d207b7d1fcf8286f9aee34e6f12f101d2ec77c1229f92964696.squashfs
mount_dir=vm
mkdir -p ${mount_dir}/ro
mkdir -p ${mount_dir}/new

mount -o loop,ro -t squashfs ${rootfs_dir}/${runtime} ${mount_dir}/ro
cp -ar ${mount_dir}/ro ${mount_dir}/new
umount ${mount_dir}/ro

#mksquashfs ${mount_dir}/new new.squashfs -noappend -quiet
9 changes: 9 additions & 0 deletions devtools/2_pack_program.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
set -euf

in=programs/$1
out_dir=built_programs/$1
mkdir -p $out_dir
out=$out_dir/program.squashfs
rm -f $out
mksquashfs $in $out -noappend -quiet
44 changes: 44 additions & 0 deletions devtools/3_pack_dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/sh
set -euf

if docker --version >/dev/null 2>&1; then
echo "Docker is already installed."
else
apt install docker.io -y
fi

if [ $# -ne 1 ]; then
echo "Error: Please provide the application directory as an argument."
echo "Usage: $0 <app_dir>"
exit 1
fi

app_dir=programs/$1
deps_file="${app_dir}/requirements.txt"
output_file="built_${app_dir}/packages.squashfs"

echo "Packing dependencies for ${app_dir} into ${output_file} using ${deps_file}"

if [ ! -d "$app_dir" ]; then
echo "Error: The application directory '$app_dir' does not exist."
exit 1
fi
if [ ! -f "$deps_file" ]; then
echo "Error: The requirements file '$deps_file' does not exist."
exit 1
fi

rm -f "$output_file"
touch "$output_file"

docker run --rm -t --platform linux/amd64 \
-v "$(pwd)/${deps_file}:/opt/requirements.txt" \
-v "$(pwd)/${output_file}:/opt/packages.squashfs" \
debian:bookworm /bin/bash \
-c "apt-get update -y;
apt-get install python3-pip squashfs-tools -y;
python3 -V;
pip install -t /opt/packages -r /opt/requirements.txt;
mksquashfs /opt/packages /opt/packages.squashfs -noappend -quiet"

echo "Dependencies packed into ${output_file} successfully."
8 changes: 8 additions & 0 deletions devtools/5_run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
set -euf

rm -f /tmp/v.sock
rm -f /tmp/firecracker.socket
./bin/firecracker --api-sock /tmp/firecracker.socket --config-file ./utils/vm_config_base.json
rm -f /tmp/v.sock
rm -f /tmp/firecracker.socket
64 changes: 64 additions & 0 deletions devtools/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from pathlib import Path

from pydantic import BaseModel, PositiveInt

ROOTFS_PATH = "./runtimes/test.squashfs"
KERNEL_PATH = "./bin/vmlinux.bin"
VSOCK_PATH = "/tmp/v.sock"


class BootSource(BaseModel):
kernel_image_path: Path = Path(KERNEL_PATH)
boot_args: str = "console=ttyS0 reboot=k panic=1 pci=off ro noapic nomodules random.trust_cpu=on"

@staticmethod
def args(enable_console: bool = True, writable: bool = False):
default = "reboot=k panic=1 pci=off noapic nomodules random.trust_cpu=on"
if writable:
default = default + " rw"
else:
default = default + " ro"
if enable_console:
return "console=ttyS0 " + default
else:
return default


class Drive(BaseModel):
drive_id: str = "rootfs"
path_on_host: Path = Path(ROOTFS_PATH)
is_root_device: bool = True
is_read_only: bool = True


class MachineConfig(BaseModel):
vcpu_count: PositiveInt = 1
mem_size_mib: PositiveInt = 512
smt: bool = False


class Vsock(BaseModel):
vsock_id: str = "1"
guest_cid: PositiveInt = 3
uds_path: str = VSOCK_PATH


class NetworkInterface(BaseModel):
iface_id: str = "eth0"
guest_mac: str = "AA:FC:00:00:00:01"
host_dev_name: str


class FirecrackerConfig(BaseModel):
boot_source: BootSource
drives: list[Drive]
machine_config: MachineConfig
vsock: Vsock | None
network_interfaces: list[NetworkInterface] | None

class Config:
allow_population_by_field_name = True

@staticmethod
def alias_generator(x: str):
return x.replace("_", "-")
24 changes: 24 additions & 0 deletions devtools/draft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import asyncio
import logging
from pathlib import Path

from run import MicroVM

logging.basicConfig(
level=logging.DEBUG,
format="%(relativeCreated)4f |V %(levelname)s | %(message)s",
)
logger = logging.getLogger(__name__)

vm = MicroVM(
vm_id=0,
firecracker_bin_path=Path("./bin/firecracker"),
jailer_base_directory=Path("/tmp/jail"),
use_jailer=False,
jailer_bin_path=Path("./bin/release-v1.10.1-x86_64/jailer-v1.10.1-x86_64"),
init_timeout=5.0,
enable_log=True,
)

config_path = Path("utils/vm_config_base.json")
asyncio.run(vm.start(config_path))
2 changes: 2 additions & 0 deletions devtools/reqs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
msgpack==1.0.7
systemd-python==235
Loading
Loading