Fixed config parsing module (sorta) and added example config file

This commit is contained in:
nocturn9x 2022-01-09 22:46:25 +01:00
parent d41e67f413
commit 76f2df1969
8 changed files with 118 additions and 56 deletions

View File

@ -58,7 +58,6 @@ want to make sure your disks are mounted and that your network has been set up.
``` ```
[Service] [Service]
name = ssh # The name of the service name = ssh # The name of the service
description = Secure Shell Server # A short description description = Secure Shell Server # A short description
type = simple # Other option: oneshot (i.e. runs only once, implies supervised=false) type = simple # Other option: oneshot (i.e. runs only once, implies supervised=false)
@ -70,7 +69,6 @@ restartDelay = 10 # NimD will wait this many seconds before
supervised = true # This is the default. Disable it if you don't need NimD to watch for it supervised = true # This is the default. Disable it if you don't need NimD to watch for it
[Logging] [Logging]
stderr = /var/log/sshd # Path of the stderr log for the service stderr = /var/log/sshd # Path of the stderr log for the service
stdout = /var/log/sshd # Path of the stdout log for the service stdout = /var/log/sshd # Path of the stdout log for the service
stdin = /dev/null # Path of the stdin fd for the service stdin = /dev/null # Path of the stdin fd for the service
@ -99,20 +97,22 @@ INI-like structure), but the options are obviously different. An example config
``` ```
[Logging] [Logging]
level = info # Levels are: trace, debug, info, warning, error, critical, fatal level = info # Levels are: trace, debug, info, warning, error, critical, fatal
logFile = /var/log/nimd # Path to log file logFile = /var/log/nimd # Path to log file
[Filesystem] [Filesystem]
autoMount = true # Automatically parses /etc/fstab and mounts disks
autoMount = true # Automatically parses /etc/fstab and mounts disks autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown
autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown fstabPath = /etc/fstab # Path to your system's fstab (defaults to /etc/fstab)
fstabPath = /etc/fstab # Path to your system's fstab (defaults to /etc/fstab) createDirs = "/path/to/dir:perms," # Creates these directories with the given permissions on boot. Empty to disable
createDirs = /path/to/dir1, /path/to/dir2 # Creates these directories on boot. Empty to disable createSymlinks = "/path/to/symlink:/path/to/dest," # Creates these symlinks on boot. Empty to disable
createSymlinks = /path/to/symlink:/path/to/dest, ... # Creates these symlinks on boot. Empty to disable
[Misc] [Misc]
controlSocket = /var/run/nimd.sock # Path to the Unix domain socket to create for IPC controlSocket = /var/run/nimd.sock # Path to the Unix domain socket to create for IPC
onDependencyConflict = skip # Other option: warn, error setHostname = true # Set to true to set the machine's hostname automatically from /etc/hostname
onDependencyConflict = skip # Other option: warn, error
workers = 1 # Number of worker processes to use when spawning services
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
``` ```

View File

@ -1,12 +1,11 @@
from macros import error from macros import error
from ospaths import splitFile, `/` from os import splitFile, `/`
# -d:musl # -d:musl
when defined(musl): when defined(musl):
var muslGccPath: string var muslGccPath: string
echo " [-d:musl] Building a static binary using musl .." echo " [-d:musl] Building a static binary using musl .."
muslGccPath = findExe("musl-gcc") muslGccPath = findExe("musl-gcc")
# echo "debug: " & muslGccPath
if muslGccPath == "": if muslGccPath == "":
error("'musl-gcc' binary was not found in PATH.") error("'musl-gcc' binary was not found in PATH.")
switch("gcc.exe", muslGccPath) switch("gcc.exe", muslGccPath)
@ -15,7 +14,7 @@ when defined(musl):
proc binOptimize(binFile: string) = proc binOptimize(binFile: string) =
## Optimize size of the ``binFile`` binary. ## Optimize size of the binFile binary.
echo "" echo ""
if findExe("strip") != "": if findExe("strip") != "":
echo "Running 'strip -s' .." echo "Running 'strip -s' .."
@ -36,11 +35,10 @@ task musl, "Builds an optimized static binary using musl":
"\n Usage Example: nim musl FILE.nim.") "\n Usage Example: nim musl FILE.nim.")
let let
nimFile = paramStr(numParams) ## The nim file name *must* be the last. nimFile = paramStr(numParams) ## The nim file name *must* be the last
(dirName, baseName, _) = splitFile(nimFile) (dirName, baseName, _) = splitFile(nimFile)
binFile = dirName / baseName # Save the binary in the same dir as the nim file binFile = dirName / baseName # Save the binary in the same dir as the nim file
nimArgs = "c -d:musl -d:release --opt:size " & nimFile nimArgs = "c -d:musl -d:release --opt:size --gc:orc" & nimFile
# echo "[debug] nimFile = " & nimFile & ", binFile = " & binFile
# Build binary # Build binary
echo "\nRunning 'nim " & nimArgs & "' .." echo "\nRunning 'nim " & nimArgs & "' .."

21
nimd.conf Normal file
View File

@ -0,0 +1,21 @@
[Misc]
controlSocket = /var/run/nimd.sock
setHostname = true
onDependencyConflict = skip
workers = 1
restartDelay = 10
sigtermDelay = 90
[Logging]
level = info
logFile = /var/log/nimd
[Filesystem]
autoMount = true
autoUnmount = true
fstabPath = /etc/fstab
createDirs = "test:764,/dev/disk:123"
createSymlinks = """/dev/fd:/proc/self/fd,/dev/fd/0:/proc/self/fd/0,/dev/fd/1:/proc/self/fd/1,/dev/fd/2:/proc/self/fd/2,
/dev/std/in:/proc/self/fd/0,/dev/std/out:/proc/self/fd/1,/dev/std/err:/proc/self/fd/2,
/dev/std/in:/,/dev/std/out:/does/not/exist,/dev/std/err:/proc/self/fd/2
"""

View File

@ -16,18 +16,18 @@ import os
import net import net
import ../util/[logging, misc] import ../util/[logging, misc, config]
import services import services
import control import control
import shutdown import shutdown
proc mainLoop*(logger: Logger, workers: int = 1, startServices: bool = true) = proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) =
## NimD's main execution loop ## NimD's main execution loop
if startServices: if startServices:
logger.info("Processing default runlevel") logger.info("Processing default runlevel")
startServices(logger, workers=workers, level=Default) startServices(logger, workers=config.workers, level=Default)
logger.debug(&"Unblocking signals") logger.debug(&"Unblocking signals")
unblockSignals(logger) unblockSignals(logger)
logger.info("System initialization complete, idling on control socket") logger.info("System initialization complete, idling on control socket")
@ -68,7 +68,7 @@ proc mainLoop*(logger: Logger, workers: int = 1, startServices: bool = true) =
discard discard
clientSocket.close() clientSocket.close()
except: except:
logger.critical(&"A critical error has occurred while running, restarting the mainloop in 30 seconds! Error -> {getCurrentExceptionMsg()}") logger.critical(&"A critical error has occurred while running, restarting the mainloop in {config.restartDelay} seconds! Error -> {getCurrentExceptionMsg()}")
sleepSeconds(30) sleepSeconds(config.restartDelay)
# We *absolutely* cannot die # We *absolutely* cannot die
mainLoop(logger, startServices=false) mainLoop(logger, config, startServices=false)

View File

@ -39,7 +39,12 @@ proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandle
var shutdownHandlers: seq[ShutdownHandler] = @[] var shutdownHandlers: seq[ShutdownHandler] = @[]
var sigTermDelay: float = 10.0 var sigTermDelay: float = 90
proc setSigTermDelay*(delay: int = 90) =
# Sets the sigtermDelay variable
sigTermDelay = float(delay)
proc addShutdownHandler*(handler: ShutdownHandler) = proc addShutdownHandler*(handler: ShutdownHandler) =
@ -90,7 +95,7 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
# We're in emergency mode: do not crash the kernel, spawn a shell and exit # 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.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") logger.info("Terminating child processes with SIGKILL")
discard execCmd("/bin/sh") # TODO: Is this fine? maybe use execProcess discard execCmd(os.getEnv("SHELL", "/bin/sh")) # TODO: Is this fine? maybe use execProcess
discard posix.kill(-1, SIGKILL) discard posix.kill(-1, SIGKILL)
quit(-1) quit(-1)
logger.warning("The system is shutting down") logger.warning("The system is shutting down")
@ -113,7 +118,7 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
if anyUserlandProcessLeft(): if anyUserlandProcessLeft():
logger.info("Terminating child processes with SIGKILL") logger.info("Terminating child processes with SIGKILL")
discard posix.kill(-1, SIGKILL) discard posix.kill(-1, SIGKILL)
logger.warning("Shutdown procedure complete, sending final termination signal") logger.warning("Shutdown procedure complete, NimD is exiting")
proc reboot*(logger: Logger) = proc reboot*(logger: Logger) =

View File

@ -17,7 +17,7 @@ import posix
import os import os
# NimD's own stuff # NimD's own stuff
import util/[logging, constants, misc] import util/[logging, constants, misc, config]
import core/[mainloop, fs, shutdown, services] import core/[mainloop, fs, shutdown, services]
@ -40,8 +40,7 @@ proc addStuff =
addSymlink(newSymlink(dest="/dev/std/in", source="/proc/self/fd/0")) # Should say link already exists addSymlink(newSymlink(dest="/dev/std/in", source="/proc/self/fd/0")) # Should say link already exists
addDirectory(newDirectory("test", 764)) # Should create a directory addDirectory(newDirectory("test", 764)) # Should create a directory
addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists
# Shutdown handler to unmount disks
addShutdownHandler(newShutdownHandler(unmountAllDisks))
# Adds test services # Adds test services
var echoer = newService(name="echoer", description="prints owo", exec="/bin/echo owoooooooooo", var echoer = newService(name="echoer", description="prints owo", exec="/bin/echo owoooooooooo",
runlevel=Boot, kind=Simple, workDir=getCurrentDir(), runlevel=Boot, kind=Simple, workDir=getCurrentDir(),
@ -67,10 +66,11 @@ proc addStuff =
proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab", setHostname: bool = true, workerCount: int = 1) = proc main(logger: Logger, config: NimDConfig) =
## NimD's entry point and setup ## NimD's entry point and setup
## function ## function
setStdIoUnbuffered() # Colors and output synchronization don't work otherwise 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") logger.debug("Starting NimD: A minimal, self-contained, dependency-based Linux init system written in Nim")
logger.info(&"NimD version {NimdVersion.major}.{NimdVersion.minor}.{NimdVersion.patch} is starting up!") logger.info(&"NimD version {NimdVersion.major}.{NimdVersion.minor}.{NimdVersion.patch} is starting up!")
logger.trace("Calling getCurrentProcessId()") logger.trace("Calling getCurrentProcessId()")
@ -86,7 +86,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
nimDExit(logger, EPERM, emerg=false) # EPERM - Operation not permitted nimDExit(logger, EPERM, emerg=false) # EPERM - Operation not permitted
logger.trace("Setting up signal handlers") logger.trace("Setting up signal handlers")
onSignal(SIGABRT, SIGALRM, SIGHUP, SIGILL, SIGKILL, SIGQUIT, SIGSTOP, SIGSEGV, SIGTSTP, onSignal(SIGABRT, SIGALRM, SIGHUP, SIGILL, SIGKILL, SIGQUIT, SIGSTOP, SIGSEGV, SIGTSTP,
SIGTRAP, SIGPIPE, SIGUSR1, SIGUSR2, 6, SIGFPE, SIGBUS, SIGURG, SIGTERM, SIGINT): # 6 is SIGIOT SIGTRAP, SIGPIPE, SIGUSR1, SIGUSR2, 6, SIGFPE, SIGBUS, SIGURG, SIGTERM, SIGINT): # 6 is SIGIOT
# Can't capture local variables because this implicitly generates # Can't capture local variables because this implicitly generates
# a noconv procedure, so we use getDefaultLogger() instead # a noconv procedure, so we use getDefaultLogger() instead
getDefaultLogger().warning(&"Ignoring signal {sig} ({strsignal(sig)})") # Nim injects the variable "sig" into the scope. Gotta love those macros getDefaultLogger().warning(&"Ignoring signal {sig} ({strsignal(sig)})") # Nim injects the variable "sig" into the scope. Gotta love those macros
@ -94,11 +94,15 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
# One of the key features of an init system is reaping child # One of the key features of an init system is reaping child
# processes! # processes!
reapProcess(getDefaultLogger()) reapProcess(getDefaultLogger())
if config.autoUnmount:
logger.debug("Setting up shutdown handler for automatic disk unmounting")
# Shutdown handler to unmount disks
addShutdownHandler(newShutdownHandler(unmountAllDisks))
addStuff() addStuff()
try: try:
if mountDisks: if config.autoMount:
logger.info("Mounting filesystem") logger.info("Mounting filesystem")
mountDisks(logger, fstab) mountDisks(logger, config.fstab)
else: else:
logger.info("Skipping disk mounting, assuming this has already been done") logger.info("Skipping disk mounting, assuming this has already been done")
logger.info("Creating symlinks") logger.info("Creating symlinks")
@ -111,7 +115,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
except: except:
logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}") logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}")
nimDExit(logger, 131, emerg=false) nimDExit(logger, 131, emerg=false)
if setHostname: if config.setHostname:
logger.info("Setting hostname") logger.info("Setting hostname")
logger.debug(&"Hostname was set to '{misc.setHostname(logger)}'") logger.debug(&"Hostname was set to '{misc.setHostname(logger)}'")
else: else:
@ -119,9 +123,9 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
logger.debug("Entering critical fork() section: blocking signals") logger.debug("Entering critical fork() section: blocking signals")
blockSignals(logger) # They are later unblocked in mainLoop blockSignals(logger) # They are later unblocked in mainLoop
logger.info("Processing boot runlevel") logger.info("Processing boot runlevel")
startServices(logger, workers=workerCount, level=Boot) startServices(logger, workers=config.workers, level=Boot)
logger.debug("Starting main loop") logger.debug("Starting main loop")
mainLoop(logger, workers=workerCount) mainLoop(logger, config)
when isMainModule: when isMainModule:
@ -167,9 +171,10 @@ when isMainModule:
else: else:
echo "Usage: nimd [options]" echo "Usage: nimd [options]"
quit(EINVAL) # EINVAL - Invalid argument quit(EINVAL) # EINVAL - Invalid argument
logger.debug("Calling NimD entry point")
setStdIoUnbuffered() # Colors don't work otherwise!
try: try:
main(logger) main(logger, parseConfig(logger, "/etc/nimd/nimd.conf"))
except: except:
logger.fatal(&"A fatal unrecoverable error has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}") logger.fatal(&"A fatal unrecoverable error has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}")
nimDExit(logger, 131) # ENOTRECOVERABLE - State not recoverable nimDExit(logger, 131) # ENOTRECOVERABLE - State not recoverable

View File

@ -18,37 +18,42 @@ import strutils
import strformat import strformat
import ../util/logging import logging
import services import ../core/fs
import fs
type type
NimDConfig* = ref object NimDConfig* = ref object
## Configuration object ## Configuration object
logLevel*: LogLevel logLevel*: LogLevel
logDir*: Directory logFile*: string
services*: tuple[runlevel: RunLevel, services: seq[Service]] workers*: int
directories*: seq[Directory] directories*: seq[Directory]
symlinks*: seq[Symlink] symlinks*: seq[Symlink]
filesystems*: seq[Filesystem] filesystems*: seq[Filesystem]
restartDelay*: int restartDelay*: int
sigtermDelay*: int
autoMount*: bool autoMount*: bool
autoUnmount*: bool autoUnmount*: bool
fstab*: string fstab*: string
sock*: string sock*: string
onDependencyConflict*: string
setHostname*: bool
const defaultSections = ["Misc", "Logging", "Filesystem"] const defaultSections* = ["Misc", "Logging", "Filesystem"]
const defaultConfig = { const defaultConfig* = {
"Logging": { "Logging": {
"stderr": "/var/log/nimd", "logFile": "/var/log/nimd",
"stdout": "/var/log/nimd",
"level": "info" "level": "info"
}.toTable(), }.toTable(),
"Misc": { "Misc": {
"controlSocket": "/var/run/nimd.sock", "controlSocket": "/var/run/nimd.sock",
"onDependencyConflict": "skip" "onDependencyConflict": "skip",
"setHostname": "true",
"workers": "1",
"sigtermDelay": "90",
"restartDelay": "30"
}.toTable(), }.toTable(),
"Filesystem": { "Filesystem": {
"autoMount": "true", "autoMount": "true",
@ -59,9 +64,16 @@ const defaultConfig = {
"virtualDisks": "" "virtualDisks": ""
}.toTable() }.toTable()
}.toTable() }.toTable()
const levels = {"trace": Trace, "debug": Debug, "info": Info,
"warning": Warning, "error": Error, "critical": Critical, const levels = {
"fatal": Fatal}.toTable() "trace": Trace,
"debug": Debug,
"info": Info,
"warning": Warning,
"error": Error,
"critical": Critical,
"fatal": Fatal
}.toTable()
proc parseConfig*(logger: Logger, file: string = "/etc/nimd/nimd.conf"): NimDConfig = proc parseConfig*(logger: Logger, file: string = "/etc/nimd/nimd.conf"): NimDConfig =
@ -89,16 +101,31 @@ proc parseConfig*(logger: Logger, file: string = "/etc/nimd/nimd.conf"): NimDCon
elif section notin defaultSections: elif section notin defaultSections:
logger.warning(&"Unknown section '{section}' found in config file, skipping it") logger.warning(&"Unknown section '{section}' found in config file, skipping it")
else: else:
if not data.hasKey(section):
data[section] = newTable[string, string]()
for key in defaultConfig[section].keys(): for key in defaultConfig[section].keys():
data[section][key] = cfgObject.getSectionValue(section, key, defaultConfig[section][key]) data[section][key] = cfgObject.getSectionValue(section, key, defaultConfig[section][key])
for dirInfo in data["Filesystem"]["createDirs"].split(","): for dirInfo in data["Filesystem"]["createDirs"].split(","):
temp = dirInfo.split(":") temp = dirInfo.split(":")
directories.add(newDirectory(temp[0], uint64(parseInt(temp[1])))) directories.add(newDirectory(temp[0].strip(), uint64(parseInt(temp[1].strip()))))
for symInfo in data["Filesystem"]["createSymlinks"].split(","): for symInfo in data["Filesystem"]["createSymlinks"].split(","):
temp = symInfo.split(":") temp = symInfo.split(":")
symlinks.add(newSymlink(temp[0], temp[1])) symlinks.add(newSymlink(temp[0], temp[1]))
if data["Logging"]["level"] notin levels: if data["Logging"]["level"] notin levels:
logger.warning(&"""Unknown logging level '{data["Logging"]["level"]}', defaulting to {defaultConfig["Logging"]["level"]}""") logger.warning(&"""Unknown logging level '{data["Logging"]["level"]}', defaulting to '{defaultConfig["Logging"]["level"]}'""")
data["Logging"]["level"] = defaultConfig["Logging"]["level"] data["Logging"]["level"] = defaultConfig["Logging"]["level"]
result = NimDConfig(logLevel: levels[data["Logging"]["level"]], result = NimDConfig(logLevel: levels[data["Logging"]["level"]],
filesystems: filesystems) logFile: data["Logging"]["logFile"],
filesystems: filesystems,
symlinks: symlinks,
directories: directories,
autoMount: parseBool(data["Filesystem"]["autoMount"].toLowerAscii()),
autoUnmount: parseBool(data["Filesystem"]["autoUnmount"].toLowerAscii()),
fstab: data["Filesystem"]["fstabPath"],
sock: data["Misc"]["controlSocket"],
onDependencyConflict: data["Misc"]["onDependencyConflict"].toLowerAscii(),
restartDelay: parseInt(data["Misc"]["restartDelay"]),
sigtermDelay: parseInt(data["Misc"]["sigtermDelay"]),
workers: parseInt(data["Misc"]["workers"]),
setHostname: parseBool(data["Misc"]["setHostname"])
)

View File

@ -46,7 +46,13 @@ var defaultLevel = LogLevel.Info
var logFile = "/var/log/nimd" var logFile = "/var/log/nimd"
var logToFileOnly: bool = false var logToFileOnly: bool = false
## This mess is needed to make sure stderr writes are mostly atomic. Sort of
proc setLogFile*(file: string) =
# Sets the log file
logFile = file
## This mess is needed to make sure stderr writes are mostly atomic. Sort of.
## No error handling yet. Deal with it ## No error handling yet. Deal with it
var customStderrFd = dup(stderr.getFileHandle()) var customStderrFd = dup(stderr.getFileHandle())
discard dup3(stderr.getFileHandle(), customStderrFd, O_APPEND) discard dup3(stderr.getFileHandle(), customStderrFd, O_APPEND)