diff --git a/examples/router-with-l2tp.nix b/examples/router-with-l2tp.nix index 263b380..08d1ce3 100644 --- a/examples/router-with-l2tp.nix +++ b/examples/router-with-l2tp.nix @@ -177,6 +177,17 @@ in rec { }; }; + services.restart-on-change = longrun { + name = "wlan0-restart-on-change"; + run = '' + ${pkgs.watch-outputs}/bin/watch-outputs -r wlan0.link.hostapd ${config.services.secrets} wpa_passphrase + ''; + dependencies = [ + config.services.hostap-liminix + config.services.hostap-liminix5 + ]; + }; + services.bootstrap-dhcpc = svc.network.dhcp.client.build { interface = config.services.wwan; dependencies = [ config.services.hostname ]; diff --git a/pkgs/default.nix b/pkgs/default.nix index dd0eb41..21af04c 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -114,6 +114,7 @@ in { tufted = callPackage ./tufted { }; uevent-watch = callPackage ./uevent-watch { }; usb-modeswitch = callPackage ./usb-modeswitch { }; + watch-outputs = callPackage ./watch-outputs { }; writeAshScript = callPackage ./write-ash-script { }; writeAshScriptBin = callPackage ./write-ash-script/bin.nix { }; writeFennel = callPackage ./write-fennel { }; diff --git a/pkgs/watch-outputs/Makefile b/pkgs/watch-outputs/Makefile new file mode 100644 index 0000000..b0d6b6c --- /dev/null +++ b/pkgs/watch-outputs/Makefile @@ -0,0 +1,3 @@ +check: + ./output-template '{{' '}}' < example.ini > output + diff -u output example.ini.expected diff --git a/pkgs/watch-outputs/default.nix b/pkgs/watch-outputs/default.nix new file mode 100644 index 0000000..cd51903 --- /dev/null +++ b/pkgs/watch-outputs/default.nix @@ -0,0 +1,35 @@ +{ + fetchurl, + writeFennel, + fennel, + runCommand, + lua, + anoia, + linotify, + lualinux, + stdenv +}: +let name = "watch-outputs"; +in stdenv.mkDerivation { + inherit name; + src = ./.; + + buildInputs = [lua]; +# doCheck = true; + + buildPhase = '' + cp -p ${writeFennel name { + packages = [ + anoia + lualinux + linotify + fennel + ] ; + mainFunction = "run"; + } ./watch-outputs.fnl } ${name} + ''; +# checkPhase = "make check"; + installPhase = '' + install -D ${name} $out/bin/${name} + ''; +} diff --git a/pkgs/watch-outputs/example-service/.outputs/addresses/1/attribute b/pkgs/watch-outputs/example-service/.outputs/addresses/1/attribute new file mode 100644 index 0000000..b54f8c3 --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/addresses/1/attribute @@ -0,0 +1 @@ +a11 diff --git a/pkgs/watch-outputs/example-service/.outputs/addresses/3/attribute b/pkgs/watch-outputs/example-service/.outputs/addresses/3/attribute new file mode 100644 index 0000000..711c1a1 --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/addresses/3/attribute @@ -0,0 +1 @@ +a33 diff --git a/pkgs/watch-outputs/example-service/.outputs/addresses/5/attribute b/pkgs/watch-outputs/example-service/.outputs/addresses/5/attribute new file mode 100644 index 0000000..8092345 --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/addresses/5/attribute @@ -0,0 +1 @@ +a55 diff --git a/pkgs/watch-outputs/example-service/.outputs/addresses/6/attribute b/pkgs/watch-outputs/example-service/.outputs/addresses/6/attribute new file mode 100644 index 0000000..9139598 --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/addresses/6/attribute @@ -0,0 +1 @@ +a66 diff --git a/pkgs/watch-outputs/example-service/.outputs/colours/black b/pkgs/watch-outputs/example-service/.outputs/colours/black new file mode 100644 index 0000000..c4949eb --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/colours/black @@ -0,0 +1 @@ +000000 diff --git a/pkgs/watch-outputs/example-service/.outputs/colours/blue b/pkgs/watch-outputs/example-service/.outputs/colours/blue new file mode 100644 index 0000000..df785ee --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/colours/blue @@ -0,0 +1 @@ +0000ff diff --git a/pkgs/watch-outputs/example-service/.outputs/colours/green b/pkgs/watch-outputs/example-service/.outputs/colours/green new file mode 100644 index 0000000..9f07fb6 --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/colours/green @@ -0,0 +1 @@ +00ff00 diff --git a/pkgs/watch-outputs/example-service/.outputs/colours/red b/pkgs/watch-outputs/example-service/.outputs/colours/red new file mode 100644 index 0000000..db6f1d8 --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/colours/red @@ -0,0 +1 @@ +ff0000 diff --git a/pkgs/watch-outputs/example-service/.outputs/name b/pkgs/watch-outputs/example-service/.outputs/name new file mode 100644 index 0000000..dbf1b39 --- /dev/null +++ b/pkgs/watch-outputs/example-service/.outputs/name @@ -0,0 +1 @@ +eth1 diff --git a/pkgs/watch-outputs/example.ini b/pkgs/watch-outputs/example.ini new file mode 100644 index 0000000..c968fce --- /dev/null +++ b/pkgs/watch-outputs/example.ini @@ -0,0 +1,3 @@ +wpa_passphrase={{ output("./example-service","colours/black") }} +think = {{ string.format("%q", output("./example-service","colours/blue")) }} +argonaut = {{ json_quote "hello\ngoodbye\tnext\027" }} diff --git a/pkgs/watch-outputs/example.ini.expected b/pkgs/watch-outputs/example.ini.expected new file mode 100644 index 0000000..ba1960c --- /dev/null +++ b/pkgs/watch-outputs/example.ini.expected @@ -0,0 +1,3 @@ +wpa_passphrase=000000 +think = "0000ff" +argonaut = "hello\ngoodbye\tnext\u001B" diff --git a/pkgs/watch-outputs/output-template.fnl b/pkgs/watch-outputs/output-template.fnl new file mode 100644 index 0000000..95be3c6 --- /dev/null +++ b/pkgs/watch-outputs/output-template.fnl @@ -0,0 +1,44 @@ +(local svc (require :anoia.svc)) + +(fn json-escape [s] + ;; All Unicode characters may be placed within the quotation marks, + ;; except for the characters that MUST be escaped: + ;; quotation mark, reverse solidus, and the control characters (U+0000 + ;; through U+001F). (RFC 8259) + (-> s + (string.gsub + "[\"\b\f\n\r\t]" { + "\b" "\\b" + "\"" "\\\"" + "\f" "\\f" + "\n" "\\n" + "\r" "\\r" + "\t" "\\t" + }) + (string.gsub + "([\x00-\x1b])" + (fn [x] (string.format "\\u%04X" (string.byte x)))))) + + +(fn substitute [text opening closing] + (let [delim (.. opening "(.-)" closing) + myenv { + : string + :output + (fn [service-path path] + (let [s (assert (svc.open (.. service-path "/.outputs")))] + (s:output path))) + :lua_quote #(string.format "%q" %1) + :json_quote (fn [x] (.. "\"" (json-escape x) "\"")) + }] + (string.gsub text delim + (fn [x] + (assert ((load (.. "return " x) x :t myenv)) + (string.format "missing value for %q" x)))))) + +(fn run [] + (let [[opening closing] arg + out (substitute (: (io.input) :read "*a") opening closing)] + (io.write out))) + +{ : run } diff --git a/pkgs/watch-outputs/watch-outputs.fnl b/pkgs/watch-outputs/watch-outputs.fnl new file mode 100644 index 0000000..deb270f --- /dev/null +++ b/pkgs/watch-outputs/watch-outputs.fnl @@ -0,0 +1,63 @@ +(local { : system : assoc : split : table= } (require :anoia)) +(local svc (require :anoia.svc)) +(local { : view } (require :fennel)) +(local { : kill } (require :lualinux)) + +(fn split-paths [paths] + (icollect [_ path (ipairs paths)] + (split "/" path))) + +(fn parse-args [args] + (match args + ["-r" service & rest] (assoc (parse-args rest) + :controlled-service service + :action :restart) + ["-R" service & rest] (assoc (parse-args rest) + :controlled-service service + :action :restart-all) + ["-s" signal service & rest] (assoc (parse-args rest) + :controlled-service service + :action [:signal signal]) + [watched-service & paths] { : watched-service + :paths (split-paths paths) + })) + +(fn dig [tree path] + (match path + [el & more] (dig (. tree el) more) + [el] (. tree el) + [] tree)) + +(fn changed? [paths old-tree new-tree] + (accumulate [changed? false + _ path (ipairs paths)] + (or changed? (not (table= (dig old-tree path) (dig new-tree path)))))) + + +(fn do-action [action service] + (case action + :restart (system "s6-svc -r /run/service/%s" service) + :restart-all (system "s6-rc -b -d %q; s6-rc-up-tree %q" service service) + [:signal n] (system "s6-svc -s %d /run/service/%s" n service))) + +(fn run [] + (let [{ + : controlled-service + : action + : watched-service + : paths } (parse-args arg) + dir (.. watched-service "/.outputs") + _ (print :service-dir dir) + service (assert (svc.open dir))] + (print "watching " watched-service) + (accumulate [tree (service:output ".") + v (service:events)] + (let [new-tree (service:output ".")] + (print :was (view tree) :now (view new-tree)) + (when (changed? paths tree new-tree) + (print "watched path event:" action controlled-service) + (do-action action controlled-service)) + new-tree)))) + + +{ : run }