Compare commits

...

4 Commits

Author SHA1 Message Date
Mattia Giambirtone 99fd4171ed NimD now checks if another instance is running before starting up 2022-03-12 17:22:40 +01:00
Mattia Giambirtone e34e48f87c Updated README with note to scripts folder for testing 2022-03-12 15:55:50 +01:00
Mattia Giambirtone df1371c13f Added build and startup scripts 2022-03-12 15:54:20 +01:00
Mattia Giambirtone d240d05cef Minor changes to nimDExit and shutdown.nim 2022-03-12 15:51:31 +01:00
8 changed files with 186 additions and 14 deletions

3
.gitignore vendored
View File

@ -5,7 +5,6 @@ main
*.iso
nimd
main
boot.sh
rootfs
packervm
test
@ -14,7 +13,5 @@ initrd*
vmlinuz-linux
vmlinuz*
debian*
start.sh
rebuild.sh
vm.qcow2
*.tar.*

View File

@ -116,4 +116,10 @@ workers = 1 # Number of worker processes to use
restartDelay = 10 # Delay (seconds) that nimd will wait before restarting itself after crashes
sigtermDelay = 90 # Delay (seconds) that nimd will wait before terminating child processes with
# SIGKILL after sending a more gentle SIGTERM upon shutdown or exit
```
```
## Testing NimD
NimD is not quite ready for production yet, but in the `scripts` folder you can find a few simple bash scripts to test NimD
in a minimal Alpine Linux VM using QEMU. Note that due to weirdness in how stdout is handled on the VGA port, the VM will use
the serial port (ttyS0) as output by default (you can change this in the kernel parameters)

115
scripts/boot.sh Executable file
View File

@ -0,0 +1,115 @@
#!/usr/bin/env bash
set -Eeuo pipefail
trap cleanup SIGINT SIGTERM ERR EXIT
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
usage() {
cat << EOF
Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-v] [-m] [-b] -k kernel -i initrd -r rootfs
Alpinemini vm
Available options:
-h, --help Print this help and exit
-v, --verbose Print script debug info
-k, --kernel Specify kernel file
-i, --initrd Specify initrd file
-m, --memory Set maximum vm memory
-b, --build Build a new disk (and then boot)
EOF
exit
}
cleanup() {
trap - SIGINT SIGTERM ERR EXIT
}
setup_colors() {
if [[ -t 2 ]] && [[ -z "${NO_COLOR-}" ]] && [[ "${TERM-}" != "dumb" ]]; then
NOFORMAT='\033[0m' RED='\033[0;31m' GREEN='\033[0;32m' ORANGE='\033[0;33m' BLUE='\033[0;34m' PURPLE='\033[0;35m' CYAN='\033[0;36m' YELLOW='\033[1;33m'
else
NOFORMAT='' RED='' GREEN='' ORANGE='' BLUE='' PURPLE='' CYAN='' YELLOW=''
fi
}
msg() {
echo >&2 -e "${1-}"
}
die() {
local msg=$1
local code=${2-1}
msg "$msg"
exit "$code"
}
parse_params() {
build=0
rootfs=''
kernel=''
initrd=''
memory=''
while :; do
case "${1-}" in
-h | --help) usage ;;
-v | --verbose) set -x ;;
--no-color) NO_COLOR=1 ;;
-b | --build) build=1 ;; # build disk
-k | --kernel) # kernel file
kernel="${2-}"
shift
;;
-i | --initrd) # initrd file
initrd="${2-}"
shift
;;
-m | --memory) # max memory
memory="${2-}"
shift
;;
-?*) die "Unknown option: $1" ;;
*) break ;;
esac
shift
done
args=("$@")
# check required params and arguments
[[ -z "${kernel-}" ]] && die "${RED}Missing required parameter:${NOFORMAT} kernel"
[[ -z "${initrd-}" ]] && die "${RED}Missing required parameter:${NOFORMAT} initrd"
return 0
}
setup_colors
parse_params "$@"
if ! [ -x "$(command -v qemu-system-x86_64)" ]; then
echo '${RED}Error:${NOFORMAT} unable to find qemu-system-x86_64, please install it first.' >&2
exit 1
fi
if ! [ -x "$(command -v qemu-img)" ]; then
echo '${RED}Error:${NOFORMAT} unable to find qemu-img, please install it first.' >&2
exit 1
fi
if [[ $build -eq 1 ]]
then
msg "${CYAN}Building disk... Please wait${NOFORMAT}"
qemu-img create -f qcow2 vm.qcow2 800M
qemu-system-x86_64 -m 256M -smp 1 -drive file=packervm/packer.qcow2,if=virtio,readonly=on -drive file=vm.qcow2,if=virtio -enable-kvm -fsdev local,id=rootfs_dev,path=rootfs,security_model=none -device virtio-9p-pci,fsdev=rootfs_dev,mount_tag=rootfs -display none
fi
qemumem=''
if ! [[ $memory -eq "" ]]
then
qemumem="-m ${memory}"
fi
qemu-system-x86_64 -net nic,model=virtio,netdev=user.0 -netdev user,id=user.0 -drive file=vm.qcow2,if=virtio -enable-kvm -kernel ${kernel} -initrd ${initrd} ${qemumem} -append 'root=/dev/vda1 rw quiet modules=ext4 console=ttyS0,9600 console=ttyS0 init=/bin/nimd'

10
scripts/rebuild.sh Executable file
View File

@ -0,0 +1,10 @@
# Build the environment
mkdir -p rootfs/etc/nimd
cp nimd.conf rootfs/etc/nimd/nimd.conf
nim -o:rootfs/bin/nimd -d:release --gc:orc --opt:size --passL:"-static" compile src/main.nim
nim -o:rootfs/bin/halt -d:release --gc:orc --opt:size --passL:"-static" compile src/programs/halt.nim
nim -o:rootfs/bin/reboot -d:release --gc:orc --opt:size --passL:"-static" compile src/programs/reboot.nim
nim -o:rootfs/bin/poweroff -d:release --gc:orc --opt:size --passL:"-static" compile src/programs/poweroff.nim
# Start the VM
./scripts/boot.sh --kernel vmlinuz-linux --initrd initrd-linux.img --memory 1G --build

1
scripts/start.sh Executable file
View File

@ -0,0 +1 @@
./scripts/boot.sh --kernel vmlinuz-linux --initrd initrd-linux.img --memory 1G

View File

@ -28,8 +28,8 @@ proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) =
if startServices:
logger.info("Processing default runlevel")
startServices(logger, workers=config.workers, level=Default)
logger.debug(&"Unblocking signals")
unblockSignals(logger)
logger.debug(&"Unblocking signals")
unblockSignals(logger)
logger.info("System initialization complete, idling on control socket")
var opType: string
try:
@ -52,6 +52,7 @@ proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) =
# - 'h' -> halt
# - 's' -> Services-related operations (start, stop, get status, etc.)
# - 'l' -> Reload in-memory configuration
# - 'c' -> Check NimD status (returns "1" if up)
case opType:
of "p":
logger.info("Received shutdown request")
@ -63,11 +64,14 @@ proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) =
logger.info("Received halt request")
halt(logger)
of "s":
logger.info("Received service request")
logger.info("Received service-related request")
# TODO: Operate on services
of "l":
logger.info("Received reload request")
mainLoop(logger, parseConfig(logger, "/etc/nimd/nimd.conf"), startServices=false)
of "c":
logger.info("Received check request, responding")
clientSocket.send("1")
else:
logger.warning(&"Received unknown operation type '{opType}' via control socket, ignoring it")
discard

View File

@ -32,16 +32,14 @@ type ShutdownHandler* = ref object
const reboot_codes = {"poweroff": 0x4321fedc'i64, "reboot": 0x01234567'i64, "halt": 0xcdef0123}.toTable()
var shutdownHandlers: seq[ShutdownHandler] = @[]
var sigTermDelay: float = 90
proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler =
result = ShutdownHandler(body: body)
var shutdownHandlers: seq[ShutdownHandler] = @[]
var sigTermDelay: float = 90
proc setSigTermDelay*(delay: int = 90) =
# Sets the sigtermDelay variable
sigTermDelay = float(delay)
@ -97,8 +95,8 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
# We're in emergency mode: do not crash the kernel, spawn a shell and exit
logger.fatal("NimD has entered emergency mode and cannot continue. You will be now (hopefully) dropped in a root shell: you're on your own. May the force be with you")
logger.info("Terminating child processes with SIGKILL")
discard execCmd(os.getEnv("SHELL", "/bin/sh")) # TODO: Is this fine? maybe use execProcess
discard posix.kill(-1, SIGKILL)
discard execCmd(os.getEnv("SHELL", "/bin/sh")) # TODO: Is this fine? maybe use execProcess
quit(-1)
logger.warning("The system is shutting down")
logger.info("Processing shutdown runlevel")

View File

@ -14,6 +14,7 @@
import parseopt
import strformat
import posix
import net
import os
# NimD's own stuff
@ -68,10 +69,51 @@ proc addStuff =
addService(shell)
proc checkControlSocket(logger: Logger, config: NimDConfig): bool =
## Performs some startup checks on nim's control
## socket
result = true
var stat_result: Stat
if posix.stat(cstring(config.sock), stat_result) == -1:
logger.warning(&"Could not stat() {config.sock}, assuming NimD instance isn't running")
# I stole this from /usr/lib/python3.10/stat.py
elif (int(stat_result.st_mode) and 0o170000) != 0o140000:
logger.fatal(&"{config.sock} exists and is not a socket")
result = false
elif dirExists(config.sock):
logger.info("Control socket path is a directory, appending nimd.sock to it")
config.sock = config.sock.joinPath("nimd.sock")
else:
logger.debug("Trying to reach current NimD instance")
var sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP)
try:
sock.connectUnix(config.sock)
logger.info("Control socket already exists, trying to reach current NimD instance")
except OSError:
logger.warning(&"Could not connect to control socket at {config.sock} ({getCurrentExceptionMsg()}), assuming NimD instance isn't running")
try:
removeFile(config.sock)
except OSError:
logger.warning(&"Could not delete dangling control socket at {config.sock} ({getCurrentExceptionMsg()})")
if sock.trySend("c"):
try:
if sock.recv(1, timeout=5) == "1":
logger.error("Another NimD instance is running! Exiting")
result = false
except OSError:
logger.warning(&"Could not read from control socket at {config.sock} ({getCurrentExceptionMsg()}), assuming NimD instance isn't running")
except TimeoutError:
logger.warning(&"Could not read from control socket at {config.sock} ({getCurrentExceptionMsg()}), assuming NimD instance isn't running")
else:
logger.fatal(&"Could not write on control socket at {config.sock}")
result = false
proc main(logger: Logger, config: NimDConfig) =
## NimD's entry point and setup
## function
if not checkControlSocket(logger, config):
return
logger.debug(&"Setting log file to '{config.logFile}'")
setLogFile(file=config.logFile)
logger.debug("Starting NimD: A minimal, self-contained, dependency-based Linux init system written in Nim")
@ -173,8 +215,7 @@ when isMainModule:
quit(EINVAL) # EINVAL - Invalid argument
else:
echo "Usage: nimd [options]"
quit(EINVAL) # EINVAL - Invalid argument
quit(EINVAL) # EINVAL - Invalid argument
setStdIoUnbuffered() # Colors don't work otherwise!
try:
main(logger, parseConfig(logger, "/etc/nimd/nimd.conf"))