mirror of
https://github.com/foo-dogsquared/nixos-config.git
synced 2025-04-18 18:19:12 +00:00
nixos/programs/sessiond: init
This commit is contained in:
parent
d1dc2953c7
commit
1d3bc3c013
@ -5,6 +5,7 @@
|
||||
./programs/distrobox.nix
|
||||
./programs/gnome-session
|
||||
./programs/pop-launcher.nix
|
||||
./programs/sessiond
|
||||
./programs/wezterm.nix
|
||||
./services/archivebox.nix
|
||||
./services/gallery-dl.nix
|
||||
|
124
modules/nixos/programs/sessiond/default.nix
Normal file
124
modules/nixos/programs/sessiond/default.nix
Normal file
@ -0,0 +1,124 @@
|
||||
{ config, lib, pkgs, utils, ... }:
|
||||
|
||||
let
|
||||
cfg = config.programs.sessiond;
|
||||
|
||||
sessionPackages = lib.mapAttrsToList
|
||||
(name: session:
|
||||
let
|
||||
displaySession = ''
|
||||
[Desktop Entry]
|
||||
Name=${session.fullName}
|
||||
Comment=${session.description}
|
||||
Exec="@out@/libexec/${name}-session"
|
||||
Type=Application
|
||||
DesktopNames=${lib.concatStringsSep ";" session.desktopNames};
|
||||
'';
|
||||
|
||||
sessionScript = ''
|
||||
#!${pkgs.runtimeShell}
|
||||
|
||||
${lib.getExe' cfg.package "sessionctl"} run "${name}.target"
|
||||
'';
|
||||
in
|
||||
pkgs.runCommandLocal "${name}-desktop-session-files"
|
||||
{
|
||||
inherit displaySession sessionScript;
|
||||
passAsFile = [ "displaySession" "sessionScript" ];
|
||||
passthru.providedSessions = [ name ];
|
||||
}
|
||||
''
|
||||
SESSION_SCRIPT="$out/libexec/${name}-session"
|
||||
install -Dm0755 "$sessionScriptPath" "$SESSION_SCRIPT"
|
||||
substituteAllInPlace "$SESSION_SCRIPT"
|
||||
|
||||
DISPLAY_SESSION_FILE="$out/share/xsessions/${name}.desktop"
|
||||
install -Dm0644 "$displaySessionPath" "$DISPLAY_SESSION_FILE"
|
||||
substituteAllInPlace "$DISPLAY_SESSION_FILE"
|
||||
''
|
||||
)
|
||||
cfg.sessions;
|
||||
|
||||
sessionSystemdUnits = lib.mapAttrsToList
|
||||
(name: session:
|
||||
let
|
||||
inherit (utils.systemdUtils.lib)
|
||||
pathToUnit serviceToUnit targetToUnit timerToUnit socketToUnit;
|
||||
|
||||
sessionComponents =
|
||||
lib.foldlAttrs
|
||||
(acc: name: component:
|
||||
acc // {
|
||||
"${component.id}.service" = serviceToUnit component.id component.serviceUnit;
|
||||
"${component.id}.target" = targetToUnit component.id component.targetUnit;
|
||||
} // lib.optionalAttrs (component.socketUnit != null) {
|
||||
"${component.id}.socket" = socketToUnit component.id component.socketUnit;
|
||||
} // lib.optionalAttrs (component.timerUnit != null) {
|
||||
"${component.id}.timer" = timerToUnit component.id component.timerUnit;
|
||||
} // lib.optionalAttrs (component.pathUnit != null) {
|
||||
"${component.id}.path" = pathToUnit component.id component.pathUnit;
|
||||
})
|
||||
{ }
|
||||
session.components;
|
||||
in
|
||||
sessionComponents // {
|
||||
"${name}.service" = serviceToUnit name session.serviceUnit;
|
||||
"${name}.target" = targetToUnit name session.targetUnit;
|
||||
}
|
||||
)
|
||||
cfg.sessions;
|
||||
in
|
||||
{
|
||||
options.programs.sessiond = {
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.sessiond;
|
||||
defaultText = "pkgs.sessiond";
|
||||
description = ''
|
||||
The package containing sessiond executable and systemd units. This
|
||||
module will use the `sessiond` executable for the generated session
|
||||
script.
|
||||
'';
|
||||
};
|
||||
|
||||
sessions = lib.mkOption {
|
||||
type = with lib.types; attrsOf (submoduleWith {
|
||||
specialArgs = {
|
||||
inherit utils pkgs;
|
||||
sessiondPkg = cfg.package;
|
||||
};
|
||||
modules = [ ./submodules/session-type.nix ];
|
||||
});
|
||||
description = ''
|
||||
A set of desktop sessions to be configured with sessiond. Each of the
|
||||
attribute name will be used as the identifier of the desktop
|
||||
environment.
|
||||
|
||||
::: {.tip}
|
||||
While you can make identifiers in any way, it is
|
||||
encouraged to stick to a naming scheme. Here's two common ways to name
|
||||
a desktop environment.
|
||||
|
||||
* Reverse DNS-like scheme (e.g., `com.example.MoseyBranch`).
|
||||
* Kebab-case (e.g., `mosey-branch`).
|
||||
:::
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.sessions != { }) {
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
# Install all of the desktop session files.
|
||||
services.xserver.displayManager.sessionPackages = sessionPackages;
|
||||
|
||||
# Import those systemd units from sessiond as well.
|
||||
systemd.packages = [ cfg.package ];
|
||||
systemd.user.units = lib.mkMerge sessionSystemdUnits;
|
||||
|
||||
# We're disabling the upstream sessiond service since there can be multiple
|
||||
# sessiond sessions here.
|
||||
systemd.user.services.sessiond.enable = false;
|
||||
};
|
||||
}
|
228
modules/nixos/programs/sessiond/submodules/component-type.nix
Normal file
228
modules/nixos/programs/sessiond/submodules/component-type.nix
Normal file
@ -0,0 +1,228 @@
|
||||
{ name, config, lib, session, utils, ... }: {
|
||||
options = {
|
||||
description = lib.mkOption {
|
||||
type = lib.types.nonEmptyStr;
|
||||
description = "One-sentence description of the component.";
|
||||
example = "Desktop widgets";
|
||||
};
|
||||
|
||||
# Most of the systemd config types are trying to eliminate as much of the
|
||||
# NixOS systemd extensions as much as possible. For more details, see
|
||||
# `config` attribute of the `sessionType`.
|
||||
serviceUnit = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
inherit (utils.systemdUtils.lib) unitConfig serviceConfig;
|
||||
inherit (utils.systemdUtils.unitOptions) commonUnitOptions serviceOptions;
|
||||
in
|
||||
lib.types.submodule [
|
||||
commonUnitOptions
|
||||
serviceOptions
|
||||
serviceConfig
|
||||
unitConfig
|
||||
];
|
||||
description = ''
|
||||
systemd service configuration to be generated. This should be
|
||||
configured if the session is managed by systemd.
|
||||
|
||||
:::{.note}
|
||||
This has the same options as {option}`systemd.user.services.<name>`
|
||||
but without certain options from stage 2 counterparts such as
|
||||
`reloadTriggers` and `restartTriggers`.
|
||||
|
||||
By default, this module sets the service unit as part of the respective
|
||||
target unit (i.e., `PartOf=$COMPONENTID.target`).
|
||||
|
||||
On a typical case, you shouldn't mess with much of the dependency
|
||||
ordering with the service unit. You should configure `targetUnit` for
|
||||
that instead.
|
||||
:::
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
|
||||
targetUnit = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
inherit (utils.systemdUtils.lib) unitConfig;
|
||||
inherit (utils.systemdUtils.unitOptions) commonUnitOptions;
|
||||
in
|
||||
lib.types.submodule [
|
||||
commonUnitOptions
|
||||
unitConfig
|
||||
];
|
||||
description = ''
|
||||
systemd target configuration to be generated. This should be
|
||||
configured if the session is managed by systemd.
|
||||
|
||||
:::{.note}
|
||||
This is generated by default alongside the service where it is
|
||||
configured to be a part of the target unit.
|
||||
|
||||
This has the same options as {option}`systemd.user.targets.<name>`
|
||||
but without certain options from stage 2 counterparts such as
|
||||
`reloadTriggers` and `restartTriggers`.
|
||||
:::
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
|
||||
timerUnit = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
inherit (utils.systemdUtils.unitOptions) timerOptions commonUnitOptions;
|
||||
inherit (utils.systemdUtils.lib) unitConfig;
|
||||
in
|
||||
with lib.types; nullOr (submodule [
|
||||
commonUnitOptions
|
||||
timerOptions
|
||||
unitConfig
|
||||
]);
|
||||
description = ''
|
||||
An optional systemd timer configuration to be generated. This should
|
||||
be configured if the session is managed by systemd.
|
||||
|
||||
:::{.note}
|
||||
This has the same options as {option}`systemd.user.timers.<name>`
|
||||
but without certain options from stage 2 counterparts such as
|
||||
`reloadTriggers` and `restartTriggers`.
|
||||
:::
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
socketUnit = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
inherit (utils.systemdUtils.unitOptions) socketOptions commonUnitOptions;
|
||||
inherit (utils.systemdUtils.lib) unitConfig;
|
||||
in
|
||||
with lib.types; nullOr (submodule [
|
||||
commonUnitOptions
|
||||
socketOptions
|
||||
unitConfig
|
||||
]);
|
||||
description = ''
|
||||
An optional systemd socket configuration to be generated. This should
|
||||
be configured if the session is managed by systemd.
|
||||
|
||||
:::{.note}
|
||||
This has the same options as {option}`systemd.user.sockets.<name>`
|
||||
but without certain options from stage 2 counterparts such as
|
||||
`reloadTriggers` and `restartTriggers`.
|
||||
:::
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
pathUnit = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
inherit (utils.systemdUtils.unitOptions) pathOptions commonUnitOptions;
|
||||
inherit (utils.systemdUtils.lib) unitConfig;
|
||||
in
|
||||
with lib.types; nullOr (submodule [
|
||||
commonUnitOptions
|
||||
pathOptions
|
||||
unitConfig
|
||||
]);
|
||||
description = ''
|
||||
An optional systemd path configuration to be generated. This should
|
||||
be configured if the session is managed by systemd.
|
||||
|
||||
:::{.note}
|
||||
This has the same options as {option}`systemd.user.paths.<name>`
|
||||
but without certain options from stage 2 counterparts such as
|
||||
`reloadTriggers` and `restartTriggers`.
|
||||
:::
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
id = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The identifier of the component used in generating filenames for its
|
||||
`.desktop` files and as part of systemd unit names.
|
||||
'';
|
||||
default = "${session.name}.${name}";
|
||||
defaultText = "\${session-name}.\${name}";
|
||||
readOnly = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
/*
|
||||
Setting some recommendation and requirements for sessiond components.
|
||||
Note there are the missing directives that COULD include some sane
|
||||
defaults here.
|
||||
|
||||
* The `Unit.OnFailure=` and `Unit.OnFailureJobMode=` directives. Since
|
||||
different components don't have the same priority and don't handle
|
||||
failures the same way, we didn't set it here. This is on the user to
|
||||
know how different desktop components interact with each other
|
||||
especially if one of them failed.
|
||||
|
||||
TODO: Is `Type=notify` a good default?
|
||||
* `Service.Type=` is obviously not included since not all desktop
|
||||
components are the same either. Some of them could a D-Bus service,
|
||||
some of them are oneshots, etc. Not to mention, this is already implied
|
||||
to be `Type=simple` by systemd anyways.
|
||||
|
||||
* `Service.OOMScoreAdjust=` have different values for different
|
||||
components so it isn't included.
|
||||
|
||||
* Most sandboxing options. Aside from the fact we're dealing with a
|
||||
systemd user unit, much of them are unnecessary and rarely needed (if
|
||||
ever like `Service.PrivateTmp=`?) so we didn't set such defaults here.
|
||||
|
||||
As you can tell, this module does not provide a framework for the user
|
||||
to easily compose their own desktop environment. THIS MODULE ALREADY
|
||||
DOES A LOT, ALRIGHT! CUT ME SOME SLACK!
|
||||
|
||||
Take note that the default service configuration is leaning on the
|
||||
desktop component being a simple type of service like how most NixOS
|
||||
service modules are deployed.
|
||||
*/
|
||||
serviceUnit = {
|
||||
description = lib.mkDefault config.description;
|
||||
|
||||
# The typical workflow for service units to have them set as part of
|
||||
# the respective target unit.
|
||||
requisite = [ "${config.id}.target" ];
|
||||
before = [ "${config.id}.target" ];
|
||||
partOf = [ "${config.id}.target" ];
|
||||
|
||||
# Some sane service configuration for a desktop component.
|
||||
serviceConfig = {
|
||||
Slice = lib.mkDefault "session.slice";
|
||||
Restart = lib.mkDefault "on-failure";
|
||||
TimeoutStopSec = lib.mkDefault 5;
|
||||
};
|
||||
|
||||
startLimitBurst = lib.mkDefault 3;
|
||||
startLimitIntervalSec = lib.mkDefault 15;
|
||||
|
||||
unitConfig = {
|
||||
# We leave those up to the target units to start the services.
|
||||
RefuseManualStart = lib.mkDefault true;
|
||||
RefuseManualStop = lib.mkDefault true;
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
Similarly, there are things that COULD make it here but didn't for a
|
||||
variety of reasons.
|
||||
|
||||
* `Unit.PartOf=`, `Unit.Requisite=`, and the like since some components
|
||||
require starting up earlier than the others. We could include it here
|
||||
if we make it clear in the documentation or if it proves to be a
|
||||
painful experience to configure this by a first-timer. For now, this is
|
||||
on the user to know.
|
||||
*/
|
||||
targetUnit = {
|
||||
wants = [ "${config.id}.service" ];
|
||||
description = lib.mkDefault config.description;
|
||||
};
|
||||
};
|
||||
}
|
235
modules/nixos/programs/sessiond/submodules/session-type.nix
Normal file
235
modules/nixos/programs/sessiond/submodules/session-type.nix
Normal file
@ -0,0 +1,235 @@
|
||||
{ name, config, pkgs, lib, utils, sessiondPkg, ... }:
|
||||
|
||||
let
|
||||
# For an updated list, see `menu/menu-spec.xml` from
|
||||
# https://gitlab.freedesktop.org/xdg/xdg-specs.
|
||||
validDesktopNames = [
|
||||
"GNOME"
|
||||
"GNOME-Classic"
|
||||
"GNOME-Flashback"
|
||||
"KDE"
|
||||
"LXDE"
|
||||
"LXQt"
|
||||
"MATE"
|
||||
"Razor"
|
||||
"ROX"
|
||||
"TDE"
|
||||
"Unity"
|
||||
"XFCE"
|
||||
"EDE"
|
||||
"Cinnamon"
|
||||
"Pantheon"
|
||||
"Budgie"
|
||||
"Enlightenment"
|
||||
"DDE"
|
||||
"Endless"
|
||||
"Old"
|
||||
];
|
||||
|
||||
# This is used both as the configuration format for sessiond.conf and its
|
||||
# hooks.
|
||||
settingsFormat = pkgs.formats.toml { };
|
||||
sessionSettingsFile = settingsFormat.generate "sessiond-conf-${name}" config.settings;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
fullName = lib.mkOption {
|
||||
type = lib.types.nonEmptyStr;
|
||||
description = "The display name of the desktop environment.";
|
||||
default = name;
|
||||
example = "Mosey Branch";
|
||||
};
|
||||
|
||||
desktopNames = lib.mkOption {
|
||||
type = with lib.types; listOf nonEmptyStr;
|
||||
description = ''
|
||||
Names to be used as `DesktopNames=` entry of the session `.desktop`
|
||||
file. Useful if you're creating a customized version of an already
|
||||
existing desktop session.
|
||||
|
||||
::: {.note}
|
||||
This module sanitizes the values by prepending the given names with
|
||||
`X-` if they aren't part of the registered values from XDG spec.
|
||||
:::
|
||||
'';
|
||||
default = [ config.fullName ];
|
||||
defaultText = "[ <session>.fullName ]";
|
||||
apply = names:
|
||||
builtins.map
|
||||
(name:
|
||||
if (lib.elem name validDesktopNames) || (lib.hasPrefix "X-" name) then
|
||||
name
|
||||
else
|
||||
"X-${name}")
|
||||
names;
|
||||
example = [ "GNOME" "Garden" ];
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = settingsFormat.type;
|
||||
default = { };
|
||||
example = {
|
||||
Idle = {
|
||||
Inputs = [ "motion" "button-press" ];
|
||||
IdleSec = 60;
|
||||
};
|
||||
|
||||
Lock = {
|
||||
OnIdle = true;
|
||||
OnSleep = true;
|
||||
};
|
||||
|
||||
DPMS.Enable = true;
|
||||
};
|
||||
description = ''
|
||||
The settings associated with the sessiond session. For more
|
||||
details, please see {manpage}`sessiond.conf(5)`. If not given, it
|
||||
will use the default configuration from the compiled package.
|
||||
'';
|
||||
};
|
||||
|
||||
description = lib.mkOption {
|
||||
type = lib.types.nonEmptyStr;
|
||||
description = ''
|
||||
A one-sentence description of the desktop environment.
|
||||
'';
|
||||
default = "${config.fullName} desktop environment";
|
||||
defaultText = lib.literalExpression "\${<name>.fullName} desktop environment";
|
||||
example = "A desktop environment featuring a scrolling compositor.";
|
||||
};
|
||||
|
||||
components = lib.mkOption {
|
||||
type = with lib.types; attrsOf (submoduleWith {
|
||||
specialArgs = {
|
||||
inherit utils;
|
||||
session = {
|
||||
inherit (config) fullName desktopNames description;
|
||||
inherit name;
|
||||
};
|
||||
};
|
||||
modules = [ ./component-type.nix ];
|
||||
});
|
||||
description = ''
|
||||
The individual components to be launched with the desktop session.
|
||||
'';
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
targetUnit = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
inherit (utils.systemdUtils.lib) unitConfig;
|
||||
inherit (utils.systemdUtils.unitOptions) commonUnitOptions;
|
||||
in
|
||||
lib.types.submodule [
|
||||
commonUnitOptions
|
||||
unitConfig
|
||||
];
|
||||
description = ''
|
||||
systemd target configuration to be generated for
|
||||
`<name>.target`.
|
||||
|
||||
By default, the session target will have all of its components from
|
||||
{option}`<session>.requiredComponents` under `Wants=` directive. It
|
||||
also assumes all of them have a target unit at
|
||||
`''${requiredComponent}.target`.
|
||||
|
||||
:::{.note}
|
||||
This has the same options as {option}`systemd.user.targets.<name>`
|
||||
but without certain options from stage 2 counterparts such as
|
||||
`reloadTriggers` and `restartTriggers`.
|
||||
:::
|
||||
'';
|
||||
defaultText = ''
|
||||
{
|
||||
wants = ... # All of the required components as a target unit.
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
serviceUnit = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
inherit (utils.systemdUtils.lib) unitConfig serviceConfig;
|
||||
inherit (utils.systemdUtils.unitOptions) commonUnitOptions serviceOptions;
|
||||
in
|
||||
lib.types.submodule [
|
||||
commonUnitOptions
|
||||
serviceOptions
|
||||
serviceConfig
|
||||
unitConfig
|
||||
];
|
||||
description = ''
|
||||
systemd service configuration to be generated for the sessiond session
|
||||
itself.
|
||||
|
||||
:::{.note}
|
||||
This has the same options as {option}`systemd.user.services.<name>`
|
||||
but without certain options from stage 2 counterparts such as
|
||||
`reloadTriggers` and `restartTriggers`.
|
||||
|
||||
By default, this module sets the service unit as part of the respective
|
||||
target unit (i.e., `PartOf=$COMPONENTID.target`).
|
||||
|
||||
On a typical case, you shouldn't mess with much of the dependency
|
||||
ordering with the service unit. You should configure `targetUnit` for
|
||||
that instead.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
extraArgs = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
description = ''
|
||||
A list of arguments from {program}`sessiond` to be added for the session
|
||||
script.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
[
|
||||
"--hooksd=''${./config/sessiond/hooks.d}"
|
||||
]
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Append the session argument.
|
||||
config = {
|
||||
extraArgs = lib.optional (config.settings != { }) "--config=${sessionSettingsFile}";
|
||||
|
||||
targetUnit = {
|
||||
description = config.description;
|
||||
requires = [ "${name}.service" ];
|
||||
wants =
|
||||
let
|
||||
componentTargetUnits =
|
||||
lib.mapAttrsToList (_: component: "${component.id}.target") config.components;
|
||||
in
|
||||
componentTargetUnits;
|
||||
};
|
||||
|
||||
serviceUnit = {
|
||||
description = config.description;
|
||||
partOf = [ "${name}.target" ];
|
||||
before = [ "${name}.target" ];
|
||||
requisite = [ "${name}.target" ];
|
||||
requires = [ "dbus.socket" ];
|
||||
after = [ "dbus.socket" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = lib.mkForce "dbus";
|
||||
BusName = lib.mkForce "org.sessiond.session1";
|
||||
ExecStart = lib.mkForce "${lib.getExe' sessiondPkg "sessiond"} ${lib.concatStringsSep " " config.extraArgs}";
|
||||
Restart = "always";
|
||||
};
|
||||
|
||||
unitConfig = {
|
||||
RefuseManualStart = true;
|
||||
RefuseManualStop = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user