feat: riir
This commit is contained in:
parent
d9038b92cc
commit
716a656fa3
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
justfile
|
||||
.direnv/
|
||||
_build/
|
||||
result
|
0
bin/.ocamlformat
Normal file
0
bin/.ocamlformat
Normal file
4
bin/dune
Normal file
4
bin/dune
Normal file
@ -0,0 +1,4 @@
|
||||
(executable
|
||||
(public_name smooooth)
|
||||
(name main)
|
||||
(libraries smooooth))
|
14
bin/main.ml
Normal file
14
bin/main.ml
Normal file
@ -0,0 +1,14 @@
|
||||
let () =
|
||||
Daemon.notify Daemon.State.Ready |> ignore;
|
||||
let conf =
|
||||
(try Yojson.Safe.from_file "/etc/smooooth.json" |> Smooooth.config_of_yojson
|
||||
with Sys_error e ->
|
||||
Printf.eprintf "Failed to open config - %s\n" e;
|
||||
exit 1)
|
||||
|> function
|
||||
| Ok c -> c
|
||||
| Error e ->
|
||||
Printf.eprintf "Fatal error parsing config - %s\n" e;
|
||||
exit 1
|
||||
in
|
||||
Lwt_main.run @@ Smooooth.main conf.path conf.blockers
|
14
default.nix
Normal file
14
default.nix
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
pkgs,
|
||||
git,
|
||||
ocamlPackages,
|
||||
buildInputs,
|
||||
}:
|
||||
ocamlPackages.buildDunePackage {
|
||||
pname = "smooooth";
|
||||
version = "0.01a";
|
||||
src = ./.;
|
||||
meta.mainProgram = "smooooth";
|
||||
nativeBuildInputs = [ git ];
|
||||
inherit buildInputs;
|
||||
}
|
26
dune-project
Normal file
26
dune-project
Normal file
@ -0,0 +1,26 @@
|
||||
(lang dune 3.18)
|
||||
|
||||
(name smooooth)
|
||||
|
||||
(generate_opam_files true)
|
||||
|
||||
(source
|
||||
(github username/reponame))
|
||||
|
||||
(authors "Author Name <author@example.com>")
|
||||
|
||||
(maintainers "Maintainer Name <maintainer@example.com>")
|
||||
|
||||
(license LICENSE)
|
||||
|
||||
(documentation https://url/to/documentation)
|
||||
|
||||
(package
|
||||
(name smooooth)
|
||||
(synopsis "A short synopsis")
|
||||
(description "A longer description")
|
||||
(depends ocaml)
|
||||
(tags
|
||||
("add topics" "to describe" your project)))
|
||||
|
||||
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html
|
6
flake.lock
generated
6
flake.lock
generated
@ -17,11 +17,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1747728033,
|
||||
"narHash": "sha256-NnXFQu7g4LnvPIPfJmBuZF7LFy/fey2g2+LCzjQhTUk=",
|
||||
"lastModified": 1747885982,
|
||||
"narHash": "sha256-rSuxACdwx5Ndr2thpjqcG89fj8mSSp96CFoCt0yrdkY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2f9173bde1d3fbf1ad26ff6d52f952f9e9da52ea",
|
||||
"rev": "a16efe5d2fc7455d7328a01f4692bfec152965b3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
61
flake.nix
61
flake.nix
@ -8,29 +8,52 @@
|
||||
inputs:
|
||||
with inputs;
|
||||
let
|
||||
get = builtins.attrValues;
|
||||
forAllSystems =
|
||||
function:
|
||||
nixpkgs.lib.genAttrs (import nix-systems) (system: function nixpkgs.legacyPackages.${system});
|
||||
deps = forAllSystems (pkgs: {
|
||||
dev = get {
|
||||
inherit (pkgs) just;
|
||||
inherit (pkgs.ocamlPackages)
|
||||
utop
|
||||
dune_3
|
||||
ocaml-lsp
|
||||
ocamlformat
|
||||
ocamlformat-rpc-lib
|
||||
;
|
||||
};
|
||||
build =
|
||||
get {
|
||||
inherit (pkgs.ocamlPackages)
|
||||
ocaml
|
||||
lwt
|
||||
lwt_ppx
|
||||
inotify
|
||||
angstrom
|
||||
systemd
|
||||
yojson
|
||||
ppx_deriving_yojson
|
||||
;
|
||||
}
|
||||
++ [ self.packages.${pkgs.system}.obus ];
|
||||
});
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (pkgs: {
|
||||
default =
|
||||
nix:
|
||||
pkgs.writeShellApplication {
|
||||
name = "smooooth";
|
||||
runtimeInputs = builtins.attrValues {
|
||||
inherit (pkgs)
|
||||
gnugrep
|
||||
coreutils
|
||||
libnotify
|
||||
inotify-tools
|
||||
git
|
||||
;
|
||||
inherit nix;
|
||||
};
|
||||
text = builtins.readFile ./smooooth.sh;
|
||||
};
|
||||
devShells = forAllSystems (pkgs: {
|
||||
default = import ./shell.nix {
|
||||
inherit pkgs;
|
||||
deps = deps.${pkgs.system};
|
||||
};
|
||||
});
|
||||
|
||||
packages = forAllSystems (pkgs: {
|
||||
default = pkgs.callPackage ./default.nix {
|
||||
buildInputs = deps.${pkgs.system}.build;
|
||||
};
|
||||
obus = pkgs.callPackage ./nix/obus.nix { };
|
||||
});
|
||||
|
||||
nixosModules.smooooth =
|
||||
{
|
||||
config,
|
||||
@ -40,9 +63,7 @@
|
||||
}:
|
||||
{
|
||||
imports = [ ./module.nix ];
|
||||
services.smooooth.package = (
|
||||
self.packages.${pkgs.system}.default config.services.smooooth.nixPackage
|
||||
);
|
||||
services.smooooth.package = self.packages.${pkgs.system}.default;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
0
lib/.ocamlformat
Normal file
0
lib/.ocamlformat
Normal file
36
lib/build.ml
Normal file
36
lib/build.ml
Normal file
@ -0,0 +1,36 @@
|
||||
open Lwt.Syntax
|
||||
open Lwt.Infix
|
||||
|
||||
let run_and_collect cmd =
|
||||
let* list = Lwt_process.pread_lines cmd |> Lwt_stream.to_list in
|
||||
list |> List.fold_left (fun acc el -> acc ^ " " ^ el) "" |> Lwt.return
|
||||
|
||||
let rebuild_conf path =
|
||||
let* dir = Lwt_io.create_temp_dir ~suffix:"smooooth" () in
|
||||
let* hostname = Lwt_unix.gethostname () in
|
||||
let* nix_build =
|
||||
Lwt_process.exec
|
||||
( "nix",
|
||||
[|
|
||||
"nix";
|
||||
"build";
|
||||
"--out-link";
|
||||
dir ^ "/system";
|
||||
path ^ "#nixosConfigurations." ^ hostname
|
||||
^ ".config.system.build.toplevel";
|
||||
|] )
|
||||
in
|
||||
let* res = match nix_build with
|
||||
| Unix.WEXITED e when e = 0 ->
|
||||
Lwt_process.exec
|
||||
( "pkexec",
|
||||
[| "pkexec"; dir ^ "/system/bin/switch-to-configuration"; "switch" |] ) >>= fun _ -> Lwt.return_true
|
||||
| _ -> Lwt.return_false
|
||||
(* let* () = Lwt_io.printlf "nix build output:\n%s" nix_build in *)
|
||||
in
|
||||
(* let* () = Lwt_io.printlf "activation output:\n%s" activation in *)
|
||||
let* () = Lwt_io.delete_recursively dir in
|
||||
Lwt.return res
|
||||
(* buffer four to five lines and keep them up in the notification ? *)
|
||||
(* nom-like current-build output ? *)
|
||||
(* catch any build error and try to display it usefully *)
|
4
lib/config.ml
Normal file
4
lib/config.ml
Normal file
@ -0,0 +1,4 @@
|
||||
type blocker_cond = Stop | Die [@@deriving yojson]
|
||||
type blocker = { name : string; cond : blocker_cond } [@@deriving yojson]
|
||||
type blocker_list = blocker list [@@deriving yojson]
|
||||
type config = { path : string; blockers : blocker_list } [@@deriving yojson]
|
5
lib/dune
Normal file
5
lib/dune
Normal file
@ -0,0 +1,5 @@
|
||||
(library
|
||||
(name smooooth)
|
||||
(libraries lwt obus obus.notification inotify.lwt angstrom systemd yojson)
|
||||
(preprocess
|
||||
(pps lwt_ppx ppx_deriving_yojson)))
|
29
lib/parse.ml
Normal file
29
lib/parse.ml
Normal file
@ -0,0 +1,29 @@
|
||||
open Angstrom
|
||||
|
||||
let is_integer = function '0' .. '9' -> true | _ -> false
|
||||
let integer = skip_while is_integer
|
||||
let is_paren = function '(' .. ')' -> true | _ -> false
|
||||
let paren = satisfy is_paren
|
||||
let is_whitespace = function ' ' | '\t' -> true | _ -> false
|
||||
let whitespace = skip_while is_whitespace
|
||||
let is_newline = function '\n' | '\r' -> true | _ -> false
|
||||
let newline = satisfy is_newline
|
||||
let name = skip_while (function '-' .. 'z' | ' ' -> true | _ -> false)
|
||||
|
||||
let is_status = function
|
||||
| 'R' | 'S' | 'D' | 'Z' | 'T' | 't' | 'X' | 'I' -> true
|
||||
| _ -> false
|
||||
|
||||
let status = satisfy is_status
|
||||
let procname = paren *> name <* paren
|
||||
|
||||
(* 8520 (smooooth) R *)
|
||||
let state =
|
||||
integer *> whitespace *> procname *> whitespace *> status <* skip_many any_char
|
||||
|
||||
let get_state str =
|
||||
Angstrom.parse_string ~consume:Prefix state str |> function
|
||||
| Ok s -> Lwt.return s
|
||||
| Error e ->
|
||||
Printf.printf "Parser error on: %s\n..while parsing: %s" e str;
|
||||
Lwt.return '?'
|
94
lib/proc.ml
Normal file
94
lib/proc.ml
Normal file
@ -0,0 +1,94 @@
|
||||
open Lwt.Syntax
|
||||
open Lwt.Infix
|
||||
open Config
|
||||
|
||||
let read_file path =
|
||||
let open Lwt_io in
|
||||
with_file ~mode:Input path (fun f -> f |> read_chars |> Lwt_stream.to_string)
|
||||
|
||||
let scrape_pid uid pid =
|
||||
let proc = "/proc/" ^ pid in
|
||||
let* ours = Lwt_unix.stat proc >|= fun x -> x.st_uid = uid in
|
||||
if ours then
|
||||
let* command = proc ^ "/comm" |> read_file >|= String.trim in
|
||||
let* state =
|
||||
let* s = proc ^ "/stat" |> read_file in
|
||||
s |> Parse.get_state
|
||||
in
|
||||
let* cwd = proc ^ "/cwd" |> Lwt_unix.readlink in
|
||||
Lwt.return_some (String.trim command, state, cwd)
|
||||
else Lwt.return_none
|
||||
|
||||
let scrape_all uid =
|
||||
let* pids =
|
||||
Lwt_unix.files_of_directory "/proc"
|
||||
|> Lwt_stream.filter (fun name ->
|
||||
String.fold_left
|
||||
(fun result char ->
|
||||
if not result then false
|
||||
else match char with '0' .. '9' -> true | _ -> false)
|
||||
true name)
|
||||
|> Lwt_stream.to_list
|
||||
in
|
||||
(* scrape procfs for info *)
|
||||
pids
|
||||
|> Lwt_list.map_p (fun x ->
|
||||
Lwt.catch
|
||||
(fun () -> scrape_pid uid x)
|
||||
(function
|
||||
| Unix.Unix_error (_, _, _) ->
|
||||
(* sick of these errors and i don't fucking care !!! *)
|
||||
(* let* () = *)
|
||||
(* Lwt_io.eprintlf "Error scraping pid %s: %s" x *)
|
||||
(* (Unix.error_message e) *)
|
||||
(* in *)
|
||||
Lwt.return_none
|
||||
| Lwt_io.Channel_closed e ->
|
||||
let* () = Lwt_io.eprintlf "channel closed: %s" e in
|
||||
Lwt.return_none
|
||||
| _ ->
|
||||
let* () = Lwt_io.eprintlf "Unknown error during pid scrape" in
|
||||
Lwt.return_none))
|
||||
|
||||
|
||||
|
||||
let get_blockers ~(blockers : blocker_list) ~config_path
|
||||
~procdata =
|
||||
let is_at_rest state cond =
|
||||
match state with 'T' when cond = Stop -> true | 'X' -> true | _ -> false
|
||||
in
|
||||
let* blockers =
|
||||
procdata
|
||||
|> Lwt_list.filter_p (function
|
||||
| Some (cmd, state, cwd) ->
|
||||
let blocker = List.find_opt (fun b -> b.name = cmd) blockers in
|
||||
let cond = (Option.value ~default:{name=""; cond=Stop} blocker).cond in
|
||||
let cmd_block = Option.is_some blocker in
|
||||
let state_block =
|
||||
not (is_at_rest state cond)
|
||||
and cwd_block =
|
||||
if String.length cwd >= String.length config_path then
|
||||
String.sub cwd 0 (String.length config_path) = config_path
|
||||
else false
|
||||
in
|
||||
let blocked = cmd_block && state_block && cwd_block in
|
||||
Lwt.return blocked
|
||||
(* can we improve error handling here ? *)
|
||||
| None -> Lwt.return_false)
|
||||
in
|
||||
List.length blockers > 0 |> Lwt.return
|
||||
|
||||
let rec scrape_loop ~blockers ~config_path ~uid =
|
||||
(* get all relevant process info *)
|
||||
let* procdata = scrape_all uid in
|
||||
(* determine who is blocking our update *)
|
||||
let* blocked = get_blockers ~blockers ~config_path ~procdata in
|
||||
(* have a snooze and come back *)
|
||||
if blocked then
|
||||
let* () = Lwt_unix.sleep 2.0 in
|
||||
scrape_loop ~blockers ~config_path ~uid
|
||||
else Lwt.return_unit
|
||||
|
||||
let await_blockers ~config_path ~blockers =
|
||||
let uid = Unix.getuid () in
|
||||
scrape_loop ~blockers ~config_path ~uid
|
53
lib/smooooth.ml
Normal file
53
lib/smooooth.ml
Normal file
@ -0,0 +1,53 @@
|
||||
include Config
|
||||
open Lwt.Syntax
|
||||
|
||||
let ( <| ) = ( @@ )
|
||||
|
||||
|
||||
let send_noti noti ~body ~icon =
|
||||
let* new_noti =
|
||||
match !noti with
|
||||
| Some n ->
|
||||
Notification.notify ~replace:n ~summary:"smooooth" ~body ~icon ()
|
||||
| None -> Notification.notify ~summary:"smooooth" ~body ~icon ()
|
||||
in
|
||||
noti := Some new_noti;
|
||||
Lwt.return_unit
|
||||
|
||||
let silence silent f = if silent then Lwt.return_unit else f ()
|
||||
|
||||
let main config_path blockers =
|
||||
let noti = ref None in
|
||||
let rec loop () =
|
||||
(* TODO different actions depending on event? *)
|
||||
let* _, _, _, path = Watch.watch_changes config_path in
|
||||
let main_loop () =
|
||||
let* () = send_noti noti ~body:"Awaiting blockers." ~icon:"info" in
|
||||
let* () = Lwt_io.printlf "awaiting blockers" in
|
||||
let* () = Proc.await_blockers ~config_path ~blockers in
|
||||
let* () = send_noti noti ~body:"Rebuilding NixOS config." ~icon:"info" in
|
||||
let* () = Lwt_io.printlf "rebuilding config" in
|
||||
let* res = Build.rebuild_conf config_path in
|
||||
let* () = match res with
|
||||
| true -> let* () =
|
||||
send_noti noti
|
||||
~body:"New config built and activated.\nAwaiting further changes."
|
||||
~icon:"info"
|
||||
in
|
||||
Lwt_io.printlf "complete"
|
||||
| false -> let* () =
|
||||
send_noti noti
|
||||
~body:"Error building and activating config.\nAwaiting further changes."
|
||||
~icon:"info"
|
||||
in
|
||||
Lwt_io.printlf "build error" in
|
||||
loop ()
|
||||
in
|
||||
match path with
|
||||
| Some p when not (p = "flake.lock") -> main_loop ()
|
||||
| None -> main_loop ()
|
||||
| Some _ -> loop ()
|
||||
in
|
||||
let* () = Lwt_io.printlf "awaiting changes" in
|
||||
let* () = send_noti noti ~body:"Awaiting config changes." ~icon:"info" in
|
||||
loop ()
|
15
lib/watch.ml
Normal file
15
lib/watch.ml
Normal file
@ -0,0 +1,15 @@
|
||||
open Lwt.Syntax
|
||||
|
||||
let watch_changes path =
|
||||
let* notify = Lwt_inotify.create () in
|
||||
let* _watch =
|
||||
Lwt_inotify.add_watch notify path
|
||||
[
|
||||
Inotify.S_Create;
|
||||
Inotify.S_Close_write;
|
||||
Inotify.S_Modify;
|
||||
Inotify.S_Move;
|
||||
Inotify.S_Delete;
|
||||
]
|
||||
in
|
||||
Lwt_inotify.read notify
|
54
module.nix
54
module.nix
@ -13,7 +13,7 @@ in
|
||||
options.services.smooooth = {
|
||||
enable = mkEnableOption "the smooooth nixos hot reloader";
|
||||
blockers = mkOption {
|
||||
description = "Names of processes that may block reloading when holding a flake (sub)path open.";
|
||||
description = "names of processes that may block reloading by holding a flake (sub)path open. default state is \"stop\"";
|
||||
default = [
|
||||
"nano"
|
||||
"nvim"
|
||||
@ -22,21 +22,25 @@ in
|
||||
"hx"
|
||||
];
|
||||
example = ''
|
||||
[ "hx" ]
|
||||
[ "hx" { nano = "die"; nvim = "stop"; } { fish = "die"; } ]
|
||||
'';
|
||||
type = types.listOf types.str;
|
||||
type =
|
||||
with types;
|
||||
listOf (
|
||||
either str (
|
||||
attrsOf (enum [
|
||||
"stop"
|
||||
"die"
|
||||
])
|
||||
)
|
||||
);
|
||||
};
|
||||
path = mkOption {
|
||||
description = "Path to the root of your flake.";
|
||||
description = "path to the root of your flake.";
|
||||
type = types.str;
|
||||
};
|
||||
pollingRate = mkOption {
|
||||
description = "How frequently to poll for blockers when waiting on a reload.";
|
||||
default = 10;
|
||||
type = types.int;
|
||||
};
|
||||
nixPackage = mkOption {
|
||||
description = "Your preferred package providing a `nix` executable.";
|
||||
description = "preferred package providing a `nix` executable.";
|
||||
default = pkgs.nix;
|
||||
type = types.package;
|
||||
};
|
||||
@ -47,22 +51,36 @@ in
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# idk probably need more soteria workarounds
|
||||
security.polkit.enable = lib.mkIf (config.security.soteria.enable == false) true;
|
||||
environment.etc."smooooth.json".text = builtins.toJSON {
|
||||
inherit (cfg) path;
|
||||
blockers =
|
||||
let
|
||||
withDefaults = map (
|
||||
b: if builtins.typeOf b == "string" then { "${b}" = "stop"; } else b
|
||||
) cfg.blockers;
|
||||
merged = lib.mergeAttrsList withDefaults;
|
||||
# FIXME clumsy, could do better
|
||||
transformed = lib.mapAttrsToList (n: v: {
|
||||
name = n;
|
||||
cond = [
|
||||
(lib.toSentenceCase v)
|
||||
];
|
||||
}) merged;
|
||||
in
|
||||
transformed;
|
||||
};
|
||||
systemd.user = {
|
||||
services.smooooth = {
|
||||
enable = true;
|
||||
path = [
|
||||
cfg.package
|
||||
];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
serviceConfig = {
|
||||
Restart = "always";
|
||||
Type = "simple";
|
||||
ExecStart =
|
||||
let
|
||||
blockers = builtins.concatStringsSep "|" cfg.blockers;
|
||||
in
|
||||
"${lib.getExe cfg.package} ${cfg.path} ${blockers} ${toString cfg.pollingRate}";
|
||||
Type = "notify";
|
||||
ExecStart = "${lib.getExe cfg.package}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
35
nix/obus.nix
Normal file
35
nix/obus.nix
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
ocamlPackages,
|
||||
fetchFromGitHub,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (ocamlPackages)
|
||||
lwt
|
||||
lwt_ppx
|
||||
lwt_log
|
||||
lwt_react
|
||||
xmlm
|
||||
menhir
|
||||
;
|
||||
in
|
||||
ocamlPackages.buildDunePackage rec {
|
||||
pname = "obus";
|
||||
version = "1.2.5";
|
||||
src = fetchFromGitHub {
|
||||
owner = "ocaml-community";
|
||||
repo = "obus";
|
||||
tag = version;
|
||||
hash = "sha256-Rf79NDhAC1MG8Iyr/V2exTlY6+COKoSSnbkBc8dx/Hg=";
|
||||
};
|
||||
nativeBuildInputs = [
|
||||
menhir
|
||||
];
|
||||
propagatedBuildInputs = [
|
||||
lwt
|
||||
lwt_ppx
|
||||
lwt_log
|
||||
lwt_react
|
||||
xmlm
|
||||
];
|
||||
}
|
24
shell.nix
Normal file
24
shell.nix
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
pkgs,
|
||||
deps,
|
||||
}:
|
||||
pkgs.mkShell {
|
||||
packages = deps.dev ++ deps.build;
|
||||
|
||||
shellHook =
|
||||
let
|
||||
justfile = ''
|
||||
set quiet
|
||||
|
||||
build:
|
||||
nix build --offline --out-link result
|
||||
online:
|
||||
nix build --out-link result
|
||||
run: build
|
||||
result/bin/smooooth
|
||||
local:
|
||||
dune build
|
||||
'';
|
||||
in
|
||||
''echo "${justfile}" > justfile'';
|
||||
}
|
32
smooooth.opam
Normal file
32
smooooth.opam
Normal file
@ -0,0 +1,32 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "A short synopsis"
|
||||
description: "A longer description"
|
||||
maintainer: ["Maintainer Name <maintainer@example.com>"]
|
||||
authors: ["Author Name <author@example.com>"]
|
||||
license: "LICENSE"
|
||||
tags: ["add topics" "to describe" "your" "project"]
|
||||
homepage: "https://github.com/username/reponame"
|
||||
doc: "https://url/to/documentation"
|
||||
bug-reports: "https://github.com/username/reponame/issues"
|
||||
depends: [
|
||||
"dune" {>= "3.18"}
|
||||
"ocaml"
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@runtest" {with-test}
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/username/reponame.git"
|
||||
x-maintenance-intent: ["(latest)"]
|
46
smooooth.sh
46
smooooth.sh
@ -1,46 +0,0 @@
|
||||
SACRED_SPACE="$1"
|
||||
BLOCKERS="$2"
|
||||
PERIOD="$3"
|
||||
|
||||
scrapePids() {
|
||||
for pid in /proc/*; do
|
||||
if [[ "$pid" =~ .*[0-9]*$ ]]; then
|
||||
if grep -E "$BLOCKERS" -q "$pid"/comm; then
|
||||
if [[ "$(readlink "$pid"/cwd)" == "$SACRED_SPACE"* ]]; then
|
||||
echo "$pid with name $(cat "$pid"/comm) is blocking"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "Starting up for $SACRED_SPACE with blockers $BLOCKERS and polling of $PERIOD"
|
||||
|
||||
while true; do
|
||||
|
||||
inotifywait -r -e modify -e move -e create -e delete "$SACRED_SPACE"
|
||||
|
||||
|
||||
notify-send "smooooth" "config change detected. waiting for blockers to resolve.."
|
||||
while scrapePids; do
|
||||
echo "found blocker in $SACRED_SPACE, waiting.."
|
||||
sleep "$PERIOD"
|
||||
done
|
||||
|
||||
echo "building system"
|
||||
notify-send "smooooth" "rebuilding your nixos config - please stand by"
|
||||
temp="$(mktemp -d)"
|
||||
build="$temp/system"
|
||||
nix build --out-link "$build" "$SACRED_SPACE"#nixosConfigurations."$HOSTNAME".config.system.build.toplevel
|
||||
|
||||
echo "built and linked at $build - attempting to activate system"
|
||||
notify-send "smooooth" "activating your new config"
|
||||
switch="$build/bin/switch-to-configuration"
|
||||
/run/wrappers/bin/pkexec "$switch" switch
|
||||
|
||||
echo "cleaning up"
|
||||
rm -r "$temp"
|
||||
|
||||
done
|
0
test/test_smooooth.ml
Normal file
0
test/test_smooooth.ml
Normal file
Loading…
Reference in New Issue
Block a user