diff --git a/src/stream/commander/impl.clj b/src/stream/commander/impl.clj index 92766a7..6630ee3 100644 --- a/src/stream/commander/impl.clj +++ b/src/stream/commander/impl.clj @@ -4,7 +4,18 @@ [taoensso.timbre :as timbre :refer [log trace debug info warn error fatal report logf tracef debugf infof warnf errorf fatalf reportf - spy get-env]])) + spy get-env]]) + (:import [de.thjom.java.systemd Systemd Manager Systemd$InstanceType UnitStateListener])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; Systemd Instances ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; These are instanciated toplevel and are allowed to crash the +;; program if not successfull. + +(def ^:private systemd (. Systemd get Systemd$InstanceType/USER)) +(def ^:private manager (. systemd getManager)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Unit file Management ; @@ -20,14 +31,89 @@ :command command :target target})) +(defn get-unit-path [name] + (str (System/getProperty "user.home") "/.config/systemd/user/" name ".service")) + (defn create-unit-file! "Creates or overwrites a service unit file in the appropriate directory and returns the file path." + ([name command description] + (create-unit-file! name command description "default.target")) + ([name command description target] + (let [unit-contents (render-unit-file command description target) + path (get-unit-path name) + file (io/as-file path)] + (.mkdirs (.getParentFile file)) + (spit path unit-contents) + (debug "Wrote a unit file to:" path) + path))) + + +(defn remove-unit-file! + "Removes the unit file with the given name. Returns true if file was + deleted." + [name] + (let [path (get-unit-path name)] + (debug "Deleting a unit file:" path) + (.delete (io/as-file path)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ; Systemd Handling ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; For now these work with the service name. May be generalized to a +;; record some time. + +(defn reload-systemd! + "Reloads the systemd user instance." + [] + (. manager reload)) + +(defn- get-service! [name] + (. manager getService name)) + +(defn start-service! + "Starts the userspace service with the name." + [name] + (. (get-service! name) start "replace")) + +(defn restart-service! + "Restarts the userspace service with the name." + [name] + (. (get-service! name) restart "replace")) + +(defn stop-service! + "Stops the userspace service with the name." + [name] + (. (get-service! name) stop "replace")) + +(defn get-service-status! + "Gets the ActiveState for the process of the name. + + Refer to + [the systemd docs](https://www.freedesktop.org/wiki/Software/systemd/dbus/) + for further information." + [name] + (keyword (. (get-service! name) getActiveState))) + +(defn get-service-load-state! + "Gets the LoadState for the process of the name. + + Refer to + [the systemd docs](https://www.freedesktop.org/wiki/Software/systemd/dbus/) + for further information." + [name] + (keyword (. (get-service! name) getLoadState))) + +(defn create-service! + "Creates a unit file and reloads systemd. See `create-unit-file`." [name command description target] - (let [unit-contents (render-unit-file command description target) - path (str (System/getProperty "user.home") "/.config/systemd/user/" name ".service") - file (io/as-file path)] - (.mkdirs (.getParentFile file)) - (spit path unit-contents) - (debug "Wrote a unit file to:" path) - path)) + (create-unit-file! name command description target) + (reload-systemd!)) + +(defn remove-service! + "Stops the service, removes the unit file and reloads systemd." + [name] + (stop-service! name) + (remove-unit-file! name) + (reload-systemd!)) diff --git a/test/stream/commander_test.clj b/test/stream/commander_test.clj index 6089093..ede2841 100644 --- a/test/stream/commander_test.clj +++ b/test/stream/commander_test.clj @@ -7,10 +7,35 @@ (deftest unit-files (testing "The rendering." (is (#'impl/render-unit-file "test" "test" "test"))) + (let [name (str (java.util.UUID/randomUUID))] (testing "Writing a unit file." (let [file (io/as-file (impl/create-unit-file! name "test" "test" "test"))] - (is (.exists file)) - (.delete file))))) + (is (.exists file)))) + + (testing "Deleting a unit file" + (impl/remove-unit-file! name) + (is (not (.exists (io/as-file (impl/get-unit-path name)))))))) + +(deftest systemd-services + (let [name (str (java.util.UUID/randomUUID)) + unit-path (impl/create-unit-file! name + "cat /dev/zero" "test service")] + (testing "loading the service" + (is :not-found (impl/get-service-load-state! name)) + (impl/reload-systemd!) + (is :loaded (impl/get-service-load-state! name))) + + (testing "starting the service" + (impl/start-service! name) + (is :active (impl/get-service-load-state! name))) + + (testing "stopping the service" + (impl/stop-service! name) + (is :inactive (impl/get-service-load-state! name))) + + (testing "removing the service" + (impl/remove-service! name) + (is :not-found (impl/get-service-load-state! name)))))