command -v sh >/dev/null 2>&1 && ok "POSIX-Shell"
for cmd in cp mv cat rm ls; do command -v "$cmd" >/dev/null 2>&1 && ok "$cmd present"
done
command -v sh >/dev/null 2>&1 && ok "POSIX-Shell"
for cmd in cp mv cat rm ls; do command -v "$cmd" >/dev/null 2>&1 && ok "$cmd present"
done
command -v sh >/dev/null 2>&1 && ok "POSIX-Shell"
for cmd in cp mv cat rm ls; do command -v "$cmd" >/dev/null 2>&1 && ok "$cmd present"
done
command -v sudo >/dev/null 2>&1 && ok "sudo present"
if ps -e 2>/dev/null | head -n1 | grep -q 'PID'; then ok "ps works correctly"
fi
command -v sudo >/dev/null 2>&1 && ok "sudo present"
if ps -e 2>/dev/null | head -n1 | grep -q 'PID'; then ok "ps works correctly"
fi
command -v sudo >/dev/null 2>&1 && ok "sudo present"
if ps -e 2>/dev/null | head -n1 | grep -q 'PID'; then ok "ps works correctly"
fi
if pgrep -x sshd >/dev/null 2>&1 || pgrep -x dropbear >/dev/null 2>&1; then ok "sshd/dropbear running"
fi
command -v arp-scan >/dev/null 2>&1 && ok "arp-scan present"
if pgrep -x sshd >/dev/null 2>&1 || pgrep -x dropbear >/dev/null 2>&1; then ok "sshd/dropbear running"
fi
command -v arp-scan >/dev/null 2>&1 && ok "arp-scan present"
if pgrep -x sshd >/dev/null 2>&1 || pgrep -x dropbear >/dev/null 2>&1; then ok "sshd/dropbear running"
fi
command -v arp-scan >/dev/null 2>&1 && ok "arp-scan present"
Buildroot
├── builds cross-compiler (host-gcc, ~20 min)
├── compiles kernel → bzImage
├── compiles packages (openssh, curl, arp-scan ...)
└── assembles rootfs → rootfs.ext2
Buildroot
├── builds cross-compiler (host-gcc, ~20 min)
├── compiles kernel → bzImage
├── compiles packages (openssh, curl, arp-scan ...)
└── assembles rootfs → rootfs.ext2
Buildroot
├── builds cross-compiler (host-gcc, ~20 min)
├── compiles kernel → bzImage
├── compiles packages (openssh, curl, arp-scan ...)
└── assembles rootfs → rootfs.ext2
sudo apt install -y docker.io
systemctl enable --now docker sudo docker run --privileged --dns 8.8.8.8 \ -it --name minlinux debian:bookworm bash
sudo apt install -y docker.io
systemctl enable --now docker sudo docker run --privileged --dns 8.8.8.8 \ -it --name minlinux debian:bookworm bash
sudo apt install -y docker.io
systemctl enable --now docker sudo docker run --privileged --dns 8.8.8.8 \ -it --name minlinux debian:bookworm bash
apt-get update
apt-get install -y \ wget make gcc g++ unzip bc \ libncurses-dev rsync cpio xz-utils \ bzip2 file perl patch python3 git qemu-system-x86
apt-get update
apt-get install -y \ wget make gcc g++ unzip bc \ libncurses-dev rsync cpio xz-utils \ bzip2 file perl patch python3 git qemu-system-x86
apt-get update
apt-get install -y \ wget make gcc g++ unzip bc \ libncurses-dev rsync cpio xz-utils \ bzip2 file perl patch python3 git qemu-system-x86
BUILDROOT_VERSION="2026.02"
wget "https://buildroot.org/downloads/buildroot-${BUILDROOT_VERSION}.tar.xz"
tar -xf buildroot-${BUILDROOT_VERSION}.tar.xz -C /opt/buildroot --strip-components=1
BUILDROOT_VERSION="2026.02"
wget "https://buildroot.org/downloads/buildroot-${BUILDROOT_VERSION}.tar.xz"
tar -xf buildroot-${BUILDROOT_VERSION}.tar.xz -C /opt/buildroot --strip-components=1
BUILDROOT_VERSION="2026.02"
wget "https://buildroot.org/downloads/buildroot-${BUILDROOT_VERSION}.tar.xz"
tar -xf buildroot-${BUILDROOT_VERSION}.tar.xz -C /opt/buildroot --strip-components=1
cd /opt/buildroot
make qemu_x86_64_defconfig
cd /opt/buildroot
make qemu_x86_64_defconfig
cd /opt/buildroot
make qemu_x86_64_defconfig
cat >> .config << 'EOF' # --- System ---
BR2_TARGET_GENERIC_HOSTNAME="minlinux"
BR2_TARGET_GENERIC_ROOT_PASSWD="aeb"
BR2_SYSTEM_DHCP="eth0" # --- Minimize size ---
BR2_STRIP_strip=y # --- Filesystem ---
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_SIZE="64M"
BR2_TARGET_ROOTFS_CPIO=y
BR2_TARGET_ROOTFS_CPIO_GZIP=y # --- SSH ---
BR2_PACKAGE_DROPBEAR=n
BR2_PACKAGE_OPENSSH=y
BR2_PACKAGE_OPENSSH_SERVER=y
BR2_PACKAGE_OPENSSH_CLIENT=y
BR2_PACKAGE_OPENSSH_KEY_UTILS=y # --- Network ---
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_ARP_SCAN=y # --- curl + TLS ---
BR2_PACKAGE_LIBCURL=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_CA_CERTIFICATES=y # --- sudo ---
BR2_PACKAGE_SUDO=y # --- pgrep ---
BR2_PACKAGE_PROCPS_NG=y # --- netcat-openbsd ---
BR2_PACKAGE_NETCAT_OPENBSD=y # --- BusyBox: sh, ls, cp, mv, cat, rm, ps, tar, awk, ping ---
BR2_PACKAGE_BUSYBOX=y EOF make olddefconfig
cat >> .config << 'EOF' # --- System ---
BR2_TARGET_GENERIC_HOSTNAME="minlinux"
BR2_TARGET_GENERIC_ROOT_PASSWD="aeb"
BR2_SYSTEM_DHCP="eth0" # --- Minimize size ---
BR2_STRIP_strip=y # --- Filesystem ---
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_SIZE="64M"
BR2_TARGET_ROOTFS_CPIO=y
BR2_TARGET_ROOTFS_CPIO_GZIP=y # --- SSH ---
BR2_PACKAGE_DROPBEAR=n
BR2_PACKAGE_OPENSSH=y
BR2_PACKAGE_OPENSSH_SERVER=y
BR2_PACKAGE_OPENSSH_CLIENT=y
BR2_PACKAGE_OPENSSH_KEY_UTILS=y # --- Network ---
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_ARP_SCAN=y # --- curl + TLS ---
BR2_PACKAGE_LIBCURL=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_CA_CERTIFICATES=y # --- sudo ---
BR2_PACKAGE_SUDO=y # --- pgrep ---
BR2_PACKAGE_PROCPS_NG=y # --- netcat-openbsd ---
BR2_PACKAGE_NETCAT_OPENBSD=y # --- BusyBox: sh, ls, cp, mv, cat, rm, ps, tar, awk, ping ---
BR2_PACKAGE_BUSYBOX=y EOF make olddefconfig
cat >> .config << 'EOF' # --- System ---
BR2_TARGET_GENERIC_HOSTNAME="minlinux"
BR2_TARGET_GENERIC_ROOT_PASSWD="aeb"
BR2_SYSTEM_DHCP="eth0" # --- Minimize size ---
BR2_STRIP_strip=y # --- Filesystem ---
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_SIZE="64M"
BR2_TARGET_ROOTFS_CPIO=y
BR2_TARGET_ROOTFS_CPIO_GZIP=y # --- SSH ---
BR2_PACKAGE_DROPBEAR=n
BR2_PACKAGE_OPENSSH=y
BR2_PACKAGE_OPENSSH_SERVER=y
BR2_PACKAGE_OPENSSH_CLIENT=y
BR2_PACKAGE_OPENSSH_KEY_UTILS=y # --- Network ---
BR2_PACKAGE_IPROUTE2=y
BR2_PACKAGE_IPUTILS=y
BR2_PACKAGE_ARP_SCAN=y # --- curl + TLS ---
BR2_PACKAGE_LIBCURL=y
BR2_PACKAGE_LIBCURL_CURL=y
BR2_PACKAGE_CA_CERTIFICATES=y # --- sudo ---
BR2_PACKAGE_SUDO=y # --- pgrep ---
BR2_PACKAGE_PROCPS_NG=y # --- netcat-openbsd ---
BR2_PACKAGE_NETCAT_OPENBSD=y # --- BusyBox: sh, ls, cp, mv, cat, rm, ps, tar, awk, ping ---
BR2_PACKAGE_BUSYBOX=y EOF make olddefconfig
export FORCE_UNSAFE_CONFIGURE=1
make -j$(nproc) 2>&1 | tee /tmp/build.log
export FORCE_UNSAFE_CONFIGURE=1
make -j$(nproc) 2>&1 | tee /tmp/build.log
export FORCE_UNSAFE_CONFIGURE=1
make -j$(nproc) 2>&1 | tee /tmp/build.log
bzImage ~6.4M
rootfs.ext2 ~60M
rootfs.cpio.gz ~11M
bzImage ~6.4M
rootfs.ext2 ~60M
rootfs.cpio.gz ~11M
bzImage ~6.4M
rootfs.ext2 ~60M
rootfs.cpio.gz ~11M
if ! pgrep -x sshd >/dev/null; then warn "sshd inactive"
fi
if ! pgrep -x sshd >/dev/null; then warn "sshd inactive"
fi
if ! pgrep -x sshd >/dev/null; then warn "sshd inactive"
fi
curl -sL https://www.google.com/ >/dev/null 2>&1 # FAIL
curl -sL https://www.google.com/ >/dev/null 2>&1 # FAIL
curl -sL https://www.google.com/ >/dev/null 2>&1 # FAIL
# Raw TCP: uses -q (quit after EOF delay)
nc -l -p 12345 -q 1 >/dev/null 2>&1 & # Port scan: uses -z (zero-I/O mode)
nc -z -w1 "$CLIENT_IP" "$PORT"
# Raw TCP: uses -q (quit after EOF delay)
nc -l -p 12345 -q 1 >/dev/null 2>&1 & # Port scan: uses -z (zero-I/O mode)
nc -z -w1 "$CLIENT_IP" "$PORT"
# Raw TCP: uses -q (quit after EOF delay)
nc -l -p 12345 -q 1 >/dev/null 2>&1 & # Port scan: uses -z (zero-I/O mode)
nc -z -w1 "$CLIENT_IP" "$PORT"
[OK] POSIX-Shell
[OK] cp, mv, cat, rm, ls
[OK] ps
[OK] lo interface
[OK] ssh client
[NOK] sshd couldn't be started ← pgrep missing
[OK] SSH key setup
[OK] Ping / DNS
[OK] arp-scan
[NOK] nc missing ← never built
[OK] curl / HTTP
[NOK] HTTPS failed ← no CA certs
[NOK] HTTP banner not found ← nc cascade
[OK] POSIX-Shell
[OK] cp, mv, cat, rm, ls
[OK] ps
[OK] lo interface
[OK] ssh client
[NOK] sshd couldn't be started ← pgrep missing
[OK] SSH key setup
[OK] Ping / DNS
[OK] arp-scan
[NOK] nc missing ← never built
[OK] curl / HTTP
[NOK] HTTPS failed ← no CA certs
[NOK] HTTP banner not found ← nc cascade
[OK] POSIX-Shell
[OK] cp, mv, cat, rm, ls
[OK] ps
[OK] lo interface
[OK] ssh client
[NOK] sshd couldn't be started ← pgrep missing
[OK] SSH key setup
[OK] Ping / DNS
[OK] arp-scan
[NOK] nc missing ← never built
[OK] curl / HTTP
[NOK] HTTPS failed ← no CA certs
[NOK] HTTP banner not found ← nc cascade
cd /opt/buildroot
export FORCE_UNSAFE_CONFIGURE=1 make procps-ng-rebuild
make netcat-openbsd-rebuild
cd /opt/buildroot
export FORCE_UNSAFE_CONFIGURE=1 make procps-ng-rebuild
make netcat-openbsd-rebuild
cd /opt/buildroot
export FORCE_UNSAFE_CONFIGURE=1 make procps-ng-rebuild
make netcat-openbsd-rebuild
find output/target -name "pgrep" # → output/target/bin/pgrep ✓
find output/target -name "nc" # → output/target/usr/bin/nc ✓
find output/target -name "pgrep" # → output/target/bin/pgrep ✓
find output/target -name "nc" # → output/target/usr/bin/nc ✓
find output/target -name "pgrep" # → output/target/bin/pgrep ✓
find output/target -name "nc" # → output/target/usr/bin/nc ✓
make rootfs-ext2
make rootfs-ext2
make rootfs-ext2
✓ POSIX Shell
✓ ls, cp, mv, cat, rm
✓ ps
✓ lo interface
✓ ssh / key setup
✓ Ping 8.8.8.8 / DNS
Network: 10.0.2.15/24
-- Live Hosts (ARP) --
10.0.2.2 52:55:0a:00:02:02
10.0.2.3 52:55:0a:00:02:03
✓ All tests complete
✓ POSIX Shell
✓ ls, cp, mv, cat, rm
✓ ps
✓ lo interface
✓ ssh / key setup
✓ Ping 8.8.8.8 / DNS
Network: 10.0.2.15/24
-- Live Hosts (ARP) --
10.0.2.2 52:55:0a:00:02:02
10.0.2.3 52:55:0a:00:02:03
✓ All tests complete
✓ POSIX Shell
✓ ls, cp, mv, cat, rm
✓ ps
✓ lo interface
✓ ssh / key setup
✓ Ping 8.8.8.8 / DNS
Network: 10.0.2.15/24
-- Live Hosts (ARP) --
10.0.2.2 52:55:0a:00:02:02
10.0.2.3 52:55:0a:00:02:03
✓ All tests complete - --privileged: needed later when we mount the rootfs image as a loop device
- --dns 8.8.8.8: without this, apt inside the container silently fails to resolve hostnames - FORCE_UNSAFE_CONFIGURE=1: bypasses the check that refuses to run as root (we're in Docker, we're always root)
- -j$(nproc): parallel compile jobs; if you have <16GB RAM, drop to -j4 to avoid OOM kills
- tee /tmp/build.log: saves output so you can scroll back if the build crashes at minute 45 - Read the audit script before choosing packages. 15 minutes upfront saves hours of debugging.
- make olddefconfig can silently drop your config options. Always verify what ended up in the image, not what you put in the config.
- HTTPS requires CA certificates, not just TLS support. BR2_PACKAGE_CA_CERTIFICATES=y is not optional.
- No single netcat handles all flag combinations. netcat-openbsd is the pragmatic choice here.
- Incremental rebuilds are fast. make <package>-rebuild + make rootfs-ext2 takes seconds. No need to start from scratch. - Packaging into a bootable ISO
- Squeezing under 20MB (rootfs.cpio.gz is 11MB + bzImage at 6.4MB = ~17.4MB before ISO overhead.. it's going to be tight)
- Implementing the OS Fingerprinting TODO