Full Code of NetBSDfr/smolBSD for AI

main 8481574b7d2b cached
139 files
300.5 KB
173.2k tokens
17 symbols
1 requests
Download .txt
Showing preview only (341K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<div align="center" markdown="1">

<img src="www/smolBSD.png" width=150px>

**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)

</div>

# 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. 

<div align="center" markdown="1">

**microvm typical boot process**

<img src="www/boot.png" width=400px>

</div>

# 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 <tag>` 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 <image_file>` or `./smoler.sh push <image_name>`
```sh
$ ./smoler.sh push myimage-amd64:latest
```
* **Pull an image:** `./smoler.sh pull <image_name>`
```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-<arch>.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<port number>`, 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 `+<size>`, will reduce the disk image size to disk real usage + `<size>` 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/<arch>/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 <img src="static/smolBSD.png" alt="" width="10%">

## 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/<vm>', 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/<vmname>/<command>')
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/<vmname>')
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
================================================
<!DOCTYPE html>
<html>
<head>
  <title># smolBSD VMs</title>
  <link rel="icon" href="static/smol.ico">
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <link
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
    rel="stylesheet"
    integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
    crossorigin="anonymous">
  <script
    src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
    crossorigin="anonymous"></script>
  <style>
    #app { max-width: 50%; }
    .blinking {
      animation: blink 1s infinite;
    }
    @keyframes blink {
      50% {
        opacity: 0;
      }
    }
    .cpu-usage {
      width: 50px;
      height: 20px;
      border-color: black;
      border-radius: 2px;
      margin-left: 10px;
    }
  </style>
</head>
<body>
  <div id="app" class="container mt-5">

    <h1># smolBSD VM Manager <img src="static/smolBSD.png" alt="" width="10%"></h1>

    <nav>
      <div class="nav nav-tabs" id="nav-tab" role="tablist">
        <button class="nav-link active" id="nav-manage-tab" data-bs-toggle="tab"
          data-bs-target="#nav-manage" type="button" role="tab"
          aria-controls="nav-manage" aria-selected="true">Manage</button>
        <button class="nav-link" id="nav-infos-tab" data-bs-toggle="tab"
          data-bs-target="#nav-infos" type="button" role="tab"
          aria-controls="nav-infos" aria-selected="false">Info</button>
      </div>
    </nav>

    <div class="tab-content mt-3" id="nav-tabContent">

      <!-- Management tab -->

      <div class="tab-pane show active" id="nav-manage" role="tabpanel"
        aria-labelledby="nav-manage-tab">

        <ul class="list-group"> <!-- main list -->

          <!-- VM list + buttons -->

          <li v-for="(vm, vmName) in vmList" class="list-group-item d-flex justify-content-between align-items-center">
            <div class="flex-grow-1">{{ vmName }}</div>
            <div v-if="vmList[vmName].status === 'running'" class="cpu-usage me-4" :id="`cpu-usage-${vmName}`"
              data-bs-toggle="tooltip" data-bs-placement="top" data-bs-animation="false"
              :title="`${vmName} CPU usage: ${vmList[vmName].cpuUsage }%`">
              <div class="progress" style="height: 100%;">
                <div class="progress-bar" role="progressbar"
                  :style="{ width: `${vmList[vmName].cpuUsage }%` }"
                  :aria-valuenow="vm.cpuUsage" aria-valuemin="0" aria-valuemax="100">
                </div>
              </div>
            </div>
            <div class="btn-group" role="group" aria-label="Actions">
              <button :id="`button-${vmName}`"
                  :class="['btn', 'btn-sm', 'me-2', vm.status === 'stopped' ? 'btn-success' : 'btn-danger']"
                  @click="toggleVM(vmName)">
                {{ vm.status === 'running' ? 'stop' : 'start' }}
              </button>
              <button class="btn btn-sm btn-light me-2"
                :disabled="vm.editprotect==='True'"
		@click="editVM(vmName)">✏️</button>
              <button class="btn btn-sm btn-light me-2"
                :disabled="vm.rmprotect==='True'"
                @click="deleteVM(vmName)">🗑️</button>
              <button class="btn btn-sm btn-light me-2"
                title="telnet to Virtual Machine TCP serial port"
                :disabled="!vm.serial_port"
                @click="openSerialPort(vmName)">💻</button>
            </div>
          </li>

          <!-- create button -->

          <li class="list-group-item">
            <div class="d-flex justify-content-between align-items-center">
              <div>new vm</div>
              <button class="btn btn-sm" :class="showInputs ? 'btn-danger' : 'btn-primary' " @click="toggleInputs(Action.CREATE)">
                {{ showInputs ? 'cancel' : 'create' }}
              </button>
            </div>

            <!-- hidden inputs for vm creation / modification -->

            <div v-if="showInputs" class="mt-2">
              <div class="input-group">
                <input v-model="VM.name" type="text" class="form-control" placeholder="VM Name" aria-label="VM Name">
              </div>
              <div class="input-group mt-2"
                data-bs-toggle="tooltip" data-bs-placement="top" data-bs-animation="false"
                title="Virtual Machine memory amount in megabytes">
                <input v-model="VM.mem" type="text" class="form-control" placeholder="Memory" aria-label="128m">
              </div>
              <div class="input-group mt-2"
                data-bs-toggle="tooltip" data-bs-placement="top" data-bs-animation="false"
                title="Number of CPU cores">
                <input v-model="VM.cores" type="text" class="form-control" placeholder="Cores" aria-label="Cores">
              </div>
              <div class="input-group mt-2"
                data-bs-toggle="tooltip" data-bs-placement="top" data-bs-animation="false"
                title="Port forwarding, QEMU syntax">
                <input v-model="VM.hostfwd" type="text" class="form-control" placeholder="::8880-:80[,::22202-:22,...]" aria-label="HostFwd">
              </div>
              <div class="input-group mt-2"
                data-bs-toggle="tooltip" data-bs-placement="top" data-bs-animation="false"
                title="Host directory to share with guest as 9p device">
                <input v-model="VM.share" type="text" class="form-control" placeholder="Shared directory" aria-label="Share">
              </div>
              <div class="input-group mt-2">
                <input v-model="VM.tcpserial" type="checkbox" id="tcpserial" class="form-check-input">
                <label for="tcpserial" class="form-check-label ms-2">TCP serial console</label>
              </div>
              <div class="input-group mt-2">
                <input v-model="VM.rmprotect" type="checkbox" id="rmprotect" class="form-check-input">
                <label for="rmprotect" class="form-check-label ms-2">rm protected</label>
              </div>
              <div class="input-group mt-2">
                <input v-model="VM.editprotect" type="checkbox" id="editprotect" class="form-check-input">
                <label for="editprotect" class="form-check-label ms-2">Edit protected</label>
              </div>
              <button class="btn btn-success btn-sm mt-3" @click="createVM">save</button>
            </div>
          </li>

        </ul> <!-- end main list  -->

      </div> <!-- manage tab -->

      <!-- Info tab -->

      <div class="tab-pane" id="nav-infos" role="tabpanel"
        aria-labelledby="nav-infos-tab">
        <ul>
            <li><b>site</b> <a href="https://smolBSD.org/">https://smolBSD.org/</a></li>
            <li><b>version</b> </li>
            <li><b>load</b></li>
        </ul>
      </div>

    </div> <!-- tab content -->
  </div>

  <script>
    const app = Vue.createApp({
      data() {
        return {
          Action: {
            CREATE: 0,
            EDIT: 1,
          },
          vmList: {},
          showInputs: false,
          VM: {
            name: '',
            mem: '',
            cores: '',
            hostfwd: '',
            share: '',
            tcpserial: false,
            rmprotect: false,
            editprotect: false,
          },
        };
      },
      methods: {
        toggleInputs(actionType) {
          if (actionType === this.Action.CREATE) {
            this.zeroVM();
          }
          this.showInputs = !this.showInputs;
        },
        getVMList() {
          fetch('/vmlist')
            .then(response => response.json())
            .then(data => {
              this.vmList = data;
            })
            .catch(error => console.error('Error fetching VM list:', error));
        },
        getState(vm) {
          const state = vm.status === 'stopped'
            ? {
                 button: 'btn-success', action: 'start', status: 'stopped'
              }
            : {
                 button: 'btn-danger', action: 'stop', status: 'running'
              };
          return state;
        },
        zeroVM() {
          for (const key in this.VM) {
            if (this.VM.hasOwnProperty(key)) {
              if (typeof this.VM[key] === 'string') {
                this.VM[key] = ''; // Reset string properties
              } else if (typeof this.VM[key] === 'boolean') {
                this.VM[key] = false; // Reset boolean properties
              }
            }
          }
        },
        toggleVM(vmName) {
          var state = this.getState(this.vmList[vmName]);
          const btnElement = document.getElementById(`button-${vmName}`);
          btnElement.classList.add('blinking');
          btnElement.classList.remove(state.button)
          btnElement.classList.add('btn-secondary')

          fetch(`/${state.action}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ vm_name: vmName }),
          })
          .then(response => response.json())
          .then(data => {
            if (data.success) {
              var retry = 0;
              const intervalId = setInterval(() => {
                this.getVMList(); // refresh vm statuses
                if (this.vmList[vmName].status !== data.status) {
                  state = this.getState(data);
                  btnElement.classList.remove('blinking');
                  btnElement.classList.add(state.button);
                  this.vmList[vmName].status = state.status;
                  clearInterval(intervalId);
                }
                if (retry++ > 3) {
                  btnElement.classList.remove('blinking');
                  btnElement.classList.add(state.button);
                  clearInterval(intervalId);
                }
              }, 1000);
            }
          })
          .catch(error => {
            alert(`Error: ${error.message}`);
          });
        },
        createVM() {
          fetch('/saveconf', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              vm: this.VM.name,
              mem: this.VM.mem,
              cores: this.VM.cores,
              hostfwd: this.VM.hostfwd,
              share: this.VM.share,
              tcpserial: this.VM.tcpserial,
              rmprotect: this.VM.rmprotect,
              editprotect: this.VM.editprotect
            })
          })
          .then(response => response.json())
          .then(data => {
            if (data.success) {
              this.getVMList();
              this.toggleInputs(this.Action.CREATE);
              alert(`VM ${this.VM.name} saved successfully.`);
            }
          })
          .catch(error => {
            alert(`Error: ${error.message}`);
          });
        },
        deleteVM(vmName) {
          fetch(`/rm/${vmName}`, {
            method: 'DELETE',
            headers: { 'Content-Type': 'application/json' },
          })
          .then(response => response.json())
          .then(data => {
            if (data.success) {
              this.getVMList();
              alert(`VM ${vmName} deleted successfully.`);
            } else {
              alert(`Failed to delete VM: ${data.message}`);
            }
          })
          .catch(error => {
            alert(`Error deleting VM: ${error.message}`);
          });
        },
        editVM(vmName) {
          const setBooleanFromStr = (value) => value === "True";

          this.zeroVM();
          this.VM.name = vmName;
          this.VM.mem = this.vmList[vmName].mem;
          this.VM.cores = this.vmList[vmName].cores;
          this.VM.hostfwd = this.vmList[vmName].hostfwd;
          this.VM.share = this.vmList[vmName].share;
          if (this.vmList[vmName].serial_port) {
            this.VM.tcpserial = true;
          }
          this.VM.rmprotect = setBooleanFromStr(this.vmList[vmName].rmprotect);
          this.VM.editprotect = setBooleanFromStr(this.vmList[vmName].editprotect);

          if (!this.showInputs) {
            this.toggleInputs(this.Action.EDIT);
          }
        },
        openSerialPort(vmName) {
          const serialPort = this.vmList[vmName]?.serial_port;
          if (serialPort) {
            // on Linux this relies on xdg-open(1) using konsole(1) to open telnet://
            const serialurl = `telnet://localhost:${this.vmList[vmName]?.serial_port}`
            window.location=serialurl
          }
        },
        copySerialPort(vmName) {
          const serialPort = this.vmList[vmName]?.serial_port;
          if (serialPort) {
            const cpcommand = `telnet localhost ${this.vmList[vmName]?.serial_port}`
            if (!navigator.clipboard) {
              alert(`navigator.clipboard not available in HTTP: ${cpcommand}`);
              return;
            }
            navigator.clipboard.writeText(cpcommand)
              .then(() => {
                alert(`${cpcommand} copied to clipboard`);
              })
              .catch(err => {
                alert(`Failed to copy: ${err}`);
              });
          }
        },
        cpuUsage() {
          Object.keys(this.vmList).forEach(vmName => {
            if (this.vmList[vmName].status != 'running') {
              return;
            }
            fetch(`/cpu_usage/${vmName}`)
              .then(response => response.json())
              .then(data => {
                this.vmList[vmName].cpuUsage = data;
                //console.log(data);
              })
              .catch(error => console.error('Error fetching CPU usage:', error));
          });
        },
      },

      mounted() {
        this.getVMList();
        setInterval(this.cpuUsage, 1000);
        var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
        var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
          return new bootstrap.Tooltip(tooltipTriggerEl)
        })
      }
    });

    app.mount('#app'); // Mount the Vue app to the `#app` div
  </script>
</body>
</html>


================================================
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 <<EOF >>/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 <<EOF
useradd -m ${NBUSER}
chsh -s /usr/pkg/bin/bash ${NBUSER}
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'' 's/status-right "\$RS"/status-right ":: powered by 🚩 + smolBSD "/' \
	${NBHOME}/.tmux/tmux-power.tmux
chmod +x ${NBHOME}/.tmux/tmux-power.tmux

# for convenience
PYDOTVERS=3.${PYVERS#3}
for p in python pip pipx uv- uvx-; do
	ln -s /usr/pkg/bin/\${p}3.${PYVERS#3} /usr/pkg/bin/\${p%-}
done
EOF

RUN cat <<EOF >${NBHOME}/.vimrc
set nocompatible
set ts=8
set noai
syntax on
set mouse-=a

noremap <C-t> :tabnew<CR>
noremap <C-right> :tabnext<CR>
noremap <C-left> :tabprevious<CR>
noremap <C-l> :tabnext<CR>
noremap <C-k> :tabprevious<CR>
EOF

RUN cat <<EOF >${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 <<EOF >${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 <<EOF >/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 <<EOF >/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 <<EOF >.tmux.conf
set -g status off
EOF

RUN cat <<EOF >.profile
printf "\n⌚ Wait for crush to load...\n\n"

crush

exit
EOF

RUN cat <<EOF >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 '<html><body>Up!</body></html>' >/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
<html><body>up!</body></html>
```

## 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    <none>           <none>
```
And curl it!
```sh
$ curl http://10.42.0.21:8080
<html><body>up!</body></html>
 ```

[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<<EOF
timeout=0
consdev=${BIOSCONSOLE}
EOF
fi

disksize=$(du -s ${mnt}|cut -f1)
cd .. # get out mountpoint
umount $mnt

if [ -n "$MINIMIZE" ]; then
	addspace=$(( ${MINIMIZE#*+} * 2048 ))
	[ $addspace -eq 0 ] && addspace=$((disksize / 10))
	disksize=$((disksize + addspace)) # give 10% MB more
	echo "${ARROW} resizing image to $((disksize / 2048))MB"
	resize_ffs -y -s ${disksize} /dev/${mountdev}
	fsck_ffs -c4 -f -y /dev/${mountdev} >/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/tty
exec ./smol.sh -k $KERNEL -i rescue.img


================================================
FILE: scripts/uname.sh
================================================
#!/bin/sh

[ $# -lt 1 ] && exit 1

arg=$1

# Normalize architecture name
unamesh() {
	[ -n "$ARCH" ] || ARCH=$(uname -m 2>/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 "<html><body>up!</body></html>" >${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
================================================
<div align="center" markdown="1">

**Running smolClaw with a local inference server**

<img src="images/smolTelegram.png">

</div>

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
================================================
<div align="center" markdown="1">

<img src="images/smolClaw.png" width=500px>

# smolClaw

<img src="images/smolcap.png" width=500px>

[picoclaw][1] running on a microVM!

</div>

**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/<programming language>/<program name>`, 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 <<EOF
useradd -m smol
cd /home/smol
mkdir -p .ssh www
EOF

COPY $PUBKEY /home/smol/.ssh/authorized_keys

RUN <<EOF
chmod 600 /home/smol/.ssh/authorized_keys
chown -R smol /home/smol
EOF

EXPOSE 8880

CMD /etc/rc.d/sshd onestart && \
    caddy file-server --listen :8880 --root /home/smol/www
```

In _smolBSD_'s directory, create a `share` directory and paste the previously copied _SSH_ public key in `share/ssh.pub`.

>[!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
================================================
<div align="center" markdown="1">
<img src="logo.svg" height="150px">
</div>

# 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<<EOF
/browser/:/browser/index.php
/htmlol/:/htmlol/index.php
EOF

echo "${INFO} \"LHV tools\" prerequisites installation ..."
if [ -d ${toolsdir} ]; then rm -fr ${toolsdir}; fi
mkdir ${toolsdir}

link="https://lehollandaisvolant.net/tout/tools/tools.tar.7z"
${FETCH} -o ${toolsdir}/$(basename ${link}) ${link} 
if [ $? -ne 0 ]; then
	echo -e "${ERROR} \"LHV tools\" download failed.\nExit."
	. etc/include/shutdown
fi

# We can't use pipe like "curl -o- http://... | 7.z x ..." with 7z files. This file
# format can not be streamed, even with "-si" option. Have to use multiple steps.
# See https://7-zip.opensource.jp/chm/cmdline/switches/stdin.htm.
# 
# The archive will no longer be useful after unzipping and unarchiving, so,
# to avoid space disc consumption, download and decompression are made in the
# tmp/ directory of smolBSD, on the host file system.

echo -n "${ARROW} un-7zipping |"
# Unlike the "tar" command, "7z" is not necessarily installed everywhere. So it's
# installed on the microvm with "ADDPKGS" in options.mk and used here.
usr/pkg/bin/7z e -o${toolsdir} ${toolsdir}/$(basename ${link}) 2>&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,</section>,</section>\n<footer id="footer"><a href="//lehollandaisvolant.net">by <em>Timo Van Neerden</em></a></footer>,g' ${wwwroot}/barcode/index.php
sed -i'' 's,<a href="/">by <em>Timo Van Neerden,<a href="//lehollandaisvolant.net">by <em>Timo Van Neerden,g' ${wwwroot}/*/index.php
sed -i'' 's,<a href="/">Timo Van Neerden,<a href="//lehollandaisvolant.net">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<<EOF
export HOME=${NBHOME}
export EDITOR=nvim
export VISUAL=\${EDITOR}
export PAGER=less
export NBUSER=${NBUSER}
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin

lightgreen="${lightgreen}"
bold="${bold}"
normal="${normal}"

LANG=en_US.UTF-8; export LANG

alias cat="bat -pp"
alias less="bat -p"
alias vim=nvim
alias vi=nvim

stty speed 115200 >/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 <command>\${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<<EOF
# spawn a login shell in order to read ~/.bash_profile
unbind C-b
set -g prefix ^Q
bind q send-prefix
set-option -g default-command "bash -l"
set -g default-terminal "tmux-256color"
set -g @tmux_power_theme 'moon'
run '~/.tmux/tmux-power.tmux'
EOF
		chown -R ${NBUSER} ${NBHOME} ${NBHOME}/.*

		printbold "⭐ adding $NBUSER to group wheel"
		usermod -G wheel $NBUSER
		printbold "⭐ permit $NBUSER to ${lightgreen}doas${normal}"
		echo "permit nopass keepenv setenv { PATH } :wheel" \
			>/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<<EOF
#!/bin/sh

cd /dev
sh MAKEDEV -M -M all
cd -
EOF
chmod +x ${PREFIX}/etc/nitro/SYS/setup

cat >${PREFIX}/etc/nitro/getty-0/run<<EOF
#!/bin/sh
exec /usr/libexec/getty Pc constty
EOF
cat >${PREFIX}/etc/nitro/getty-0/finish<<EOF
#!/bin/sh
exit 0
EOF
for s in run finish
do
	chmod +x ${PREFIX}/etc/nitro/getty-0/$s
done

cat >etc/motd<<EOF

Welcome to nitroBSD! 🔥

EOF


================================================
FILE: service/nitrosshd/NETBSD_ONLY
================================================


================================================
FILE: service/nitrosshd/README.md
================================================
# nitroSSHd

This microservice starts an _OpenSSH_ daemon with the [nitro][1] `init` system.

As it uses `union` `tmpfs` which is unsupported with `ext2`, it must be built with either a _NetBSD_ host or the [builder image][2].  
Add the desired SSH public keys in the `service/nitrosshd/etc` directory in file(s) ending with `.pub`.

Building on GNU/Linux or MacOS
```sh
$ bmake SERVICE=nitrosshd build
```
Building on NetBSD
```sh
$ make SERVICE=nitrosshd base
```
Start the service:
```sh
$ ./startnb.sh -f etc/nitrosshd.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/nitrosshd.conf`.

[1]: https://github.com/leahneukirchen/nitro
[2]: https://github.com/NetBSDfr/smolBSD/tree/main/service/build


================================================
FILE: service/nitrosshd/options.mk
================================================
MOUNTRO=y


================================================
FILE: service/nitrosshd/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
VERSION=0.4.1
${FETCH} -o packages/nitro.tgz https://imil.net/NetBSD/nitro-${VERSION}.tgz?$RANDOM

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/LOG \
	${PREFIX}/etc/nitro/sshd home
do
	mkdir -p ${d}
done

ln -sf /var/run/nitro/nitro.sock ${PREFIX}/etc/nitro.sock

# ptyfs is needed to provide ttys to clients
echo "ptyfs /dev/pts ptyfs rw 0 0" >> 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<<EOF
#!/bin/sh

cd /dev
sh MAKEDEV -M -M all
cd -

. /etc/include/basicrc

mount -t tmpfs -o -s1M tmpfs /home
mount -t tmpfs -o -s10M tmpfs /tmp
mount -t tmpfs -o -s1M -o union tmpfs /var/run
mount -t tmpfs -o -s10M -o union tmpfs /var/log
mount -t tmpfs -o -s20M -o union tmpfs /etc
# union mount is not recursive
mount -t tmpfs -o -s1M -o union tmpfs /etc/ssh

exit 0
EOF
chmod +x ${PREFIX}/etc/nitro/SYS/setup
cat >${PREFIX}/etc/nitro/LOG/run<<EOF
#!/bin/sh
exec cat >/dev/console 2>&1
EOF
chmod +x ${PREFIX}/etc/nitro/LOG/run
cat >${PREFIX}/etc/nitro/sshd/run<<EOF
#!/bin/sh
exec 2>&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<<EOF
#!/bin/sh
exit 0
EOF
for s in run finish
do
	chmod +x ${PREFIX}/etc/nitro/sshd/$s
done


================================================
FILE: service/nitrosshd/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


================================================
FILE: service/pipe/etc/rc
================================================
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin

[ ! -f "/dev/MAKEDEV" ] && cp -f /etc/MAKEDEV* /dev

dmesg | grep -q viocon && \
	(cd /dev && ./MAKEDEV ttyVI01 ttyVI02 && chmod 666 /dev/ttyVI*) || \
	echo "no viocon(4) support"

while read cmd; do
	eval "$cmd"
done < /dev/ttyVI02


================================================
FILE: service/pipe/options.mk
================================================
.if defined(MINIMIZE) && ${MINIMIZE} == y
ADDPKGS=pkgin pkg_tarup pkg_install sqlite3 rsync curl
.endif


================================================
FILE: service/pipe/sailor.conf
================================================
. /service/common/sailor.vars

shipname=pipe


================================================
FILE: service/rescue/etc/rc
================================================
#!/bin/sh

export HOME=/
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/rescue
umask 022

[ "$(sysctl -n kern.root_device)" = "md0" ] || mount -a

sh


================================================
FILE: service/rescue/options.mk
================================================
SETS=rescue.${SETSEXT} etc.${SETSEXT}
IMGSIZE=20
MOUNTRO=y


================================================
FILE: service/runbsd/etc/banner.ans
================================================
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓                                                      ▓▓
▓▓▓▓                                                      ▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓[38;5;231;4
Download .txt
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
Download .txt
SYMBOL INDEX (17 symbols across 1 files)

FILE: app/app.py
  function get_vmlist (line 22) | def get_vmlist():
  function list_files (line 49) | def list_files(path):
  function get_port (line 59) | def get_port(vmname, service, default_port):
  function query_qmp (line 73) | def query_qmp(command, vmname):
  function get_pid (line 94) | def get_pid(vmname):
  function get_cpu_usage (line 105) | def get_cpu_usage(vmname):
  function index (line 119) | def index():
  function assets (line 124) | def assets():
  function vm_list (line 128) | def vm_list():
  function getkernels (line 133) | def getkernels():
  function getimages (line 138) | def getimages():
  function start_vm (line 143) | def start_vm():
  function stop_vm (line 161) | def stop_vm():
  function saveconf (line 179) | def saveconf():
  function rm_file (line 225) | def rm_file(vm):
  function qmp (line 244) | def qmp(vmname, command):
  function cpu_usage (line 253) | def cpu_usage(vmname):
Condensed preview — 139 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (387K chars).
[
  {
    "path": ".github/workflows/main.yml",
    "chars": 2081,
    "preview": "name: Build smolBSD image\n\non:\n  push:\n    branches:\n      - main\n    paths-ignore:\n      - '**.md'\n      - 'www/**'\n   "
  },
  {
    "path": ".gitignore",
    "chars": 246,
    "preview": "# editors\n/.idea\n# downloads & temporary files\n/tmp/\n*.img\n/kernels/*\n/sets/**\n/images/*\n/pkgs/**\n/db/**\nnetbsd-*\n# app "
  },
  {
    "path": "LICENSE",
    "chars": 1272,
    "preview": "Copyright 2023 Emile `iMil' Heitor\n\nRedistribution and use in source and binary forms, with or without modification,\nare"
  },
  {
    "path": "Makefile",
    "chars": 6835,
    "preview": "ARCH!=\t\tARCH=${ARCH} scripts/uname.sh -m\nMACHINE!=\tscripts/uname.sh -p\nOS!=\t\tuname -s\nSETSEXT?=\ttar.xz\n\n.if ${ARCH} == \""
  },
  {
    "path": "README.md",
    "chars": 14587,
    "preview": "<div align=\"center\" markdown=\"1\">\n\n<img src=\"www/smolBSD.png\" width=150px>\n\n**smolBSD**\n\nbuild your own minimal BSD UNIX"
  },
  {
    "path": "app/.flaskenv",
    "chars": 151,
    "preview": "# Flask Defaults\n\nFLASK_APP=app\nFLASK_DEBUG=True\nFLASK_ENV=development\nFLASK_CWD=..\n#FLASK_LOGLEVEL=50\n\nFLASK_RUN_HOST=\""
  },
  {
    "path": "app/README.md",
    "chars": 528,
    "preview": "# smolBSD VM Manager <img src=\"static/smolBSD.png\" alt=\"\" width=\"10%\">\n\n## App Usage\n\n## QuickStart\n\n~~~\nscripts/app-run"
  },
  {
    "path": "app/app.py",
    "chars": 7460,
    "preview": "import json\nimport logging\nimport os\nimport psutil\nimport socket\nimport subprocess\nimport dotenv\nimport sys\nfrom flask i"
  },
  {
    "path": "app/index.html",
    "chars": 14309,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title># smolBSD VMs</title>\n  <link rel=\"icon\" href=\"static/smol.ico\">\n  <script src=\"h"
  },
  {
    "path": "app/requirements.txt",
    "chars": 27,
    "preview": "Flask\npsutil\npython-dotenv\n"
  },
  {
    "path": "bin/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "contribs/knockd.sh",
    "chars": 483,
    "preview": "#!/bin/sh\n\n# usage\n# server side: PORTS=\"1050 2000 3000\" SERVICE=\"sshd\" contribs/knockd.sh\n# client side: PORTS=\"1050 20"
  },
  {
    "path": "contribs/knockssh.sh",
    "chars": 317,
    "preview": "#!/bin/sh\n\n# sample script to use with knockd.sh, I use it from Termux\n# on my Android phone\n\necho -n \"port: \"\nstty -ech"
  },
  {
    "path": "dockerfiles/Dockerfile.basic",
    "chars": 81,
    "preview": "FROM base,etc\n\nLABEL smolbsd.service=\"basic\"\nLABEL smolbsd.minimize=\"y\"\n\nCMD ksh\n"
  },
  {
    "path": "dockerfiles/Dockerfile.bsdshell",
    "chars": 986,
    "preview": "# unusual \"FROM\" naming, multiple RUN don't matter, and non-JSON CMD\n# hadolint global ignore=DL3006,DL3025,DL3059\nFROM "
  },
  {
    "path": "dockerfiles/Dockerfile.caddy",
    "chars": 401,
    "preview": "# Mandatory, either comma separated base sets (here base and etc)\n# or base image name i.e. base-amd64.img\nFROM base,etc"
  },
  {
    "path": "dockerfiles/Dockerfile.clawd",
    "chars": 3511,
    "preview": "# smolClaw: run an picoclaw instance in a microVM\n#\n# build this service:\n# $ ./smoler.sh build dockerfiles/Dockerfile.c"
  },
  {
    "path": "dockerfiles/Dockerfile.crush",
    "chars": 1761,
    "preview": "# smol'd version of crush https://github.com/charmbracelet/crush\n#\n# copy a configured crush.json in the directory of th"
  },
  {
    "path": "dockerfiles/Dockerfile.dockinx",
    "chars": 676,
    "preview": "FROM base,etc\n\nLABEL smolbsd.service=dockinx\nLABEL smolbsd.minimize=y\nLABEL smolbsd.publish=8800:80\n\nARG JUST=ATEST\n\nRUN"
  },
  {
    "path": "dockerfiles/Dockerfile.tiny",
    "chars": 285,
    "preview": "# hadolint global ignore=DL3006,DL3025 # FROM naming and non-JSON CMD\nFROM base,etc\n\nLABEL smolbsd.service=\"tiny\"\n# stri"
  },
  {
    "path": "etc/base.conf",
    "chars": 21,
    "preview": "fwcfgvar=\"MOUNTRO=y\"\n"
  },
  {
    "path": "etc/bozohttpd.conf",
    "chars": 225,
    "preview": "# optional\nmem=128m\n# optional\ncores=1\n# optional port forward\nhostfwd=::8180-:80\n# kernel parameters to append\n#append="
  },
  {
    "path": "etc/clawd.conf",
    "chars": 73,
    "preview": "hostfwd=::18789-:18789,::18800-:18800,::2289-:22\nimgtag=latest\nuse_pty=y\n"
  },
  {
    "path": "etc/games.conf",
    "chars": 187,
    "preview": "# Because text-based games deserve power! \n#mem=32768m # Not less, fool!\n#cores=64 # min requirement\n#\n# ... these value"
  },
  {
    "path": "etc/lhv-tools.conf",
    "chars": 146,
    "preview": "# optional\nmem=128m\n# optional\ncores=1\n# optional port forward\nhostfwd=::8180-:80\n# optional qmp\nqmp_port=4444\n# optiona"
  },
  {
    "path": "etc/live.conf",
    "chars": 228,
    "preview": "ARCH=\"$(scripts/uname.sh -m)\"\n# make live for amd64 or make ARCH=evbarm-aarch64 live\nimg=images/NetBSD-${ARCH}-live.img\n"
  },
  {
    "path": "etc/nbakery.conf",
    "chars": 224,
    "preview": "# optional\nmem=512m\n# optional\ncores=2\n# optional port forward\nhostfwd=::2299-:22\n# optional anything or empty\n#bridgene"
  },
  {
    "path": "etc/nitrosshd.conf",
    "chars": 225,
    "preview": "# optional\nmem=256m\n# optional\ncores=1\n# optional port forward\nhostfwd=::2022-:22\n# optional anything or empty\n#bridgene"
  },
  {
    "path": "etc/rescue.conf",
    "chars": 276,
    "preview": "# optional\nmem=128m\n# optional\ncores=1\n# optional qmp\n#qmp_port=4444\n# optional serial\n#serial_port=5555\n# optional port"
  },
  {
    "path": "etc/runbsd.conf",
    "chars": 224,
    "preview": "# optional\nmem=512m\n# optional\ncores=2\n# optional port forward\nhostfwd=::2298-:22\n# optional anything or empty\n#bridgene"
  },
  {
    "path": "etc/sshd.conf",
    "chars": 224,
    "preview": "# optional\nmem=256m\n# optional\ncores=1\n# optional port forward\nhostfwd=::2022-:22\n# optional anything or empty\n#bridgene"
  },
  {
    "path": "etc/systembsd.conf",
    "chars": 225,
    "preview": "# optional\nmem=512m\n# optional\ncores=2\n# optional port forward\nhostfwd=::2297-:22\n# optional anything or empty\n#bridgene"
  },
  {
    "path": "etc/tslog.conf",
    "chars": 229,
    "preview": "# optional\nmem=256m\n# optional\ncores=1\n# optional port forward\nhostfwd=::2299-:22\n# optional anything or empty\n#bridgene"
  },
  {
    "path": "etc/usershell.conf",
    "chars": 279,
    "preview": "# optional\nmem=128m\n# optional\ncores=1\n# optional qmp\n#qmp_port=4444\n# optional serial\n#serial_port=5555\n# optional port"
  },
  {
    "path": "k8s/Dockerfile",
    "chars": 416,
    "preview": "FROM alpine:latest\n\nRUN apk add --quiet --no-cache qemu-system-x86_64 uuidgen\n\nARG NBIMG=bozohttpd-amd64.img\nARG MEM=256"
  },
  {
    "path": "k8s/README.md",
    "chars": 3330,
    "preview": "# smolBSD pod example\n\nA _smolBSD_ system can be spawned inside a container, thus bringing a decent level of security to"
  },
  {
    "path": "k8s/generic-device-plugin.yaml",
    "chars": 1388,
    "preview": "apiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: generic-device-plugin\n  namespace: kube-system\n  labels:\n    app.k"
  },
  {
    "path": "k8s/smolbozo.yaml",
    "chars": 458,
    "preview": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: smolbozo\n  namespace: smolbsd\n  labels:\n    app: smolbozo\nspec:\n  containers:"
  },
  {
    "path": "misc/vmbatch.md",
    "chars": 1278,
    "preview": "# VM batch creation and bench\n\nYou need [smolBSD](https://github.com/NetBSDfr/smolBSD) to test the following\n\n## Read-on"
  },
  {
    "path": "mkimg.sh",
    "chars": 9004,
    "preview": "#!/bin/sh\n\nset -e\n\nprogname=${0##*/}\n\nusage()\n{\n\tcat 1>&2 << _USAGE_\nUsage: $progname [-s service] [-m megabytes] [-i im"
  },
  {
    "path": "mnt/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "scripts/app-run.sh",
    "chars": 677,
    "preview": "#!/bin/sh\n#\n# See app/README.md for documentation\n#\n\nprogname=${0##*/}\nprogdir=${0%/*}\ncwd=$(realpath $progdir)\n\nset -eu"
  },
  {
    "path": "scripts/fetch.sh",
    "chars": 710,
    "preview": "#!/bin/sh\n\nflag=$1\nif [ \"$flag\" = \"-o\" ]; then\n\toutfile=$2\n\tfullurl=$3\nelse\n\tfullurl=$2\nfi\n\ncase $fullurl in\n*\\**)\n\tdldi"
  },
  {
    "path": "scripts/freshchk.sh",
    "chars": 612,
    "preview": "#!/bin/sh\n\n# if the host does not have internet access\n[ -n \"$NONET\" ] && exit 0\n\n. service/common/choupi\n\nURL=$1\nDEST=$"
  },
  {
    "path": "scripts/sh",
    "chars": 1894,
    "preview": "#!/bin/sh\n# smolBSD quick bootstrapper\n# Usage: curl -fsSL https://smolbsd.org/sh | sh\n\nset -eu\n\nVERS=\"11\"\nCURL=\"curl -f"
  },
  {
    "path": "scripts/uname.sh",
    "chars": 581,
    "preview": "#!/bin/sh\n\n[ $# -lt 1 ] && exit 1\n\narg=$1\n\n# Normalize architecture name\nunamesh() {\n\t[ -n \"$ARCH\" ] || ARCH=$(uname -m "
  },
  {
    "path": "service/base/etc/rc",
    "chars": 87,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n. /etc/include/mount9p\n\nksh\n\n. /etc/include/shutdown\n"
  },
  {
    "path": "service/base/postinst/dostuff.sh",
    "chars": 51,
    "preview": "#!/bin/sh\n\necho \"iMil was here\" > tmp/postinst.txt\n"
  },
  {
    "path": "service/biosboot/README.md",
    "chars": 865,
    "preview": "# BIOS Boot Service\n\n## About\n\nThis image is meant to build a _BIOS_ bootable image, for use with _Virtual Machine Manag"
  },
  {
    "path": "service/biosboot/etc/rc",
    "chars": 87,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n. /etc/include/mount9p\n\nksh\n\n. /etc/include/shutdown\n"
  },
  {
    "path": "service/biosboot/options.mk",
    "chars": 26,
    "preview": "BIOSBOOT=y\nBIOSCONSOLE=pc\n"
  },
  {
    "path": "service/bozohttpd/etc/rc",
    "chars": 95,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n\n/usr/libexec/bozohttpd -f /var/www\n\n. /etc/include/shutdown\n"
  },
  {
    "path": "service/bozohttpd/postinst/mkhtml.sh",
    "chars": 97,
    "preview": "wwwroot=var/www\n\nmkdir -p $wwwroot\n\necho \"<html><body>up!</body></html>\" >${wwwroot}/index.html\n\n"
  },
  {
    "path": "service/bsdshell/sailor.conf",
    "chars": 200,
    "preview": ". /service/common/sailor.vars\n\nshipname=usershell\nshipbins=\"$shipbins /usr/bin/chsh /bin/ksh /usr/bin/ssh /usr/bin/ssh-k"
  },
  {
    "path": "service/build/etc/rc",
    "chars": 1145,
    "preview": ". /etc/include/vars\n. /etc/include/choupi\n. /etc/include/basicrc\n. /etc/include/mount9p\n\n# needed to fetch HTTPS\nmount -"
  },
  {
    "path": "service/build/etc/resolv.conf",
    "chars": 20,
    "preview": "nameserver 10.0.2.3\n"
  },
  {
    "path": "service/build/options.mk",
    "chars": 54,
    "preview": "MOUNTRO=y\nADDPKGS=pkgin pkg_tarup pkg_install sqlite3\n"
  },
  {
    "path": "service/build/postinst/prepare.sh",
    "chars": 255,
    "preview": "#!/bin/sh\n\nmkdir -p usr/pkg/etc/pkgin\necho \"https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/${ARCH#evbarm-}/${PKGVERS}"
  },
  {
    "path": "service/clawd/LOCAL.md",
    "chars": 2098,
    "preview": "<div align=\"center\" markdown=\"1\">\n\n**Running smolClaw with a local inference server**\n\n<img src=\"images/smolTelegram.png"
  },
  {
    "path": "service/clawd/README.md",
    "chars": 3293,
    "preview": "<div align=\"center\" markdown=\"1\">\n\n<img src=\"images/smolClaw.png\" width=500px>\n\n# smolClaw\n\n<img src=\"images/smolcap.png"
  },
  {
    "path": "service/clawd/SCHIZOCLAW.md",
    "chars": 5074,
    "preview": "# Schizo setup\n\nWhile it's on [the roadmap][1], [picoclaw][2] still don't have multi-agents support.\nAlso, I intend to u"
  },
  {
    "path": "service/common/basicrc",
    "chars": 1443,
    "preview": "\nexport HOME=/\nexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin\numask 022\n\nif [ \"$(sysctl -n kern.ro"
  },
  {
    "path": "service/common/choupi",
    "chars": 407,
    "preview": "case $CHOUPI in\n*)\n\tSTAR=\"⭐\"\n\tARROW=\"➡️\"\n\tCHECK=\"✅\"\n\tWARN=\"⚠️\"\n\tINFO=\"ℹ️\"\n\tWHITEBULLET=\"⚪\"\n\tERROR=\"❌\"\n\tCPU=\"🔲\"\n\tFREEZE=\""
  },
  {
    "path": "service/common/funcs",
    "chars": 512,
    "preview": "# helper functions\n\nrsynclite()\n{\n\tsrc=$1\n\tdst=$2\n\twhile case \"$src\" in --exclude=*) true;; *) false;; esac; do\n\t\texclud"
  },
  {
    "path": "service/common/mount9p",
    "chars": 245,
    "preview": "if dmesg |grep -q vio9; then\n\t[ -z \"$MOUNT9P\" ] && MOUNT9P=/mnt\n\t[ -f /etc/MAKEDEV ] && cp /etc/MAKEDEV /dev\n\tcd /dev &&"
  },
  {
    "path": "service/common/pkgin",
    "chars": 1387,
    "preview": ". /etc/include/choupi\n\n# install necessary packages\nif [ ! -d /var/db/pkgin ]; then\n\tcase $CHOUPI in\n\t[yY]*)\n\t\tprintf \"\\"
  },
  {
    "path": "service/common/qemufwcfg",
    "chars": 203,
    "preview": "QEMUFWCFG=/var/qemufwcfg\n\n/sbin/mount_qemufwcfg $QEMUFWCFG\n\nfor file in ${QEMUFWCFG}/opt/org.smolbsd.var.*\ndo\n\t[ ! -f $f"
  },
  {
    "path": "service/common/sailor.vars",
    "chars": 998,
    "preview": "# smolBSD build drive\nshippath=\"/drive2\"\n# default shipped binaries\nshipbins=\"/bin/sh /sbin/init /usr/bin/printf /sbin/m"
  },
  {
    "path": "service/common/shutdown",
    "chars": 173,
    "preview": "sync; sync\ndmesg | grep -q 'viocon0: adding port' && \\\n\t(\n\t\tmount -u -o ro /\n\t\tsync; sync\n\t\techo 'JEMATA!' > /dev/ttyVI0"
  },
  {
    "path": "service/common/vars",
    "chars": 69,
    "preview": "BASEPATH=/mnt\nDRIVE2=/drive2\nCHOUPI=y\n\nexport BASEPATH DRIVE2 CHOUPI\n"
  },
  {
    "path": "service/crush/README.md",
    "chars": 1936,
    "preview": "# 💥 Crush service\n\n## 📖 About\n\nThis microservice runs [crush](https://github.com/charmbracelet/crush), an AI-powered ter"
  },
  {
    "path": "service/games/README.md",
    "chars": 1548,
    "preview": "# games service \n\nVery useless service for [smolBSD](https://github.com/NetBSDfr/smolBSD) allowing to play text-based ga"
  },
  {
    "path": "service/games/etc/rc",
    "chars": 460,
    "preview": "#!/bin/sh\n\nexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin\n. /etc/include/choupi\n\nif [ ! -f /usr/pk"
  },
  {
    "path": "service/lhv-tools/README.md",
    "chars": 2441,
    "preview": "<div align=\"center\" markdown=\"1\">\n<img src=\"logo.svg\" height=\"150px\">\n</div>\n\n# lhv-tools\n\n## Introduction\n\n\"lhv\" stands"
  },
  {
    "path": "service/lhv-tools/etc/rc",
    "chars": 295,
    "preview": "#!/bin/sh\n\nexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin\n. /etc/include/choupi\n. /etc/include/bas"
  },
  {
    "path": "service/lhv-tools/options.mk",
    "chars": 122,
    "preview": "IMGSIZE=650\nADDPKGS=p7zip curl libidn2 libunistring readline pcre2 libxml2 nghttp2 libidn php84 php84-mbstring php84-cur"
  },
  {
    "path": "service/lhv-tools/postinst/postinstall.sh",
    "chars": 3407,
    "preview": "#!/bin/sh\n\n. /etc/include/choupi\nwwwroot=\"var/www\"\ntoolsdir=\"../tmp/lhv-tools\"\n\n# To avoid \"warning: TERM is not set\" me"
  },
  {
    "path": "service/mport/etc/rc",
    "chars": 105,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n\ncd /dev && /bin/sh /etc/MAKEDEV ttyVI01\n\nksh\n\n. /etc/include/shutdown\n"
  },
  {
    "path": "service/nbakery/etc/fstab",
    "chars": 188,
    "preview": "NAME=nbakeryroot\t\t/\t\tffs\trw,log\t\t\t1\t1\nptyfs\t\t/dev/pts\tptyfs\trw\t\t\t0\t0\nkernfs\t\t/kern\t\tkernfs\trw,noauto\t\t0\t0\nprocfs\t\t/proc\t"
  },
  {
    "path": "service/nbakery/etc/rc",
    "chars": 3787,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n\n# why are fds not created by MAKEDEV -MM init?\ncd /dev && sh MAKEDEV fd\ncd -\n\nexport "
  },
  {
    "path": "service/nbakery/etc/smol.ansi",
    "chars": 57511,
    "preview": "\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0m \u001b[0m\u001b[38;2;0;0;0"
  },
  {
    "path": "service/nbakery/options.mk",
    "chars": 13,
    "preview": "IMGSIZE=1024\n"
  },
  {
    "path": "service/nitro/postinst/00-nitro.sh",
    "chars": 1091,
    "preview": "#!/bin/sh\n\n# bare minimum\nmknod -m 600 dev/console c 0 0\nmknod -m 666 dev/null c 2 2\n\nmkdir -p packages\nif [ \"$ARCH\" = \""
  },
  {
    "path": "service/nitrosshd/NETBSD_ONLY",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "service/nitrosshd/README.md",
    "chars": 815,
    "preview": "# nitroSSHd\n\nThis microservice starts an _OpenSSH_ daemon with the [nitro][1] `init` system.\n\nAs it uses `union` `tmpfs`"
  },
  {
    "path": "service/nitrosshd/options.mk",
    "chars": 10,
    "preview": "MOUNTRO=y\n"
  },
  {
    "path": "service/nitrosshd/postinst/00-nitro.sh",
    "chars": 1935,
    "preview": "#!/bin/sh\n\n# bare minimum\nmknod -m 600 dev/console c 0 0\nmknod -m 666 dev/null c 2 2\n\nmkdir -p packages\nVERSION=0.4.1\n${"
  },
  {
    "path": "service/nitrosshd/postinst/keygen.sh",
    "chars": 531,
    "preview": "# taken from NetBSD's /etc/rc.d/sshd\nkeygen=\"/usr/bin/ssh-keygen\"\numask 022\nnew_key_created=false\nwhile read type bits f"
  },
  {
    "path": "service/pipe/etc/rc",
    "chars": 303,
    "preview": "export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin\n\n[ ! -f \"/dev/MAKEDEV\" ] && cp -f /etc/MAKEDEV* /de"
  },
  {
    "path": "service/pipe/options.mk",
    "chars": 104,
    "preview": ".if defined(MINIMIZE) && ${MINIMIZE} == y\nADDPKGS=pkgin pkg_tarup pkg_install sqlite3 rsync curl\n.endif\n"
  },
  {
    "path": "service/pipe/sailor.conf",
    "chars": 45,
    "preview": ". /service/common/sailor.vars\n\nshipname=pipe\n"
  },
  {
    "path": "service/rescue/etc/rc",
    "chars": 146,
    "preview": "#!/bin/sh\n\nexport HOME=/\nexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:/rescue\numask 022\n\n[ \"$(sysctl -n kern.root_device)\" "
  },
  {
    "path": "service/rescue/options.mk",
    "chars": 59,
    "preview": "SETS=rescue.${SETSEXT} etc.${SETSEXT}\nIMGSIZE=20\nMOUNTRO=y\n"
  },
  {
    "path": "service/runbsd/etc/banner.ans",
    "chars": 35015,
    "preview": "\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5"
  },
  {
    "path": "service/runbsd/etc/fstab",
    "chars": 174,
    "preview": "ROOT.a\t\t/\t\tffs\trw\t\t\t1\t1\nptyfs\t\t/dev/pts\tptyfs\trw\t\t\t0\t0\nkernfs\t\t/kern\t\tkernfs\trw,noauto\t\t0\t0\nprocfs\t\t/proc\t\tprocfs\trw,noa"
  },
  {
    "path": "service/runbsd/etc/rc",
    "chars": 558,
    "preview": "#!/bin/sh\n\nexport HOME=/\nexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin\numask 022\n\n# tmpfs dev is "
  },
  {
    "path": "service/runbsd/etc/rc.conf",
    "chars": 217,
    "preview": "\n[ -r /etc/defaults/rc.conf ] && . /etc/defaults/rc.conf\n\nrc_configured=YES\nifconfig_vioif0=\"inet 10.0.2.15/24\"\ndefaultr"
  },
  {
    "path": "service/runbsd/etc/sysctl.conf",
    "chars": 44,
    "preview": "# this takes ages\nnet.inet6.ip6.dad_count=0\n"
  },
  {
    "path": "service/runbsd/postinst/00-runit.sh",
    "chars": 984,
    "preview": "#!/bin/sh\n\n# https://smarden.org/runit/replaceinit\n# switch from /sbin/init to runit!\n\nPKGURL=\"https://cdn.netbsd.org/pu"
  },
  {
    "path": "service/runbsd/postinst/01-sv.sh",
    "chars": 128,
    "preview": "#!/bin/sh\n\nmkdir -p etc/sv/sshd\nfor l in run finish\ndo\n\tln -s /bin/rcd2run.sh etc/sv/sshd/$l\ndone\n\nln -s /etc/sv/sshd se"
  },
  {
    "path": "service/runbsd/postinst/02-tools.sh",
    "chars": 431,
    "preview": "#!/bin/sh\n\n# script to use /etc/rc.d as runit run/finish\n\ncat >bin/rcd2run.sh<<EOF\n#!/bin/sh\nexec 2>&1\n\nDIR_PATH=\\$(pwd)"
  },
  {
    "path": "service/sshd/README.md",
    "chars": 714,
    "preview": "# SSHd service\n\nThis microservice starts an _OpenSSH_ daemon.\n\nAs it uses `union` `tmpfs` which is unsupported with `ext"
  },
  {
    "path": "service/sshd/etc/rc",
    "chars": 385,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n\nmount -t tmpfs -o -s1M tmpfs /home\nmount -t tmpfs -o -s10M tmpfs /tmp\nmount -t tmpfs "
  },
  {
    "path": "service/sshd/options.mk",
    "chars": 104,
    "preview": ".if defined(MINIMIZE) && ${MINIMIZE} == y\nADDPKGS=pkgin pkg_tarup pkg_install sqlite3 rsync curl\n.endif\n"
  },
  {
    "path": "service/sshd/postinst/keygen.sh",
    "chars": 872,
    "preview": "# taken from NetBSD's /etc/rc.d/sshd\nkeygen=\"/usr/bin/ssh-keygen\"\numask 022\nnew_key_created=false\nwhile read type bits f"
  },
  {
    "path": "service/sshd/sailor.conf",
    "chars": 213,
    "preview": ". /service/common/sailor.vars\n\nshipname=sshd\nshipbins=\"$shipbins /bin/ksh /usr/sbin/sshd /usr/bin/ssh-keygen /usr/libexe"
  },
  {
    "path": "service/systembsd/etc/banner.ans",
    "chars": 58211,
    "preview": "\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5;16;48;5;16m▓\u001b[38;5"
  },
  {
    "path": "service/systembsd/etc/dinit.d/boot",
    "chars": 56,
    "preview": "type = internal\n\ndepends-ms: getty\n\nwaits-for.d: boot.d\n"
  },
  {
    "path": "service/systembsd/etc/dinit.d/getty",
    "chars": 136,
    "preview": "type = process\ncommand = /usr/libexec/getty Pc constty\ntermsignal = HUP\nsmooth-recovery = true\n\ndepends-on: motd\ndepends"
  },
  {
    "path": "service/systembsd/etc/dinit.d/loginready",
    "chars": 115,
    "preview": "type = internal\nrestart = false\noptions = runs-on-console\n\ndepends-on: rc.boot\nwaits-for: rc.fs\nwaits-for: syslogd\n"
  },
  {
    "path": "service/systembsd/etc/dinit.d/motd",
    "chars": 82,
    "preview": "type = scripted\ncommand = /etc/rc.d/motd start\nrestart = false\n\ndepends-on: rc.fs\n"
  },
  {
    "path": "service/systembsd/etc/dinit.d/rc.boot",
    "chars": 116,
    "preview": "type = scripted\ncommand = /etc/dinit.d/rc.boot.sh\nrestart = false\nlogfile = /var/log/rc.boot.log\n\ndepends-on: rc.fs\n"
  },
  {
    "path": "service/systembsd/etc/dinit.d/rc.boot.sh",
    "chars": 158,
    "preview": "#!/bin/sh\n\n# basic services to start at boot\nSTARTSVC=\"\nbootconf.sh\nttys\nsysctl\nentropy\nnetwork\nlocal\n\"\n\nfor svc in $STA"
  },
  {
    "path": "service/systembsd/etc/dinit.d/rc.dev",
    "chars": 110,
    "preview": "# tmpfs dev needs to execute before mounting\ntype = scripted\ncommand = /etc/dinit.d/rc.dev.sh\nrestart = false\n"
  },
  {
    "path": "service/systembsd/etc/dinit.d/rc.dev.sh",
    "chars": 271,
    "preview": "#!/bin/sh\n\n# tmpfs dev is usually done by init(8)\ncd /dev\n# /dev is a union fs, permissions are recorded and bad\n# after"
  },
  {
    "path": "service/systembsd/etc/dinit.d/rc.fs",
    "chars": 154,
    "preview": "type = scripted\ncommand = /etc/dinit.d/rc.fs.sh start\nstop-command = /etc/dinit.d/rc.fs.sh stop\nrestart = false\noptions "
  },
  {
    "path": "service/systembsd/etc/dinit.d/rc.fs.sh",
    "chars": 209,
    "preview": "#!/bin/sh\n\n/etc/rc.d/mountcritlocal $1\n/etc/rc.d/mountcritremote $1\n# only this one has start/stop\n/etc/rc.d/mountall $1"
  },
  {
    "path": "service/systembsd/etc/dinit.d/sshd",
    "chars": 94,
    "preview": "type = scripted\ncommand = /etc/rc.d/sshd onestart\n\ndepends-on: loginready\n\nwaits-for: syslogd\n"
  },
  {
    "path": "service/systembsd/etc/dinit.d/syslogd",
    "chars": 136,
    "preview": "type = scripted\ncommand = /etc/rc.d/syslogd onestart\nstop-command = /etc/rc.d/syslogd onestop\n\noptions: starts-log\n\ndepe"
  },
  {
    "path": "service/systembsd/etc/dinit.d/wscons",
    "chars": 73,
    "preview": "type = scripted\ncommand = /etc/rc.d/wscons start\n\ndepends-on: loginready\n"
  },
  {
    "path": "service/systembsd/etc/fstab",
    "chars": 174,
    "preview": "ROOT.a\t\t/\t\tffs\trw\t\t\t1\t1\nptyfs\t\t/dev/pts\tptyfs\trw\t\t\t0\t0\nkernfs\t\t/kern\t\tkernfs\trw,noauto\t\t0\t0\nprocfs\t\t/proc\t\tprocfs\trw,noa"
  },
  {
    "path": "service/systembsd/etc/rc",
    "chars": 669,
    "preview": "#!/bin/sh\n\nexport HOME=/\nexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/pkg/bin:/usr/pkg/sbin\numask 022\n\n/sbin/mount -a\n"
  },
  {
    "path": "service/systembsd/etc/rc.conf",
    "chars": 221,
    "preview": "\n[ -r /etc/defaults/rc.conf ] && . /etc/defaults/rc.conf\n\nrc_configured=YES\nifconfig_vioif0=\"inet 10.0.2.15/24\"\ndefaultr"
  },
  {
    "path": "service/systembsd/etc/rc.local",
    "chars": 118,
    "preview": "# for design porposes, make root shell ksh\ngrep -q '^root.*bin/sh' /etc/passwd && \\\n\tusermod -s /bin/ksh root || true\n"
  },
  {
    "path": "service/systembsd/etc/sysctl.conf",
    "chars": 44,
    "preview": "# this takes ages\nnet.inet6.ip6.dad_count=0\n"
  },
  {
    "path": "service/systembsd/postinst/00-dinit.sh",
    "chars": 480,
    "preview": "#!/bin/sh\n\n# bare minimum\nmknod -m 600 dev/console c 0 0\nmknod -m 600 dev/constty c 0 1\nmknod -m 666 dev/tty c 1 0\nmknod"
  },
  {
    "path": "service/systembsd/postinst/01-custom.sh",
    "chars": 307,
    "preview": "#!/bin/sh\n\ncat >>root/.shrc<<EOF\nLANG=en_US.UTF-8; export LANG\n\nalias d='dinitctl'\nalias shutdown=\"dinitctl shutdown\"\n\n["
  },
  {
    "path": "service/tiny/sailor.conf",
    "chars": 134,
    "preview": ". /service/common/sailor.vars\n\nshipname=tiny\n# add binaries to default ones (see in common/sailor.vars)\nshipbins=\"$shipb"
  },
  {
    "path": "service/tslog/etc/rc",
    "chars": 264,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n. /etc/include/pkgin\n. /etc/include/mount9p\n\ncommand -v perl >/dev/null || pkgin -y in"
  },
  {
    "path": "service/usershell/README.md",
    "chars": 521,
    "preview": "# User Shell Service\n\n## About\n\nThis microservice starts a minimal user shell (`ksh`).\n\nIt comes with all typical _BSD_ "
  },
  {
    "path": "service/usershell/etc/rc",
    "chars": 94,
    "preview": "#!/bin/sh\n\n. /etc/include/basicrc\n\nhostname shell\nsu bsd -c \"ksh -l\"\n\n. /etc/include/shutdown\n"
  },
  {
    "path": "service/usershell/options.mk",
    "chars": 88,
    "preview": "MINIMIZE=y\nMOUNTRO=y\nIMGSIZE=100\nADDPKGS=pkgin pkg_tarup pkg_install sqlite3 rsync curl\n"
  },
  {
    "path": "service/usershell/postinst/custom.sh",
    "chars": 155,
    "preview": "#!/bin/sh\n\nrm -f etc/shrc # wipe defaults\ncat >>etc/profile<<EOF\nHOSTNAME=\\$(hostname)\nPS1=\"\\$(printf '\\e[1;31m\\${USER}\\"
  },
  {
    "path": "service/usershell/sailor.conf",
    "chars": 220,
    "preview": ". /service/common/sailor.vars\n\nshipname=usershell\nshipbins=\"$shipbins /bin/ksh /usr/bin/ssh /usr/bin/ssh-keygen /usr/bin"
  },
  {
    "path": "smoler/build.sh",
    "chars": 7979,
    "preview": "# Converts a basic Dockerfile to a smolBSD service\n\nset -e\n\nusage()\n{\n\techo \"usage: $0 [--build-arg KEY=val ...] [-t tag"
  },
  {
    "path": "smoler/img.sh",
    "chars": 2112,
    "preview": "#!/bin/sh\n\nOS=$(uname -s|tr 'A-Z' 'a-z')\n\ncase $OS in\nlinux|darwin) ;;\n*)\n\techo \"unsupported platform\"\n\texit 1\n\t;;\nesac\n"
  },
  {
    "path": "smoler.sh",
    "chars": 646,
    "preview": "#!/bin/sh\n\nset -e\n\nprogname=${0##*/}\n\ncase $1 in\nbuild)\n\t/bin/sh smoler/build.sh $@\n\t;;\npush|pull|images)\n\t/bin/sh smole"
  },
  {
    "path": "startnb.sh",
    "chars": 8542,
    "preview": "#!/bin/sh\n\nusage()\n{\n\tcat 1>&2 << _USAGE_\nUsage:\t${0##*/} -f conffile | -k kernel -i image [-c CPUs] [-m memory]\n\t[-a ke"
  },
  {
    "path": "www/index.html",
    "chars": 10366,
    "preview": "<!doctype html>\n<html lang=\"en\" class=\"dark\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=dev"
  }
]

About this extraction

This page contains the full source code of the NetBSDfr/smolBSD GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 139 files (300.5 KB), approximately 173.2k tokens, and a symbol index with 17 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!