diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index a31d9898d74db..23dba127730fd 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -73,6 +73,8 @@ ## New Modules {#sec-release-24.11-new-modules} +- [vudials](https://vudials.com/), server for displaying CPU, GPU, and other metrics on VU1 e-ink displays. Available as [services.vudials](#opt-services.vudials.enable) service. + - [TaskChampion Sync-Server](https://github.com/GothenburgBitFactory/taskchampion-sync-server), a [Taskwarrior 3](https://taskwarrior.org/docs/upgrade-3/) sync server, replacing Taskwarrior 2's sync server named [`taskserver`](https://github.com/GothenburgBitFactory/taskserver). - [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr), proxy server to bypass Cloudflare protection. Available as [services.flaresolverr](#opt-services.flaresolverr.enable) service. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 6f5357382dc56..17ba044c03edb 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -941,6 +941,7 @@ ./services/monitoring/vmagent.nix ./services/monitoring/vmalert.nix ./services/monitoring/vnstat.nix + ./services/monitoring/vudials.nix ./services/monitoring/watchdogd.nix ./services/monitoring/zabbix-agent.nix ./services/monitoring/zabbix-proxy.nix diff --git a/nixos/modules/services/monitoring/vudials.nix b/nixos/modules/services/monitoring/vudials.nix new file mode 100644 index 0000000000000..006917c55995b --- /dev/null +++ b/nixos/modules/services/monitoring/vudials.nix @@ -0,0 +1,132 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.vudials; +in +{ + options.services.vudials = { + enable = mkEnableOption "VU Dials"; + + user = mkOption { + type = types.str; + default = "vudials"; + description = "User account under which VU Dials runs."; + }; + + group = mkOption { + type = types.str; + default = "vudials"; + description = "Group under which VU Dials runs."; + }; + + port = mkOption { + type = types.port; + default = 5340; + description = "Port on which VU Dials listens."; + }; + + cpudial = mkOption { + type = types.str; + default = ""; + description = "UID of the dial that will display CPU load"; + }; + + gpudial = mkOption { + type = types.str; + default = ""; + description = "UID of the dial that will display GPU load"; + }; + + memdial = mkOption { + type = types.str; + default = ""; + description = "UID of the dial that will display memory load"; + }; + + dskdial = mkOption { + type = types.str; + default = ""; + description = "UID of the dial that will display % of the disk space used on root partition"; + }; + + }; + + config = mkIf cfg.enable { + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + description = "VU Server user"; + }; + + users.groups.${cfg.group} = { }; + + services.udev.extraRules = '' + ACTION=="add", SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", ATTRS{serial}=="DQ0164KM", SYMLINK+="vuserver-$attr{serial}", TAG+="systemd", ENV{SYSTEMD_WANTS}="vuserver@$attr{serial}.service", MODE="0666" + ACTION=="remove", SUBSYSTEM=="tty", ENV{ID_VENDOR_ID}=="0403", ENV{ID_MODEL_ID}=="6015", ENV{ID_SERIAL_SHORT}=="DQ0164KM", RUN+="${pkgs.systemd}/bin/systemctl stop vuserver@$env{ID_SERIAL_SHORT}.service" + ''; + + systemd.services."vuserver@" = { + description = "VU Server for %I. Provides API, admin web page, and pushed updates to USB dials"; + partOf = [ "vuserver.target" ]; + + serviceConfig = { + ExecStart = "${pkgs.vuserver}/bin/vuserver"; + User = cfg.user; + Group = cfg.group; + Restart = "on-failure"; + WorkingDirectory = "${pkgs.vuserver}/lib"; + RuntimeDirectory = "vuserver"; + LogsDirectory = "vuserver"; + StateDirectory = "vuserver"; + TimeoutStopSec = "1s"; + + Environment = [ + "STATEDIR=%S/vuserver" + "LOGSDIR=%L/vuserver" + "RUNTIMEDIR=%t/vuserver" + "DEVICE=/dev/vuserver-%I" + "PORT=${toString cfg.port}" + ]; + }; + }; + + systemd.targets.vuserver = { }; + + systemd.services.vuclient = { + enable = true; + description = "Monitor computer and push info to VU server."; + wantedBy = [ "multi-user.target" ]; + wants = [ "vuserver.target" ]; + after = [ "vuserver.target" ]; + serviceConfig = { + ExecStart = "${pkgs.vuclient}/bin/vuclient"; + TimeoutStopSec = "5s"; + Restart = "on-failure"; + Environment = [ + "CPUDIAL=${cfg.cpudial}" + "GPUDIAL=${cfg.gpudial}" + "MEMDIAL=${cfg.memdial}" + "DSKDIAL=${cfg.dskdial}" + ]; + }; + }; + + powerManagement.powerDownCommands = lib.mkAfter '' + systemctl stop vuclient.service + sleep 1 + ''; + + powerManagement.powerUpCommands = lib.mkAfter '' + systemctl start vuclient.service + ''; + + environment.systemPackages = [ pkgs.vuserver ]; + }; +} diff --git a/pkgs/by-name/vu/vuclient/package.nix b/pkgs/by-name/vu/vuclient/package.nix new file mode 100644 index 0000000000000..9a862471830da --- /dev/null +++ b/pkgs/by-name/vu/vuclient/package.nix @@ -0,0 +1,31 @@ +{ + lib, + stdenv, + fetchFromGitHub, +}: + +stdenv.mkDerivation rec { + pname = "vuclient"; + version = "1.0.1"; + + src = fetchFromGitHub { + owner = "bonds"; + repo = "vuclient"; + rev = "v${version}"; + hash = "sha256-L8TMHIA2WaYyF9Uv295ygZ5LJRaf9zRhRRHrD5WpVBE="; + }; + + installPhase = '' + mkdir -p $out/bin + cp $src/vuclient $out/bin/vuclient + chmod +x $out/bin/vuclient + ''; + + meta = with lib; { + description = "A script that samples perf stats and sends them to a vuserver driving VU1 dials."; + homepage = "https://github.com/bonds/vuclient/"; + license = licenses.mit; + platforms = platforms.linux; + maintainers = with maintainers; [ bonds ]; + }; +} diff --git a/pkgs/by-name/vu/vuserver/package.nix b/pkgs/by-name/vu/vuserver/package.nix new file mode 100644 index 0000000000000..d41113dc2834e --- /dev/null +++ b/pkgs/by-name/vu/vuserver/package.nix @@ -0,0 +1,85 @@ +{ + lib, + python3Packages, + fetchFromGitHub, + makeWrapper, +}: + +python3Packages.buildPythonApplication rec { + pname = "vuserver"; + version = "20240329"; + + src = fetchFromGitHub { + owner = "SasaKaranovic"; + repo = "VU-Server"; + rev = "v${version}"; + sha256 = "sha256-K2oJrqgNGRus5bvYHdhtyDQeHvCbW+fAlQLMn00Ovco="; + }; + + format = "other"; + + nativeBuildInputs = [ makeWrapper ]; + + propagatedBuildInputs = with python3Packages; [ + tornado + numpy + pillow + requests + pyyaml + ruamel-yaml + pyserial + ]; + + dontBuild = true; + + patchPhase = '' + substituteInPlace dials/base_logger.py \ + --replace "logFile = f'/home/{getpass.getuser()}/KaranovicResearch/vudials/server.log'" \ + "logFile = os.path.join(os.environ.get('LOGSDIR'), 'vuserver.log')" + substituteInPlace server.py \ + --replace "pid_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), file_name)" \ + "pid_file = os.path.join(os.environ.get('RUNTIMEDIR'), 'pid')" + substituteInPlace server.py \ + --replace "WEB_ROOT = os.path.join(BASEDIR_PATH, 'www')" \ + "WEB_ROOT = os.path.join(os.environ.get('RUNTIMEDIR'), 'www')" + substituteInPlace database.py \ + --replace "database_path = os.path.join(os.path.dirname(__file__))" \ + "database_path = os.environ.get('STATEDIR')" + substituteInPlace server_config.py \ + --replace "'port': 3000" \ + "'port': os.environ.get('PORT')" + substituteInPlace server_config.py \ + --replace "'master_key': 'cTpAWYuRpA2zx75Yh961Cg'" \ + "'master_key': os.environ.get('KEY')" + ''; + + installPhase = '' + mkdir -p "$out/lib" + cp -r * "$out/lib" + rm "$out/lib/config.yaml" + + makeWrapper \ + ${python3Packages.python.interpreter} \ + $out/bin/vuserver \ + --run "cp -r $out/lib/www /run/vuserver/www" \ + --run "chown -R vudials:vudials /run/vuserver/www" \ + --run "chmod 775 /run/vuserver/www/assets/js/" \ + --run "export KEY=\$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 64)" \ + --run "echo \$KEY > /run/vuserver/key" \ + --run "echo \"const API_MASTER_KEY = '\$KEY';\" > /run/vuserver/www/assets/js/vu1_gui_root.js.new" \ + --run "sed 1d /run/vuserver/www/assets/js/vu1_gui_root.js >> /run/vuserver/www/assets/js/vu1_gui_root.js.new" \ + --run "mv /run/vuserver/www/assets/js/vu1_gui_root.js.new /run/vuserver/www/assets/js/vu1_gui_root.js" \ + --chdir "$out/lib" \ + --add-flags "$out/lib/server.py" \ + --set PYTHONPATH "$PYTHONPATH:$out/lib" \ + ''; + + doCheck = false; + + meta = with lib; { + description = "VU Server for controlling VU dials"; + homepage = "https://github.com/SasaKaranovic/VU-Server"; + license = licenses.mit; + maintainers = with maintainers; [ bonds ]; + }; +}