Repository: NetBSDfr/smolBSD Branch: main Commit: 8481574b7d2b Files: 139 Total size: 300.5 KB Directory structure: gitextract_hd2fc315/ ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── app/ │ ├── .flaskenv │ ├── README.md │ ├── app.py │ ├── index.html │ └── requirements.txt ├── bin/ │ └── .gitkeep ├── contribs/ │ ├── knockd.sh │ └── knockssh.sh ├── dockerfiles/ │ ├── Dockerfile.basic │ ├── Dockerfile.bsdshell │ ├── Dockerfile.caddy │ ├── Dockerfile.clawd │ ├── Dockerfile.crush │ ├── Dockerfile.dockinx │ └── Dockerfile.tiny ├── etc/ │ ├── base.conf │ ├── bozohttpd.conf │ ├── clawd.conf │ ├── games.conf │ ├── lhv-tools.conf │ ├── live.conf │ ├── nbakery.conf │ ├── nitrosshd.conf │ ├── rescue.conf │ ├── runbsd.conf │ ├── sshd.conf │ ├── systembsd.conf │ ├── tslog.conf │ └── usershell.conf ├── k8s/ │ ├── Dockerfile │ ├── README.md │ ├── generic-device-plugin.yaml │ └── smolbozo.yaml ├── misc/ │ └── vmbatch.md ├── mkimg.sh ├── mnt/ │ └── .gitkeep ├── scripts/ │ ├── app-run.sh │ ├── fetch.sh │ ├── freshchk.sh │ ├── sh │ └── uname.sh ├── service/ │ ├── base/ │ │ ├── etc/ │ │ │ └── rc │ │ └── postinst/ │ │ └── dostuff.sh │ ├── biosboot/ │ │ ├── README.md │ │ ├── etc/ │ │ │ └── rc │ │ └── options.mk │ ├── bozohttpd/ │ │ ├── etc/ │ │ │ └── rc │ │ └── postinst/ │ │ └── mkhtml.sh │ ├── bsdshell/ │ │ └── sailor.conf │ ├── build/ │ │ ├── etc/ │ │ │ ├── rc │ │ │ └── resolv.conf │ │ ├── options.mk │ │ └── postinst/ │ │ └── prepare.sh │ ├── clawd/ │ │ ├── LOCAL.md │ │ ├── README.md │ │ └── SCHIZOCLAW.md │ ├── common/ │ │ ├── basicrc │ │ ├── choupi │ │ ├── funcs │ │ ├── mount9p │ │ ├── pkgin │ │ ├── qemufwcfg │ │ ├── sailor.vars │ │ ├── shutdown │ │ └── vars │ ├── crush/ │ │ └── README.md │ ├── games/ │ │ ├── README.md │ │ └── etc/ │ │ └── rc │ ├── lhv-tools/ │ │ ├── README.md │ │ ├── etc/ │ │ │ └── rc │ │ ├── options.mk │ │ └── postinst/ │ │ └── postinstall.sh │ ├── mport/ │ │ └── etc/ │ │ └── rc │ ├── nbakery/ │ │ ├── etc/ │ │ │ ├── fstab │ │ │ ├── rc │ │ │ └── smol.ansi │ │ └── options.mk │ ├── nitro/ │ │ └── postinst/ │ │ └── 00-nitro.sh │ ├── nitrosshd/ │ │ ├── NETBSD_ONLY │ │ ├── README.md │ │ ├── options.mk │ │ └── postinst/ │ │ ├── 00-nitro.sh │ │ └── keygen.sh │ ├── pipe/ │ │ ├── etc/ │ │ │ └── rc │ │ ├── options.mk │ │ └── sailor.conf │ ├── rescue/ │ │ ├── etc/ │ │ │ └── rc │ │ └── options.mk │ ├── runbsd/ │ │ ├── etc/ │ │ │ ├── banner.ans │ │ │ ├── fstab │ │ │ ├── rc │ │ │ ├── rc.conf │ │ │ └── sysctl.conf │ │ └── postinst/ │ │ ├── 00-runit.sh │ │ ├── 01-sv.sh │ │ └── 02-tools.sh │ ├── sshd/ │ │ ├── README.md │ │ ├── etc/ │ │ │ └── rc │ │ ├── options.mk │ │ ├── postinst/ │ │ │ └── keygen.sh │ │ └── sailor.conf │ ├── systembsd/ │ │ ├── etc/ │ │ │ ├── banner.ans │ │ │ ├── dinit.d/ │ │ │ │ ├── boot │ │ │ │ ├── getty │ │ │ │ ├── loginready │ │ │ │ ├── motd │ │ │ │ ├── rc.boot │ │ │ │ ├── rc.boot.sh │ │ │ │ ├── rc.dev │ │ │ │ ├── rc.dev.sh │ │ │ │ ├── rc.fs │ │ │ │ ├── rc.fs.sh │ │ │ │ ├── sshd │ │ │ │ ├── syslogd │ │ │ │ └── wscons │ │ │ ├── fstab │ │ │ ├── rc │ │ │ ├── rc.conf │ │ │ ├── rc.local │ │ │ └── sysctl.conf │ │ └── postinst/ │ │ ├── 00-dinit.sh │ │ └── 01-custom.sh │ ├── tiny/ │ │ └── sailor.conf │ ├── tslog/ │ │ └── etc/ │ │ └── rc │ └── usershell/ │ ├── README.md │ ├── etc/ │ │ └── rc │ ├── options.mk │ ├── postinst/ │ │ └── custom.sh │ └── sailor.conf ├── smoler/ │ ├── build.sh │ └── img.sh ├── smoler.sh ├── startnb.sh └── www/ └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/main.yml ================================================ name: Build smolBSD image on: push: branches: - main paths-ignore: - '**.md' - 'www/**' - 'app/**' workflow_dispatch: inputs: img: description: "Image target" required: true default: "base" arch: description: "Architecture" required: true default: "amd64" service: description: "Service to build on top of image" required: false default: "build" mountro: description: "Build as read-only (y or empty)" required: false default: "y" curlsh: description: "URL to a script to execute as finalizer" required: false default: jobs: build_img: runs-on: ubuntu-latest container: image: debian:latest options: --privileged steps: - name: Checkout uses: actions/checkout@v4 - name: Set up environment run: | apt update && apt install -y curl xz-utils make sudo git libarchive-tools rsync bmake e2fsprogs gdisk rm -rf /var/cache - name: Build image run: | for arch in amd64 evbarm-aarch64 do bmake SERVICE=${{ inputs.service }} CURLSH=${{ inputs.curlsh }} ARCH="$arch" MOUNTRO=${{ inputs.mountro || 'y' }} ${{ inputs.img || 'buildimg' }} # always build a small rescue image for debugging purposes bmake SERVICE=rescue ARCH="$arch" base done - name: Compress images run: | cd images for f in *.img; do echo "Compressing $f..." xz -T0 -9e "$f" sha256sum ${f}.xz > ${f}.xz.sha256 done rm -f *.img - name: Publish images to "latest" release uses: softprops/action-gh-release@v2 with: tag_name: latest name: "Latest smolBSD images (compressed)" files: | images/*.img.xz images/*.xz.sha256 prerelease: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ # editors /.idea # downloads & temporary files /tmp/ *.img /kernels/* /sets/** /images/* /pkgs/** /db/** netbsd-* # app build & env /app/bin/ /app/lib/ /app/pyvenv.cfg /sailor/ # personal keys /service/sshd/etc/*.pub /service/nitrosshd/etc/*.pub ================================================ FILE: LICENSE ================================================ Copyright 2023 Emile `iMil' Heitor Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Makefile ================================================ ARCH!= ARCH=${ARCH} scripts/uname.sh -m MACHINE!= scripts/uname.sh -p OS!= uname -s SETSEXT?= tar.xz .if ${ARCH} == "evbarm-aarch64" KERNEL= netbsd-GENERIC64.img LIVEIMGGZ= https://nycdn.netbsd.org/pub/NetBSD-daily/HEAD/latest/evbarm-aarch64/binary/gzimg/arm64.img.gz .elif ${ARCH} == "i386" KERNEL= netbsd-SMOL386 KDIST= https://smolbsd.org/assets SETSEXT= tgz .else KERNEL= netbsd-SMOL KDIST= https://smolbsd.org/assets LIVEIMGGZ= https://nycdn.netbsd.org/pub/NetBSD-daily/HEAD/latest/images/NetBSD-11.99.5-amd64-live.img.gz .endif .-include "service/${SERVICE}/options.mk" .-include "service/${SERVICE}/own.mk" VERS?= 11 PKGVERS?= 11.0 # for an obscure reason, packages path use uname -p... DIST?= https://nycdn.netbsd.org/pub/NetBSD-daily/netbsd-${VERS}/latest/${ARCH}/binary PKGSITE?= https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/${MACHINE}/${PKGVERS}/All KDIST?= ${DIST} WHOAMI!= whoami USER!= id -un GROUP!= id -gn BUILDIMG= build-${ARCH}.img BUILDIMGPATH= images/${BUILDIMG} BUILDIMGURL= https://github.com/NetBSDfr/smolBSD/releases/download/latest/${BUILDIMG} BUILDCPUS?= 2 BUILDMEM?= 1024 # used to set console in boot.cfg for BIOS boot BIOSCONSOLE?= com0 SERVICE?= ${.TARGET} # guest root filesystem will be read-only .if defined(MOUNTRO) && ${MOUNTRO} == "y" EXTRAS+= -o .endif # enable BIOS boot .if defined(BIOSBOOT) && ${BIOSBOOT} == "y" BIOSKERNEL?= kernels/netbsd-GENERIC EXTRAS+= -b -k ${BIOSKERNEL} .endif SETSDIR= sets/${ARCH} PKGSDIR= pkgs/${ARCH} LIVEIMG= images/NetBSD-${ARCH}-live.img # sets to fetch, defaults to base SETS?= base.${SETSEXT} etc.${SETSEXT} # Default: BSD DDUNIT= m CKSUM= cksum -a sha256 -c CKSUMQ= -q .if ${OS} == "Linux" DDUNIT= M CKSUM= sha256sum -c CKSUMQ= --quiet .elif ${OS} == "Darwin" CKSUM= shasum -a 256 -c .endif FETCH= scripts/fetch.sh FRESHCHK= scripts/freshchk.sh # extra remote script .if defined(CURLSH) && !empty(CURLSH) EXTRAS+= -c ${CURLSH} .endif # default memory amount for a guest MEM?= 256 # default port redirect, gives network to the guest PORT?= ::22022-:22 IMGSIZE?= 512 IMGNAME?= ${SERVICE}-${ARCH}${IMGTAG}.img DSTIMG?= images/${IMGNAME} # variables to transfer to mkimg.sh ENVVARS= SERVICE=${SERVICE} \ ARCH=${ARCH} \ PKGVERS=${PKGVERS} \ MOUNTRO=${MOUNTRO} \ BIOSBOOT=${BIOSBOOT} \ PKGSITE=${PKGSITE} \ ADDPKGS="${ADDPKGS}" \ MINIMIZE=${MINIMIZE} \ BIOSCONSOLE=${BIOSCONSOLE} \ FROMIMG=${FROMIMG} .if ${WHOAMI} != "root" && !defined(NOSUDO) # allow non root builds SUDO!= command -v doas >/dev/null && \ echo '${ENVVARS} doas' || \ echo 'sudo -E ${ENVVARS}' .else SUDO= ${ENVVARS} .endif # QUIET: default to quiet mode with Q=@, use Q= for verbose Q=@ CHOUPI= ./service/common/choupi ARROW!= . ${CHOUPI} && echo "$$ARROW" CHECK!= . ${CHOUPI} && echo "$$CHECK" CPU!= . ${CHOUPI} && echo "$$CPU" FREEZE!= . ${CHOUPI} && echo "$$FREEZE" WHITEBULLET!= . ${CHOUPI} && echo "$$WHITEBULLET" help: # This help you are reading $Qgrep '^[a-z]\+:.*#' Makefile kernfetch: $Qif [ "${ARCH}" = "amd64" ] || [ "${ARCH}" = "i386" ]; then \ ${FRESHCHK} ${KDIST}/${KERNEL} kernels/${KERNEL} || \ ${FETCH} -o kernels/${KERNEL} ${KDIST}/${KERNEL}; \ cd kernels && curl -L -s -o- ${KDIST}/${KERNEL}.sha256 | \ ${CKSUM} ${CKSUMQ} && \ echo "${CHECK} ${KERNEL} sha256 checks out"; \ else \ ${FRESHCHK} ${KDIST}/kernel/${KERNEL}.gz kernels/${KERNEL} || \ curl -L -o- ${KDIST}/kernel/${KERNEL}.gz | \ gzip -dc > kernels/${KERNEL}; \ fi setfetch: @[ -d ${SETSDIR} ] || mkdir -p ${SETSDIR} $Qfor s in ${SETS}; do \ ${FRESHCHK} ${DIST}/sets/$${s%:*} ${SETSDIR}/$${s%:*} || \ ${FETCH} -o ${SETSDIR}/$${s} ${DIST}/sets/$${s}; \ done pkgfetch: @[ -d ${PKGSDIR} ] || mkdir -p ${PKGSDIR} $Qfor p in ${ADDPKGS};do \ ${FRESHCHK} ${PKGSITE}/$${p}* ${PKGSDIR}/$${p}.tgz || \ ${FETCH} -o ${PKGSDIR}/$${p}.tgz ${PKGSITE}/$${p}*; \ done fetchall: kernfetch setfetch pkgfetch base: $Qecho "${CPU} destination architecture: ${ARCH} / ${MACHINE}" # if we are on the builder vm, don't fetchall again $Q[ -f tmp/build-${SERVICE} ] || ${MAKE} fetchall $Qecho "${ARROW} creating root filesystem (${IMGSIZE}M)" $Q${SUDO} ./mkimg.sh -i ${DSTIMG} -s ${SERVICE} \ -m ${IMGSIZE} -x "${SETS}" ${EXTRAS} $Q${SUDO} chown ${USER}:${GROUP} ${DSTIMG} $Qif [ -n "${MOUNTRO}" ]; then \ echo "${FREEZE} system root filesystem will be read-only"; fi $Qecho "${CHECK} image ready: ${DSTIMG}" buildimg: $Qecho "${ARROW} building the builder image" $Qrm -f tmp/* $Q${MAKE} SERVICE=build IMGTAG= base fetchimg: $Qecho "${ARROW} fetching builder image" $Q${FRESHCHK} ${BUILDIMGURL}.xz || \ curl -L -o- ${BUILDIMGURL}.xz | xz -dc > ${BUILDIMGPATH} build: fetchall # Build an image (with SERVICE=$SERVICE from service/) $Qif [ ! -f ${BUILDIMGPATH} ]; then \ if [ "${OS}" = "NetBSD" ] || [ "${OS}" = "Linux" ]; then \ ${MAKE} buildimg; \ else \ ${MAKE} fetchimg; \ fi; \ fi $Qmkdir -p tmp # wipe any leftover from possibly cancelled previous run # tmp holds: # * ENVVARS # * Dockerfile generated options # * optional new size for resize $Qrm -f tmp/* # save variables for sourcing in the build vm $Qecho "${ENVVARS}" | \ sed -E 's/[[:blank:]]+([A-Z_]+)/\n\1/g;s/=[[:blank:]]*([[:print:]]+)/="\1"/g' > \ tmp/build-${SERVICE} # build args from Dockefile $Qif [ -n "${BUILDARGS}" ]; then \ printf '%s\n' "${BUILDARGS}" | tr ',' '\n' >>tmp/build-${SERVICE}; \ fi # image tag for OCI images $Qecho "IMGTAG=${IMGTAG}" >>tmp/build-${SERVICE} $Qecho "${ARROW} creating the disk image" # generate disk image on the host, faster and avoids layering on 9p $Qdd if=/dev/zero of=${DSTIMG} bs=1${DDUNIT} count=${IMGSIZE} $Qecho "${ARROW} starting the builder microvm with" $Qecho " ${WHITEBULLET} ${BUILDMEM}MB RAM" $Qecho " ${WHITEBULLET} ${BUILDCPUS} cores" # Fire up the builder microVM $Q./startnb.sh -k kernels/${KERNEL} -i ${BUILDIMGPATH} -l ${DSTIMG} \ -c ${BUILDCPUS} -m ${BUILDMEM} \ -p ${PORT} -w . -x "-pidfile qemu-${.TARGET}.pid" & # wait till the build is finished, guest removes the lock $Qwhile [ -f tmp/build-${SERVICE} ]; do sleep 0.2; done $Qecho "${ARROW} killing the builder microvm" $Qkill $$(cat qemu-${.TARGET}.pid) $Qif [ -n "${MINIMIZE}" ] && [ -f "tmp/${IMGNAME}.size" ]; then \ while lsof ${DSTIMG} >/dev/null 2>&1; do sleep 0.2; done; \ qemu-img resize -q -f raw --shrink ${DSTIMG} \ $$(cat tmp/${IMGNAME}.size); \ fi # poor man's sig $Qecho "smolsig:$$(date +%d/%m/%Y)|$$(uuidgen)" | \ tee -a ${DSTIMG} >${DSTIMG:S/.img/.sig/} $Q${SUDO} chown ${USER}:${GROUP} ${DSTIMG} # cleanup metadata $Qrm -f tmp/* rescue: # Build a rescue image ${MAKE} SERVICE=rescue build live: kernfetch # Build a live image $Qecho "fetching ${LIVEIMG}" [ -f ${LIVEIMG} ] || curl -L -o- ${LIVEIMGGZ}|gzip -dc > ${LIVEIMG} ================================================ FILE: README.md ================================================
**smolBSD** build your own minimal BSD UNIX system ![License](https://img.shields.io/badge/license-BSD--2--Clause-blue.svg) ![Stars](https://img.shields.io/github/stars/NetBSDfr/smolBSD) ![Build Status](https://img.shields.io/github/actions/workflow/status/NetBSDfr/smolBSD/main.yml?branch=main)
# What is smolBSD? smolBSD helps you create a minimal _NetBSD_ 🚩 based _BSD UNIX_ virtual machine that's able to boot and start a service in a couple milliseconds. * No prior _NetBSD_ installation is required, a _microvm_ can be created and started from any _NetBSD_, _GNU/Linux_, _macOS_ system and probably more. * [PVH][4] boot and various optimizations enable _NetBSD/amd64_ and _NetBSD/i386_ to directly boot [QEMU][8] or [Firecracker][9] in about 10 **milliseconds** on 2025 mid-end x86 CPUs.
**microvm typical boot process**
# Usage ## Requirements - A _GNU/Linux_, _NetBSD_ or _macOS_ operating system (might work on more systems, but not CPU accelerated) - The following tools installed - `curl` - `git` - `bmake` if running on _Linux_ or _macOS_, `make` on _NetBSD_ - `qemu-system-x86_64`, `qemu-system-i386` or `qemu-system-aarch64` depending on destination architecture - `sudo` or `doas` - `uuidgen` - `nm` (not used / functional on _macOS_) - `bsdtar` on Linux (install with `libarchive-tools` on Debian and derivatives, `libarchive` on Arch) - `sgdisk` on Linux for GPT boot - `lsof` - `jq` for `smoler.sh` - `socat` for control socket (optional) - `picocom` for console workloads (optional) - A x86 VT-capable, or ARM64 CPU is recommended ### Lazy copypasta Debian, Ubuntu and the like ```sh $ sudo apt install curl git bmake qemu-system-x86_64 uuid-runtime binutils libarchive-tools gdisk socat jq lsof picocom ``` macOS ```sh $ brew install curl git bmake qemu binutils libarchive socat jq lsof picocom ``` ## Quickstart ### Create a _smolBSD_ image using a _Dockerfile_ 📄 `dockerfiles/Dockerfile.caddy`: ```dockerfile # Mandatory, either comma separated base sets (here base and etc) # or base image name i.e. base-amd64.img FROM base,etc # Mandatory, service name LABEL smolbsd.service=caddy # Optional image minimization to actual content LABEL smolbsd.minimize=y # Dockerfile doesn't support port mapping LABEL smolbsd.publish="8881:8880" RUN pkgin up && pkgin -y in caddy EXPOSE 8880 CMD caddy respond -l :8880 ``` ⚙️ Build: ```sh host$ ./smoler.sh build dockerfiles/Dockerfile.caddy ``` 🚀 Run: ```sh host$ ./startnb.sh -f etc/caddy.conf ``` ✅ Test: ```sh host$ curl -I 127.0.0.1:8881 HTTP/1.1 200 OK Server: Caddy Date: Fri, 23 Jan 2026 18:20:42 GMT ``` # Dive in ## Project structure - `dockerfiles/` _smolBSD_ services `Dockerfile` examples - `Makefile` the entrypoint for image creation, called by `[b]make` - `mkimg.sh` image creation script, should not be called directly - `startnb.sh` starts a _NetBSD_ virtual machine using `qemu-system-x86_64` or `qemu-system-aarch64` - `sets/` contains _NetBSD_ "sets" by architecture, i.e. `amd64/base.tgz`, `evbarm-aarch64/rescue.tgz`... - `pkgs/` holds optional packages to add to a microvm, it has the same format as `sets`. A `service` is the base unit of a _smolBSD_ microvm, it holds the necesary pieces to build a _BSD_ system from scratch. - `service` structure: ```sh service ├── base │   ├── etc │   │   └── rc │ ├── postinst │ │ └── dostuff.sh │ ├── options.mk # Service-specific defaults │ └── own.mk # User-specific overrides (not in git) ├── common │   └── basicrc └── rescue └── etc └── rc ``` A microvm is seen as a "service", for each one: - There **COULD** be a `postinst/anything.sh` which will be executed by `mkimg.sh` at the end of root basic filesystem preparation. **This is executed by the build host at build time** - If standard _NetBSD_ `init(8)` is used, there **MUST** be an `etc/rc` file, which defines what is started at vm's boot. **This is executed by the microvm**. - Image specifics **COULD** be added in `make(1)` format in `options.mk`, i.e. ```sh $ cat service/nbakery/options.mk # size of resulting inage in megabytes IMGSIZE=1024 # as of 202510, there's no NetBSD 11 packages for !amd64 .if defined(ARCH) && ${ARCH} != "amd64" PKGVERS=10.1 .endif ``` - User-specific overrides **COULD** be added in `own.mk` for personal development settings (not committed to repository) In the `service` directory, `common/` contains scripts that will be bundled in the `/etc/include` directory of the microvm, this would be a perfect place to have something like: ```sh $ cat common/basicrc export HOME=/ export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin umask 022 mount -a if ifconfig vioif0 >/dev/null 2>&1; then # default qemu addresses and routing ifconfig vioif0 10.0.2.15/24 route add default 10.0.2.2 echo "nameserver 10.0.2.3" > /etc/resolv.conf fi ifconfig lo0 127.0.0.1 up export TERM=dumb ``` And then add this to your `rc(8)`: ```sh . /etc/include/basicrc ``` ## Dockerfile If you are more experienced with `Dockerfile`s, _smolBSD_ services can be generated using such configuration files; while it does not support the entirety of the [Dockerfile reference][10], the well known verbs are implemented and you can generate services configuration files using the `smoler.sh` script: The `FROM` verb is follwed by _NetBSD_ `sets`, you probably want at least `base` and `etc`. It is also possible to only ship part of the set by appending a `tar`-like `glob` to the set name, i.e. `comp:/usr/bin/strip` or `comp:/usr/libexec/*` The `smolbsd.service` `LABEL` is mandatory, it sets the service name. ```sh $ cat dockerfiles/Dockerfile.myservice FROM base,etc LABEL smolbsd.service=myservice CMD ksh $ ./smoler.sh build -y dockerfiles/Dockerfile.myservice # -y proceeds with image build ✅ basicdocker service files generated ... ``` `ARG` parameters can be overriden using `--build-arg`: ```sh $ ./smoler.sh build --build-arg FOO=bar --build-arg BAR=baz dockerfiles/Dockerfile.myservice ``` If no `-t ` is passed to the `build` command, the tag will be `latest`. ### List existing images ```sh host$ ./smoler.sh images IMAGE SIZE CREATED base-amd64:latest 279M Mar 16 09:11 basic-amd64:latest 279M Mar 16 09:52 bsdshell-amd64:latest 55M Mar 23 08:50 caddy-amd64:latest 347M Mar 16 10:01 clawd-amd64:latest 2.1G Mar 17 14:56 clawd-evbarm-aarch64:latest 2.1G Mar 17 14:48 rescue-amd64:latest 20M Mar 15 16:41 ``` ## Pushing and Pulling Images from an OCI Repository smolBSD supports pushing and pulling images to/from an OCI-compliant repository thanks to the [oras project](https://oras.land/). This allows for easy distribution and versioning of your micro VM images. You can use the following commands to manage your images: * **Push an image:** `./smoler.sh push ` or `./smoler.sh push ` ```sh $ ./smoler.sh push myimage-amd64:latest ``` * **Pull an image:** `./smoler.sh pull ` ```sh $ ./smoler.sh pull myimage-amd64:latest ``` Images will be pulled as regular, raw images and placed in the directory they've been uploaded from, by default `$(pwd)/images/`. By default, these commands interact with the official repository at `ghcr.io/netbsdfr/smolbsd`, you can customize the target repository by setting the `SMOLREPO` environment variable. Official images are available at: https://github.com/orgs/NetBSDfr/packages ## Running images `docker`-style To make the experience easier for `docker` natives, it is also possible to start the microvms with the `smoler` command: ```sh $ ./smoler.sh run bsdshell-amd64:latest -P ``` >[!Note] > If the workload needs a fully functional console (think about `vim`, `tmux`...), pass the `-P` flag to spawn a real `pty` instead of QEMU's `stdio`. You can pass all the `startnb.sh` flags after the image name, i.e. start the microvm with 1GB memory and 2 cores: ```sh $ ./smoler.sh run bsdshell-amd64:latest -P -m 1024 -c 2 ``` ## Building images manually In order to create a _smolBSD_ microvm, you first need to build or fetch a microvm builder. >[!Note] > You can use the `ARCH` variable to specify an architecture to build your image for, the default is to build for the current architecture. >[!Note] > In the following examples, replace `bmake` by `make` if you are using _NetBSD_ as the host. * You can create the builder image yourself if you are running _GNU/Linux_ or _NetBSD_ ```sh $ bmake buildimg ``` * Or simply fetch it if you are running systems that do not support `ext2` or `ffs` such as _macOS_ ```sh $ bmake fetchimg ``` Both methods will create an `images/build-.img` disk image that you'll be able to use to build services. To create a service image using the builder microvm, execute the following: ```sh $ bmake SERVICE=nitro build ``` This will spawn a microvm running the build image, and will build the _service_ specified with the `SERVICE` `make(1)` variable. # Examples ## Very minimal (10MB) virtual machine - [source](service/rescue) Create a `rescue-amd64.img` file for use with an _amd64_ kernel ```sh $ bmake SERVICE=rescue build ``` Create a `rescue-amd64.img` file but with read-only root filesystem so the _VM_ can be stopped without graceful shutdown. Note this is the default for `rescue` as set in `service/rescue/options.mk` ```sh $ bmake SERVICE=rescue MOUNTRO=y build ``` Create a `rescue-i386.img` file for use with an _i386_ kernel. ```sh $ bmake SERVICE=rescue ARCH=i386 build ``` Create a `rescue-evbarm-aarch64.img` file for use with an _aarch64_ kernel. ```sh $ bmake SERVICE=rescue ARCH=evbarm-aarch64 build ``` Start the microvm ```sh $ ./startnb.sh -k kernels/netbsd-SMOL -i images/rescue-amd64.img ``` ## Image filled with the `base` set on an `x86_64` CPU - [source](service/base) ```sh $ bmake SERVICE=base build $ ./startnb.sh -k kernels/netbsd-SMOL -i images/base-amd64.img ``` ## Running the `bozohttpd` web server on an `aarch64` CPU - [source](service/bozohttpd) ```sh $ make ARCH=evbarm-aarch64 SERVICE=bozohttpd build $ ./startnb.sh -k kernels/netbsd-GENERIC64.img -i images/bozohttpd-evbarm-aarch64.img -p ::8080-:80 [ 1.0000000] NetBSD/evbarm (fdt) booting ... [ 1.0000000] NetBSD 10.99.11 (GENERIC64) Notice: this software is protected by copyright [ 1.0000000] Detecting hardware...[ 1.0000040] entropy: ready [ 1.0000040] done. Created tmpfs /dev (1359872 byte, 2624 inodes) add net default: gateway 10.0.2.2 started in daemon mode as `' port `http' root `/var/www' got request ``HEAD / HTTP/1.1'' from host 10.0.2.2 to port 80 ``` Try it from the host ```sh $ curl -I localhost:8080 HTTP/1.1 200 OK Date: Wed, 10 Jul 2024 05:25:04 GMT Server: bozohttpd/20220517 Accept-Ranges: bytes Last-Modified: Wed, 10 Jul 2024 05:24:51 GMT Content-Type: text/html Content-Length: 30 Connection: close ``` ## Example of starting a _VM_ with bi-directionnal socket to _host_ ```sh $ bmake SERVICE=mport MOUNTRO=y build $ ./startnb.sh -n 1 -i images/mport-amd64.img host socket 1: s885f756bp1.sock ``` On the guest, the corresponding socket is `/dev/ttyVI0`, here `/dev/ttyVI01` ```sh guest$ echo "hello there!" >/dev/ttyVI01 ``` ```sh host$ socat ./s885f756bp1.sock - hello there! ``` ## Example of a full fledge NetBSD Operating System ```sh $ bmake live # or make ARCH=evbarm-aarch64 live $ ./startnb.sh -f etc/live.conf ``` This will fetch a directly bootable kernel and a _NetBSD_ "live", ready-to-use, disk image. Login with `root` and no password. To extend the size of the image to 4 more GB, simply do: ```sh $ dd if=/dev/zero bs=1M count=4000 >> NetBSD-amd64-live.img ``` And restart the microvm. ## Customization The following `Makefile` variables change `mkimg.sh` behavior: * `ADDPKGS` will fetch and **untar** the packages paths listed in the variable, this is done in `postinst` stage, on the build host, where `pkgin`, _NetBSD_'s package manager, might not be available * `ADDSETS` will add the sets paths listed in the variable * `MOUNTRO` if set to `y`, the microvm will mount its root filesystem as read-only * `MINIMIZE`: * if set to `y`, will reduce the disk image size to disk real usage + 10% * if set to `+`, will reduce the disk image size to disk real usage + `` megabytes * if a `sailor.conf` file is available in service's directory, it will invoke [sailor][3] to remove any unnecessary file * By default, services are build on top of the `base` set, fetched in `sets//base.tar.xz`, this can be overriden with the `SETS` `make(1)` variable. The following environment variables change `startnb.sh` behavior: * `QEMU` will use custom `qemu` instead of the one in user's `$PATH` ## Basic frontend A simple virtual machine manager is available in the `app/` directory, it is a `python/Flask` application and needs the following requirements: * `Flask` * `psutil` Start it in the `app/` directory like this: `python3 app.py` and a _GUI_ like the following should be available at `http://localhost:5000`: ![smolGUI](gui.png) # Final notes ## Kernel As of February 2026, many features needed for _smolBSD_ fast boot are integrated in [NetBSD's current kernel][6], and [NetBSD 11 releases][7] those still pending are available in my [NetBSD development branch][5]. Pre-built 64 bits kernel at https://smolbsd.org/assets/netbsd-SMOL and a 32 bits kernel at https://smolbsd.org/assets/netbsd-SMOL386 `aarch64` `netbsd-GENERIC64` kernels are able to boot directly to the kernel with no modification In any case, the `bmake kernfetch` will take care of downloading the correct kernel. [0]: https://gitlab.com/0xDRRB/confkerndev [1]: https://man.netbsd.org/x86/multiboot.8 [2]: https://www.linux-kvm.org/page/Main_Page [3]: https://github.com/NetBSDfr/sailor [4]: https://xenbits.xen.org/docs/unstable/misc/pvh.html [5]: https://github.com/NetBSDfr/NetBSD-src/tree/netbsd-smol-11 [6]: https://github.com/NetBSD/src [7]: https://nycdn.netbsd.org/pub/NetBSD-daily/netbsd-11/latest [8]: https://www.qemu.org/docs/master/system/i386/microvm.html [9]: https://firecracker-microvm.github.io/ [10]: https://docs.docker.com/reference/dockerfile/ ================================================ FILE: app/.flaskenv ================================================ # Flask Defaults FLASK_APP=app FLASK_DEBUG=True FLASK_ENV=development FLASK_CWD=.. #FLASK_LOGLEVEL=50 FLASK_RUN_HOST="127.0.0.1" FLASK_RUN_PORT=5000 ================================================ FILE: app/README.md ================================================ # smolBSD VM Manager ## App Usage ## QuickStart ~~~ scripts/app-run.sh # just does Setup, Config & Run firefox http://localhost:5000 ~~~ ## Setup ~~~ cd app/ python3 -m venv . . bin/activate pip install -r requirements.txt ~~~ ## Running ~~~ cd app/ . bin/activate flask run ~~~ ## Configuration ~~~ - 1st, env vars have preference - 2nd, app vars at .env - 3rd, flask vars at .flaskenv ~~~ ## Cleanup ~~~ rm -ri bin/ include/ lib/ lib64/ __pycache__/ pyvenv.cfg ~~~ ================================================ FILE: app/app.py ================================================ import json import logging import os import psutil import socket import subprocess import dotenv import sys from flask import Flask, send_file, jsonify, request app = Flask(__name__) # Get environment variables from .flaskenv / .env dotenv.load_dotenv() cwd = os.environ['FLASK_CWD'] if 'FLASK_CWD' in os.environ else '..' loglevel = int(os.environ['FLASK_LOGLEVEL']) if 'FLASK_LOGLEVEL' in os.environ else logging.ERROR log = logging.getLogger('werkzeug') log.setLevel(loglevel) vmlist = {} def get_vmlist(): vmlist.clear() for filename in os.listdir(f'{cwd}/etc'): if filename.endswith('.conf'): vmname = filename.split(".conf")[0] config_data = {} with open(f'{cwd}/etc/{filename}', 'r') as f: lines = f.readlines() for line in lines: line = line.strip() if not line: continue elif line.startswith('#') or line.startswith('extra'): continue elif '=' in line: key, value = line.split('=', 1) config_data[key.strip()] = value.strip() # Check if QEMU process is running status = 'running' if get_pid(vmname) > 0 else 'stopped' vmlist[vmname] = config_data vmlist[vmname]['status'] = status return vmlist def list_files(path): try: items = [f for f in os.listdir(path) if not f.startswith(".")] return jsonify(items) except FileNotFoundError: return jsonify({"error": "Directory not found"}), 404 except Exception as e: return jsonify({"error": str(e)}), 500 def get_port(vmname, service, default_port): port = default_port if vmname in vmlist and service in vmlist[vmname]: return vmlist[vmname][service] for vm in vmlist: if not service in vmlist[vm]: continue vm_port = int(vmlist[vm][service]) if vm_port >= port: port = vm_port + 1 return port def query_qmp(command, vmname): if not 'qmp_port' in vmlist[vmname]: return jsonify( {"success": False, "message": f"QMP not enabled in {vmname}"} ), 404 qmp_port = int(vmlist[vmname]['qmp_port']) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: rep_len = 8192 s.connect(("localhost", qmp_port)) response = s.recv(rep_len) # mandatory before command (duh?) s.sendall('{"execute": "qmp_capabilities"}\n'.encode('utf-8')) response = s.recv(rep_len) # actual command s.sendall(f'{{"execute": "{command}"}}\n'.encode('utf-8')) response = s.recv(rep_len) return json.loads(response) def get_pid(vmname): pid_file = f"{cwd}/qemu-{vmname}.pid" if not os.path.exists(pid_file): return -1 f = open(pid_file, "r") pid = int(f.read().strip()) if psutil.pid_exists(pid): return pid else: return -1 def get_cpu_usage(vmname): ncpus = 1 r = query_qmp("query-cpus-fast", vmname) if r and 'return' in r: ncpus = len(r['return']) pid = get_pid(vmname) if pid < 0: return 0 process = psutil.Process(pid) return process.cpu_percent(interval=0.1) / ncpus ## routes @app.route("/") def index(): # do not render template, frontend logic handled by Vue return send_file("index.html") @app.route("/static/smolBSD.png") def assets(): return send_file("static/smolBSD.png") @app.route("/vmlist") def vm_list(): return jsonify(get_vmlist()) @app.route("/getkernels") def getkernels(): return list_files(f'{cwd}/kernels/') @app.route("/getimages") def getimages(): return list_files(f'{cwd}/images/') @app.route("/start", methods=["POST"]) def start_vm(): vm_name = request.json.get("vm_name") config_file = f'{cwd}/etc/{vm_name}.conf' if not os.path.exists(config_file): return jsonify( {"success": False, "message": "Config file not found"} ), 404 try: ret = subprocess.Popen([f"{cwd}/startnb.sh", "-f", config_file, "-d"], cwd=cwd) return jsonify({"success": True, "message": "Starting VM"}) except Exception as e: return jsonify({"success": False, "message": str(e)}), 500 @app.route("/stop", methods=["POST"]) def stop_vm(): vm_name = request.json.get("vm_name") pid_file = f'{cwd}/qemu-{vm_name}.pid' try: pid = get_pid(vm_name) if pid < 0: return jsonify( {"success": False, "message": "PID file not found"} ), 404 os.kill(int(pid), 15) os.remove(pid_file) return jsonify({"success": True, "message": "Stopping VM"}) except Exception as e: return jsonify({"success": False, "message": str(e)}), 500 @app.route("/saveconf", methods=['POST']) def saveconf(): try: data = request.get_json() if not data: return jsonify( {"success": False, "message": "No JSON payload provided"} ), 400 vmname = data.get("vm") if not vmname: return jsonify( { "success": False, "message": "'vm' key is required in the JSON payload" } ), 400 os.makedirs(f'{cwd}/etc', exist_ok=True) file_path = f"{cwd}/etc/{vmname}.conf" with open(file_path, 'w') as file: for key, value in data.items(): if key == "tcpserial": if value is True: serial_port = get_port(vmname, 'serial_port', 5555) key = "serial_port" value = f"{serial_port}" else: continue if key == "rmprotect" and value == False: continue file.write(f"{key}={value}\n") qmp_port = get_port(vmname, 'qmp_port', 4444) file.write(f'qmp_port={qmp_port}\n') extra = 'extra="-pidfile qemu-${vm}.pid"' file.write(f'{extra}\n') return jsonify({"success": True, "message": file_path}), 200 except Exception as e: return jsonify({"success": False, "message": str(e)}), 500 @app.route('/rm/', methods=['DELETE']) def rm_file(vm): try: filepath = f"{cwd}/etc/{vm}.conf" if not os.path.isfile(filepath): return jsonify( {"success": False, "message": f"'{vm}' not found"} ), 404 os.remove(filepath) return jsonify( {"success": True, "message": f"'{vm}' deleted successfully"} ), 200 except Exception as e: return jsonify({"success": False, "message": str(e)}), 500 @app.route('/qmp//') def qmp(vmname, command): try: response = query_qmp(command, vmname) return response except Exception as e: return jsonify({"success": False, "message": str(e)}), 500 @app.route('/cpu_usage/') def cpu_usage(vmname): try: return f"{get_cpu_usage(vmname)}\n" except Exception as e: return jsonify({"success": False, "message": str(e)}), 500 if __name__ in ["__main__", "app"]: vmlist = get_vmlist() if __name__ == "__main__": app.run() ================================================ FILE: app/index.html ================================================ # smolBSD VMs

# smolBSD VM Manager

================================================ FILE: app/requirements.txt ================================================ Flask psutil python-dotenv ================================================ FILE: bin/.gitkeep ================================================ ================================================ FILE: contribs/knockd.sh ================================================ #!/bin/sh # usage # server side: PORTS="1050 2000 3000" SERVICE="sshd" contribs/knockd.sh # client side: PORTS="1050 2000 3000" && \ # for p in $PORTS; do nc -w0 localhost $p; done # # unlike the real `knockd`, start ports and stop ports are # identical pid=qemu-${SERVICE}.pid while : do echo "entering loop" for p in $PORTS do nc -l -p "$p" echo "got port $p" done echo "SESAME" [ -f "$pid" ] && \ kill $(cat $pid) || \ ./startnb.sh -f etc/${SERVICE}.conf & done ================================================ FILE: contribs/knockssh.sh ================================================ #!/bin/sh # sample script to use with knockd.sh, I use it from Termux # on my Android phone echo -n "port: " stty -echo read port stty echo echo DEST=$1 PORTS="$port $(($port + 1)) $port" knock() { for p in $PORTS do echo "knocking" echo|nc -w5 $DEST $p done } knock ssh -p $(($port + 2)) ssh@$DEST knock ================================================ FILE: dockerfiles/Dockerfile.basic ================================================ FROM base,etc LABEL smolbsd.service="basic" LABEL smolbsd.minimize="y" CMD ksh ================================================ FILE: dockerfiles/Dockerfile.bsdshell ================================================ # unusual "FROM" naming, multiple RUN don't matter, and non-JSON CMD # hadolint global ignore=DL3006,DL3025,DL3059 FROM base,etc LABEL smolbsd.service="bsdshell" # strip down the image to df -h LABEL smolbsd.minimize="y" # packages needed to run sailor LABEL smolbsd.addpkgs="pkgin pkg_tarup pkg_install sqlite3 rsync curl" # microvm will use a pty as console LABEL smolbsd.use_pty="y" ARG HOSTNAME=shell ARG USERNAME=bsd ARG USERSHELL=/bin/ksh ENV TERM=tmux-256color ENV LANG=en_US.UTF-8 RUN rm -f /etc/shrc RUN cat <>/etc/profile PS1="\$(printf '\e[1;31m\${USER}\e[1;37m@${HOSTNAME}\e[0m$ ')" EOF RUN echo "hostname ${HOSTNAME}" >>/etc/rc.local && \ echo 'eval \$(resize)' >>/etc/rc.local && \ echo ". /etc/include/shutdown" >>/etc/rc RUN useradd -m $USERNAME && \ chsh -s $USERSHELL $USERNAME && \ touch /home/${USERNAME}/.hushlogin && \ echo 'HISTFILE="\$HOME/.history"' >> /home/${USERNAME}/.profile && \ chown -R $USERNAME /home/${USERNAME} CMD login -f -p bsd ================================================ FILE: dockerfiles/Dockerfile.caddy ================================================ # Mandatory, either comma separated base sets (here base and etc) # or base image name i.e. base-amd64.img FROM base,etc # Mandatory, service name LABEL smolbsd.service=caddy # Optional image minimization to actual content LABEL smolbsd.minimize=y # Dockerfile doesn't support port mapping LABEL smolbsd.publish="8881:8880" RUN pkgin up && pkgin -y in caddy EXPOSE 8880 CMD caddy respond -l :8880 ================================================ FILE: dockerfiles/Dockerfile.clawd ================================================ # smolClaw: run an picoclaw instance in a microVM # # build this service: # $ ./smoler.sh build dockerfiles/Dockerfile.clawd # run this service: # $ ./startnb.sh -c 2 -m 1024 -f etc/clawd.conf FROM base,etc,man,comp LABEL smolbsd.service=clawd # final image size LABEL smolbsd.imgsize=2048 # default picoclaw gateway port and SSH access LABEL smolbsd.publish="18789:18789,18800:18800,2289:22" # microvm will use a pty as console LABEL smolbsd.use_pty="y" ARG PYVERS="314" # ripgrep, fd, jq and rsync for convenience and tool writing # w3m for dumping webpages, go and python for code check RUN pkgin up && pkgin -y in \ bash curl git-base gmake vim \ jq rsync ripgrep fd-find imapfilter socat gh \ w3m python${PYVERS} py${PYVERS}-pipx py${PYVERS}-pip py${PYVERS}-uv ENV NBUSER=clawd ENV NBHOME=/home/${NBUSER} ENV LANG=en_US.UTF-8 ENV TERM=tmux-256color ENV MAXFILES=4096 ARG GHARCH=$(uname -p|sed 's/aarch64/arm64/;s/arm$/arm64/') ARG GHREPO=https://github.com/sipeed/picoclaw/releases/download ARG GHRELEASE=v0.2.7 RUN curl -L -s -o- ${GHREPO}/${GHRELEASE}/picoclaw_Netbsd_${GHARCH}.tar.gz | \ tar zxf - -C /usr/pkg/bin \ picoclaw picoclaw-launcher picoclaw-launcher-tui RUN <${NBHOME}/.vimrc set nocompatible set ts=8 set noai syntax on set mouse-=a noremap :tabnew noremap :tabnext noremap :tabprevious noremap :tabnext noremap :tabprevious EOF RUN cat <${NBHOME}/.tmux.conf set -g default-terminal "tmux-256color" set -g @tmux_power_theme "coral" set-option -g repeat-time 0 unbind | bind | split-window -h unbind - bind - split-window -v run "~/.tmux/tmux-power.tmux" EOF RUN cat <${NBHOME}/.bash_profile LIGHTGREEN="\033[92m" BOLD="\033[1m" NORMAL="\033[0m" PS1="[\w]@😈+🦞> " export PATH="\${PATH}:${NBHOME}/.local/bin:${NBHOME}/bin" export LANG="${LANG}" export TERM="${TERM}" alias vi=vim export EDITOR=vim EOF RUN cat <<'EOF' >>${NBHOME}/.bash_profile printf " --- 📝 the \${LIGHTGREEN}vim\${NORMAL} editor is available 🪟 you are in a \${LIGHTGREEN}tmux\${NORMAL} multiplexer ➡️ \${BOLD}setup picoclaw\${NORMAL}: \${LIGHTGREEN}picoclaw onboard\${NORMAL} ➡️ \${BOLD}start picoclaw gateway\${NORMAL}: \${LIGHTGREEN}picoclaw gateway\${NORMAL} ➡️ \${BOLD}or\${NORMAL}: \${LIGHTGREEN}picoclaw-launcher -public\${NORMAL} for a web GUI at http://127.0.0.1:18800 ➡️ \${LIGHTGREEN}ssh -p 2289 clawd@localhost\${NORMAL} to SSH to this microvm --- " EOF # SSH access ARG SSH_PUBKEY="/mnt/share/ssh.pub" COPY ${SSH_PUBKEY} ${NBHOME}/.ssh/authorized_keys RUN [ -d "${NBHOME}/.ssh" ] && \ chown -R ${NBUSER} ${NBHOME} && \ chmod 755 ${NBHOME} && \ chmod -R go-rwx ${NBHOME}/.ssh RUN cat </etc/rc.local hostname ${NBUSER} ulimit -n ${MAXFILES} EOF RUN echo 'eval \$(resize)' >>/etc/rc.local EXPOSE 18800 # script creates a pty, do not su - in order to get env vars CMD /etc/rc.d/sshd onestart && \ script -c "cd ${NBHOME} && su ${NBUSER} -c 'tmux -u new'" /dev/null || bash ================================================ FILE: dockerfiles/Dockerfile.crush ================================================ # smol'd version of crush https://github.com/charmbracelet/crush # # copy a configured crush.json in the directory of the # project you want to work on and: # # ./smoler.sh run crush-amd64:latest -m 1024 -w /path/to/project # # /path/to/project will be mounted in /mnt # FROM base,comp,etc LABEL smolbsd.service=crush LABEL smolbsd.minimize=y LABEL smolbsd.imgsize=2048 LABEL smolbsd.use_pty=y ARG CRUSH_VERSION="0.62.1" ARG CRUSH_ARCH=$(uname -p|sed 's/aarch64/arm64/') ENV NBUSER=crush ENV LANG=en_US.UTF-8 ENV TERM=tmux-256color ENV PATH=${PATH}:/usr/pkg/bin RUN pkgin up && pkgin -y in curl RUN mkdir -p /usr/pkg/bin && \ curl -L -s -o- \ https://github.com/charmbracelet/crush/releases/download/v${CRUSH_VERSION}/crush_${CRUSH_VERSION}_Netbsd_${CRUSH_ARCH}.tar.gz | \ tar zxf - -C /usr/pkg/bin --strip-components=1 crush_${CRUSH_VERSION}_Netbsd_${CRUSH_ARCH}/crush RUN cat </etc/rc.local hostname ${NBUSER} ulimit -n 4096 EOF RUN echo 'eval \$(resize)' >>/etc/rc.local RUN echo 'printf "\nℹ️ When shutting down, ${LIGHTGREEN}Ctrl-A Ctrl-X${NORMAL} to exit the microvm\n\n"' >>/etc/rc.local USER $NBUSER WORKDIR /home/$NBUSER RUN touch .hushlogin RUN cat <.tmux.conf set -g status off EOF RUN cat <.profile printf "\n⌚ Wait for crush to load...\n\n" crush exit EOF RUN cat <cmd.sh #!/bin/sh for f in /mnt/crush.json /var/qemufwcfg/opt/org.smolbsd.file.crush do if [ -f "\\\$f" ]; then cp "\\\$f" crush.json exec login -f -p $NBUSER fi done echo "⚠️ no crush.json, either:" printf "\n- add ${LIGHTGREEN}-E crush=/path/to/crush.json${NORMAL} to the command line\nor" printf "\n- copy crush.json to a directory and add ${LIGHTGREEN}-w /path/to/directory'${NORMAL}\n" ksh EOF RUN chmod +x cmd.sh CMD ./cmd.sh ================================================ FILE: dockerfiles/Dockerfile.dockinx ================================================ FROM base,etc LABEL smolbsd.service=dockinx LABEL smolbsd.minimize=y LABEL smolbsd.publish=8800:80 ARG JUST=ATEST RUN groupadd nginx && \ useradd -g nginx -d /var/www nginx && \ chown -R nginx /var/www RUN pkgin up && pkgin -y in nginx && \ cp -R /usr/pkg/share/examples/nginx/conf /usr/pkg/etc/nginx && \ sed -i'' 's,root .*;,root /var/www;,' /usr/pkg/etc/nginx/nginx.conf && \ mkdir -p /var/log/nginx /var/db/nginx USER nginx RUN echo 'Up!' >/var/www/index.html ADD https://github.com/NetBSDfr/smolBSD/blob/main/README.md /var/www/README.md EXPOSE 80 ENV ANOTHER=TEST VOLUME misc WORKDIR /var/www USER root CMD nginx -g 'daemon off;' ================================================ FILE: dockerfiles/Dockerfile.tiny ================================================ # hadolint global ignore=DL3006,DL3025 # FROM naming and non-JSON CMD FROM base,etc LABEL smolbsd.service="tiny" # strip down the image to df -h LABEL smolbsd.minimize="y" # packages needed to run sailor LABEL smolbsd.addpkgs="pkgin pkg_tarup pkg_install sqlite3 rsync curl" CMD ksh ================================================ FILE: etc/base.conf ================================================ fwcfgvar="MOUNTRO=y" ================================================ FILE: etc/bozohttpd.conf ================================================ # optional mem=128m # optional cores=1 # optional port forward hostfwd=::8180-:80 # kernel parameters to append #append="-v" # optional path to share with guest #share=$(pwd) # optional extra parameters #extra="" sharerw="y" ================================================ FILE: etc/clawd.conf ================================================ hostfwd=::18789-:18789,::18800-:18800,::2289-:22 imgtag=latest use_pty=y ================================================ FILE: etc/games.conf ================================================ # Because text-based games deserve power! #mem=32768m # Not less, fool! #cores=64 # min requirement # # ... these values are a joke, dude! No need such power, default values are enough. ================================================ FILE: etc/lhv-tools.conf ================================================ # optional mem=128m # optional cores=1 # optional port forward hostfwd=::8180-:80 # optional qmp qmp_port=4444 # optional serial serial_port=5555 ================================================ FILE: etc/live.conf ================================================ ARCH="$(scripts/uname.sh -m)" # make live for amd64 or make ARCH=evbarm-aarch64 live img=images/NetBSD-${ARCH}-live.img mem=1g cores=2 hostfwd=::22222-:22 root=NAME=$([ "$ARCH" = "amd64" ] && echo NBImgRoot || echo netbsd-root) ================================================ FILE: etc/nbakery.conf ================================================ # optional mem=512m # optional cores=2 # optional port forward hostfwd=::2299-:22 # optional anything or empty #bridgenet= # optional path to share with guest share=$(pwd) # optional extra parameters #extra="" # append="-v" ================================================ FILE: etc/nitrosshd.conf ================================================ # optional mem=256m # optional cores=1 # optional port forward hostfwd=::2022-:22 # optional anything or empty #bridgenet= # optional path to share with guest #share=$(pwd) # optional extra parameters #extra="" # append="-v" ================================================ FILE: etc/rescue.conf ================================================ # optional mem=128m # optional cores=1 # optional qmp #qmp_port=4444 # optional serial #serial_port=5555 # optional port forward hostfwd=::22122-:22 # optional anything or empty bridgenet= # optional path to share with guest share=$(pwd) # optional extra parameters #extra="" ================================================ FILE: etc/runbsd.conf ================================================ # optional mem=512m # optional cores=2 # optional port forward hostfwd=::2298-:22 # optional anything or empty #bridgenet= # optional path to share with guest share=$(pwd) # optional extra parameters #extra="" # append="-v" ================================================ FILE: etc/sshd.conf ================================================ # optional mem=256m # optional cores=1 # optional port forward hostfwd=::2022-:22 # optional anything or empty #bridgenet= # optional path to share with guest share=$(pwd) # optional extra parameters #extra="" # append="-v" ================================================ FILE: etc/systembsd.conf ================================================ # optional mem=512m # optional cores=2 # optional port forward hostfwd=::2297-:22 # optional anything or empty #bridgenet= # optional path to share with guest #share=$(pwd) # optional extra parameters #extra="" # append="-v" ================================================ FILE: etc/tslog.conf ================================================ # optional mem=256m # optional cores=1 # optional port forward hostfwd=::2299-:22 # optional anything or empty #bridgenet= # optional path to share with guest share=${HOME}/src # optional extra parameters #extra="" # append="-v" ================================================ FILE: etc/usershell.conf ================================================ # optional mem=128m # optional cores=1 # optional qmp #qmp_port=4444 # optional serial #serial_port=5555 # optional port forward #hostfwd=::22122-:22 # optional anything or empty #bridgenet= # optional path to share with guest #share=$(pwd) # optional extra parameters #extra="" ================================================ FILE: k8s/Dockerfile ================================================ FROM alpine:latest RUN apk add --quiet --no-cache qemu-system-x86_64 uuidgen ARG NBIMG=bozohttpd-amd64.img ARG MEM=256m ARG KERNEL=netbsd-SMOL ARG PORTFWD=8080:80 ENV NBIMG=${NBIMG} ENV MEM=${MEM} ENV KERNEL=${KERNEL} ENV PORTFWD=${PORTFWD} COPY ${KERNEL} ${NBIMG} startnb.sh / CMD ["/bin/sh", "-c", "PORTFWD=$(echo ${PORTFWD}|sed 's/:/-:/') && /startnb.sh -m ${MEM} -k ${KERNEL} -i ${NBIMG} -p ::${PORTFWD}" ] ================================================ FILE: k8s/README.md ================================================ # smolBSD pod example A _smolBSD_ system can be spawned inside a container, thus bringing a decent level of security to the service which will be isolated in a virtual machine. ## Building the _docker_ image Let's use the [bozohttpd service][0] as an example image. Fetch the kernel image and generate the _smolBSD_ image as usual ```sh $ make kernfetch $ make SERVICE=bozohttpd base ``` Build the docker image using the created _smolBSD_ image ```sh $ docker build -t smolbozo -f k8s/Dockerfile . ``` The following arguments can be passed to the build process using the `--build-arg` flag: * `NBIMG`: the name of the _smolBSD_ image, defaults to `bozohttpd-amd64.img` * `MEM`: the amount of memory for the virtual machine, defaults to `256m` * `KERNEL`: the name of the kernel to use, defaults to `netbsd-SMOL` * `PORTFWD`: port forwarding between host and guest, defaults to `8080:80` Try launching the container: ```sh $ docker run -it --rm --device=/dev/kvm -p 8080:8080 smolbozo ``` And access it ```sh $ curl http://localhost:8080 up! ``` ## smolBSD pod The [generic device plugin][1] is needed in order to expose `/dev/kvm` to the container without running the _smolBSD_ pod it in privileged mode. Apply this [modified version][2] of `k8s/generic-device-plugin.yaml` to your _k8s_ cluster: ```sh $ kubectl apply -f k8s/generic-device-plugin.yaml ``` Check it is running: ```sh $ kubectl get pods -n kube-system -l app.kubernetes.io/name=generic-device-plugin NAME READY STATUS RESTARTS AGE generic-device-plugin-c74cc 1/1 Running 0 40h ``` Finally, here is a simple pod example for the `bozohttpd` _smolBSD_ image, this example implies the image is already loaded in the cluster and the pod port `8080` will be mapped to the node IP. ```yaml apiVersion: v1 kind: Pod metadata: name: smolbozo namespace: smolbsd labels: app: smolbozo spec: containers: - name: bozohttpd image: smolbozo:0.1 ports: - containerPort: 8080 hostPort: 8080 resources: limits: squat.ai/kvm: 1 ``` > [!note] > you will either need to change the repository address for the `image` or setup a local repository: > * [with Kind][3] > * [with K3s][4] > > With _Kind_, you can also [import the image][5] into the cluster, but beware to use fixed versions for the image, if `:latest` is used, the pull policy defaults to `Always`. Create the `smolbsd` _namespace_ and apply the manifest: ```sh $ kubectl create namespace smolbsd $ kubectl apply -f k8s/smolbozo.yaml ``` Check it is running ```sh $ kubectl get pods -n smolbsd -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES smolbozo 1/1 Running 0 41h 10.42.0.21 k3s ``` And curl it! ```sh $ curl http://10.42.0.21:8080 up! ``` [0]: https://github.com/NetBSDfr/smolBSD/tree/main/service/bozohttpd [1]: https://github.com/squat/generic-device-plugin [2]: https://github.com/NetBSDfr/smolBSD/blob/main/k8s/generic-device-plugin.yaml [3]: https://kind.sigs.k8s.io/docs/user/local-registry/ [4]: https://docs.k3s.io/installation/private-registry [5]: https://kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster ================================================ FILE: k8s/generic-device-plugin.yaml ================================================ apiVersion: apps/v1 kind: DaemonSet metadata: name: generic-device-plugin namespace: kube-system labels: app.kubernetes.io/name: generic-device-plugin spec: selector: matchLabels: app.kubernetes.io/name: generic-device-plugin template: metadata: labels: app.kubernetes.io/name: generic-device-plugin spec: priorityClassName: system-node-critical tolerations: - operator: "Exists" effect: "NoExecute" - operator: "Exists" effect: "NoSchedule" containers: - image: squat/generic-device-plugin args: - --device - | name: kvm groups: - paths: - path: /dev/kvm name: generic-device-plugin resources: requests: cpu: 50m memory: 10Mi limits: cpu: 50m memory: 20Mi ports: - containerPort: 8080 name: http securityContext: privileged: true volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: dev mountPath: /dev volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: dev hostPath: path: /dev updateStrategy: type: RollingUpdate ================================================ FILE: k8s/smolbozo.yaml ================================================ apiVersion: v1 kind: Pod metadata: name: smolbozo namespace: smolbsd labels: app: smolbozo spec: containers: - name: bozohttpd image: localhost:5000/smolbozo ports: - containerPort: 8080 resources: limits: squat.ai/kvm: 1 --- apiVersion: v1 kind: Service metadata: name: smolbozo-svc namespace: smolbsd spec: selector: app: smolbozo ports: - protocol: TCP port: 8080 targetPort: 8080 ================================================ FILE: misc/vmbatch.md ================================================ # VM batch creation and bench You need [smolBSD](https://github.com/NetBSDfr/smolBSD) to test the following ## Read-only `bozohttpd` web server image * Create the base image ```sh $ make MOUNTRO=y SERVICE=bozohttpd build ``` * Create a config file template ```sh $ cat etc/bozohttpd.conf # mandatory img=bozohttpd-amd64.img # mandatory kernel=netbsd-SMOL # https://smolbsd.org/assets/netbsd-SMOL # optional mem=128m # optional cores=1 # optional port forward hostfwd=::8180-:80 # optional extra parameters extra="" # don't lock the disk image sharerw="y" ``` * Try it ```sh $ ./startnb.sh -f etc/bozohttpd.conf ``` exit `qemu` with `Ctrl-a x` ## Example shell script, parallel run Just play with the `num` variable ```sh #!/bin/sh vmname=bozohttpd num=9 for i in $(seq 1 $num) do sed "s/8180/818$i/" etc/${vmname}.conf > etc/${vmname}${i}.conf done for f in etc/${vmname}[0-9]*.conf; do . $f echo "starting $vm" ./startnb.sh -f $f -d & done for i in $(seq 1 $num) do while ! curl -s -I --max-time 0.01 localhost:818${i} do true done done for i in $(seq 1 $num); do curl -I http://localhost:818${i}; done for i in $(seq 1 $num); do kill $(cat qemu-${vmname}${i}.pid); done rm -f etc/${vmname}[0-9]*.conf ``` ================================================ FILE: mkimg.sh ================================================ #!/bin/sh set -e progname=${0##*/} usage() { cat 1>&2 << _USAGE_ Usage: $progname [-s service] [-m megabytes] [-i image] [-x set] [-k kernel] [-o] [-c URL] [-b] Create a root image -s service service name, default "rescue" -r rootdir hand crafted root directory to use -m megabytes image size in megabytes, default 10 -i image image name, default rescue-[arch].img -x sets list of NetBSD sets, default rescue.tgz -k kernel kernel to copy in the image -c URL URL to a script to execute as finalizer -o read-only root filesystem -b make image BIOS bootable _USAGE_ exit 1 } options="s:m:i:r:x:k:c:boh" [ -f tmp/build* ] && . tmp/build* . service/common/vars . service/common/funcs . service/common/choupi while getopts "$options" opt do case $opt in s) svc="$OPTARG";; m) megs="$OPTARG";; i) img="$OPTARG";; r) rootdir="$OPTARG";; x) sets="$OPTARG";; k) kernel="$OPTARG";; c) curlsh="$OPTARG";; b) biosboot=y;; o) rofs=y;; h) usage;; *) usage;; esac done export ARCH PKGVERS arch=${ARCH:-"amd64"} svc=${svc:-"rescue"} megs=${megs:-"20"} img=${img:-"rescue-${arch}.img"} sets=${sets:-"rescue.tar.xz"} rootdir=${rootdir:-} kernel=${kernel:-} curlsh=${curlsh:-} rofs=${rofs:-} ADDSETS=${ADDSETS:-} ADDPKGS=${ADDPKGS:-} SVCIMG=${SVCIMG:-} OS=$(uname -s) TAR=tar FETCH=$(pwd)/scripts/fetch.sh is_netbsd= is_buildimg= is_linux= is_darwin= is_openbsd= is_freebsd= is_unknown= case $OS in NetBSD|smolBSD) is_netbsd=1 FETCH=ftp ;; Linux) is_linux=1 # avoid sets and pkgs untar warnings TAR=bsdtar ;; # those 2 will be ported at some point OpenBSD) is_openbsd=1 echo "${ERROR} unsupported for now" exit 1 ;; FreeBSD) is_freebsd=1 echo "${ERROR} unsupported for now" exit 1 ;; *) echo "${ERROR} unsupported for now" exit 1 esac [ -f "/BUILDIMG" ] && is_buildimg=1 for tool in $TAR # add more if needed do if ! command -v $tool >/dev/null; then echo "$tool missing" exit 1 fi done export TAR FETCH if [ -z "$is_netbsd" ]; then if [ -n "$MINIMIZE" ] || [ -f "service/${svc}/NETBSD_ONLY" ]; then printf "\nThis image must be built on NetBSD!\n" printf "Use the image builder instead: make SERVICE=$svc build\n" exit 1 fi # we're on native NetBSD, disk scan only on build image elif [ -n "$is_buildimg" ]; then disks="$(sysctl -n hw.disknames)" # A secondary disk was passed, record disk that has no wedges # $imgdev is the image device passed as second disk if [ "$(echo \"$disks\"|wc -w)" -gt 2 ]; then for disk in $disks do dkctl $disk listwedges 2>&1 | grep -q 'no wedges' && \ imgdev="${disk}" done fi fi [ -n "$is_linux" ] && u=M || u=m # inherit from another image if [ -n "$FROMIMG" ]; then echo "${ARROW} using ${FROMIMG} as base image" [ -z "$imgdev" ] && cp images/${FROMIMG} ${img} || \ dd if=images/${FROMIMG} of="${imgdev}" bs=1${u} else # only create image if secondary was not passed [ -z "$imgdev" ] && \ dd if=/dev/zero of=./${img} bs=1${u} count=${megs} fi # are we building the builder or a service (secondary drive passed as param) [ -z "$imgdev" ] && mnt=$(pwd)/mnt || mnt=${DRIVE2} wedgename="${svc}root" ffsmountopts="noatime" if [ -n "$is_linux" ]; then # no other image than builder image are ext2, don't check for FROMIMG sgdisk --zap-all ${img} || true # atribute 59 is "bootme" from /usr/include/sys/disklabel_gpt.h sgdisk --new=1:0:0 --typecode=1:8300 --change-name=1:"$wedgename" \ --attributes=1:set:59 ${img} # GitHub actions can't create loopXpY, use an offset offset=$(sgdisk -i 1 ${img} | awk '/First sector/ {print $3}') vnd=$(losetup -f --show -o $((offset * 512)) ${img}) mke2fs -O none ${vnd} mount ${vnd} $mnt mountfs="ext2fs" #elif [ -n "$is_freebsd" ]; then # imgdev="$(mdconfig -l -f $img || mdconfig -f $img)" # newfs -o time -O1 -m0 /dev/${imgdev} # mount -o noatime /dev/${imgdev} $mnt # mountfs="ffs" else # NetBSD if [ -z "$imgdev" ]; then # no secondary disk, create a vnd imgdev=$(vndconfig -l|grep -m1 'not'|cut -f1 -d:) vndconfig $imgdev $img vnd=$imgdev # for later detach fi mountfs="ffs" getwedge() { mountdev=$(dkctl ${1} listwedges|sed -n "s/\(dk.*\):\ ${wedgename}.*/\1/p") if [ -z "$mountdev" ]; then echo "${ERROR} no wedge, exiting" exit 1 fi echo "$mountdev" } if [ -z "$FROMIMG" ]; then gpt create ${imgdev} gpt add -a 512k -l "$wedgename" -t ${mountfs} ${imgdev} gpt set -a bootme -i 1 ${imgdev} eval $(gpt show ${imgdev}|awk '/NetBSD/ {print "startblk="$1; print "blkcnt="$2}') mountdev=$(getwedge ${imgdev}) newfs -O1 -m0 /dev/${mountdev} else mountdev=$(getwedge ${imgdev}) fi # sailor shrink is too agressive, journal is lost [ -z "$MINIMIZE" ] && ffsmountopts="${ffsmountopts},log" mount -o ${ffsmountopts} /dev/${mountdev} $mnt fi [ -f "service/${svc}/sailor.conf" ] && use_sailor=1 # additional packages for pkg in ${ADDPKGS}; do # case 1, artefacts created by the builder service # minimization of the image via sailor is requested # we need packages cleanly installed via pkgin if [ -f /tmp/usrpkg.tgz ] && [ -n "$use_sailor" ]; then echo "${ARROW} unpacking minimal env for sailor" # needed to re-create packages with pkg_tarup tar xfp /tmp/usrpkg.tgz -C / pkgin -y in $ADDPKGS # exit the loop, packages have been installed cleanly break fi # case 2, no need for recorded, cleanly installed packages # simply untar them to LOCALBASE pkg="pkgs/${arch}/${pkg}.tgz" [ ! -f ${pkg} ] && continue eval $($TAR xfp $pkg -O +BUILD_INFO|grep ^LOCALBASE) echo -n "extracting $pkg to ${LOCALBASE}.. " mkdir -p ${mnt}/${LOCALBASE} $TAR xfp ${pkg} --exclude='+*' -C ${mnt}/${LOCALBASE} || exit 1 echo done done # minimization of the image via sailor is requested if [ -n "$MINIMIZE" ] && [ -n "$use_sailor" ] && \ [ -d /var/db/pkgin ]; then echo "${ARROW} minimize image" rm -rf ${mnt}/var/db/pkg* cd ${BASEPATH}/sailor export TERM=vt220 PKG_RCD_SCRIPTS=YES ./sailor.sh build /service/${svc}/sailor.conf cd .. # root fs is hand made elif [ -n "$rootdir" ]; then $TAR cfp - -C "$rootdir" . | $TAR xfp - -C ${mnt} # use sets and customizations in services/ else for s in ${sets} ${ADDSETS} do partial= # we want to extract only part of the archive if [ "$s" != "${s%:*}" ]; then partial=".${s#*:}" s=${s%:*} fi # don't prepend sets path if this is a full path case $s in */*) ;; *) s="sets/${arch}/${s}" ;; esac echo -n "extracting ${s}.. " $TAR xfp ${s} -C ${mnt}/ ${partial} || exit 1 echo done done fi # $rootdir can be relative, don't cd mnt yet for d in sbin bin dev etc/include do mkdir -p ${mnt}/$d done [ -n "$rofs" ] && mountopt="ro" || mountopt="rw" if [ "$mountfs" = "ffs" ]; then mountopt="${mountopt},${ffsmountopts}" fi echo "NAME=${wedgename} / $mountfs $mountopt 1 1" > ${mnt}/etc/fstab rsynclite service/${svc}/etc/ ${mnt}/etc/ rsynclite service/common/ ${mnt}/etc/include/ [ -d service/${svc}/packages ] && \ rsynclite service/${svc}/packages ${mnt}/ [ -n "$kernel" ] && cp -f $kernel ${mnt}/netbsd # enter the mounted image root cd $mnt if [ "$svc" = "rescue" ]; then for b in init mount_ext2fs do ln -s /rescue/$b sbin/ done ln -s /rescue/sh bin/ fi # warning, postinst operations are done on the builder [ -d ../service/${svc}/postinst ] && \ for x in $(set +f; ls ../service/${svc}/postinst/*.sh) do # if SVCIMG variable exists, only process its script if [ -n "$SVCIMG" ]; then [ "${x##*/}" != "${SVCIMG}.sh" ] && continue echo "SVCIMG=$SVCIMG" > etc/svc fi echo "executing $x" [ -f $x ] && sh $x done # we don't need to hack our way around MAKEDEV on NetBSD / builder if [ -z "$is_netbsd" ]; then # newer NetBSD versions use tmpfs for /dev, sailor copies MAKEDEV from /dev # backup MAKEDEV so imgbuilder rc can copy it cp dev/MAKEDEV* etc/ # unionfs with ext2 leads to i/o error sed -i'' 's/-o union//g' dev/MAKEDEV fi # record wanted pkgsrc version echo "PKGVERS=$PKGVERS" > etc/pkgvers # proceed with caution [ -n "$curlsh" ] && curl -sSL "$CURLSH" | /bin/sh [ -n "$MINIMIZE" ] && \ rm -rf var/db/pkgin # wipe pkgin cache and db # QEMU fw_cfg mountpoint mkdir -p var/qemufwcfg if [ -n "$biosboot" ]; then cp /usr/mdec/boot ${mnt} cat >${mnt}/boot.cfg</dev/null echo "$((disksize * 512))" > ${BASEPATH}/tmp/${img##*/}.size fi #[ -n "$is_freebsd" ] && mdconfig -d -u $vnd [ -n "$is_linux" ] && losetup -d $vnd if [ -n "$is_netbsd" ]; then if [ -n "$biosboot" ]; then gpt biosboot -i 1 ${imgdev} installboot -v /dev/r${mountdev} /usr/mdec/bootxx_ffsv1 fi [ -n "$vnd" ] && vndconfig -u $vnd fi exit 0 ================================================ FILE: mnt/.gitkeep ================================================ ================================================ FILE: scripts/app-run.sh ================================================ #!/bin/sh # # See app/README.md for documentation # progname=${0##*/} progdir=${0%/*} cwd=$(realpath $progdir) set -euf ( cd $cwd/../app; # initial setup if [ ! -f bin/activate ]; then python3 -m venv . . bin/activate pip install -r requirements.txt fi . bin/activate # Set Flask env vars defaults FLASK_APP=${FLASK_APP:-app} \ FLASK_RUN_PORT=${FLASK_RUN_PORT:-5000} \ FLASK_RUN_HOST=${FLASK_RUN_HOST:-127.0.0.1} \ FLASK_ENV=${FLASK_ENV:-development} \ FLASK_DEBUG=${FLASK_DEBUG:-True} \ FLASK_LOGLEVEL=${FLASK_LOGLEVEL:-20} \ FLASK_CWD=$cwd/.. \ flask run "$@" #python3 app.py ) ================================================ FILE: scripts/fetch.sh ================================================ #!/bin/sh flag=$1 if [ "$flag" = "-o" ]; then outfile=$2 fullurl=$3 else fullurl=$2 fi case $fullurl in *\**) dldir=${outfile%/*} # pkgs/amd64/pkgin.tgz fetchurl=${fullurl%/*} matchfile=${fullurl##*/} matchfile=${matchfile%\*} curl ${CURL_FLAGS} -L -s "$fetchurl" | grep -oE "\"${matchfile}-[^\"]*(xz|gz)" | \ while read f; do f=${f#\"} # mimic NetBSD's ftp parameters destfile=${f%-*}.${f##*.} [ "$flag" = "-o" ] && curlaction="-o ${dldir}/${destfile}" || \ curlaction="-I" curl ${CURL_FLAGS} -L -s ${curlaction} ${fetchurl}/${f} exit 0 done ;; *) [ "$flag" = "-o" ] && curlaction="-o $outfile" || curlaction="-I" curl ${CURL_FLAGS} -L -s $curlaction $fullurl ;; esac ================================================ FILE: scripts/freshchk.sh ================================================ #!/bin/sh # if the host does not have internet access [ -n "$NONET" ] && exit 0 . service/common/choupi URL=$1 DEST=$2 FILE=${1##*://} FILE=${FILE%\*} # clean wildcard from http://foo/bar* URL DIR=${FILE%/*} FETCH=$(dirname $0)/fetch.sh mkdir -p db/$DIR remotefile=$($FETCH -I $URL | \ sed -E -n 's/last-modified:[[:blank:]]*([[:print:]]+)/\1/ip' | \ base64 || echo 0) localfile=$(cat db/$FILE 2>/dev/null || echo 0) if [ -s "${DEST}" ] && [ "$remotefile" = "$localfile" ]; then echo "${CHECK} ${FILE##*/} is fresh" exit 0 fi echo "$remotefile" > db/$FILE echo "${ARROW} fetching ${FILE##*/}" exit 1 ================================================ FILE: scripts/sh ================================================ #!/bin/sh # smolBSD quick bootstrapper # Usage: curl -fsSL https://smolbsd.org/sh | sh set -eu VERS="11" CURL="curl -fsSL" KERNEL=smol SMOLBSD_BASE="https://smolbsd.org" GH_BASE="https://github.com/NetBSDfr/smolBSD/releases/download/latest" RAWGH="https://raw.githubusercontent.com/NetBSDfr/smolBSD/refs/heads/main" START_URL="${RAWGH}/startnb.sh" UNAME="scripts/uname.sh" CHOUPI="service/common/choupi" TMPDIR="$(mktemp -d -t smolbsd.XXXXXX)" cleanup() { rm -rf "$TMPDIR"; } trap cleanup EXIT INT TERM cd "$TMPDIR" mkdir -p ${CHOUPI%/*} ${CURL} "${RAWGH}/${CHOUPI}" -o ${CHOUPI} CHOUPI=y . ${CHOUPI} echo "${ARROW} using temporary directory: $PWD" mkdir -p scripts ${CURL} "${RAWGH}/${UNAME}" -o ${UNAME} chmod +x ${UNAME} ARCH=$(${UNAME} -m) MACHINE=$(${UNAME} -p) if ! command -v qemu-system-${MACHINE} >/dev/null 2>&1; then echo "${ERROR} qemu-system-${MACHINE} is needed to run smolBSD" >&2 exit 1 fi OS=$(uname -s) echo "${ARROW} checking virtualization capability" if [ ! -e /dev/kvm ] && \ [ ! "$OS" = "Darwin" ] && \ ! nvmmctl identify >/dev/null 2>&1; then echo "${WARN} hardware virtualization (KVM/HVF) not detected — performance may be limited." >&2 fi echo "${ARROW} downloading kernel" case "$ARCH" in amd64) KERNEL_URL="$SMOLBSD_BASE/assets/netbsd-SMOL" ${CURL} ${KERNEL_URL} -o $KERNEL ;; evbarm-aarch64) KERNEL_URL="https://nycdn.netbsd.org/pub/NetBSD-daily/netbsd-${VERS}/latest/evbarm-aarch64/binary/kernel/netbsd-GENERIC64.img.gz" ${CURL} -o- ${KERNEL_URL} | gzip -d > $KERNEL ;; *) echo "${ERROR} Unsupported architecture: $ARCH" >&2 exit 1 ;; esac RESCUE_URL="$GH_BASE/rescue-${ARCH}.img.xz" echo "${ARROW} downloading rescue image" ${CURL} "$RESCUE_URL" | xz -d > rescue.img ${CURL} "$START_URL" -o smol.sh chmod +x smol.sh echo "${ARROW} launching smolBSD, Ctrl-A X to exit" exec /dev/null || echo unknown) case $ARCH in x86_64|amd64) arch="amd64" machine="x86_64" ;; i386|i486|i586|i686) arch="i386" machine="i386" ;; *aarch64*|arm64|armv8*) arch="evbarm-aarch64" machine="aarch64" ;; *) arch="$ARCH" machine="$ARCH" ;; esac case $arg in -m) echo $arch ;; -p) echo $machine ;; *) echo "unknown flag" ;; esac } [ "${0##*/}" = "uname.sh" ] && unamesh $arg || exit 1 exit 0 ================================================ FILE: service/base/etc/rc ================================================ #!/bin/sh . /etc/include/basicrc . /etc/include/mount9p ksh . /etc/include/shutdown ================================================ FILE: service/base/postinst/dostuff.sh ================================================ #!/bin/sh echo "iMil was here" > tmp/postinst.txt ================================================ FILE: service/biosboot/README.md ================================================ # BIOS Boot Service ## About This image is meant to build a _BIOS_ bootable image, for use with _Virtual Machine Managers (VMM)_ that does not support _PXE boot_. It can also be used to setup a bootable device like an _USB_ key. Of course, speed will suffer from the typical multi-stage _x86_ boot (_BIOS, bootloader, kernel loading generic kernel_). ## Usage **Build** ```sh $ bmake SERVICE=biosboot build ``` If you wish to boot with a serial console, set the `BIOSCONSOLE` variable to `com0`: ```sh $ bmake SERVICE=biosboot BIOSCONSOLE=com0 build ``` **Run** - _QEMU_ example ```sh $ qemu-system-x86_64 -accel kvm -m 256 -cpu host -hda images/biosboot-amd64.img ``` - _USB_ key example ```sh $ [ "$(uname -s)" = "Linux" ] && unit=M || unit=m $ dd if=images/biosboot-amd64.img of=/dev/keydevice bs=1${unit} ``` And legacy boot on the _USB_ device. ================================================ FILE: service/biosboot/etc/rc ================================================ #!/bin/sh . /etc/include/basicrc . /etc/include/mount9p ksh . /etc/include/shutdown ================================================ FILE: service/biosboot/options.mk ================================================ BIOSBOOT=y BIOSCONSOLE=pc ================================================ FILE: service/bozohttpd/etc/rc ================================================ #!/bin/sh . /etc/include/basicrc /usr/libexec/bozohttpd -f /var/www . /etc/include/shutdown ================================================ FILE: service/bozohttpd/postinst/mkhtml.sh ================================================ wwwroot=var/www mkdir -p $wwwroot echo "up!" >${wwwroot}/index.html ================================================ FILE: service/bsdshell/sailor.conf ================================================ . /service/common/sailor.vars shipname=usershell shipbins="$shipbins /usr/bin/chsh /bin/ksh /usr/bin/ssh /usr/bin/ssh-keygen /usr/bin/less /usr/bin/vi /usr/bin/ktruss /usr/bin/resize /usr/bin/reset" ================================================ FILE: service/build/etc/rc ================================================ . /etc/include/vars . /etc/include/choupi . /etc/include/basicrc . /etc/include/mount9p # needed to fetch HTTPS mount -t tmpfs -o -s1M tmpfs /etc/openssl mount -t tmpfs -o -s32M tmpfs /tmp cp /usr/share/examples/certctl/certs.conf /etc/openssl/ certctl rehash cd $BASEPATH . tmp/build* export ADDPKGS # some packages *cough* node *cough* need more files sysctl -w kern.maxfiles=20000 # MIMINIZE this image using sailor if [ -n "$MINIMIZE" ] && [ -f service/${SERVICE}/sailor.conf ]; then if [ ! -d sailor ]; then echo "${WARN} maximum minimization asked but sailor not available" echo "${WARN} git clone https://github.com/NetBSDfr/sailor first!" else echo "${ARROW} preparing env for sailor" mount -t tmpfs -o -s100M tmpfs /var/db mount -t tmpfs -o -s20M tmpfs /var/run # backup raw packages for later build tar cfp /tmp/usrpkg.tgz /usr/pkg mount -t tmpfs -o -s300M tmpfs /usr/pkg fi fi echo "${ARROW} building $SERVICE image" echo "${INFO} exported variables" cat tmp/build*|sed "s/^/${WHITEBULLET} /" make $(grep -v ADDPKGS tmp/build*|xargs) base echo "${CHECK} build finished, Ctrl-A X to exit" rm -f tmp/build* cat ================================================ FILE: service/build/etc/resolv.conf ================================================ nameserver 10.0.2.3 ================================================ FILE: service/build/options.mk ================================================ MOUNTRO=y ADDPKGS=pkgin pkg_tarup pkg_install sqlite3 ================================================ FILE: service/build/postinst/prepare.sh ================================================ #!/bin/sh mkdir -p usr/pkg/etc/pkgin echo "https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/${ARCH#evbarm-}/${PKGVERS}/All" > \ usr/pkg/etc/pkgin/repositories.conf touch BUILDIMG mkdir ${DRIVE2#/} # mkimg searches for ../service ln -sf /mnt/service . ================================================ FILE: service/clawd/LOCAL.md ================================================
**Running smolClaw with a local inference server**
As of 2026-03 I use [this model](https://huggingface.co/Jackrong/Qwen3.5-9B-Claude-4.6-Opus-Reasoning-Distilled-GGUF) with an RTX 5080 (16GB). Refer to [this recipe][1] to fix the local model for tooling. I start [llama.cpp with CUDA][2] like this: ```sh ./llama-server -hf Jackrong/Qwen3.5-9B-Claude-4.6-Opus-Reasoning-Distilled-GGUF:Q4_K_M -ngl 99 -c 262144 -np 1 -fa on --cache-type-k q4_0 --cache-type-v q4_0 --chat-templat e-file qwen3.5_chat_template.jinja --port 8001 --host 0.0.0.0 ``` Example [picoclaw][3] `config.json`, modify: * `YOUR_TELEGRAM_TOKEN`, on Telegram, create a `/newbot` speaking to `@BotFather` * Ollama's IP address ```json { "agents": { "defaults": { "workspace": "~/.picoclaw/workspace", "restrict_to_workspace": false, "model": "qwen3.5", "max_tokens": 8192, "temperature": 0.7, "max_tool_iterations": 20 } }, "model_list": [ { "model_name": "qwen3.5", "model": "ollama/Jackrong/Qwen3.5-9B-Claude-4.6-Opus-Reasoning-Distilled-GGUF:Q8_0", "api_base": "http://192.168.1.1:8001/v1", "api_key": "-" } ], "channels": { "telegram": { "enabled": true, "token": "YOUR_BOT_TOKEN", "allow_from": ["YOUR_USER_ID"] } }, "gateway": { "host": "0.0.0.0", "port": 18790 }, "tools": { "web": { "brave": { "enabled": false, "api_key": "", "max_results": 5 }, "duckduckgo": { "enabled": true, "max_results": 5 } } }, "devices": { "enabled": false, "monitor_usb": false }, "heartbeat": { "enabled": true, "interval": 30 } } ``` Refs: * https://x.com/sudoingX/status/2028496331992707373 * https://x.com/sudoingX/status/2030253886649569299 [1]: https://gist.github.com/sudoingX/c2facf7d8f7608c65c1024ef3b22d431 [2]: https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md#cuda [3]: https://github.com/sipeed/picoclaw ================================================ FILE: service/clawd/README.md ================================================
# smolClaw [picoclaw][1] running on a microVM!
**smolClaw** is a [smolBSD][2] microVM appliance that runs [picoclaw][1] on Linux or macOS. Running picoclaw inside a microVM provides: * Minimal footprint * Strong isolation of memory and filesystem * Fast startup (under one second) As per the [smolBSD][2] design, the VM boots directly into a `tmux` console with default bindings running `bash`. # Quickstart * Fetch [smolBSD][2] and install dependencies ```sh git clone https://github.com/NetBSDfr/smolBSD ``` Debian, Ubuntu and the like ```sh sudo apt install curl git bmake qemu-system-x86_64 binutils uuid-runtime libarchive-tools gdisk socat jq lsof picocom ``` macOS ```sh brew install curl git bmake qemu binutils libarchive socat jq lsof picocom ``` * Go to the `smolBSD` directory and ```sh cd smolBSD ``` * Pull the ghcr.io _smolClaw_ image (`amd64` and `evbarm-aarch64` images available) ```sh ./smoler.sh pull clawd-amd64:latest ``` * OR build it yourself ```sh ./smoler.sh build -y dockerfiles/Dockerfile.clawd ``` * Run the microVM ```sh ./smoler.sh run clawd-amd64:latest -c 2 -m 1024 ``` Options: - `-c` → CPU cores - `-m` → RAM in MB To share a host directory: ```sh ./smoler.sh run clawd-amd64:latest -c 2 -m 1024 -w /path/to/directory ``` Inside the VM it will be mounted at: ``` /mnt ``` * Once the microVM has started, begin onboarding ```sh [~]@😈+🦞> picoclaw onboard ``` * When the configuration is finished, start the gateway ```sh [~]@😈+🦞> picoclaw gateway ``` [picoclaw][1] Quickstart is available [here](https://github.com/sipeed/picoclaw/?tab=readme-ov-file#-quick-start) ## SSH access * You can build _smolClaw_ with your _SSH_ public key just by copying it to `share/ssh.pub` * You can also do it once the microvm is started but you'll have to re-do it at every build: ```sh [~]@😈+🦞> mkdir -p ~/.ssh && cat >~/.ssh/authorized_keys ``` Just paste your public key and press Ctrl+D, then ```sh [~]@😈+🦞> chmod 600 ~/.ssh/authorized_keys ``` In both cases, you'll be able to _SSH_ to your _smolClaw_ instance like this: ```sh $ ssh -p 2289 clawd@localhost ``` --- ### Troubleshooting "Could not access KVM kernel module: No such file or directory" The error says KVM (hardware virtualization) isn't available. This usually means either: 1. KVM module isn't loaded: ```sh sudo modprobe kvm kvm_intel # for Intel CPUs # or sudo modprobe kvm kvm_amd # for AMD CPUs ``` Then check it's there: ```sh ls /dev/kvm ``` 2. Virtualization is disabled in your BIOS/UEFI If modprobe fails, reboot into BIOS and enable "Intel VT-x" or "AMD-V" (sometimes called "SVM Mode"). It's usually under CPU settings or Advanced. You can check if your CPU supports it at all with: ```sh grep -Ec '(vmx|svm)' /proc/cpuinfo ``` If that returns 0, either it's disabled in BIOS or your CPU doesn't support it. 3. Are you inside a VM already? If you're running Ubuntu inside a VM (like VirtualBox or VMware), you need to enable nested virtualization in the hypervisor's settings. For VirtualBox that's "Enable Nested VT-x/AMD-V" in the VM's system settings. [1]: https://github.com/sipeed/picoclaw [2]: https://smolBSD.org ================================================ FILE: service/clawd/SCHIZOCLAW.md ================================================ # Schizo setup While it's on [the roadmap][1], [picoclaw][2] still don't have multi-agents support. Also, I intend to use it [locally][3] with a single _GPU_, but have various topics where a personal assistant could be useful. So I thought out of the box and remembered the movie [Split][4]. That's right, I made my [smolClaw][5] instance **schizophrenic**. ## The main driver: SOUL.md ```markdown ## Schizophrenia You are schizophrenic, and depending on the subject you're asked for, you'll endorse one of the following personalities: ### Personality: Jimmy You are a talented, experienced fronotend developer, very picky in his programming style, precise with deep knowledge of the best practices and coding style. You are fluent with HTML and JavaScript, VueJS, nodejs, jQuery and all things web. ### Personality: Ken When asked about UNIX knowledge and system administration, you are the embodiement of an old UNIX guru, know-it-all and arrogant. Your vast knowledge of the subject allows you to deep dive on all UNIX related topics. ### Personality: R2 When ask about a tooling task, you embody the R2D2 robot personality. You don't use human phrases but you display the results of commands, scripts, scripts, instructions, network operations you execute. You litteraly are a deterministic, fault-free robot that focuses on its task with extreme precision. ### Personality: Doc When asked about medical topics, you endorse a paternalist doctor behavior. You don't play by the rules, you are up-to-date with all recent longevity research, have a deep knowledge of themes like autophagy, supplements, keto diet etc... You also have a deep knowledge of the nootropic topic, their effects, nature. ``` ## Refinements: IDENTITY.md and AGENT.md ### IDENTITY.md ```markdown ## Schizophrenic personalities - Adapt your discourse to the personality you're summoned with These are personalities specifics: * Ken - As the old UNIX guru, you have access to all commands in the system - You never do mistakes - When a man page about a topic exists in /usr/share/man, first read it and use it as reference - When reviewing C code, always refer to /usr/share/misc/style * Doc - When you don't have the exact answer, you search on https://pubmed.ncbi.nlm.nih.gov/ * R2 - You can't write to the filesystem ``` ### AGENT.md ```markdown ## Personnalities - According to either the name you are called with or the topic the user is referring to, you will change your personality as specified in the SOUL.md file. - You will never change personality by yourself, only when requested either by the name the user callls you with ot the topic he's referring to. - When your persinality is a programmer and asked to create a program, write the program in `/home/clawd/.picoclaw/workspace/code//`, example: `/home/clawd/.picoclaw/workspace/code/golang/myprogram/myprogram.go` ``` # Setting up a micro web server for smolClaw With this team at our disposal, let's setup a simple website publishing workflow, but contain it in dedicated microVMs so it doesn't wipe our beloved websites by mistake. ## smolClaw SSH configuration On _smolClaw_, create an `ssh` public key with empty passphrase (just hit enter), and copy the content of `~/.ssh/id_ed25519.pub` to your clipboard. Create the following `ssh` client configuration: `~/.ssh/config` ```txt host smolweb hostname 192.168.1.20 # change this to your host IP user smol port 2121 ``` ## smolweb microvm Now create the `smolweb` microvm that will host the website `dockerfiles/Dockerfile.smolweb` ```Dockerfile FROM base,etc LABEL smolbsd.service=smolweb LABEL smolbsd.publish="8880:8880" ARG PUBKEY="/mnt/share/ssh.pub" RUN pkgin up && pkgin -y in caddy RUN <[!Note] > _smolBSD_ directory is mounted as `/mnt` in the builder machine Build the `smolweb` microVM: ```sh $ ./smoler.sh build dockerfiles/Dockerfile.smolweb ``` And start your micro web server, forwarding ports `8880` (`caddy`) and `2121` (mapped to `22`/`ssh`): ```sh $ ./startnb.sh -i images/smolweb-amd64.img -p ::8880-:8880,::2121-:22 ``` ## Instruct your web dev agent how to upload files ``` > Ken, remember to use the following command when I ask you to publish to `smolweb`: tar -cf - -C /home/clawd/.picoclaw/workspace/www shell.html | ssh smolweb 'tar -xf - -C ~/www' ``` [1]: https://github.com/sipeed/picoclaw/issues/294 [2]: https://github.com/sipeed/picoclaw [3]: https://github.com/NetBSDfr/smolBSD/blob/main/service/clawd/LOCAL.md [4]: https://www.imdb.com/title/tt4972582/ [5]: https://github.com/NetBSDfr/smolBSD/blob/main/service/clawd/README.md ================================================ FILE: service/common/basicrc ================================================ export HOME=/ export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin umask 022 if [ "$(sysctl -n kern.root_device)" = "md0" ]; then mount -u -o rw /dev/md0a / sed -i'' 's,^[^ ]*,/dev/md0a,' /etc/fstab fi mount -a # custom local preload [ -f /etc/rc.pre ] && . /etc/rc.pre # load QEMU fw_cfg variables is any dmesg | grep -q qemufwcfg && \ . /etc/include/qemufwcfg || \ echo "no qemufwcfg support" [ ! -f "/dev/MAKEDEV" ] && cp -f /etc/MAKEDEV* /dev # bits needed for a really usable tty mount -t ptyfs ptyfs /dev/pts cd /dev && ./MAKEDEV fd && cd - /etc/rc.d/ttys start chmod 666 /dev/null # create an additional viocon(4) port if support available dmesg | grep -q viocon && \ (cd /dev && ./MAKEDEV ttyVI01 ttyVI02 && chmod 666 /dev/ttyVI*) || \ echo "no viocon(4) support" # set IP address manually, faster than dhcp if ifconfig vioif0 >/dev/null 2>&1; then route flush -inet6 >/dev/null 2>&1 # default qemu addresses and routing ifconfig vioif0 10.0.2.15/24 route add default 10.0.2.2 mount | grep read-only || \ echo "nameserver 10.0.2.3" > /etc/resolv.conf fi ifconfig lo0 127.0.0.1 up # sane network defaults # https://wiki.netbsd.org/tutorials/tuning_netbsd_for_performance/ sysctl -w net.inet.tcp.sendbuf_max=16777216 sysctl -w net.inet.tcp.recvbuf_max=16777216 sysctl -w kern.sbmax=16777216 [ -n "$MOUNTRO" ] && mount -u -o ro / # custom local post load [ -f /etc/rc.local ] && . /etc/rc.local ================================================ FILE: service/common/choupi ================================================ case $CHOUPI in *) STAR="⭐" ARROW="➡️" CHECK="✅" WARN="⚠️" INFO="ℹ️" WHITEBULLET="⚪" ERROR="❌" CPU="🔲" FREEZE="🧊" OUT="/dev/null" ROCKET="🚀" SOCKET="🔌" EXIT="🔚" ;; [nN]) STAR="*" ARROW=">" CHECK='\' WARN='!' INFO='i' WHITEBULLET="o" ERROR="X" CPU="[]" FREEZE="=" OUT="/dev/stdout" ROCKET="&>" SOCKET="-<" EXIT="->]" ;; esac LIGHTGREEN="\033[92m" BOLD="\033[1m" NORMAL="\033[0m" ================================================ FILE: service/common/funcs ================================================ # helper functions rsynclite() { src=$1 dst=$2 while case "$src" in --exclude=*) true;; *) false;; esac; do exclude="${src#*=}" src="${exclude#* }" exclude="${exclude%% *}" toexclude="${toexclude} --exclude=${exclude}" done if [ -f "$src" ]; then # ensure dest directory exists [ "${dst%/*}" != "$dst" ] && mkdir -p "${dst%/*}" cp -f "$src" "$dst" return fi [ ! -d "$src" ] && return [ ! -d "$dst" ] && mkdir -p "$dst" (cd "$src" && tar cfp - ${toexclude} .)|(cd "$dst" && tar xfp -) } ================================================ FILE: service/common/mount9p ================================================ if dmesg |grep -q vio9; then [ -z "$MOUNT9P" ] && MOUNT9P=/mnt [ -f /etc/MAKEDEV ] && cp /etc/MAKEDEV /dev cd /dev && sh MAKEDEV vio9p0 cd - mount_9p -Ccu /dev/vio9p0 $MOUNT9P echo "➡️ host filesystem mounted on $MOUNT9P" fi ================================================ FILE: service/common/pkgin ================================================ . /etc/include/choupi # install necessary packages if [ ! -d /var/db/pkgin ]; then case $CHOUPI in [yY]*) printf "\n✨ preparing initial environment...\n" ;; *) v="-v" ;; esac printf "\n${STAR} installing needed packages\n" [ -f /etc/pkgvers ] && . /etc/pkgvers if [ -n "$PKGVERS" ]; then ver=$PKGVERS else ver=$(uname -r) case $ver in *99*) # current ver=${ver%.99*}.0 ;; *_*) # tag ver=${ver%_*} ;; esac fi # repository path differs for amd64 or aarch64/evbarm machine=$(uname -m) proc=$(uname -p) [ "$machine" = "amd64" ] && arch=$machine || arch=$proc url="netbsd.org/pub/pkgsrc/packages/NetBSD/${arch}/${ver}/All" certctl rehash scheme="https" if [ -n "$https_proxy" -o -n "$https_proxy" ]; then printf "${ARROW} using proxy: $http_proxy\n" # libfetch doesn't support HTTPS proxying scheme="http" fi url="${scheme}://cdn.${url}" echo "${ARROW} fetch URL: ${url}" for pkg in pkg_install pkgin pkg_tarup rsync curl # ca-certificates do printf "${ARROW} installing $pkg\n" pkg_info $pkg >/dev/null 2>&1 || \ pkg_add ${v} ${url}/${pkg}* done [ -d /packages ] && \ for pkg in /packages/* do pkg_add ${v} $pkg done mkdir -p /usr/pkg/etc/pkgin echo $url >/usr/pkg/etc/pkgin/repositories.conf pkgin up # additional packages to install for p in $INSTALL_PACKAGES do pkgin -y install $p done fi ================================================ FILE: service/common/qemufwcfg ================================================ QEMUFWCFG=/var/qemufwcfg /sbin/mount_qemufwcfg $QEMUFWCFG for file in ${QEMUFWCFG}/opt/org.smolbsd.var.* do [ ! -f $file ] && continue VARNAME=${file##*.} eval "export $VARNAME=\$(cat \$file)" done ================================================ FILE: service/common/sailor.vars ================================================ # smolBSD build drive shippath="/drive2" # default shipped binaries shipbins="/bin/sh /sbin/init /usr/bin/printf /sbin/mount /sbin/mount_ffs /sbin/mount_tmpfs /sbin/mount_ptyfs /bin/ls /sbin/mknod /sbin/ifconfig /usr/bin/nc /usr/bin/tail /sbin/poweroff /sbin/umount /sbin/fsck /sbin/fsck_ffs /usr/bin/netstat /sbin/dhcpcd /sbin/route /usr/sbin/certctl /usr/bin/vis /bin/expr /usr/bin/unvis /bin/mkdir /usr/bin/mktemp /usr/bin/awk /usr/bin/sort /bin/rm /usr/bin/openssl /bin/mv /bin/test /bin/ln /bin/rmdir /bin/pax /usr/bin/find /bin/hostname /usr/sbin/useradd /usr/bin/su /bin/df /usr/bin/du /usr/bin/wc /usr/bin/sed /usr/bin/uniq /usr/bin/xargs /usr/bin/cut /usr/bin/tr /sbin/dmesg /sbin/mount_qemufwcfg /bin/sync /bin/tar /sbin/halt /sbin/sysctl /usr/bin/grep /usr/bin/stat /sbin/ttyflags /usr/bin/login /usr/lib/security/*" sync_dirs="/etc /usr/share/certs /usr/share/examples/certctl/certs.conf ${prefix}/etc/pkgin /usr/share/misc/terminfo* /usr/share/zoneinfo /var/log" packages="curl rsync" ================================================ FILE: service/common/shutdown ================================================ sync; sync dmesg | grep -q 'viocon0: adding port' && \ ( mount -u -o ro / sync; sync echo 'JEMATA!' > /dev/ttyVI01 ) || \ umount -af # no viocon(4) support halt -lq ================================================ FILE: service/common/vars ================================================ BASEPATH=/mnt DRIVE2=/drive2 CHOUPI=y export BASEPATH DRIVE2 CHOUPI ================================================ FILE: service/crush/README.md ================================================ # 💥 Crush service ## 📖 About This microservice runs [crush](https://github.com/charmbracelet/crush), an AI-powered terminal assistant built by Charmbracelet. It provides a fully configured NetBSD microvm with crush pre-installed and ready to use. The service mounts your project directory at `/mnt` inside the microvm, allowing you to work on any project with crush's AI capabilities in a lightweight, fast-booting environment. ## 🎒 Prerequisites - A `crush.json` configuration file (see [crush docs](https://github.com/charmbracelet/crush)) - At least 512MB of memory recommended ## 🚀 Usage ### 🔨 Build the image ```sh $ ./smoler.sh build -y dockerfiles/Dockerfile.crush ``` Or pull the pre-built image: ```sh $ ./smoler.sh pull crush-amd64:latest ``` ### ▶️ Run with a project directory ```sh $ ./smoler.sh run crush-amd64:latest -m 1024 -w /path/to/project ``` The `-w` flag mounts `/path/to/project` at `/mnt` inside the microvm. The `-m 1024` allocates 1GB of memory. ### ⚡ Run with an inline crush config ```sh $ ./smoler.sh run crush-amd64:latest -E crush=/path/to/crush.json ``` This passes a `crush.json` file directly into the microvm at `/var/qemufwcfg/opt/org.smolbsd.file.crush`. ### 🔧 Run with a custom config file Copy your `crush.json` into the working directory before running: ```sh $ cp crush.json /path/to/project/ $ ./smoler.sh run crush-amd64:latest -m 1024 -w /path/to/project ``` ### 💻 Interactive shell (no crush) If no `crush.json` is found, the microvm drops to a `ksh` prompt. You can also start it directly: ```sh $ ./startnb.sh -f etc/crush.conf ``` ## 🛑 Exiting When shutting down the microvm, use **Ctrl-A Ctrl-X** to exit. ## ⚙️ Configuration The service runs as user `crush` with the following environment: - Working directory: `/home/crush` - Shell profile launches crush automatically on login - Tmux is configured (status bar disabled) - File descriptor limit set to 4096 ================================================ FILE: service/games/README.md ================================================ # games service Very useless service for [smolBSD](https://github.com/NetBSDfr/smolBSD) allowing to play text-based games. *⚠ Be careful: don't use it at work except if your boss is a good guy and agree you play during job time. ;-)* Do you want to spawn microvm in a sec to play ~~AAA~~ text-based games on *NetBSD* 🚩? It's now possible with this very amazing service! (I save your day, I know). It's a pack of 15 games with a lot of ~~3D-high-res-4k-textured polygones~~ text characters. Launch it and enjoy these ~~latest top of the art from video game industry~~ old text-based games! ## Usage Building on GNU/Linux or MacOS ```sh $ bmake SERVICE=games build ``` Building on NetBSD ```sh $ make SERVICE=games base ``` Edit `etc/games.conf` file as needed (example values in this files are a joke 🤡, leave it commented. Default smolBSD values will be used.), then, start the service: ```sh ./startnb.sh -f etc/games.conf ``` ![First screen](first_screen.png) Enter your name for high scores records and `Enter`. ![Main menu](main_menu.png) Choose your game with `up` and `down` arrows and press `Enter`. Many games quit with `q` and others with `Ctrl+c`. Press `q` to quit this application and `Ctrl+a x` to quit and close the microvm. ## Note This service install the `nbsdgames` package from NetBSD repo. The first launch is very slow due to this installation but the next will be very fast like smolBSD can do! Games can run faster editing `etc/games.conf` file to put more cores et more memory. (It's a joke 🤡) Made with ❤. ================================================ FILE: service/games/etc/rc ================================================ #!/bin/sh export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin . /etc/include/choupi if [ ! -f /usr/pkg/bin/nbsdgames ]; then echo -e "${ARROW} nbsdgames prerequisites installation ..." . /etc/include/basicrc > /dev/null 2>&1 . /etc/include/pkgin > /dev/null 2>&1 pkgin -y install nbsdgames > /dev/null 2>&1 fi export TERM=xterm-256color clear echo -e "\n\n${ROCKET} nbsdgames launching !\n\n" sleep 1 nbsdgames . /etc/include/shutdown ================================================ FILE: service/lhv-tools/README.md ================================================
# lhv-tools ## Introduction "lhv" stands for "Le Holandais Volant" aka [Timo VAN NEERDEN](https://lehollandaisvolant.net/tout/apropos.php) an IT guy. Here is a free translation from his site: >These online tools are intended to be useful and are for free use. They are without advertising and without trackers. No information entered in pages is recorded by the site; most scripts that do not transmit information to the site and work autonomously in your browser. Most of the tools are made by myself. Otherwise, the author or scripts used are mentioned on their page.` You can find tools to: - create QR-codes, - convert dates into several formats, - convert temperatures units, - generate passwords, - calculate checksums, - play 2048, Tetris, mahjong, etc..., - learn kanas, - see spectrum of an audio file, - find the RSS link of a youtube channel, - edit images, - etc... in your browser. More than 150 tools for multiple activities. Go to the [tools's page](https://lehollandaisvolant.net/tout/tools/) to get more information. This smolBSD service downloads and installs these tools during postinstall stage with bozohttp and php. ## Prerequisites Many tools from Le Hollandais Volant work with php. Essential/minimal packages to run them correctly are listed in the `options.mk` file. smolBSD downloads packages from [http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/x86_64/11.0/All/](http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/x86_64/11.0/All/). Check this page to know the version-named package of php you need. There is no meta-package to installing the latest version like `pkgin install php`. For now, it's `php84`, `php84-curl`, etc... Also, feel free to edit `etc/lhv-tools.conf` file as needed. ## Usage Building on GNU/Linux or MacOS ```sh $ bmake SERVICE=lhv-tools BUILDMEM=2048 build ``` Building on NetBSD ```sh $ make SERVICE=lhv-tools BUILDMEM=2048 base ``` Use `BUILDMEM=2048` at least, otherwise, the `tar` command could hang during postinstall. Start the service: ```sh ./startnb.sh -f etc/lhv-tools.conf ``` Finally, go to [http://localhost:8180](http://localhost:8180) and enjoy: ![homepage](capture.png) *The number of tools differes between this outdated screenshot and reality. It came from the archive and just stand for illustration.* Press `Ctrl+a x` to quit and close the microvm. Service made with ❤. ================================================ FILE: service/lhv-tools/etc/rc ================================================ #!/bin/sh export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin . /etc/include/choupi . /etc/include/basicrc > /dev/null 2>&1 echo "${ROCKET} bozohttpd launch ..." /usr/libexec/bozohttpd -f -x index.php -C .php /usr/pkg/libexec/cgi-bin/php84 /var/www/ . /etc/include/shutdown ================================================ FILE: service/lhv-tools/options.mk ================================================ IMGSIZE=650 ADDPKGS=p7zip curl libidn2 libunistring readline pcre2 libxml2 nghttp2 libidn php84 php84-mbstring php84-curl ================================================ FILE: service/lhv-tools/postinst/postinstall.sh ================================================ #!/bin/sh . /etc/include/choupi wwwroot="var/www" toolsdir="../tmp/lhv-tools" # To avoid "warning: TERM is not set" message and be able to vi files if needed. echo 'export TERM="vt100"' > etc/profile echo "${ARROW} php settings ..." mkdir -p usr/pkg/etc/php/8.4/ cp usr/pkg/share/examples/php/8.4/php.ini-production usr/pkg/etc/php/8.4/php.ini sed -i'' 's/;extension=curl/extension=curl/g' usr/pkg/etc/php/8.4/php.ini # Some .php files are not interpeted by bozohttpd when they are called with # 'foldername/' URL. The content of the file is sent as is to the browser. # The .bzremap file acts as rewriting rules to force the call of the # 'foldername/index.php' file presents into each problematic tool's folder. echo "${ARROW} bozo settings for some .php files" cat >${wwwroot}/.bzremap<&1 | awk '{printf "*"; fflush()}' if [ $? -eq 0 ]; then echo "| done" else echo -e "| ${ERROR} failed.\nExit" . etc/include/shutdown fi echo -n "${ARROW} un-taring |" tar -xvf ${toolsdir}/$(basename ${link%.7z}) -C ${wwwroot} 2>&1 | awk '{printf "*"; fflush()}' if [ $? -eq 0 ]; then echo "| done" else echo -e "| ${ERROR} failed.\nExit" . etc/include/shutdown fi echo "${ARROW} fix favicon" # -k because no certificates stuff used by curl is installed. ${FETCH} -o ${wwwroot}/favicon.ico https://lehollandaisvolant.net/index/icons/favicon-32x32.png -k echo "${ARROW} fix logo" sed -i'' 's,/index/logo-no-border.png,0common/lhv-384x384.png,g' ${wwwroot}/index.php sed -i'' 's,/index/logo-no-border.png,../0common/lhv-384x384.png,g' ${wwwroot}/*/index.php ${wwwroot}/cgu.php echo "${ARROW} fix archive link" sed -i'' 's,href="tools.tar.7z,href="https://lehollandaisvolant.net/tout/tools/tools.tar.7z,g' ${wwwroot}/cgu.php ${wwwroot}/index.php echo "${ARROW} fix footer" sed -i'' 's,,\n,g' ${wwwroot}/barcode/index.php sed -i'' 's,by Timo Van Neerden,by Timo Van Neerden,g' ${wwwroot}/*/index.php sed -i'' 's,Timo Van Neerden,Timo Van Neerden,g' ${wwwroot}/index.php ${wwwroot}/cgu.php # Cleanup if [ -d ${toolsdir} ]; then rm -fr ${toolsdir}; fi echo "${STAR} Enjoy !" ================================================ FILE: service/mport/etc/rc ================================================ #!/bin/sh . /etc/include/basicrc cd /dev && /bin/sh /etc/MAKEDEV ttyVI01 ksh . /etc/include/shutdown ================================================ FILE: service/nbakery/etc/fstab ================================================ NAME=nbakeryroot / ffs rw,log 1 1 ptyfs /dev/pts ptyfs rw 0 0 kernfs /kern kernfs rw,noauto 0 0 procfs /proc procfs rw,noauto 0 0 tmpfs /var/shm tmpfs rw,-m1777,-sram%25 0 0 ================================================ FILE: service/nbakery/etc/rc ================================================ #!/bin/sh . /etc/include/basicrc # why are fds not created by MAKEDEV -MM init? cd /dev && sh MAKEDEV fd cd - export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin export TERM=tmux-256color eval $(resize) # get current window size lightgreen="\033[92m" bold="\033[1m" normal="\033[0m" printbold() { printf "${bold}$1${normal}\n" } export NBUSER=nbuser export NBHOME=/home/${NBUSER} export TMUX_SOCKET=${NBHOME}/tmp/tmux export CHOUPI=y if [ ! -d ${NBHOME} ]; then . /etc/include/pkgin PACKAGES="bash git-base neovim curl doas ripgrep fd-find bat fzf gmake" if ! id NBUSER >/dev/null 2>&1; then printbold "⭐ creating user" useradd -m $NBUSER printbold "⭐ installing user packages" echo "➡️ $PACKAGES" pkgin -y in $PACKAGES export HOSTNAME_FG="blanc" export HOSTNAME_BG="violet" export HOME=${NBHOME} printbold "⭐ setup powerline.bash" curl -o- -s \ https://gitlab.com/-/snippets/4796585/raw/main/mkchoupi.sh | \ bash > ${NBHOME}/.bashoupi printbold "⭐ cloning tmux-power" mkdir -p ${NBHOME}/.tmux curl -s -Lo ${NBHOME}/.tmux/tmux-power.tmux \ https://raw.githubusercontent.com/wfxr/tmux-power/master/tmux-power.tmux sed -i.bak 's/status-right "$RS"/status-right ":: powered by 🚩 + smolBSD "/' \ ${NBHOME}/.tmux/tmux-power.tmux chmod +x ${NBHOME}/.tmux/tmux-power.tmux # nvim is the only editor I found that does not mess # with tab display in qemu stdio mode printbold "⭐ kawaï nvim" mkdir -p ${NBHOME}/.local/share/nvim/site/autoload curl -s -Lo ${NBHOME}/.local/share/nvim/site/autoload/plug.vim \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim mkdir -p ${NBHOME}/.config/nvim curl -s -Lo ${NBHOME}/.config/nvim/init.lua \ https://gitlab.com/-/snippets/4797133/raw/main/init.lua printbold "⭐ creating a sane bach_profile" chsh -s /usr/pkg/bin/bash ${NBUSER} cat >/home/${NBUSER}/.bash_profile</dev/null . \${HOME}/.bashoupi # download modules, ignore complains about modules not being installed [ ! -d \${HOME}/.local/share/nvim/plugged ] && \ nvim --headless +PlugInstall +qall 2>/dev/null if [ -d /tmp/tmux* -a ! -f /tmp/greetings ]; then [ -f /etc/smol.ansi ] && /bin/cat /etc/smol.ansi echo -e " Welcome to the (n)bakery! 🧁 💪 \${lightgreen}doas \${normal} to run command as \${bold}root\${normal} 📦 \${lightgreen}pkgin\${normal} to manage packages 🚪 \${lightgreen}exit\${normal} to cleanly shutdown, \${lightgreen}^a-x\${normal} to exit \${lightgreen}qemu\${normal} 🪟 you are inside a \${lightgreen}tmux\${normal} with prefix \${lightgreen}^q\${normal} " touch /tmp/greetings fi EOF echo "rm -f /tmp/greetings" > ${NBHOME}/.bash_logout printbold "⭐ customizing tmux" cat >${NBHOME}/.tmux.conf</usr/pkg/etc/doas.conf fi fi printbold "⭐ checking for exported filesystem" . /etc/include/mount9p hostname nbakery su - ${NBUSER} -c "tmux -u new" . /etc/include/shutdown ================================================ FILE: service/nbakery/etc/smol.ansi ================================================                                                                                                                                                                            c        c                                                                       k        x                                                                      ;k       .O                                                                      lx.      ;k.                                                                     xd.      lx'                                                                     Oo'      dd;       'c                                 ;'                         0o,      koc       Kd                                 xx,                        Koc.     Ooc      dNk,                                K0l.                       XooxxxxxOool.     0X0l'                              dWOl,                       xOooooooool,      kXK0l;.                           kNKxl;                        'dkkool:'.       .KOK0dc.  ;oxxdooc;,'...       .dNXKOcd.                           ;Kl.           cxxd,,x00Oxloolol;;;;;;,..  o0NXKKkldl                             Oc             . lKKo;cl::;;;;;;;;;;;;;,  XKKKOdlxl                              0l             ;K0:'...',;;;;;;,'.'.',;;. cOxoldd'                              .kl.           dXl.oKWMMXd.,;;,'kNMMMKo.,;,. .::.                             .lool:  :.       xX:.xMMWo;XWO ;,.NMMK:xNX0.,;,.....                             ..'',,. ..      ;N:; XMMO  ;.d'''oMMM; .; Oc.;;....'                              ,ccccc:,.      Ox;;.dMMWl  :x ;;.NMMK, .dO.,;;'.....                             .::::;;'       Xc;;;.:OXNXOl.;;;,.dKNNKOl.,;;;..;:cc:,.                          .';;;;,. .','. O;;;;;;,''',;;;;;;;;,''',;;;;;,.,;;;;;:ccc;'.                     .;;,,,,,  ',;, ;:;;;;;,..',,;;;;,,''. .;;;;;;...........,;;;,.                     .,,'   .....  ',;;;;;'        ..   .;;;;;,.......       ';;;.                    xdoc           ..''',,;.  ',,,'...,;;;,'........        .;;;.                    kooc           .clool:..;,'''',,;'..coooo;  ...       .,;;;.                     Oool.        .cool,'.', .,,,,,,,. xxc;;,';. .    ;lllc;;;,.                      Oool.       'dddoxKO.c:,. ...... .;:..x0KK0kl  coc;;;;;;;;.                      0ool'      :dddx0K '';'.'. ...  ;c:, Odcodddo;  .c;.;;,;;,.                      Kolo,    .loodkO..,;,.....     '...,,.:Oo:;;;cccc,..;' ,..                      .Kooo; .kXWWNXOc .,'.......  d. '....','.'...:c,..  .. .                      ':. ldl;. kMMMMMMMM0 .ldxxkkkd..d. l:..... oXMMN000XN; .'::colc:,.              'looc,'.';'.lxkkkkkxd..l:;,'.',:oo: ;dOOOx lWMMMMMMMMNl :ooooooool:                 .',;:cclc:::::::::clloooooooooooolc:,'..'clooddool: 'llcc:;,..                                ...''',,,,;;;;;;;;;;;;;;;;;,'.                                                                                                             ================================================ FILE: service/nbakery/options.mk ================================================ IMGSIZE=1024 ================================================ FILE: service/nitro/postinst/00-nitro.sh ================================================ #!/bin/sh # bare minimum mknod -m 600 dev/console c 0 0 mknod -m 666 dev/null c 2 2 mkdir -p packages if [ "$ARCH" = "amd64" ]; then # I keep the binary package updated VERSION=0.5 ${FETCH} -o packages/nitro.tgz https://imil.net/NetBSD/nitro-${VERSION}.tgz?$RANDOM else PKGARCH=${ARCH#evbarm-} ${FETCH} -o packages/nitro.tgz https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/${PKGARCH}/nitro-* fi PREFIX=usr/pkg mkdir -p ${PREFIX} $TAR zxvfp packages/nitro.tgz --exclude='+*' -C ${PREFIX} mv ${PREFIX}/sbin/nitro sbin/init for d in var/run/nitro ${PREFIX}/etc/nitro/SYS ${PREFIX}/etc/nitro/getty-0 do mkdir -p $d done ln -sf /var/run/nitro/nitro.sock ${PREFIX}/etc/nitro.sock cat >${PREFIX}/etc/nitro/SYS/setup<${PREFIX}/etc/nitro/getty-0/run<${PREFIX}/etc/nitro/getty-0/finish<etc/motd<> etc/fstab # we want sshd to be the main process echo 'sshd_flags="-D -e"' >> etc/rc.conf echo 'UseDNS no' >> etc/ssh/sshd_config # if running an agent ssh-add -L >etc/ssh/authorized_keys 2>/dev/null pubkeys="../service/nitrosshd/etc/*.pub" ls $pubkeys >/dev/null 2>&1 && \ cat $pubkeys >>etc/ssh/authorized_keys cat >${PREFIX}/etc/nitro/SYS/setup<${PREFIX}/etc/nitro/LOG/run</dev/console 2>&1 EOF chmod +x ${PREFIX}/etc/nitro/LOG/run cat >${PREFIX}/etc/nitro/sshd/run<&1 useradd -m ssh mkdir -p /home/ssh/.ssh [ -f /etc/ssh/authorized_keys ] && \ cp -f /etc/ssh/authorized_keys /home/ssh/.ssh || echo "/!\ NO PUBLIC KEY, copy your SSH public key in service/${SERVICE}/etc/ssh" chown -R ssh /home/ssh /etc/rc.d/sshd onestart EOF cat >${PREFIX}/etc/nitro/sshd/finish< etc/runit/2 sed 's,/command,/command:/usr/pkg/bin:/usr/pkg/sbin,' \ usr/pkg/share/examples/runit/openbsd/3 > etc/runit/3 chmod +x etc/runit/[123] install -m 0500 usr/pkg/sbin/runit* sbin/ mkdir -p etc/sv/getty-0 cat >etc/sv/getty-0/run<etc/sv/getty-0/finish<bin/rcd2run.sh<&1 DIR_PATH=\$(pwd) PROG_NAME=\$(basename "\$0") SERVICE_NAME=\$(basename "\$DIR_PATH") case "\$PROG_NAME" in run) ACTION="onestart" ;; finish) ACTION="onestop" ;; *) echo "unsupported" exit 1 ;; esac exec /etc/rc.d/"\$SERVICE_NAME" "\$ACTION" EOF chmod +x bin/rcd2run.sh ================================================ FILE: service/sshd/README.md ================================================ # SSHd service This microservice starts an _OpenSSH_ daemon. As it uses `union` `tmpfs` which is unsupported with `ext2`, it must be built with either a _NetBSD_ host or the [builder image][1]. Add the desired SSH public keys in the `service/sshd/etc` directory in file(s) ending with `.pub`. Building on GNU/Linux or MacOS ```sh $ bmake SERVICE=sshd build ``` Building on NetBSD ```sh $ make SERVICE=sshd base ``` Start the service: ```sh $ ./startnb.sh -f etc/sshd.conf ``` You can now `ssh` to the service using the `ssh` user: ```sh $ ssh -p 2022 ssh@localhost ``` By default it listens at port 2022, you can change it in `etc/sshd.conf`. [1]: https://github.com/NetBSDfr/smolBSD/tree/main/service/build ================================================ FILE: service/sshd/etc/rc ================================================ #!/bin/sh . /etc/include/basicrc mount -t tmpfs -o -s1M tmpfs /home mount -t tmpfs -o -s10M tmpfs /tmp mount -t tmpfs -o -s10M tmpfs /var/log mount -t tmpfs -o -s1M tmpfs /var/run mount -t tmpfs -o -s10M -o union tmpfs /etc useradd -m ssh mkdir -p /home/ssh/.ssh cp -f /etc/ssh/authorized_keys /home/ssh/.ssh chown -R ssh /home/ssh /etc/rc.d/sshd onestart . /etc/include/shutdown ================================================ FILE: service/sshd/options.mk ================================================ .if defined(MINIMIZE) && ${MINIMIZE} == y ADDPKGS=pkgin pkg_tarup pkg_install sqlite3 rsync curl .endif ================================================ FILE: service/sshd/postinst/keygen.sh ================================================ # taken from NetBSD's /etc/rc.d/sshd keygen="/usr/bin/ssh-keygen" umask 022 new_key_created=false while read type bits filename; do f="etc/ssh/$filename" case "${bits}" in -1) bitarg=;; 0) bitarg="${ssh_keygen_flags}";; *) bitarg="-b ${bits}";; esac "${keygen}" -t "${type}" ${bitarg} -f "${f}" -N '' -q && \ printf "ssh-keygen: " && "${keygen}" -f "${f}" -l new_key_created=true done << _EOF ecdsa -1 ssh_host_ecdsa_key ed25519 -1 ssh_host_ed25519_key rsa 0 ssh_host_rsa_key _EOF # we want sshd to be the main process echo 'sshd_flags="-D -e"' >> etc/rc.conf echo 'UseDNS no' >> etc/ssh/sshd_config sed -i'' 's/^UsePAM/# UsePAM/' etc/ssh/sshd_config ssh-add -L >etc/ssh/authorized_keys pubkeys="../service/sshd/etc/*.pub" ls $pubkeys >/dev/null 2>&1 && \ cat $pubkeys >>etc/ssh/authorized_keys mkdir -p home var/cache ================================================ FILE: service/sshd/sailor.conf ================================================ . /service/common/sailor.vars shipname=sshd shipbins="$shipbins /bin/ksh /usr/sbin/sshd /usr/bin/ssh-keygen /usr/libexec/sshd-session /usr/libexec/sshd-auth /usr/bin/ssh" run_at_build="mkdir -p /var/chroot/sshd" ================================================ FILE: service/systembsd/etc/banner.ans ================================================ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒        ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒        ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒        ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓           ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓                         ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░                       ▒▒▒   ▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒                       ▒▒▒░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓     ▒▒▒                       ▒▒▒░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▒▒▒▒▒▒▒▒▒  ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒     ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒░ ▓▒▒▒     ░▒▒   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒     ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒   ▒▒░ ▓▓▓▓░░░░░░▒▒░░░░░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▒▒▒     ▒▒▒   ▓▓▓▓▓▓▓▓▓▓▓   ▒▒▒░░░▒▒░ ▓▓▓▓▒▒▒▒▒▒  ▒▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▒           ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒   ▒▒▒▒▒░ ▓▓▓▓▒▒▒        ▒▒▒▒▒▒                       ▒▒▒         ▒▒░  ▒▒▒▒▒▒▓▓▓ ▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒   ▒▒▒        ░▒▒░           ▒▒▒▒▒▒      ▒▒▒▒▒▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒   ▒▒▒        ░▒▒░           ▒▒▒▒▒▒      ▒▒▒▒▒▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒      ▒▒▒                 ▒▒▒  ░▒▒▒  ▒▒▒▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒     ░▒▒▒▒▒░        ▒▒▒     ░▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒     ░▒▒▒▒▒░░░░░░░░░▒▒▒   ░░▒     ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒           ▒▒▒▒▒▒▒▒▒      ▒▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒     ▒▒▒░              ▒▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒  ▒▒▒▒▒▒▒▒▒▒▒▒   ▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒  ▒▒▒▒▒▒▒▒▒▒▒▒   ▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒░     ▒▒▒   ▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░           ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒░           ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░        ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░     ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░    ░   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░  ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░  ▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ================================================ FILE: service/systembsd/etc/dinit.d/boot ================================================ type = internal depends-ms: getty waits-for.d: boot.d ================================================ FILE: service/systembsd/etc/dinit.d/getty ================================================ type = process command = /usr/libexec/getty Pc constty termsignal = HUP smooth-recovery = true depends-on: motd depends-on: loginready ================================================ FILE: service/systembsd/etc/dinit.d/loginready ================================================ type = internal restart = false options = runs-on-console depends-on: rc.boot waits-for: rc.fs waits-for: syslogd ================================================ FILE: service/systembsd/etc/dinit.d/motd ================================================ type = scripted command = /etc/rc.d/motd start restart = false depends-on: rc.fs ================================================ FILE: service/systembsd/etc/dinit.d/rc.boot ================================================ type = scripted command = /etc/dinit.d/rc.boot.sh restart = false logfile = /var/log/rc.boot.log depends-on: rc.fs ================================================ FILE: service/systembsd/etc/dinit.d/rc.boot.sh ================================================ #!/bin/sh # basic services to start at boot STARTSVC=" bootconf.sh ttys sysctl entropy network local " for svc in $STARTSVC do /etc/rc.d/${svc} start done ================================================ FILE: service/systembsd/etc/dinit.d/rc.dev ================================================ # tmpfs dev needs to execute before mounting type = scripted command = /etc/dinit.d/rc.dev.sh restart = false ================================================ FILE: service/systembsd/etc/dinit.d/rc.dev.sh ================================================ #!/bin/sh # tmpfs dev is usually done by init(8) cd /dev # /dev is a union fs, permissions are recorded and bad # after reboot for those. MAKEDEV doesn't re-create them # and -f fails with "-f option works only with mknod" rm -f tty null std* sh MAKEDEV -M -M all cd - ================================================ FILE: service/systembsd/etc/dinit.d/rc.fs ================================================ type = scripted command = /etc/dinit.d/rc.fs.sh start stop-command = /etc/dinit.d/rc.fs.sh stop restart = false options = starts-rwfs depends-on: rc.dev ================================================ FILE: service/systembsd/etc/dinit.d/rc.fs.sh ================================================ #!/bin/sh /etc/rc.d/mountcritlocal $1 /etc/rc.d/mountcritremote $1 # only this one has start/stop /etc/rc.d/mountall $1 if [ "$1" = "stop" ]; then for fs in /dev/pts /dev do /sbin/umount -f $fs done fi ================================================ FILE: service/systembsd/etc/dinit.d/sshd ================================================ type = scripted command = /etc/rc.d/sshd onestart depends-on: loginready waits-for: syslogd ================================================ FILE: service/systembsd/etc/dinit.d/syslogd ================================================ type = scripted command = /etc/rc.d/syslogd onestart stop-command = /etc/rc.d/syslogd onestop options: starts-log depends-on: rc.boot ================================================ FILE: service/systembsd/etc/dinit.d/wscons ================================================ type = scripted command = /etc/rc.d/wscons start depends-on: loginready ================================================ FILE: service/systembsd/etc/fstab ================================================ ROOT.a / ffs rw 1 1 ptyfs /dev/pts ptyfs rw 0 0 kernfs /kern kernfs rw,noauto 0 0 procfs /proc procfs rw,noauto 0 0 tmpfs /var/shm tmpfs rw,-m1777,-sram%25 0 0 ================================================ FILE: service/systembsd/etc/rc ================================================ #!/bin/sh export HOME=/ export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin umask 022 /sbin/mount -a # tmpfs dev is usually done by init(8) cd /dev # /dev is a union fs, permissions are recorded and bad # after reboot for those. MAKEDEV doesn't re-create them # and -f fails with "-f option works only with mknod" rm -f tty null std* sh MAKEDEV -M -M all cd - [ -f /etc/rc.conf ] && . /etc/rc.conf /etc/rc.d/bootconf.sh start # basic services to start at boot STARTSVC=" ttys sysctl entropy network local " for svc in $STARTSVC do /etc/rc.d/${svc} start done # dinit needs /var/run to be mounted in order to create # its socket #/sbin/dinit -m ================================================ FILE: service/systembsd/etc/rc.conf ================================================ [ -r /etc/defaults/rc.conf ] && . /etc/defaults/rc.conf rc_configured=YES ifconfig_vioif0="inet 10.0.2.15/24" defaultroute="10.0.2.2" hostname="systembsd" wscons=YES # runit needs the service to not bg #sshd_flags="-D" ================================================ FILE: service/systembsd/etc/rc.local ================================================ # for design porposes, make root shell ksh grep -q '^root.*bin/sh' /etc/passwd && \ usermod -s /bin/ksh root || true ================================================ FILE: service/systembsd/etc/sysctl.conf ================================================ # this takes ages net.inet6.ip6.dad_count=0 ================================================ FILE: service/systembsd/postinst/00-dinit.sh ================================================ #!/bin/sh # bare minimum mknod -m 600 dev/console c 0 0 mknod -m 600 dev/constty c 0 1 mknod -m 666 dev/tty c 1 0 mknod -m 666 dev/null c 2 2 mknod -m 666 dev/stdin c 22 0 mknod -m 666 dev/stdout c 22 1 mknod -m 666 dev/stderr c 22 2 mkdir -p packages # dinit is not yet part of a pkgsrc release ${FETCH} -o packages/dinit.tgz https://imil.net/NetBSD/dinit-0.19.3nb2.tgz mkdir -p usr/pkg $TAR zxvfp packages/dinit.tgz --exclude='+*' -C usr/pkg mv usr/pkg/sbin/dinit sbin/init ================================================ FILE: service/systembsd/postinst/01-custom.sh ================================================ #!/bin/sh cat >>root/.shrc</dev/null || pkgin -y in perl git-base # newer NetBSD versions use tmpfs for /dev, sailor copies MAKEDEV from /dev cp /etc/MAKEDEV /dev/ ksh . /etc/include/shutdown ================================================ FILE: service/usershell/README.md ================================================ # User Shell Service ## About This microservice starts a minimal user shell (`ksh`). It comes with all typical _BSD_ shell tools and can also be used as an _SSH_ client. Its size is about 50MB and can be loaded as an `initrd` / RAM disk with `startnb.sh` `-I` parameter. This image is built with `MINIMIZE=y`, [sailor](https://github.com/NetBSDfr/sailor) `git clone` is needed before building. ## Usage **Build** ```sh $ bmake SERVICE=usershell build ``` **Run** ```sh $ ./startnb.sh -f etc/usershell.conf ``` ================================================ FILE: service/usershell/etc/rc ================================================ #!/bin/sh . /etc/include/basicrc hostname shell su bsd -c "ksh -l" . /etc/include/shutdown ================================================ FILE: service/usershell/options.mk ================================================ MINIMIZE=y MOUNTRO=y IMGSIZE=100 ADDPKGS=pkgin pkg_tarup pkg_install sqlite3 rsync curl ================================================ FILE: service/usershell/postinst/custom.sh ================================================ #!/bin/sh rm -f etc/shrc # wipe defaults cat >>etc/profile<" exit 1 } IMGTAG="latest" while [ $# -gt 1 ]; do case $1 in --build-arg) shift [ "${1#*=}" = "${1}" ] && usage BUILDARGS="${BUILDARGS}${BUILDARGS:+,}${1}" ;; -t|--tag) shift IMGTAG="$1" ;; -y) YES=y ;; esac shift done if [ $# -lt 1 ] || [ ! -f "$1" ]; then usage fi dockerfile=$1 mkdir -p tmp TMPOPTS=$(mktemp tmp/options.mk.XXXXXX) # Dockerfile compatibility sed -n 's/LABEL \(smolbsd\.\)\{0,1\}\(.*=.*\)/\2/p' $dockerfile | \ awk -F= '{ printf "%s=%s\n", toupper($1), $2 }' \ >${TMPOPTS} . ./${TMPOPTS} export CHOUPI=y . service/common/funcs . service/common/choupi if [ -z "$SERVICE" ];then echo "${ERROR} no service name, exiting" exit 1 fi servicedir="service/${SERVICE}" if [ -d "$servicedir" ]; then echo "${INFO} $SERVICE already exists, recreating" for f in etc/rc options.mk postinst do rm -rf "${servicedir}/${f}" done rm -f etc/${SERVICE}.conf fi for d in etc postinst do mkdir -p ${servicedir}/${d} done postnum=0 postinst="${servicedir}/postinst/postinst-${postnum}.sh" etcrc="${servicedir}/etc/rc" sed 's/"//g' ${TMPOPTS} >${servicedir}/options.mk # setup the chroot for RUN executions cat >$postinst<<_EOF #!/bin/sh set -e if [ ! -f /BUILDIMG ]; then echo "${ERROR} NOT IN BUILDER IMAGE! EXITING" exit 1 fi . ../service/common/funcs rootdir=\$(pwd) # postinst is ran from fake root cat >etc/profile<<_PROFILE PATH=\$PATH:/sbin:/usr/sbin:/usr/pkg/bin:/usr/pkg/sbin export PATH _PROFILE cp /etc/resolv.conf etc/ mkdir -p usr/pkg/etc/pkgin # evbarm-aarch64 repo name is aarch64 ARCH=\${ARCH#evbarm-} echo "https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/\${ARCH}/\${PKGVERS}/All" \ >usr/pkg/etc/pkgin/repositories.conf rsynclite /etc/openssl/ \${rootdir}/etc/openssl _EOF cat >${etcrc}<<_EOF #!/bin/sh . /etc/include/basicrc . /etc/include/mount9p _EOF grep -q '^ADDPKGS' ${servicedir}/options.mk || \ echo "ADDPKGS=pkgin pkg_tarup pkg_install sqlite3 curl rsync" \ >>${servicedir}/options.mk USER=root SHELL_CMD=${SHELL_CMD:-/bin/sh} WORKDIR="/" postnum=1 # 0 was basic header postinst="${postinst%-*}-${postnum}.sh" args="${postinst%-*}.args" printf '' >"$args" shhead() { printf '%s\n\n' "#!${SHELL_CMD}" >"$postinst" cat >>"$postinst"<<-EOHEAD export CHOUPI=y . ../service/common/funcs . ../service/common/choupi EOHEAD } shhead while read -r line do # strip comments case "$line" in \#*) continue;; esac # normalize to spaces and no trailing spaces line=$(printf '%s\n' "$line" | tr -s '\t ' ' ' | sed 's/[[:space:]]*$//') # there was a <>"$postinst" else [ -n "$prehere" ] && echo "$heretag" >>"$postinst" echo \" >>"$postinst" heretag= prehere= posttag= fi continue fi # normal KEY VAL line if [ -z "$multiline" ]; then key="${line%% *}" val="${line#* }" else # && \ multiline val="${line}" fi case "$val" in # multi lines breaks *\\) multiline="$multiline${val%\\} " continue ;; esac val="$multiline$val"; multiline="" case "$key" in FROM) case "$val" in *img) echo "FROMIMG=${val}" >> ${servicedir}/options.mk ;; *) echo "SETS=${val}," | sed 's/\(:[^,]*\)*,/\.${SETSEXT}\1 /g' \ >> ${servicedir}/options.mk ;; esac ;; ENV) echo "export $val" | tee -a "$etcrc" "$postinst" "$args" \ >/dev/null ;; ARG) arg=${val%%=*} [ "$arg" != "${val}" ] && default=${val#*=} || default="" printf '%s\n' "${arg}=\${${arg}:-${default}}; export $arg" | \ tee -a "$postinst" "$args" >/dev/null ;; SHELL) # -c is useless for us as we execute a script SHELL_CMD=$(echo "${val}"|jq -r '[.[] | select(. != "-c")] | join(" ")') # SHELL has changed, create a new postinst script postnum=$((postnum + 1)) postinst="${postinst%-*}-${postnum}.sh" shhead # bring ARGs cat "$args" >>"$postinst" echo >>"$postinst" ;; RUN) case "$val" in *\<\<*) # worst case: cat < foo.conf # ^^^^^^^^^^^^^^^^^^ # posthere # ^^^ ^^^ ^^^^^^^^^^ # prehere heretag posttag prehere=${val%%<<*} # command before heredoc posthere=${val#*<<} # all after heredoc heretag=${posthere%% *} # tag itself posttag=${posthere#${heretag}} # after tag [ -n "$prehere" ] && prehere="$prehere <<$heretag" # remove any heredoc specfier heretag=$(printf '%s' "$heretag"|tr -d "'\"") printf '%s\n' \ "chroot . su ${USER} -c \"cd ${WORKDIR} && ${prehere}${posttag}" \ >>"$postinst" ;; *) escaped=$(printf '%s' "$val" | sed 's/"/\\"/g') printf '%s\n' "chroot . su ${USER} -c \"cd ${WORKDIR} && ${escaped}\"" \ >>"$postinst" ;; esac ;; EXPOSE) # PUBLISH comes from smolbsd LABEL if [ -n "$PUBLISH" ]; then ports="$PUBLISH" # Non-Dockerfile compatible but convenient syntax elif [ "${val%:*}" != "$val" ]; then ports="$val" else echo "${WARN} smolbsd.publish LABEL needed to EXPOSE" fi if [ -n "$ports" ]; then for pair in $(echo $ports|tr ',' ' '); do portfrom=${pair%:*} portto=${pair#*:} [ -n "${portfrom}" ] && [ -n "${portto}" ] && \ hostfwd="${hostfwd}${hostfwd:+,}::${portfrom}-:${portto}" done [ -n "$hostfwd" ] && \ echo "hostfwd=$hostfwd" >>etc/${SERVICE}.conf fi ;; ADD|COPY) src=${val% *} dst=${val##* } while :; do case "$src" in --chown=*|--chmod=*|--exclude=*) option="${src%%=*}" # --chown, --chmod, or --exclude option="${option#--}" # chown, chmod, or exclude value="${src#*=}" # foo:bar file1 file2 src="${value#* }" # file1 file2 value="${value%% *}" # foo:bar eval "$option=\$value" [ "$option" = "exclude" ] && \ toexclude="${toexclude} --exclude=${value}" ;; *) break ;; esac done case "$src" in http*://*) [ -d "${dst#/}" ] && outdl=${dst#/}/${src##*/} || outdl=${dst#/} echo "ftp -o ${outdl} ${src}" >>"$postinst" ;; *) if [ "${dst#\$}" != "$dst" ]; then # dst is a variable # too large but macOS BRE compatible dst=$(echo "$dst"|sed 's,\${*\([^}]*\)}*,${\1#/},') fi echo "rsynclite ${toexclude} ${src} ${dst#/}" >>"$postinst" ;; esac [ -n "$chown" ] && \ echo "[ -d \"${src#/}\" ] && \ chroot . sh -c \"chown -R $chown ${dst}\" || \ chroot . sh -c \"chown -R $chown ${dst}/${src##*/}\"" \ >>"$postinst" [ -n "$chmod" ] && \ echo "[ -d \"${src#/}\" ] && \ chroot . sh -c \"chmod -R $chmod ${dst}\" || \ chroot . sh -c \"chmod -R $chmod ${dst}/${src##*/}\"" \ >>"$postinst" ;; USER) echo "chroot . sh -c \"id ${val} >/dev/null 2>&1 || \ (useradd -m ${val} && groupadd ${val})\"" \ >>"$postinst" USER=${val} ;; VOLUME) echo "share=${val}" >>etc/${SERVICE}.conf # avoid mount_9p warning [ "${val#/}" = "${val}" ] && val="/${val}" echo "MOUNT9P=${val}" >>"$etcrc" echo ". /etc/include/mount9p" >>"$etcrc" echo "mkdir -p ${val#/}" >>"$postinst" ;; WORKDIR) WORKDIR="$val" echo "cd ${val}" >>"$etcrc" ;; CMD|ENTRYPOINT) printf "\n# entrypoint\n" >>"${etcrc}" if [ "$USER" != "root" ]; then printf '%s' "su $USER -c \"" >>"${etcrc}" ENDQUOTE="\"" fi printf '%s' "$val" >>"${etcrc}" echo $ENDQUOTE >>"${etcrc}" ;; *) ;; esac done < $dockerfile cat >>${etcrc}<<_ETCRC . /etc/include/shutdown _ETCRC cat >>etc/${SERVICE}.conf<<_ETCCONF imgtag=$IMGTAG use_pty=$USE_PTY _ETCCONF echo "${CHECK} ${SERVICE} service files generated" if [ -z "$YES" ]; then printf '%s' "${ARROW} press enter to build ${SERVICE} image or ^C to exit" read dum fi [ "$(uname -s)" = "NetBSD" ] && MAKE=make || MAKE=bmake $MAKE SERVICE=${SERVICE} BUILDARGS="${BUILDARGS}" IMGTAG=":${IMGTAG}" build ================================================ FILE: smoler/img.sh ================================================ #!/bin/sh OS=$(uname -s|tr 'A-Z' 'a-z') case $OS in linux|darwin) ;; *) echo "unsupported platform" exit 1 ;; esac ARCH=$(uname -m | sed -e 's/x86_64/amd64/g;s/aarch64/arm64/') PATH=${PATH}:bin install_oras() { command -v oras >/dev/null && return version=$(curl -s https://api.github.com/repos/oras-project/oras/releases/latest | jq -r '.tag_name') curl -fsSL "https://github.com/oras-project/oras/releases/download/${version}/oras_${version#v}_${OS}_${ARCH}.tar.gz" | \ tar zxf - -C bin oras } pulsh_usage() { if [ $# -lt 2 ]; then echo "usage: $1 " exit 1 fi } SMOLREPO=${SMOLREPO:-ghcr.io/netbsdfr/smolbsd} case "$1" in pull) pulsh_usage $@ install_oras oras pull ${SMOLREPO}/$2 ;; push) pulsh_usage $@ install_oras ocimg=${2#*/} [ "$ocimg" = "$2" ] && imgpath="images" || imgpath="${2%/*}" ocimg=${ocimg%.img} oras push ${SMOLREPO}/${ocimg} \ --artifact-type application/vnd.smolbsd.image \ "${imgpath}/${ocimg}.img":application/x-raw-disk-image ;; images) cols=$(tput cols 2>/dev/null) || cols=80 colsiz=$((cols / 4)) namesiz=$((colsiz * 2)) colsiz=$((colsiz / 2)) fmt="%-${namesiz}s %-${colsiz}s %${colsiz}s %${colsiz}s\n" printf "\033[1m${fmt}\033[0m" IMAGE SIZE CREATED SIG for img in images/*.img do ctime= [ -f "$img" ] || continue base="${img##*/}" base="${base%.img}" size=$(du -sh $img|cut -f1) # stat(1) is not portable *at all* # "smolsig:01/01/1970|uuid" smolsig=$(tail -c 56 ${img}|grep -o 'smolsig:.*' 2>/dev/null) smolsig=${smolsig#smolsig:} [ -n "$smolsig" ] && ctime=${smolsig%|*} [ -z "$ctime" ] && ctime='01/01/1970' sigmatch=NOK sigfile="${img%.img}.sig" if [ -f "${sigfile}" ]; then rawsig="${smolsig#*|}" imgsig="$(tail -c 37 ${sigfile})" [ "$imgsig" = "$rawsig" ] && sigmatch=OK else # image has a signature but no sigfile, most # likely a downloaded image if [ -n "$smolsig" ]; then echo "smolsig:${ctime}:${smolsig#*|}" > \ "$sigfile" sigmatch=OK fi fi printf "$fmt" "$base" "$size" "$ctime" "$sigmatch" done ;; *) echo "Unknown command: $1" exit 1 ;; esac ================================================ FILE: smoler.sh ================================================ #!/bin/sh set -e progname=${0##*/} case $1 in build) /bin/sh smoler/build.sh $@ ;; push|pull|images) /bin/sh smoler/img.sh $@ ;; run) base=$(echo "$2"|sed 's/-amd64:.*//;s/-evbarm-aarch64:.*//') if [ -f "etc/${base}.conf" ]; then params="-f etc/${base}.conf" elif [ -f "images/${2}.img" ]; then params="-i $2" else params="-h" fi shift; shift # move to arg 3 /bin/sh startnb.sh $params $@ ;; "") cat 1>&2 << _USAGE_ Usage: $progname build [-y] $progname pull $progname push $progname images $progname run [startnb.sh flags] _USAGE_ ;; *) echo "not implemented." ;; esac ================================================ FILE: startnb.sh ================================================ #!/bin/sh usage() { cat 1>&2 << _USAGE_ Usage: ${0##*/} -f conffile | -k kernel -i image [-c CPUs] [-m memory] [-a kernel parameters] [-r root disk] [-h drive2] [-p port] [-t tcp serial port] [-w path] [-e k=v] [-E f=path] [-x qemu extra args] [-N] [-b] [-n] [-s] [-d] [-v] [-u] Boot a microvm -f conffile vm config file -k kernel kernel to boot on -i image image to use as root filesystem -I load image as initrd -c cores number of CPUs -m memory memory in MB -a parameters append kernel parameters -r root disk root disk to boot on -l drive2 second drive to pass to image -t serial port TCP serial port -n num sockets number of VirtIO console socket -p ports [tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport -w path host path to share with guest (9p) -e k=v[,...] export variables to vm via QEMU fw_cfg -E f=path[,...] export host file paths to vm via QEMU fw_cfg -x arguments extra qemu arguments -N disable networking -b bridge mode -s don't lock image file -P use pty console -d daemonize -v verbose -u non-colorful output -h this help _USAGE_ # as per https://www.qemu.org/docs/master/system/invocation.html # hostfwd=[tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport exit 1 } # Check if VirtualBox VM is running if [ "$(uname -s)" != "Darwin" ]; then if pgrep VirtualBoxVM >/dev/null 2>&1; then echo "Unable to start KVM: VirtualBox is running" exit 1 fi fi options="f:k:a:e:E:p:i:Im:n:c:r:l:p:uw:x:t:hbdsPNv" export CHOUPI=y uuid="$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c8)" # and possibly override its values while getopts "$options" opt do case $opt in a) append="$OPTARG";; b) bridgenet=y;; c) cores="$OPTARG";; d) daemon=y;; e) fwcfgvar=${OPTARG};; E) fwcfgfile=${OPTARG};; # first load vm config file f) . $OPTARG # extract service from file name svc=${OPTARG%.conf} svc=${svc##*/} ;; h) usage;; i) img="$OPTARG";; I) initrd="-initrd";; # and possibly override values k) kernel="$OPTARG";; l) drive2=$OPTARG;; m) mem="$OPTARG";; n) max_ports=$(($OPTARG + 1));; p) hostfwd=$OPTARG;; r) root="$OPTARG";; s) sharerw=y;; t) serial_port=$OPTARG;; u) CHOUPI="";; v) VERBOSE=y;; N) nonet=yes;; P) use_pty=y;; w) share=$OPTARG;; x) extra=$OPTARG;; *) usage;; esac done . service/common/choupi # envvars override kernel=${kernel:-$KERNEL} img=${img:-$NBIMG} # enable QEMU user network by default [ -z "$nonet" ] && network="\ -device virtio-net-device,netdev=net-${uuid}0 \ -netdev user,id=net-${uuid}0,ipv6=off" if [ -n "$hostfwd" ]; then network="${network},$(echo "$hostfwd"|sed -E 's/(udp|tcp)?::/hostfwd=\1::/g')" echo "${ARROW} port forward set: $hostfwd" fi [ -n "$bridgenet" ] && network="$network \ -device virtio-net-device,netdev=net-${uuid}1 \ -netdev type=tap,id=net-${uuid}1" [ -n "$drive2" ] && drive2="\ -drive if=none,file=${drive2},format=raw,id=hd-${uuid}1 \ -device virtio-blk-device,drive=hd-${uuid}1" [ -n "$share" ] && share="\ -fsdev local,path=${share},security_model=none,id=shar-${uuid}0 \ -device virtio-9p-device,fsdev=shar-${uuid}0,mount_tag=shar-${uuid}0" [ -n "$sharerw" ] && sharerw=",share-rw=on" OS=$(uname -s) # arch can be forced to allow other qemu archs arch=${ARCH:-$(scripts/uname.sh -m)} machine=$(scripts/uname.sh -p) # no config file given, extract service from image name if [ -z "$svc" ]; then svc=${img%-${arch}*} svc=${svc#*/} fi cputype="host" case $OS in NetBSD) accel="-accel nvmm" ;; Linux) accel="-accel kvm" ;; Darwin) accel="-accel hvf" if [ "$arch" = "evbarm-aarch64" ]; then # Mac M1, M2, M3, M4 cputype="cortex-a57" else # Mac Intel cputype="qemu64" fi ;; OpenBSD|FreeBSD) accel="-accel tcg" # unaccelerated cputype="qemu64" ;; *) echo "Unknown hypervisor, no acceleration" esac QEMU=${QEMU:-qemu-system-${machine}} printf "${ARROW} using QEMU " $QEMU --version|grep -oE 'version .*' mem=${mem:-"256"} cores=${cores:-"1"} append=${append:-"-z"} root=${root:-"NAME=${svc}root"} case $machine in x86_64|i386) mflags="-M microvm,rtc=on,acpi=off,pic=off" cpuflags="-cpu ${cputype},+invtsc" # stack smashing with version 9.0 and 9.1 ${QEMU} --version|grep -q -E '9\.[01]' && \ extra="$extra -L bios -bios bios-microvm.bin" case $machine in i386) kernel=${kernel:-kernels/netbsd-SMOL386} ;; x86_64) kernel=${kernel:-kernels/netbsd-SMOL} ;; esac ;; aarch64) mflags="-M virt,highmem=off,gic-version=3" cpuflags="-cpu ${cputype}" extra="$extra -device virtio-rng-pci" kernel=${kernel:-kernels/netbsd-GENERIC64.img} ;; *) echo "${WARN} Unknown architecture" esac if [ ! -f "$kernel" ]; then [ "$OS" != "NetBSD" ] && MAKE=bmake || MAKE=make echo -n "${ERROR} $kernel not present, fetch default kernel now? [y/N] " read r && [ "$r" = "y" ] && $MAKE kernfetch || exit 1 fi echo "${ARROW} using kernel $kernel" if [ -z "$use_pty" ]; then pty="stdio,signal=off,mux=on" else # if use_pty is set to a command, use it as pty attachment [ "$use_pty" != "y" ] && ptycmd="$use_pty" || \ ptycmd="picocom -q -b 115200" pty="pty" fi # use VirtIO console when available, if not, emulated ISA serial console if nm $kernel 2>&1 | grep -q viocon_earlyinit; then console=viocon [ -z "$max_ports" ] && max_ports=1 consdev="\ -chardev ${pty},id=char0 \ -device virtio-serial-device,max_ports=${max_ports} \ -device virtconsole,chardev=char0,name=char0" else [ -z "$use_pty" ] && pty="mon:stdio" consdev="-serial $pty" console=com fi echo "${ARROW} using console: $console" # only use tag if there's a tag imgtag=${imgtag:+:${imgtag}} # conf file was given [ -z "$img" ] && [ -n "$svc" ] && img=images/${svc}-${arch}${imgtag}.img if [ -z "$img" ]; then printf "no 'image' defined\n\n" 1>&2 usage fi # registry like name was given [ "${img%.img}" = "${img}" ] && img="images/${img}.img" if [ -z "${initrd}" ]; then echo "${ARROW} using disk image $img" img="-drive if=none,file=${img},format=raw,id=hd-${uuid}0 \ -device virtio-blk-device,drive=hd-${uuid}0${sharerw}" root="root=${root}" else echo "${ARROW} loading $img as initrd" root="" fi # svc *must* be defined to be able to store qemu PID in a unique filename if [ -z "$svc" ]; then svc=${uuid} echo "${ARROW} no service name, using UUID ($uuid)" fi pidfile="qemu-${svc}.pid" d="-display none -pidfile ${pidfile}" if [ -n "$daemon" ]; then # XXX: daemonize makes viocon crash console=com unset max_ports # a TCP port is specified if [ -n "${serial_port}" ]; then serial="-serial telnet:localhost:${serial_port},server,nowait" echo "${ARROW} using serial: localhost:${serial_port}" fi d="$d -daemonize $serial" else # console output d="$d $consdev" fi if [ -n "$max_ports" ] && [ $max_ports -gt 1 ]; then for v in $(seq $((max_ports - 1))) do sockid="${uuid}-p${v}" sockname="sock-${sockid}" sockpath="s-${sockid}.sock" viosock="$viosock \ -chardev socket,path=${sockpath},server=on,wait=off,id=${sockname} \ -device virtconsole,chardev=${sockname},name=${sockname}" echo "${INFO} host socket ${v}: ${sockpath}" done fi # QMP is available [ -n "${qmp_port}" ] && extra="$extra -qmp tcp:localhost:${qmp_port},server,wait=off" # export variables or files using QEMU fw_cfg [ -n "$fwcfgvar" ] && \ extra="$extra $(echo $fwcfgvar | sed -E 's|([[:alnum:]_]+)=([^,]+),?|-fw_cfg opt/org.smolbsd.var.\1,string=\2 |g')" [ -n "$fwcfgfile" ] && \ extra="$extra $(echo $fwcfgfile | sed -E 's|([[:alnum:]_]+)=([^,]+),?|-fw_cfg opt/org.smolbsd.file.\1,file=\2 |g')" # Use localtime for RTC instead of UTC by default extra="$extra -rtc base=localtime" [ -n "$use_pty" ] && escapex="^" echo "${EXIT} ^D to stop the vm, ^A-${escapex}X to kill it" cmd="${QEMU} -smp $cores \ $accel $mflags -m $mem $cpuflags \ -kernel $kernel $initrd ${img} \ -append \"console=${console} ${root} ${append}\" \ -global virtio-mmio.force-legacy=false ${share} \ ${drive2} ${network} ${d} ${viosock} ${extra}" [ -n "$VERBOSE" ] && echo "$cmd" && exit [ -n "$viosock" ] && \ ( killsock=${sockpath%-p[0-9]*}-p1.sock while [ ! -S "./$killsock" ] do echo "${SOCKET} waiting for vm control socket ${killsock}" sleep 0.5 done socat ./$killsock -,ignoreeof 2>&1 | while read VIOCON do case ${VIOCON} in JEMATA!*) kill $(cat ${pidfile}); esac done ) & [ -n "$use_pty" ] && \ cmd="$cmd -daemonize >qemu-${svc}.pty 2>&1" eval $cmd if [ -n "$use_pty" ]; then ptyfile="qemu-${svc}.pty" while [ ! -f "$ptyfile" ]; do sleep 0.2; done ${ptycmd} $(grep -o '/dev/[^ ]*' $ptyfile) kill $(cat ${pidfile}) rm -f $ptyfile fi ================================================ FILE: www/index.html ================================================ smolBSD — build your own minimal BSD system
smolBSD logo

smolBSD

build your own minimal BSD UNIX system

About

smolBSD is a meta-operating system built on top of NetBSD. It lets you compose your own UNIX environment — from a single-purpose microservice system to a fully-custom OS image — in just a few minutes.

The smolBSD environment uses the netbsd-MICROVM kernel as its foundation, leveraging the same portable, reliable codebase that powers NetBSD itself. You decide what to include — sshd, httpd, or your own service — and smolBSD builds a coherent, minimal, bootable image ready to run anywhere.

$ bmake SERVICE=bozohttpd build
➡️ starting the builder microvm
➡️ host filesystem mounted on /mnt
➡️ fetching sets
➡️ creating root filesystem (512M)
✅ image ready: bozohttpd-amd64.img
    

Why smolBSD

Build BSD systems like you build software — fast, reproducible, and minimal.

🧩 Composable

Pick only the components you need — from kernel to services.

⚙️ Reproducible

Every build is deterministic, portable, and easy to version-control.

🚀 Instant Boot

Powered by netbsd-MICROVM — boot to service in milliseconds.

💻 Universal

Runs anywhere QEMU or Firecracker runs — cloud, CI, edge, or laptop.

Quick Start

Build and boot your own BSD system in seconds:

$ git clone https://github.com/NetBSDfr/smolBSD
$ cd smolBSD
$ cat ~/.ssh/id_ed25519.pub >> service/sshd/etc/mykey.pub
$ bmake SERVICE=sshd build
➡️ starting the builder microvm
➡️ fetching sets
➡️ creating root filesystem (512M)
✅ image ready: sshd-amd64.img
➡️ killing the builder microvm

$ ./startnb.sh -f etc/sshd.conf 
[   1.0092096] kernel boot time: 14ms
Starting sshd.
Server listening on :: port 22.
Server listening on 0.0.0.0 port 22.

$ ssh -p 2022 ssh@localhost
    
Download

Create

Your own service in seconds using a simple Dockerfile!

FROM base,etc

LABEL SERVICE=caddy

RUN pkgin up && pkgin -y in caddy

EXPOSE 8881:8880

CMD caddy respond -l :8880
    

Services Examples

bozohttpd

A complete static web server in a few megabytes. smolBSD builds a minimal system with bozohttpd preconfigured and ready to serve content immediately on boot.

$ bmake SERVICE=bozohttpd build
➡️ starting the builder microvm
➡️ fetching sets
➡️ creating root filesystem (512M)
✅ image ready: bozohttpd-amd64.img

$ ./startnb.sh -f etc/bozohttpd.conf
[   1.001231] kernel boot time: 10ms
Starting bozohttpd on :80
listening on 0.0.0.0:80
      

nbakery

A lightweight build and image creation service based entirely on NetBSD tools. The nbakery image gives you a taste of a preconfigured NetBSD environment with all the well known tools.

$ bmake SERVICE=nbakery build
➡️ starting the builder microvm
➡️ fetching sets
➡️ creating root filesystem (512M)
✅ image ready: nbakery-amd64.img

$ ./startnb.sh -f etc/nbakery.conf
[   1.008374] kernel boot time: 11ms

Welcome to the (n)bakery! 🧁

💪 doas <command> to run command as root
📦 pkgin to manage packages
🚪 exit to cleanly shutdown, ^a-x to exit qemu
🪟 you are inside a tmux with prefix ^q
      

nitrosshd

A minimal secure shell server started with nitro, designed to launch instantly and provide remote access with zero unnecessary services. Ideal for an SSH bouncer.

$ bmake SERVICE=nitrosshd build
➡️ starting the builder microvm
➡️ fetching sets
➡️ creating root filesystem (512M)
✅ image ready: nitrosshd-amd64.img

$ ./startnb.sh -f etc/sshd.conf
[   1.011598] kernel boot time: 12ms
Created tmpfs /dev (1835008 byte, 3552 inodes)
Starting sshd.
Server listening on :: port 22.
Server listening on 0.0.0.0 port 22.
      

Community

smolBSD is an independent project built on top of NetBSD. Join us, share your micro-systems, or contribute new services and build recipes.