mirror of https://github.com/nocturn9x/nimd.git
Merge branch 'main' of https://github.com/nocturn9x/nimd
This commit is contained in:
commit
ea1f3cc6f9
|
@ -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.*
|
||||
|
|
|
@ -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)
|
|
@ -7,7 +7,7 @@ restartDelay = 10
|
|||
sigtermDelay = 90
|
||||
|
||||
[Logging]
|
||||
level = info
|
||||
level = debug
|
||||
logFile = /var/log/nimd
|
||||
|
||||
[Filesystem]
|
||||
|
|
|
@ -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 init=/bin/nimd'
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
./scripts/boot.sh --kernel vmlinuz-linux --initrd initrd-linux.img --memory 1G
|
|
@ -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:
|
||||
|
@ -40,40 +40,49 @@ proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) =
|
|||
logger.switchToFile()
|
||||
logger.debug("Entering accept() loop")
|
||||
while true:
|
||||
serverSocket.accept(clientSocket)
|
||||
logger.debug(&"Received connection on control socket")
|
||||
if clientSocket.recv(opType, size=1) == 0:
|
||||
logger.debug(&"Client has disconnected, waiting for new connections")
|
||||
continue
|
||||
logger.debug(&"Received operation type '{opType}' via control socket")
|
||||
# The operation type is a single byte:
|
||||
# - 'p' -> poweroff
|
||||
# - 'r' -> reboot
|
||||
# - 'h' -> halt
|
||||
# - 's' -> Services-related operations (start, stop, get status, etc.)
|
||||
# - 'l' -> Reload in-memory configuration
|
||||
case opType:
|
||||
of "p":
|
||||
logger.info("Received shutdown request")
|
||||
shutdown(logger)
|
||||
of "r":
|
||||
logger.info("Received reboot request")
|
||||
reboot(logger)
|
||||
of "h":
|
||||
logger.info("Received halt request")
|
||||
halt(logger)
|
||||
of "s":
|
||||
logger.info("Received service request")
|
||||
# TODO: Operate on services
|
||||
of "l":
|
||||
logger.info("Received reload request")
|
||||
mainLoop(logger, parseConfig(logger, "/etc/nimd/nimd.conf"), startServices=false)
|
||||
else:
|
||||
logger.warning(&"Received unknown operation type '{opType}' via control socket, ignoring it")
|
||||
discard
|
||||
clientSocket.close()
|
||||
try:
|
||||
serverSocket.accept(clientSocket)
|
||||
logger.debug(&"Received connection on control socket")
|
||||
if clientSocket.recv(opType, size=1) == 0:
|
||||
logger.debug(&"Client has disconnected, waiting for new connections")
|
||||
continue
|
||||
logger.debug(&"Received operation type '{opType}' via control socket")
|
||||
# The operation type is a single byte:
|
||||
# - 'p' -> poweroff
|
||||
# - 'r' -> reboot
|
||||
# - '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")
|
||||
shutdown(logger)
|
||||
of "r":
|
||||
logger.info("Received reboot request")
|
||||
reboot(logger)
|
||||
of "h":
|
||||
logger.info("Received halt request")
|
||||
halt(logger)
|
||||
of "s":
|
||||
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
|
||||
except:
|
||||
logger.error(&"An error occurred while idling on control socket: {getCurrentExceptionMsg()}")
|
||||
finally:
|
||||
clientSocket.close()
|
||||
clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||||
except:
|
||||
logger.critical(&"A critical error has occurred while running, restarting the mainloop in {config.restartDelay} seconds! Error -> {getCurrentExceptionMsg()}")
|
||||
logger.critical(&"A critical error has occurred while running, restarting the main loop in {config.restartDelay} seconds! Error -> {getCurrentExceptionMsg()}")
|
||||
sleepSeconds(config.restartDelay)
|
||||
# We *absolutely* cannot die
|
||||
mainLoop(logger, config, startServices=false)
|
||||
|
|
|
@ -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")
|
||||
|
|
53
src/main.nim
53
src/main.nim
|
@ -14,6 +14,7 @@
|
|||
import parseopt
|
||||
import strformat
|
||||
import posix
|
||||
import net
|
||||
import os
|
||||
|
||||
# NimD's own stuff
|
||||
|
@ -58,9 +59,9 @@ proc addStuff =
|
|||
depends=(@[newDependency(Other, errorer)]), provides=(@[]),
|
||||
stdin="/dev/null", stderr="", stdout="")
|
||||
var shell = newService(name="login", description="A simple login shell", kind=Simple,
|
||||
workDir=getCurrentDir(), runlevel=Boot, exec="/bin/login -f root",
|
||||
workDir=getCurrentDir(), runlevel=Default, exec="/bin/login -f root",
|
||||
supervised=true, restart=Always, restartDelay=0, depends=(@[]), provides=(@[]),
|
||||
useParentStreams=true, stdin="/dev/null", stderr="", stdout=""
|
||||
useParentStreams=true, stdin="/dev/null", stderr="/proc/self/fd/2", stdout="/proc/self/fd/1"
|
||||
)
|
||||
addService(errorer)
|
||||
addService(echoer)
|
||||
|
@ -68,10 +69,55 @@ 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 and posix.errno != 2:
|
||||
logger.warning(&"Could not stat() {config.sock}, assuming NimD instance isn't running")
|
||||
elif posix.errno == 2:
|
||||
logger.debug(&"Control socket path is clear, starting up")
|
||||
posix.errno = 0
|
||||
# 2 is ENOENT, which means the file does not exist
|
||||
# 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)
|
||||
# Black Lives Matter. This is sarcasm btw. Fuck the left
|
||||
|
@ -175,8 +221,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"))
|
||||
|
|
Loading…
Reference in New Issue