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/dir1, /path/to/dir2 # Creates these directories on boot. Empty to disable createDirs = "/path/to/dir:perms," # Creates these directories with the given permissions 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
setHostname = true # Set to true to set the machine's hostname automatically from /etc/hostname
onDependencyConflict = skip # Other option: warn, error 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()")
@ -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)