Figuring out how to integrate with nixos cfg.
This commit is contained in:
parent
267efb83c8
commit
e61f18116c
@ -32,5 +32,11 @@
|
|||||||
|
|
||||||
devShell =
|
devShell =
|
||||||
pkgs.mkShell { buildInputs = with pkgs; [ clojure update-deps ]; };
|
pkgs.mkShell { buildInputs = with pkgs; [ clojure update-deps ]; };
|
||||||
});
|
}) // {
|
||||||
|
overlay = final: prev: {
|
||||||
|
inherit (self.packages."${prev.system}") wallfly;
|
||||||
|
};
|
||||||
|
|
||||||
|
nixosModule = import ./module.nix;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
80
module.nix
Normal file
80
module.nix
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
let cfg = config.fudo.wallfly;
|
||||||
|
|
||||||
|
in {
|
||||||
|
options.fudo.wallfly = with types; {
|
||||||
|
enable =
|
||||||
|
mkEnableOption "Enable WallFly presence monitor for users on this host.";
|
||||||
|
|
||||||
|
location = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "Location (in Home Assistant) of this host.";
|
||||||
|
default = "unknown";
|
||||||
|
};
|
||||||
|
|
||||||
|
mqtt = {
|
||||||
|
broker-uri = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "URI of the MQTT broker.";
|
||||||
|
example = "tcp://my-mqtt.host:1883";
|
||||||
|
};
|
||||||
|
|
||||||
|
username = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "Username with which to connect to the MQTT broker.";
|
||||||
|
default = "wallfly";
|
||||||
|
};
|
||||||
|
|
||||||
|
password-file = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "Path to a file containing the MQTT user password.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
time-to-idle = mkOption {
|
||||||
|
type = int;
|
||||||
|
description =
|
||||||
|
"Number of seconds before considering the user idle on this host.";
|
||||||
|
default = "900"; # 15 minutes
|
||||||
|
};
|
||||||
|
|
||||||
|
delay-time = mkOption {
|
||||||
|
type = int;
|
||||||
|
description =
|
||||||
|
"Number of seconds to wait before polling for user activity.";
|
||||||
|
default = 30;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.user.services.wallfly = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = pkgs.writeShellScript "launch-wallfly.sh" ''
|
||||||
|
${pkgs.wallfly}/bin/wallfly \
|
||||||
|
--location=${cfg.location} \
|
||||||
|
--mqtt-broker-uri=${cfg.mqtt.broker-uri} \
|
||||||
|
--mqtt-username=${cfg.mqtt.username} \
|
||||||
|
--mqtt-password-file=${cfg.mqtt.password-file} \
|
||||||
|
--time-to-idle=${cfg.time-to-idle} \
|
||||||
|
--delay-time=${cfg.delay-time}
|
||||||
|
'';
|
||||||
|
PrivateTmp = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
Restart = "always";
|
||||||
|
StandardOutput = "journal";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
(ns wallfly.core
|
(ns wallfly.core
|
||||||
(:require [clojure.java.shell :as shell]
|
(:require [clojure.java.shell :as shell]
|
||||||
[clojure.core.async :refer [chan >!! <!! >! <! go-loop mult tap timeout alt!]]
|
[clojure.core.async :refer [chan >!! <!! go-loop timeout alt!]]
|
||||||
[clojure.string :as str :refer [trim-newline]]
|
[clojure.string :as str :refer [trim-newline]]
|
||||||
[clojure.data.json :as json]
|
[clojure.data.json :as json]
|
||||||
[clojure.tools.cli :refer [parse-opts]]
|
[clojure.tools.cli :refer [parse-opts]]
|
||||||
@ -22,11 +22,6 @@
|
|||||||
(doto (MqttClient. broker-uri client-id (MemoryPersistence.))
|
(doto (MqttClient. broker-uri client-id (MemoryPersistence.))
|
||||||
(.connect opts))))
|
(.connect opts))))
|
||||||
|
|
||||||
(defn- make-tap [c]
|
|
||||||
(let [out (chan)]
|
|
||||||
(tap c out)
|
|
||||||
out))
|
|
||||||
|
|
||||||
(defn- shell-exec [& args]
|
(defn- shell-exec [& args]
|
||||||
(let [{:keys [exit out err]} (apply shell/sh args)]
|
(let [{:keys [exit out err]} (apply shell/sh args)]
|
||||||
(if (= exit 0)
|
(if (= exit 0)
|
||||||
@ -51,30 +46,31 @@
|
|||||||
(defn- send-message [client topic msg & {:keys [retained] :or {retained false}}]
|
(defn- send-message [client topic msg & {:keys [retained] :or {retained false}}]
|
||||||
(.publish client topic (create-message msg retained)))
|
(.publish client topic (create-message msg retained)))
|
||||||
|
|
||||||
;; Given a channel, listen for idle timestamps and send messages accordingly
|
|
||||||
|
|
||||||
(defn- create-reporter [client time-to-idle location user host host-device]
|
(defn- create-reporter [client time-to-idle location user host host-device]
|
||||||
(let [base-topic (format "homeassistant/binary_sensor/wallfly_%s_%s"
|
(let [base-topic (format "homeassistant/binary_sensor/wallfly_%s_%s"
|
||||||
user host)
|
user host)
|
||||||
presence-topic (format "%s/state" base-topic)
|
presence-topic (format "%s/state" base-topic)]
|
||||||
emit-presence (fn [present] (send-message client presence-topic (if present "ON" "OFF")))]
|
|
||||||
(let [cfg-topic (format "%s/config" base-topic)
|
(let [cfg-topic (format "%s/config" base-topic)
|
||||||
payload {:name (format "%s present" user)
|
payload {:name (format "%s present on %s" user host)
|
||||||
:device_class :occupancy
|
:device_class :occupancy
|
||||||
:entity_category :diagnostic
|
:entity_category :diagnostic
|
||||||
:unique_id (format "wallfly_%s_%s" user host)
|
:unique_id (format "wallfly_%s_%s" user host)
|
||||||
:state_topic presence-topic
|
:state_topic presence-topic
|
||||||
:icon "mdi:account-check"
|
:icon "mdi:account-check"
|
||||||
|
:off_delay time-to-idle
|
||||||
:device {:identifiers [host-device]
|
:device {:identifiers [host-device]
|
||||||
:name (format "%s WallFly" host)
|
:name (format "%s WallFly" host)
|
||||||
:model "WallFly"
|
:model "WallFly"
|
||||||
:manufacturer "Fudo"
|
:manufacturer "Fudo"
|
||||||
:suggested_area location}}]
|
:suggested_area location}}]
|
||||||
(println (format "sending to %s: %s" cfg-topic (with-out-str (pprint payload))))
|
(println (format "sending to %s: %s" cfg-topic (with-out-str (pprint payload))))
|
||||||
(send-message client cfg-topic (json/write-str payload) :retained true))
|
(send-message client cfg-topic (json/write-str payload) :retained true)
|
||||||
|
;; Send one message that will persist if the host dies
|
||||||
|
(send-message client presence-topic "OFF" :retained true))
|
||||||
(fn [idle-time]
|
(fn [idle-time]
|
||||||
;; (emit-idle idle-time)
|
;; (emit-idle idle-time)
|
||||||
(emit-presence (< idle-time time-to-idle)))))
|
(when (< idle-time time-to-idle)
|
||||||
|
(send-message client presence-topic "ON")))))
|
||||||
|
|
||||||
(defn- execute! [delay-seconds report]
|
(defn- execute! [delay-seconds report]
|
||||||
(let [stop-chan (chan)
|
(let [stop-chan (chan)
|
||||||
@ -92,9 +88,8 @@
|
|||||||
[["-l" "--location LOCATION" "Location (in house) of the host running this job."]
|
[["-l" "--location LOCATION" "Location (in house) of the host running this job."]
|
||||||
["-b" "--mqtt-broker-uri URI" "URI of the MQTT broker."]
|
["-b" "--mqtt-broker-uri URI" "URI of the MQTT broker."]
|
||||||
["-u" "--mqtt-username USERNAME" "Username to use when connecting to MQTT."]
|
["-u" "--mqtt-username USERNAME" "Username to use when connecting to MQTT."]
|
||||||
["-c" "--client-id CLIENT-ID" "Client ID of this job."]
|
|
||||||
["-p" "--password-file PASSWORD-FILE" "Path to a file containing the password for this client."]
|
["-p" "--password-file PASSWORD-FILE" "Path to a file containing the password for this client."]
|
||||||
["-t" "--idle-time SECONDS" "Number of seconds before considering this host idle."]
|
["-t" "--time-to-idle SECONDS" "Number of seconds before considering this host idle."]
|
||||||
["-d" "--delay-time SECONDS" "Time to wait before polling for idle time."]])
|
["-d" "--delay-time SECONDS" "Time to wait before polling for idle time."]])
|
||||||
|
|
||||||
(defn- exit! [status msg]
|
(defn- exit! [status msg]
|
||||||
@ -114,37 +109,35 @@
|
|||||||
(let [resolved (into {} (map (partial get-key options) keys))
|
(let [resolved (into {} (map (partial get-key options) keys))
|
||||||
missing (filter (fn [k] (not (get resolved k false))) keys)]
|
missing (filter (fn [k] (not (get resolved k false))) keys)]
|
||||||
(if (seq missing)
|
(if (seq missing)
|
||||||
(exit! 2 ((str "missing arguments: " (str/join " " (map name missing)))))
|
(exit! 2 (str "missing arguments: " (str/join " " (map name missing))))
|
||||||
resolved))))
|
resolved))))
|
||||||
|
|
||||||
(let [chars (map char (apply concat (map (fn [[s e]] (range (int s) (int e))) [[\0 \9] [\a \z] [\A \Z]])))]
|
(let [chars (map char (apply concat (map (fn [[s e]] (range (int s) (int e))) [[\0 \9] [\a \z] [\A \Z]])))]
|
||||||
(defn- rand-str [n]
|
(defn- rand-str [n]
|
||||||
(apply str (take n (repeatedly #(nth chars (rand (count chars))))))))
|
(apply str (take n (repeatedly #(nth chars (rand (count chars))))))))
|
||||||
|
|
||||||
(defn -main [args]
|
(defn -main [& args]
|
||||||
(let [required-keys [:location
|
(let [required-keys [:location
|
||||||
:mqtt-broker-uri
|
:mqtt-broker-uri
|
||||||
:client-id
|
:mqtt-password-file
|
||||||
:password-file
|
|
||||||
:time-to-idle
|
:time-to-idle
|
||||||
:hostname
|
|
||||||
:delay-time
|
:delay-time
|
||||||
:mqtt-username]
|
:mqtt-username]
|
||||||
{:keys [location
|
{:keys [location
|
||||||
mqtt-broker-uri
|
mqtt-broker-uri
|
||||||
password-file
|
mqtt-password-file
|
||||||
time-to-idle
|
time-to-idle
|
||||||
delay-time
|
delay-time
|
||||||
mqtt-username]} (get-args required-keys args)
|
mqtt-username]} (get-args required-keys args)
|
||||||
catch-shutdown (chan)
|
catch-shutdown (chan)
|
||||||
password (-> password-file (slurp) (str/trim-newline))
|
password (-> mqtt-password-file (slurp) (str/trim-newline))
|
||||||
username (get-username)
|
username (get-username)
|
||||||
hostname (get-hostname)
|
hostname (get-hostname)
|
||||||
host-device (format "wallfly-%s" (get-fqdn))
|
host-device (format "wallfly-%s" (get-fqdn))
|
||||||
client-id (format "wallfly-%s" (rand-str 10))
|
client-id (format "wallfly-%s" (rand-str 10))
|
||||||
client (create-mqtt-client mqtt-broker-uri client-id mqtt-username password)
|
client (create-mqtt-client mqtt-broker-uri client-id mqtt-username password)
|
||||||
reporter (create-reporter client time-to-idle location username hostname host-device)
|
reporter (create-reporter client (Integer/parseInt time-to-idle) location username hostname host-device)
|
||||||
stop-chan (execute! delay-time reporter)]
|
stop-chan (execute! (Integer/parseInt delay-time) reporter)]
|
||||||
(.addShutdownHook (Runtime/getRuntime)
|
(.addShutdownHook (Runtime/getRuntime)
|
||||||
(Thread. (fn [] (>!! catch-shutdown true))))
|
(Thread. (fn [] (>!! catch-shutdown true))))
|
||||||
(<!! catch-shutdown)
|
(<!! catch-shutdown)
|
||||||
|
Loading…
Reference in New Issue
Block a user