add shell monitoring
This commit is contained in:
parent
d0532b9cc9
commit
48e77e9585
3 changed files with 156 additions and 1 deletions
|
@ -157,6 +157,16 @@
|
|||
inherit (pkgs.lib) foldlAttrs;
|
||||
inherit works;
|
||||
};
|
||||
# shell id is based on the services config
|
||||
shellId = builtins.hashString "sha256" (builtins.toJSON config._buildIdes.finalServices);
|
||||
monitor = import ./monitor.nix {
|
||||
inherit (pkgs) writeShellScriptBin;
|
||||
inherit shellId;
|
||||
cli = pkgs.lib.getExe cli;
|
||||
socat = pkgs.lib.getExe pkgs.socat;
|
||||
# TODO make this timeout more lenient?
|
||||
timeout = if (pkgs.lib.typeOf config.monitor == "int") then config.monitor else 20;
|
||||
};
|
||||
|
||||
# create the ides shell
|
||||
final =
|
||||
|
@ -177,14 +187,28 @@
|
|||
''
|
||||
else
|
||||
"";
|
||||
monitorRun =
|
||||
let
|
||||
inherit (pkgs.lib) getExe;
|
||||
in
|
||||
if config.monitor then
|
||||
''
|
||||
systemd-run --user -q -G -u ides-${shellId}-monitor ${getExe monitor.daemon} $PWD
|
||||
${getExe monitor.client} $$
|
||||
''
|
||||
else
|
||||
"";
|
||||
in
|
||||
(shellArgs.shellHook or "")
|
||||
+ ''
|
||||
printf '[ides]: use "ides [action] [target]" to control services. type "ides help" to find out more.\n'
|
||||
export IDES_CTL="/run/user/$(id -u)/ides-${shellId}.sock"
|
||||
''
|
||||
+ autoRun;
|
||||
+ autoRun
|
||||
+ monitorRun;
|
||||
};
|
||||
in
|
||||
# TODO make this optionally return the shell components to allow composability with other dev shell solutions
|
||||
config._buildIdes.shellFn final;
|
||||
};
|
||||
|
||||
|
|
125
lib/monitor.nix
Normal file
125
lib/monitor.nix
Normal file
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
socat,
|
||||
writeShellScriptBin,
|
||||
shellId,
|
||||
cli,
|
||||
timeout,
|
||||
...
|
||||
}:
|
||||
let
|
||||
wait = builtins.toString timeout;
|
||||
in
|
||||
{
|
||||
daemon = writeShellScriptBin "ides-monitor-${shellId}" ''
|
||||
BASE_PATH="$1"
|
||||
SOCKET=/run/user/$(id -u)/ides-${shellId}.sock
|
||||
|
||||
# if socket exists, it's already being monitored
|
||||
if [ -e "$SOCKET" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PIDS=()
|
||||
|
||||
# loop on socket forever
|
||||
while true; do
|
||||
# wait to receive a PID
|
||||
PID=$(timeout ${wait} ${socat} UNIX-LISTEN:"$SOCKET" -)
|
||||
# if received and unique, add to our watch
|
||||
if [ "$?" -eq 0 ] && ! [[ "''${PIDS[@]}" =~ $PID ]]; then
|
||||
echo adding $PID to watch
|
||||
PIDS+=($PID)
|
||||
echo pids are now ''${PIDS[@]}
|
||||
fi
|
||||
|
||||
# check process statuses
|
||||
DEAD=true
|
||||
for INDEX in "''${!PIDS[@]}"; do
|
||||
REMOVE=false
|
||||
|
||||
# get pid
|
||||
CHECK="''${PIDS[$INDEX]}"
|
||||
echo checking $CHECK
|
||||
|
||||
# check status
|
||||
ALIVE=$(kill -0 "$CHECK" 2>&1 > /dev/null)
|
||||
|
||||
# determine eligibility to act as a service root
|
||||
if $ALIVE; then
|
||||
|
||||
CMD=$(cat /proc/$CHECK/comm)
|
||||
case "$CMD" in
|
||||
# if host is an editor, we don't care about pwd
|
||||
code|codium|zed|emacs|intellij*|sublime*)
|
||||
DEAD=false
|
||||
;;
|
||||
# if it's a shell we really do
|
||||
sh|bash|zsh|fish|nu|murex|*)
|
||||
DIR=$(readlink /proc/$CHECK/cwd)
|
||||
if [[ "$DIR" == "$BASE_PATH"* ]]; then
|
||||
DEAD=false
|
||||
else
|
||||
REMOVE=true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
echo found $CHECK alive with $CMD in $DIR
|
||||
|
||||
else
|
||||
|
||||
echo found $CHECK dead
|
||||
|
||||
REMOVE=true
|
||||
|
||||
fi
|
||||
|
||||
if $REMOVE; then
|
||||
echo removing ineligible pid $CHECK
|
||||
unset "PIDS[$INDEX]"
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
if "$DEAD"; then
|
||||
echo no live pids, breaking
|
||||
break
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
# loop has broken, no valid pids left
|
||||
echo stopping all!
|
||||
${cli} stop
|
||||
'';
|
||||
|
||||
# FIXME if the client finds its parent is an editor process,
|
||||
# such as `code`, should it keep walking pids until the final parent?
|
||||
client = writeShellScriptBin "ides-notify-${shellId}" ''
|
||||
function get-parent() {
|
||||
ps --no-header -o ppid:1 $1
|
||||
}
|
||||
|
||||
function get-command() {
|
||||
cat /proc/$1/comm
|
||||
}
|
||||
|
||||
SOCKET=/var/run/user/$(id -u)/ides-${shellId}.sock
|
||||
# wait for socket to come up
|
||||
while ! [ -e "$SOCKET" ]; do
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
# check if our calling shell's parent process is direnv
|
||||
PARENT=$(get-parent $1)
|
||||
COMM=$(get-command $PARENT)
|
||||
TARGET="$1"
|
||||
echo found $COMM as parent
|
||||
if [[ "$COMM" == "direnv" ]]; then
|
||||
# if so, skip up another parent to get the process it is exporting to
|
||||
TARGET=$(get-parent $PARENT)
|
||||
fi
|
||||
# tell monitor about the devshell
|
||||
echo "$TARGET" | ${socat} - UNIX-CONNECT:"$SOCKET"
|
||||
'';
|
||||
}
|
|
@ -125,6 +125,12 @@
|
|||
default = true;
|
||||
};
|
||||
|
||||
monitor = mkOption {
|
||||
type = types.either types.bool types.int;
|
||||
description = "Enable, or set timeout period for, monitoring devshell activity and automatically destroy services after (experimental).";
|
||||
default = true;
|
||||
};
|
||||
|
||||
# to prevent generating docs for this option; see https://github.com/NixOS/nixpkgs/issues/293510
|
||||
_module.args = mkOption {
|
||||
internal = true;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue