#!/usr/bin/env janet # DaemonTamer v3: a simple and portable way to daemonize any command, # now ported to Janet (depends on spork) # See README.md for (optional) compilation and usage # Created by Luxferre in 2026, released into the public domain (import spork/json) (import spork/path) (import spork/sh) (import spork/argparse :prefix "") (def script-path (-> (dyn :current-file) os/realpath path/dirname)) (def config-path (string/format "%s/.dt.json" (os/getenv "HOME"))) (def tempdir (sh/exec-slurp "/bin/sh" "-c" "dirname $(mktemp -u)")) (def pid-file-template (string/format "%s/dt.%%s.pid" tempdir)) (def nohup-template "nohup %s >>%s 2>>%s &\necho $! > %s") (defn- read-config [] (if (os/stat config-path) (json/decode (slurp config-path)) @{})) (defn- write-config [cfg] (spit config-path (json/encode cfg))) (defn- to-service-entry [params] { :command (get params "command") :directory (or (get params "directory") script-path) :before-start (get params "before-start") :after-stop (get params "after-stop") :stdout-log (or (get params "stdout-log") "/dev/null") :stderr-log (or (get params "stderr-log") "/dev/null") }) (defn- pid-file-path [sname] (string/format pid-file-template sname)) (defn process-exists? [pid] (default pid (os/getpid)) (= 0 (os/execute ["kill" "-0" (string pid)] :p))) # Read a single-letter choice from the stdin (defn get-choice [prompt] (let [rawc (getline prompt)] (if (> (length rawc) 0) (string/ascii-lower (string/slice rawc 0 1)) nil))) # Ask user for y/n confirmation (with custom prompt) (defn user-confirm [prompt] (var resp nil) (var valid false) (while (not valid) (set resp (get-choice prompt)) (set valid (string/check-set "yn" resp))) (= resp "y")) # command handlers (defn- service-status [sname] (let [pfile (pid-file-path sname)] (if (os/stat pfile) (let [pid (scan-number (string/trim (slurp pfile)))] (if (process-exists? pid) "running" "stopped")) "stopped"))) (defn- start-service [sname entry] (if (= (service-status sname) "stopped") (do (printf "Starting service %s..." sname) (let [ workdir (entry :directory) pre-start (get entry :before-start) cmd (get entry :command) sout (get entry :stdout-log) serr (get entry :stderr-log) ] (os/cd workdir) (when pre-start (os/shell pre-start)) (os/shell (string/format nohup-template cmd sout serr (pid-file-path sname))) (printf "Service %s started" sname))) # already running service here (eprintf "Service %s is already running!" sname))) (defn- stop-service [sname entry] (printf "Stopping service %s..." sname) (let [ pfile (pid-file-path sname) post-stop (get entry :after-stop) ] (when (os/stat pfile) (let [pid (scan-number (string/trim (slurp pfile)))] (os/execute ["kill" (string pid)] :p) (os/rm pfile) (when post-stop (os/shell post-stop)) (printf "Service %s stopped" sname) )))) (defn- restart-service [sname entry] (printf "Restarting service %s..." sname) (stop-service sname entry) (start-service sname entry) ) (defn process-action [action service-name params] (let [cfg (read-config)] (case action "add" (let [service-entry (to-service-entry params)] (put cfg service-name service-entry) (write-config cfg) (printf "Service %s registered" service-name) ) "del" (let [service-entry (get cfg service-name)] (if service-entry (when (user-confirm (string/format "Really delete the service %s? (y/n) " service-name)) (put cfg service-name nil) (write-config cfg) (printf "Service %s unregistered" service-name)) (eprint "This service name does not exist!"))) "list" (eachp [sname entry] cfg (printf "%s\t%s\t%s\t%s" sname (service-status sname) (entry "command") (entry "directory"))) "status" (let [service-entry (get cfg service-name)] (if service-entry (printf "Service %s is %s." service-name (service-status service-name)) (eprint "This service name does not exist!"))) "start" (let [service-entry (to-service-entry (get cfg service-name))] (if service-entry (start-service service-name service-entry) (eprint "This service name does not exist!"))) "stop" (let [service-entry (to-service-entry (get cfg service-name))] (if service-entry (stop-service service-name service-entry) (eprint "This service name does not exist!"))) "restart" (let [service-entry (to-service-entry (get cfg service-name))] (if service-entry (restart-service service-name service-entry) (eprint "This service name does not exist!"))) (eprint "Invalid action! Allowed actions: add, del, start, stop, restart, list, status")))) (def cli-params ["DaemonTamer v3 service manager (by Luxferre, 2026)" :default {:kind :accumulate :required true :help "[action] [name]"} # positional params: action, name "command" {:kind :option :short "c" :help "The command to run"} "directory" {:kind :option :short "d" :help "Working directory"} "stdout-log" {:kind :option :short "o" :help "Log file for stdout (default /dev/null)" :default "/dev/null"} "stderr-log" {:kind :option :short "e" :help "Log file for stderr (default /dev/null)" :default "/dev/null"} "before-start" {:kind :option :short "b" :help "Pre-start hook command"} "after-stop" {:kind :option :short "a" :help "Post-stop hook command"}]) (defn main [&] (let [args (argparse ;cli-params)] (def posargs (get args :default)) (if (and posargs (or (>= (length posargs) 2) (= (posargs 0) "list"))) (do (def [action service-name] (args :default)) (process-action action service-name args) ) (do (eprint "Usage: dt {action} {service-name} [opts]\nAllowed actions: add, del, start, stop, restart, list, status") (os/exit 1) ))))