{ config, pkgs, lib, ... }: let inherit (lib) mkOption; inherit (lib.attrsets) mapAttrs mapAttrs' nameValuePair; inherit (lib) getExe getExe'; # make a firefox webapp entry for the client app make-firefox = cfg: mapAttrs' ( name: cfg: nameValuePair "${name}-client" { inherit (cfg) name id; url = "http://127.0.0.1:${builtins.toString cfg.port}"; extraSettings = config.programs.firefox.profiles.default.settings; hidden = true; } ) cfg; # make a systemd service for running the backend make-systemd = cfg: mapAttrs ( name: cfg: { Unit.Description = "${cfg.name} Backend"; Service = cfg.service; } ) cfg; # make desktop shortcuts and a script which will handle starting both the above make-xdg = cfg: mapAttrs ( name: cfg: { inherit (cfg) name icon genericName; type = "Application"; exec = "${let notify-send = "${getExe' pkgs.libnotify "notify-send"} -a \"${cfg.name}\""; systemctl = "${getExe' pkgs.systemd "systemctl"}"; dex = "${getExe pkgs.dex}"; curl = "${getExe pkgs.curl}"; in pkgs.writeShellScript "${name}" '' set -euo pipefail ${notify-send} "Launching backend.." "Please be patient." ${systemctl} --user start ${name} attempts=0 success=false while [[ $attempts -lt $((20*9)) ]]; do if [[ $(${curl} -sf http://127.0.0.1:${builtins.toString cfg.port} --output /dev/null; printf $?) -eq 0 ]]; then ${notify-send} "Backend up." "Launching client.." success=true break else attempts=$((attempts+1)) if [[ $(($attempts % 20)) -eq 0 ]]; then ${notify-send} "Launching backend.." "Still launching.. ($((attempts/2))s)" fi fi sleep 0.5 done if [[ ! $success ]]; then ${notify-send} "Failure" "Failed to launch backend!" ${systemctl} --user kill ${name} exit 1 fi ${dex} -w ~/.nix-profile/share/applications/${name}-client.desktop ${notify-send} "Goodbye" "Shutting down." ${systemctl} --user stop ${name} exit 0 ''}"; } ) cfg; in { options.localWebApps = mkOption { default = {}; type = with lib.types; attrsOf (submodule { options = { name = mkOption { type = str; description = "Display name of the webapp."; }; genericName = mkOption { type = nullOr str; description = "Generic name of the webapp."; default = null; }; icon = mkOption { type = nullOr (either str path); description = "Path to a file to use for application icon."; default = null; }; id = mkOption { type = int; description = "Firefox profile ID for the webapp's client"; }; port = mkOption { type = int; description = "Local port the webapp should host on."; }; service = mkOption { type = attrsOf str; description = "The service settings for systemd user service. Requires at least ExecStart."; }; }; }); }; config = { programs.firefox.webapps = make-firefox config.localWebApps; systemd.user.services = make-systemd config.localWebApps; xdg.desktopEntries = make-xdg config.localWebApps; }; }