Browse Source

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

main
nocturn9x 11 months ago
parent
commit
76f2df1969
  1. 22
      README.md
  2. 10
      config.nims
  3. 21
      nimd.conf
  4. 12
      src/core/mainloop.nim
  5. 11
      src/core/shutdown.nim
  6. 31
      src/main.nim
  7. 59
      src/util/config.nim
  8. 8
      src/util/logging.nim

22
README.md

@ -58,7 +58,6 @@ want to make sure your disks are mounted and that your network has been set up.
```
[Service]
name = ssh # The name of the service
description = Secure Shell Server # A short description
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
[Logging]
stderr = /var/log/sshd # Path of the stderr 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
@ -99,20 +97,22 @@ INI-like structure), but the options are obviously different. An example config
```
[Logging]
level = info # Levels are: trace, debug, info, warning, error, critical, fatal
logFile = /var/log/nimd # Path to log file
[Filesystem]
autoMount = true # Automatically parses /etc/fstab and mounts disks
autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown
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
createSymlinks = /path/to/symlink:/path/to/dest, ... # Creates these symlinks on boot. Empty to disable
autoMount = true # Automatically parses /etc/fstab and mounts disks
autoUnmount = true # Automatically parses /proc/mounts and unmounts everything on shutdown
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
createSymlinks = "/path/to/symlink:/path/to/dest," # Creates these symlinks on boot. Empty to disable
[Misc]
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
```

10
config.nims

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

21
nimd.conf

@ -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
"""

12
src/core/mainloop.nim

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

11
src/core/shutdown.nim

@ -39,7 +39,12 @@ proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandle
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) =
@ -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
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("/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)
quit(-1)
logger.warning("The system is shutting down")
@ -113,7 +118,7 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
if anyUserlandProcessLeft():
logger.info("Terminating child processes with 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) =

31
src/main.nim

@ -17,7 +17,7 @@ import posix
import os
# NimD's own stuff
import util/[logging, constants, misc]
import util/[logging, constants, misc, config]
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
addDirectory(newDirectory("test", 764)) # Should create a directory
addDirectory(newDirectory("/dev/disk", 123)) # Should say directory already exists
# Shutdown handler to unmount disks
addShutdownHandler(newShutdownHandler(unmountAllDisks))
# Adds test services
var echoer = newService(name="echoer", description="prints owo", exec="/bin/echo owoooooooooo",
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
## 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.info(&"NimD version {NimdVersion.major}.{NimdVersion.minor}.{NimdVersion.patch} is starting up!")
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
logger.trace("Setting up signal handlers")
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
# 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
@ -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
# processes!
reapProcess(getDefaultLogger())
if config.autoUnmount:
logger.debug("Setting up shutdown handler for automatic disk unmounting")
# Shutdown handler to unmount disks
addShutdownHandler(newShutdownHandler(unmountAllDisks))
addStuff()
try:
if mountDisks:
if config.autoMount:
logger.info("Mounting filesystem")
mountDisks(logger, fstab)
mountDisks(logger, config.fstab)
else:
logger.info("Skipping disk mounting, assuming this has already been done")
logger.info("Creating symlinks")
@ -111,7 +115,7 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
except:
logger.fatal(&"A fatal error has occurred while preparing filesystem, booting cannot continue. Error -> {getCurrentExceptionMsg()}")
nimDExit(logger, 131, emerg=false)
if setHostname:
if config.setHostname:
logger.info("Setting hostname")
logger.debug(&"Hostname was set to '{misc.setHostname(logger)}'")
else:
@ -119,9 +123,9 @@ proc main(logger: Logger, mountDisks: bool = true, fstab: string = "/etc/fstab",
logger.debug("Entering critical fork() section: blocking signals")
blockSignals(logger) # They are later unblocked in mainLoop
logger.info("Processing boot runlevel")
startServices(logger, workers=workerCount, level=Boot)
startServices(logger, workers=config.workers, level=Boot)
logger.debug("Starting main loop")
mainLoop(logger, workers=workerCount)
mainLoop(logger, config)
when isMainModule:
@ -167,9 +171,10 @@ when isMainModule:
else:
echo "Usage: nimd [options]"
quit(EINVAL) # EINVAL - Invalid argument
logger.debug("Calling NimD entry point")
setStdIoUnbuffered() # Colors don't work otherwise!
try:
main(logger)
main(logger, parseConfig(logger, "/etc/nimd/nimd.conf"))
except:
logger.fatal(&"A fatal unrecoverable error has occurred during startup and NimD cannot continue: {getCurrentExceptionMsg()}")
nimDExit(logger, 131) # ENOTRECOVERABLE - State not recoverable

59
src/core/config.nim → src/util/config.nim

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

8
src/util/logging.nim

@ -46,7 +46,13 @@ var defaultLevel = LogLevel.Info
var logFile = "/var/log/nimd"
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
var customStderrFd = dup(stderr.getFileHandle())
discard dup3(stderr.getFileHandle(), customStderrFd, O_APPEND)

Loading…
Cancel
Save