255 lines
6.9 KiB
Nix
255 lines
6.9 KiB
Nix
{
|
|
config,
|
|
pkgs,
|
|
lib,
|
|
...
|
|
}: let
|
|
inherit
|
|
(lib)
|
|
mkOption
|
|
mkForce
|
|
getExe
|
|
getExe'
|
|
listToAttrs
|
|
flatten
|
|
mapAttrsToList
|
|
mapAttrs
|
|
mapAttrs'
|
|
nameValuePair
|
|
toLower
|
|
replaceStrings
|
|
concatMapStringsSep
|
|
;
|
|
partOf = cfg: "${replaceStrings [" "] ["-"] (toLower cfg.name)}.target";
|
|
# make a firefox webapp + hidden .desktop entry for the client app
|
|
make-firefox = cfg:
|
|
mapAttrs' (
|
|
name: cfg:
|
|
nameValuePair "${name}-client"
|
|
{
|
|
inherit (cfg) name;
|
|
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 frontend
|
|
make-systemd-service = cfg:
|
|
mapAttrs' (
|
|
name: cfg:
|
|
if
|
|
(cfg.service
|
|
!= null)
|
|
then
|
|
nameValuePair "${cfg.name}-frontend" {
|
|
Unit = {
|
|
Description = "${cfg.name} Frontend";
|
|
WantedBy = mkForce [];
|
|
};
|
|
|
|
Service = cfg.service;
|
|
}
|
|
else nameValuePair "" {}
|
|
)
|
|
cfg;
|
|
# modify systemd units to be PartOf this target
|
|
modify-systemd-services = cfg:
|
|
listToAttrs (flatten (mapAttrsToList (
|
|
name: cfg: (map (
|
|
req: {
|
|
name = "${req}";
|
|
value = {
|
|
Unit = {
|
|
PartOf = partOf cfg;
|
|
};
|
|
};
|
|
}
|
|
)
|
|
cfg.requires.services)
|
|
)
|
|
cfg));
|
|
modify-quadlets = cfg:
|
|
listToAttrs (flatten (mapAttrsToList (
|
|
name: cfg: (map (
|
|
req: {
|
|
name = "${req}";
|
|
value = {
|
|
unitConfig = {
|
|
PartOf = partOf cfg;
|
|
};
|
|
};
|
|
}
|
|
)
|
|
cfg.requires.containers)
|
|
)
|
|
cfg));
|
|
# make a systemd target to collate dependencies
|
|
make-systemd-target = cfg:
|
|
mapAttrs (
|
|
name: cfg: {
|
|
Unit = {
|
|
Description = "${cfg.name} Target";
|
|
WantedBy = mkForce [];
|
|
Requires =
|
|
(map (req: req + ".service") cfg.requires.services)
|
|
++ (map (req: "podman-" + req + ".service") cfg.requires.containers);
|
|
};
|
|
}
|
|
)
|
|
cfg;
|
|
# make desktop shortcuts and a script which will handle starting everything
|
|
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}";
|
|
podman = "${getExe pkgs.podman}";
|
|
makeContainerCheck = container: ''[ "$(${podman} inspect -f {{.State.Health.Status}} ${container})" == "healthy" ]'';
|
|
# makeContainerCheck = container: ''
|
|
# [ ${podman} inspect -f {{.State.Status}} ${container})" != "running" ]
|
|
# '';
|
|
containerChecks =
|
|
if (cfg.requires.containers != [])
|
|
then
|
|
''
|
|
container_checks() {
|
|
if ''
|
|
+ (concatMapStringsSep " && "
|
|
(container: makeContainerCheck container)
|
|
cfg.requires.containers)
|
|
+ ''
|
|
; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
''
|
|
else ''
|
|
container_checks() {
|
|
return 0
|
|
}
|
|
'';
|
|
in
|
|
pkgs.writeShellScript "${name}"
|
|
''
|
|
set -euo pipefail
|
|
|
|
exit_error() {
|
|
${notify-send} -w "Failure" $1
|
|
exit 1
|
|
}
|
|
|
|
${containerChecks}
|
|
|
|
${notify-send} "Launching ${name} backend.." "Please be patient."
|
|
${systemctl} --user start ${name}.target || exit_error "Failed to launch!"
|
|
|
|
checks=0
|
|
until container_checks; do
|
|
sleep 2
|
|
checks=$((checks+1))
|
|
if [ $((checks%10)) -eq 0 ]; then
|
|
${notify-send} "Waiting for backend."
|
|
fi
|
|
if [ $checks -ge 60 ]; then
|
|
${systemctl} --no-block --user stop ${name}.target
|
|
exit_error "Failed to launch!"
|
|
fi
|
|
done
|
|
|
|
${notify-send} "Launching ${name}.."
|
|
${dex} -w ~/.nix-profile/share/applications/${name}-client.desktop
|
|
|
|
${notify-send} "Goodbye" "Shutting down."
|
|
${systemctl} --user stop ${name}.target
|
|
exit 0
|
|
''}";
|
|
}
|
|
)
|
|
cfg;
|
|
cfg = config.localWebApps;
|
|
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;
|
|
};
|
|
|
|
requires = mkOption {
|
|
type = nullOr (submodule {
|
|
options = {
|
|
containers = mkOption {
|
|
type = listOf str;
|
|
default = [];
|
|
};
|
|
services = mkOption {
|
|
type = listOf str;
|
|
default = [];
|
|
};
|
|
};
|
|
});
|
|
default = null;
|
|
description = "Containers or services this app requires.";
|
|
};
|
|
|
|
service = mkOption {
|
|
type = nullOr (submodule {
|
|
options = {
|
|
execStartPre = mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
execStart = mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
execStop = mkOption {
|
|
type = nullOr str;
|
|
default = null;
|
|
};
|
|
};
|
|
});
|
|
default = null;
|
|
description = "Submodule containing exec[StartPre/Start/Stop] commands for any required systemd service";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = int;
|
|
description = "Local port the webapp should host on.";
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
config = {
|
|
programs.firefox.webapps = make-firefox cfg;
|
|
|
|
systemd.user.targets = make-systemd-target cfg;
|
|
systemd.user.services = (make-systemd-service cfg) // (modify-systemd-services cfg);
|
|
services.podman.containers = modify-quadlets cfg;
|
|
|
|
xdg.desktopEntries = make-xdg cfg;
|
|
};
|
|
}
|