mirror of https://github.com/nocturn9x/nimd.git
Solved merge conflict
This commit is contained in:
commit
93b73de32a
22
README.md
22
README.md
|
@ -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)
|
||||||
|
@ -71,7 +70,6 @@ supervised = true # This is the default. Disable it if you d
|
||||||
workDir = /usr/bin # The service's working directory
|
workDir = /usr/bin # The service's working directory
|
||||||
|
|
||||||
[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
|
||||||
|
@ -100,20 +98,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
|
||||||
```
|
```
|
10
config.nims
10
config.nims
|
@ -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 & "' .."
|
||||||
|
|
|
@ -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
|
||||||
|
"""
|
|
@ -11,3 +11,26 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import os
|
||||||
|
import net
|
||||||
|
import strformat
|
||||||
|
import shutdown
|
||||||
|
|
||||||
|
|
||||||
|
import ../util/logging
|
||||||
|
import ../util/misc
|
||||||
|
|
||||||
|
|
||||||
|
proc initControlSocket*(logger: Logger, path: string = "/var/run/nimd.sock"): Socket =
|
||||||
|
## Initializes NimD's control socket (an unbuffered
|
||||||
|
## TCP Unix Domain Socket) binding it to the given
|
||||||
|
## path (defaults to /var/run/nimd.sock)
|
||||||
|
try:
|
||||||
|
logger.info(&"Initializing control socket at '{path}'")
|
||||||
|
if exists(path):
|
||||||
|
removeFile(path)
|
||||||
|
result = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP, buffered=false)
|
||||||
|
bindUnix(result, path)
|
||||||
|
except OSError:
|
||||||
|
logger.error(&"Error when binding unix socket at '{path}': {getCurrentExceptionMsg()}")
|
||||||
|
nimDExit(logger, code=int(osLastError()))
|
|
@ -13,26 +13,62 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import strformat
|
import strformat
|
||||||
import os
|
import os
|
||||||
|
import net
|
||||||
|
|
||||||
|
|
||||||
import ../util/[logging, misc]
|
import ../util/[logging, misc, config]
|
||||||
import services
|
import services
|
||||||
|
import control
|
||||||
|
import shutdown
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc mainLoop*(logger: Logger) =
|
proc mainLoop*(logger: Logger, config: NimDConfig, startServices: bool = true) =
|
||||||
## NimD's main execution loop
|
## NimD's main execution loop
|
||||||
logger.info("Processing default runlevel")
|
if startServices:
|
||||||
startServices(logger, workers=1, level=Default)
|
logger.info("Processing default runlevel")
|
||||||
logger.debug(&"Unblocking signals")
|
startServices(logger, workers=config.workers, level=Default)
|
||||||
unblockSignals(logger)
|
logger.debug(&"Unblocking signals")
|
||||||
logger.info("System initialization complete, going idle")
|
unblockSignals(logger)
|
||||||
logger.switchToFile()
|
logger.info("System initialization complete, idling on control socket")
|
||||||
|
var opType: string
|
||||||
try:
|
try:
|
||||||
|
logger.trace("Calling initControlSocket()")
|
||||||
|
var serverSocket = initControlSocket(logger)
|
||||||
|
serverSocket.listen(5)
|
||||||
|
var clientSocket = newSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||||||
|
logger.switchToFile()
|
||||||
|
logger.debug("Entering accept() loop")
|
||||||
while true:
|
while true:
|
||||||
sleepSeconds(30)
|
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.)
|
||||||
|
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":
|
||||||
|
discard # TODO
|
||||||
|
else:
|
||||||
|
logger.warning(&"Received unknown operation type '{opType}' via control socket, ignoring it")
|
||||||
|
discard
|
||||||
|
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)
|
mainLoop(logger, config, startServices=false)
|
||||||
|
|
|
@ -223,18 +223,17 @@ proc removeService*(service: Service) =
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
proc loggerWorker(logger: Logger, service: Service, process: Process) =
|
proc streamLoggerWorker(logger: Logger, service: Service, stream: Stream) =
|
||||||
## Captures the output of a given process and relays it
|
## Captures the output of a given process and relays it
|
||||||
## in a formatted manner into our logging system
|
## in a formatted manner into our logging system
|
||||||
try:
|
try:
|
||||||
logger.debug("Switching logs to file")
|
logger.debug("Switching logs to file")
|
||||||
logger.switchToFile()
|
logger.switchToFile()
|
||||||
var line: string = ""
|
var line: string = ""
|
||||||
var stream = process.outputStream
|
|
||||||
while stream.readLine(line):
|
while stream.readLine(line):
|
||||||
logger.info(&"{service.name}: {line}")
|
logger.info(&"{service.name}: {line}")
|
||||||
except:
|
except:
|
||||||
logger.error(&"An error occurred in loggerWorker: {getCurrentExceptionMsg()}")
|
logger.error(&"An error occurred in streamLoggerWorker: {getCurrentExceptionMsg()}")
|
||||||
quit(-1)
|
quit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -248,63 +247,63 @@ proc supervisorWorker(logger: Logger, service: Service, process: Process) =
|
||||||
logger.error(&"Error, cannot fork: {posix.strerror(posix.errno)}")
|
logger.error(&"Error, cannot fork: {posix.strerror(posix.errno)}")
|
||||||
elif p == 0:
|
elif p == 0:
|
||||||
logger.trace(&"New child has been spawned")
|
logger.trace(&"New child has been spawned")
|
||||||
loggerWorker(logger, service, process)
|
streamLoggerWorker(logger, service, process.outputStream)
|
||||||
else:
|
var pid = process.processID
|
||||||
var pid = process.processID
|
var status: cint
|
||||||
var status: cint
|
var returnCode: int
|
||||||
var returnCode: int
|
var sig: int
|
||||||
var sig: int
|
var process: Process
|
||||||
var process: Process
|
logger.debug("Switching logs to file")
|
||||||
logger.debug("Switching logs to file")
|
logger.switchToFile()
|
||||||
logger.switchToFile()
|
while true:
|
||||||
while true:
|
logger.trace(&"Calling waitpid() on {pid}")
|
||||||
logger.trace(&"Calling waitpid() on {pid}")
|
returnCode = posix.waitPid(cint(pid), status, WUNTRACED)
|
||||||
returnCode = posix.waitPid(cint(pid), status, WUNTRACED)
|
if WIFEXITED(status):
|
||||||
if WIFEXITED(status):
|
sig = 0
|
||||||
sig = 0
|
elif WIFSIGNALED(status):
|
||||||
elif WIFSIGNALED(status):
|
sig = WTERMSIG(status)
|
||||||
sig = WTERMSIG(status)
|
else:
|
||||||
else:
|
sig = -1
|
||||||
sig = -1
|
logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}, setting sig to {sig}")
|
||||||
logger.trace(&"Call to waitpid() set status to {status} and returned {returnCode}, setting sig to {sig}")
|
process.close()
|
||||||
case service.restart:
|
case service.restart:
|
||||||
of Never:
|
of Never:
|
||||||
logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process")
|
logger.info(&"Service '{service.name}' ({returnCode}) has exited, shutting down controlling process")
|
||||||
|
break
|
||||||
|
of Always:
|
||||||
|
if sig > 0:
|
||||||
|
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
|
||||||
|
elif sig == 0:
|
||||||
|
logger.info(&"Service '{service.name}' has exited gracefully, sleeping {service.restartDelay} seconds before restarting it")
|
||||||
|
else:
|
||||||
|
logger.info(&"Service '{service.name}' has exited, sleeping {service.restartDelay} seconds before restarting it")
|
||||||
|
removeManagedProcess(pid)
|
||||||
|
sleep(service.restartDelay * 1000)
|
||||||
|
var split = shlex(service.exec)
|
||||||
|
if split.error:
|
||||||
|
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
|
||||||
break
|
break
|
||||||
of Always:
|
var arguments = split.words
|
||||||
if sig > 0:
|
let progName = arguments[0]
|
||||||
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
|
arguments = arguments[1..^1]
|
||||||
elif sig == 0:
|
process = startProcess(progName, workingDir=service.workDir, args=arguments)
|
||||||
logger.info(&"Service '{service.name}' has exited gracefully, sleeping {service.restartDelay} seconds before restarting it")
|
pid = process.processID()
|
||||||
else:
|
of OnFailure:
|
||||||
logger.info(&"Service '{service.name}' has exited, sleeping {service.restartDelay} seconds before restarting it")
|
if sig > 0:
|
||||||
removeManagedProcess(pid)
|
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
|
||||||
sleep(service.restartDelay * 1000)
|
removeManagedProcess(pid)
|
||||||
var split = shlex(service.exec)
|
sleep(service.restartDelay * 1000)
|
||||||
if split.error:
|
var split = shlex(service.exec)
|
||||||
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
|
if split.error:
|
||||||
break
|
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
|
||||||
var arguments = split.words
|
break
|
||||||
let progName = arguments[0]
|
var arguments = split.words
|
||||||
arguments = arguments[1..^1]
|
let progName = arguments[0]
|
||||||
process = startProcess(progName, workingDir=service.workDir, args=arguments)
|
arguments = arguments[1..^1]
|
||||||
pid = process.processID()
|
process = startProcess(progName, workingDir=service.workDir, args=arguments)
|
||||||
of OnFailure:
|
pid = process.processID()
|
||||||
if sig > 0:
|
if process != nil:
|
||||||
logger.info(&"Service '{service.name}' ({returnCode}) has crashed (terminated by signal {sig}: {strsignal(cint(sig))}), sleeping {service.restartDelay} seconds before restarting it")
|
process.close()
|
||||||
removeManagedProcess(pid)
|
|
||||||
sleep(service.restartDelay * 1000)
|
|
||||||
var split = shlex(service.exec)
|
|
||||||
if split.error:
|
|
||||||
logger.error(&"Error while restarting service '{service.name}': invalid exec syntax")
|
|
||||||
break
|
|
||||||
var arguments = split.words
|
|
||||||
let progName = arguments[0]
|
|
||||||
arguments = arguments[1..^1]
|
|
||||||
process = startProcess(progName, workingDir=service.workDir, args=arguments)
|
|
||||||
pid = process.processID()
|
|
||||||
if process != nil:
|
|
||||||
process.close()
|
|
||||||
|
|
||||||
|
|
||||||
proc startService(logger: Logger, service: Service) =
|
proc startService(logger: Logger, service: Service) =
|
||||||
|
@ -323,7 +322,7 @@ proc startService(logger: Logger, service: Service) =
|
||||||
var arguments = split.words
|
var arguments = split.words
|
||||||
let progName = arguments[0]
|
let progName = arguments[0]
|
||||||
arguments = arguments[1..^1]
|
arguments = arguments[1..^1]
|
||||||
process = startProcess(progName, workingDir=service.workDir, args=arguments, options=if service.useParentStreams: {poParentStreams} else: {poUsePath, poDaemon, poStdErrToStdOut})
|
process = startProcess(progName, workingDir=service.workDir, args=arguments, options=if service.useParentStreams: {poParentStreams, poStdErrToStdOut} else: {poUsePath, poDaemon, poStdErrToStdOut})
|
||||||
if service.supervised or service.kind != Oneshot:
|
if service.supervised or service.kind != Oneshot:
|
||||||
var pid = posix.fork()
|
var pid = posix.fork()
|
||||||
if pid == -1:
|
if pid == -1:
|
||||||
|
@ -331,8 +330,9 @@ proc startService(logger: Logger, service: Service) =
|
||||||
elif pid == 0:
|
elif pid == 0:
|
||||||
logger.trace(&"New child has been spawned")
|
logger.trace(&"New child has been spawned")
|
||||||
supervisorWorker(logger, service, process)
|
supervisorWorker(logger, service, process)
|
||||||
# If the service is unsupervised we just spawn the logger worker
|
# If the service is unsupervised we just spawn the logger worker (assuming it doesn't use poParentStreams)
|
||||||
loggerWorker(logger, service, process)
|
if not service.useParentStreams:
|
||||||
|
streamLoggerWorker(logger, service, process.outputStream)
|
||||||
except:
|
except:
|
||||||
logger.error(&"Error while starting service '{service.name}': {getCurrentExceptionMsg()}")
|
logger.error(&"Error while starting service '{service.name}': {getCurrentExceptionMsg()}")
|
||||||
|
|
||||||
|
@ -374,7 +374,7 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) =
|
||||||
if service.supervised:
|
if service.supervised:
|
||||||
addManagedProcess(pid, service)
|
addManagedProcess(pid, service)
|
||||||
if len(pids) == workers:
|
if len(pids) == workers:
|
||||||
logger.debug(&"""Worker queue full, waiting for some worker{(if workers > 1: "s" else: "")} to exit...""")
|
logger.debug(&"""Worker queue full, waiting for some worker{(if workers > 1: "s" else: "")} to exit""")
|
||||||
for i, pid in pids:
|
for i, pid in pids:
|
||||||
logger.trace(&"Calling waitpid() on {pid}")
|
logger.trace(&"Calling waitpid() on {pid}")
|
||||||
var returnCode = waitPid(cint(pid), status, WUNTRACED)
|
var returnCode = waitPid(cint(pid), status, WUNTRACED)
|
||||||
|
@ -385,4 +385,4 @@ proc startServices*(logger: Logger, level: RunLevel, workers: int = 1) =
|
||||||
logger.debug(&"Waiting for completion of service spawning in runlevel {($level).toLowerAscii()}")
|
logger.debug(&"Waiting for completion of service spawning in runlevel {($level).toLowerAscii()}")
|
||||||
logger.trace(&"Calling waitpid() on {pid}")
|
logger.trace(&"Calling waitpid() on {pid}")
|
||||||
var returnCode = waitPid(cint(pid), status, WUNTRACED)
|
var returnCode = waitPid(cint(pid), status, WUNTRACED)
|
||||||
logger.trace(&"Call to waitpid() on {pid} set status to {status} and returned {returnCode}")
|
logger.trace(&"Call to waitpid() on {pid} set status to {status} and returned {returnCode}")
|
||||||
|
|
|
@ -18,6 +18,8 @@ import glob
|
||||||
import strutils
|
import strutils
|
||||||
import strformat
|
import strformat
|
||||||
import times
|
import times
|
||||||
|
import tables
|
||||||
|
import syscall
|
||||||
|
|
||||||
|
|
||||||
import ../util/logging
|
import ../util/logging
|
||||||
|
@ -29,12 +31,20 @@ type ShutdownHandler* = ref object
|
||||||
body*: proc (logger: Logger, code: int)
|
body*: proc (logger: Logger, code: int)
|
||||||
|
|
||||||
|
|
||||||
|
const reboot_codes = {"poweroff": 0x4321fedc'i64, "reboot": 0x01234567'i64, "halt": 0xcdef0123}.toTable()
|
||||||
|
|
||||||
|
|
||||||
proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler =
|
proc newShutdownHandler*(body: proc (logger: Logger, code: int)): ShutdownHandler =
|
||||||
result = ShutdownHandler(body: body)
|
result = ShutdownHandler(body: body)
|
||||||
|
|
||||||
|
|
||||||
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) =
|
||||||
|
@ -80,16 +90,19 @@ proc nimDExit*(logger: Logger, code: int, emerg: bool = true) =
|
||||||
## NimD's exit point. This function tries to shut down
|
## NimD's exit point. This function tries to shut down
|
||||||
## as cleanly as possible. When emerg equals true, it will
|
## as cleanly as possible. When emerg equals true, it will
|
||||||
## try to spawn a root shell and exit
|
## try to spawn a root shell and exit
|
||||||
|
logger.switchToConsole()
|
||||||
|
logger.info("Syncing file systems")
|
||||||
|
logger.debug(&"Calling sync() syscall has returned {syscall(SYNC)}")
|
||||||
if emerg:
|
if emerg:
|
||||||
# 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")
|
||||||
logger.info("Processing shutdown runlevel")
|
logger.info("Processing shutdown runlevel")
|
||||||
startServices(logger, Shutdown)
|
startServices(logger, RunLevel.Shutdown)
|
||||||
logger.info("Running shutdown handlers")
|
logger.info("Running shutdown handlers")
|
||||||
try:
|
try:
|
||||||
for handler in shutdownHandlers:
|
for handler in shutdownHandlers:
|
||||||
|
@ -107,5 +120,31 @@ 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")
|
||||||
quit(code)
|
|
||||||
|
|
||||||
|
proc reboot*(logger: Logger) =
|
||||||
|
## Reboots the system
|
||||||
|
logger.debug("Switching logs to console")
|
||||||
|
logger.switchToConsole()
|
||||||
|
logger.info("The system is rebooting")
|
||||||
|
nimDExit(logger, 0, emerg=false)
|
||||||
|
discard syscall(REBOOT, 0xfee1dead, 537993216, reboot_codes["reboot"])
|
||||||
|
|
||||||
|
|
||||||
|
proc shutdown*(logger: Logger) =
|
||||||
|
## Shuts the system off
|
||||||
|
logger.debug("Switching logs to console")
|
||||||
|
logger.switchToConsole()
|
||||||
|
logger.info("The system is powering off")
|
||||||
|
nimDExit(logger, 0, emerg=false)
|
||||||
|
discard syscall(REBOOT, 0xfee1dead, 537993216, reboot_codes["poweroff"])
|
||||||
|
|
||||||
|
|
||||||
|
proc halt*(logger: Logger) =
|
||||||
|
## Halts the system
|
||||||
|
logger.debug("Switching logs to console")
|
||||||
|
logger.switchToConsole()
|
||||||
|
logger.info("The system is halting")
|
||||||
|
nimDExit(logger, 0, emerg=false)
|
||||||
|
discard syscall(REBOOT, 0xfee1dead, 537993216, reboot_codes["halt"])
|
||||||
|
|
37
src/main.nim
37
src/main.nim
|
@ -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=Oneshot, workDir=getCurrentDir(),
|
runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(),
|
||||||
|
@ -52,11 +51,6 @@ proc addStuff =
|
||||||
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple,
|
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple,
|
||||||
depends=(@[newDependency(Other, echoer)]), provides=(@[]),
|
depends=(@[newDependency(Other, echoer)]), provides=(@[]),
|
||||||
stdin="/dev/null", stderr="", stdout="")
|
stdin="/dev/null", stderr="", stdout="")
|
||||||
var test = newService(name="broken", description="", exec="/bin/echo owo",
|
|
||||||
runlevel=Boot, kind=Oneshot, workDir=getCurrentDir(),
|
|
||||||
supervised=false, restart=Never, restartDelay=0,
|
|
||||||
depends=(@[newDependency(Other, echoer)]), provides=(@[]),
|
|
||||||
stdin="/dev/null", stderr="", stdout="")
|
|
||||||
var exiter = newService(name="exiter", description="la mamma di licenziat",
|
var exiter = newService(name="exiter", description="la mamma di licenziat",
|
||||||
exec="/bin/true", supervised=true, restart=Always,
|
exec="/bin/true", supervised=true, restart=Always,
|
||||||
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple,
|
restartDelay=5, runlevel=Boot, workDir="/", kind=Simple,
|
||||||
|
@ -70,15 +64,15 @@ proc addStuff =
|
||||||
addService(errorer)
|
addService(errorer)
|
||||||
addService(echoer)
|
addService(echoer)
|
||||||
addService(exiter)
|
addService(exiter)
|
||||||
addService(test)
|
|
||||||
addService(shell)
|
addService(shell)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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,7 +88,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
|
||||||
|
@ -102,11 +96,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")
|
||||||
|
@ -119,7 +117,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:
|
||||||
|
@ -127,9 +125,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)
|
mainLoop(logger, config)
|
||||||
|
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
|
@ -175,9 +173,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
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright 2021 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import net
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
||||||
|
try:
|
||||||
|
sock.connectUnix("/var/run/nimd.sock")
|
||||||
|
except OSError:
|
||||||
|
echo getCurrentExceptionMsg()
|
||||||
|
quit(-1)
|
||||||
|
echo sock.trySend("r")
|
||||||
|
sock.close()
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright 2021 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import net
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
||||||
|
try:
|
||||||
|
sock.connectUnix("/var/run/nimd.sock")
|
||||||
|
except OSError:
|
||||||
|
echo getCurrentExceptionMsg()
|
||||||
|
quit(-1)
|
||||||
|
echo sock.trySend("p")
|
||||||
|
sock.close()
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright 2021 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import net
|
||||||
|
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
var sock = newSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
||||||
|
try:
|
||||||
|
sock.connectUnix("/var/run/nimd.sock")
|
||||||
|
except OSError:
|
||||||
|
echo getCurrentExceptionMsg()
|
||||||
|
quit(-1)
|
||||||
|
echo sock.trySend("r")
|
||||||
|
sock.close()
|
|
@ -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"])
|
||||||
|
)
|
|
@ -38,11 +38,28 @@ type
|
||||||
level*: LogLevel
|
level*: LogLevel
|
||||||
handlers*: seq[LogHandler]
|
handlers*: seq[LogHandler]
|
||||||
|
|
||||||
|
|
||||||
|
proc dup3(a1, a2, a3: cint): cint {.importc.}
|
||||||
|
|
||||||
|
|
||||||
var defaultLevel = LogLevel.Info
|
var defaultLevel = LogLevel.Info
|
||||||
var logFile = "/var/log/nimd"
|
var logFile = "/var/log/nimd"
|
||||||
var logToFileOnly: bool = false
|
var logToFileOnly: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
var customStderr: File
|
||||||
|
discard open(customStderr, customStderrFd, fmAppend)
|
||||||
|
|
||||||
|
|
||||||
proc log(self: Logger, level: LogLevel = defaultLevel, message: string) # Forward declaration
|
proc log(self: Logger, level: LogLevel = defaultLevel, message: string) # Forward declaration
|
||||||
|
|
||||||
# Simple one-line procedures
|
# Simple one-line procedures
|
||||||
|
@ -79,132 +96,91 @@ proc log(self: Logger, level: LogLevel = defaultLevel, message: string) =
|
||||||
# Do NOT touch the alignment offsets or your console output and logs will look like trash
|
# Do NOT touch the alignment offsets or your console output and logs will look like trash
|
||||||
|
|
||||||
|
|
||||||
proc lockFile(logger: Logger, handle: File) =
|
|
||||||
## Locks the given file across the whole system for writing using fcntl()
|
|
||||||
if fcntl(handle.getFileHandle(), F_WRLCK) == -1:
|
|
||||||
setForegroundColor(fgRed)
|
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] Error while locking handle (code {posix.errno}, {posix.strerror(posix.errno)}): output may be mangled""")
|
|
||||||
setForegroundColor(fgDefault)
|
|
||||||
|
|
||||||
|
|
||||||
proc unlockFile(logger: Logger, handle: File) =
|
|
||||||
## Unlocks the given file across the whole system for writing using fcntl()
|
|
||||||
if fcntl(handle.getFileHandle(), F_UNLCK) == -1:
|
|
||||||
setForegroundColor(fgRed)
|
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] Error while unlocking handle (code {posix.errno}, {posix.strerror(posix.errno)}): output may be missing""")
|
|
||||||
setForegroundColor(fgDefault)
|
|
||||||
|
|
||||||
|
|
||||||
proc logTraceStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logTraceStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgMagenta)
|
setForegroundColor(fgMagenta)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logDebugStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logDebugStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgCyan)
|
setForegroundColor(fgCyan)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logInfoStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logInfoStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgGreen)
|
setForegroundColor(fgGreen)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logWarningStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logWarningStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgYellow)
|
setForegroundColor(fgYellow)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logErrorStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logErrorStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgRed)
|
setForegroundColor(fgRed)
|
||||||
stderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
customStderr.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logCriticalStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgYellow)
|
setForegroundColor(fgYellow)
|
||||||
setBackgroundColor(bgRed)
|
setBackgroundColor(bgRed)
|
||||||
stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})]""")
|
customStderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})]""")
|
||||||
setBackgroundColor(bgDefault)
|
setBackgroundColor(bgDefault)
|
||||||
stderr.writeLine(&""" {message}""")
|
customStderr.writeLine(&""" {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logFatalStderr(self: LogHandler, logger: Logger, message: string) =
|
proc logFatalStderr(self: LogHandler, logger: Logger, message: string) =
|
||||||
logger.lockFile(stderr)
|
|
||||||
setForegroundColor(fgBlack)
|
setForegroundColor(fgBlack)
|
||||||
setBackgroundColor(bgRed)
|
setBackgroundColor(bgRed)
|
||||||
stderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})]""")
|
customStderr.write(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})]""")
|
||||||
setForegroundColor(fgRed)
|
setForegroundColor(fgRed)
|
||||||
setBackgroundColor(bgDefault)
|
setBackgroundColor(bgDefault)
|
||||||
stderr.writeline(&""" {message}""")
|
customStderr.writeline(&""" {message}""")
|
||||||
setForegroundColor(fgDefault)
|
setForegroundColor(fgDefault)
|
||||||
logger.unlockFile(stderr)
|
|
||||||
|
|
||||||
|
|
||||||
proc logTraceFile(self: LogHandler, logger: Logger, message: string) =
|
proc logTraceFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} TRACE {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logDebugFile(self: LogHandler, logger: Logger, message: string) =
|
proc logDebugFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} DEBUG {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logInfoFile(self: LogHandler, logger: Logger, message: string) =
|
proc logInfoFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} INFO {"-":>4} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logWarningFile(self: LogHandler, logger: Logger, message: string) =
|
proc logWarningFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} WARNING {"-":>1} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logErrorFile(self: LogHandler, logger: Logger, message: string) =
|
proc logErrorFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<10} {"-":>1} {"":>1} ERROR {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logCriticalFile(self: LogHandler, logger: Logger, message: string) =
|
proc logCriticalFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<4} {"-":>1} CRITICAL {"-":>2} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc logFatalFile(self: LogHandler, logger: Logger, message: string) =
|
proc logFatalFile(self: LogHandler, logger: Logger, message: string) =
|
||||||
var self = StreamHandler(self)
|
var self = StreamHandler(self)
|
||||||
logger.lockFile(self.file)
|
|
||||||
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})] {message}""")
|
self.file.writeLine(&"""[{fromUnix(getTime().toUnixFloat().int).format("d/M/yyyy HH:mm:ss"):<5} {"-":>1} {"":>1} FATAL {"-":>3} ({posix.getpid():03})] {message}""")
|
||||||
logger.unlockFile(self.file)
|
|
||||||
|
|
||||||
|
|
||||||
proc switchToFile*(self: Logger) =
|
proc switchToFile*(self: Logger) =
|
||||||
|
|
Loading…
Reference in New Issue