Compare commits
10 Commits
Author | SHA1 | Date |
---|---|---|
Colin | b129321af9 | |
Colin | a5c36d39f4 | |
Colin | 787e6af646 | |
Colin | 4fd2db4e51 | |
Colin | ac04dc639f | |
Colin | 4a2eb7ebec | |
Colin | b8bf763c11 | |
Colin | b82a31a3ac | |
Colin | c8e5edd61a | |
Colin | 1b4a6207e2 |
|
@ -25,6 +25,7 @@
|
||||||
./deadd-notification-center
|
./deadd-notification-center
|
||||||
./dialect.nix
|
./dialect.nix
|
||||||
./dino.nix
|
./dino.nix
|
||||||
|
./dissent.nix
|
||||||
./element-desktop.nix
|
./element-desktop.nix
|
||||||
./epiphany.nix
|
./epiphany.nix
|
||||||
./evince.nix
|
./evince.nix
|
||||||
|
@ -50,7 +51,6 @@
|
||||||
./gpodder.nix
|
./gpodder.nix
|
||||||
./grimshot.nix
|
./grimshot.nix
|
||||||
./gthumb.nix
|
./gthumb.nix
|
||||||
./gtkcord4.nix
|
|
||||||
./handbrake.nix
|
./handbrake.nix
|
||||||
./helix.nix
|
./helix.nix
|
||||||
./imagemagick.nix
|
./imagemagick.nix
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
# - notification sounds can be handled by swaync
|
# - notification sounds can be handled by swaync
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
cfg = config.sane.programs.gtkcord4;
|
cfg = config.sane.programs.dissent;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sane.programs.gtkcord4 = {
|
sane.programs.dissent = {
|
||||||
configOption = with lib; mkOption {
|
configOption = with lib; mkOption {
|
||||||
default = {};
|
default = {};
|
||||||
type = types.submodule {
|
type = types.submodule {
|
||||||
|
@ -17,15 +17,15 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
packageUnwrapped = pkgs.gtkcord4.overrideAttrs (upstream: {
|
packageUnwrapped = pkgs.dissent.overrideAttrs (upstream: {
|
||||||
postConfigure = (upstream.postConfigure or "") + ''
|
postConfigure = (upstream.postConfigure or "") + ''
|
||||||
# gtkcord4 uses go-keyring to interface with the org.freedesktop.secrets provider (i.e. gnome-keyring).
|
# dissent uses go-keyring to interface with the org.freedesktop.secrets provider (i.e. gnome-keyring).
|
||||||
# go-keyring hardcodes `login.keyring` as the keyring to store secrets in, instead of reading `~/.local/share/keyring/default`.
|
# go-keyring hardcodes `login.keyring` as the keyring to store secrets in, instead of reading `~/.local/share/keyring/default`.
|
||||||
# `login.keyring` seems to be a special keyring preconfigured (by gnome-keyring) to encrypt everything to the user's password.
|
# `login.keyring` seems to be a special keyring preconfigured (by gnome-keyring) to encrypt everything to the user's password.
|
||||||
# that's redundant with my fs-level encryption and makes the keyring less inspectable,
|
# that's redundant with my fs-level encryption and makes the keyring less inspectable,
|
||||||
# so patch gtkcord4 to use Default_keyring instead.
|
# so patch dissent to use Default_keyring instead.
|
||||||
# see:
|
# see:
|
||||||
# - <https://github.com/diamondburned/gtkcord4/issues/139>
|
# - <https://github.com/diamondburned/dissent/issues/139>
|
||||||
# - <https://github.com/zalando/go-keyring/issues/46>
|
# - <https://github.com/zalando/go-keyring/issues/46>
|
||||||
substituteInPlace vendor/github.com/zalando/go-keyring/secret_service/secret_service.go \
|
substituteInPlace vendor/github.com/zalando/go-keyring/secret_service/secret_service.go \
|
||||||
--replace '"login"' '"Default_keyring"'
|
--replace '"login"' '"Default_keyring"'
|
||||||
|
@ -51,18 +51,17 @@ in
|
||||||
];
|
];
|
||||||
|
|
||||||
persist.byStore.private = [
|
persist.byStore.private = [
|
||||||
".cache/gtkcord4"
|
".cache/dissent"
|
||||||
".config/gtkcord4" # empty?
|
".config/dissent" # empty?
|
||||||
];
|
];
|
||||||
|
|
||||||
services.gtkcord4 = {
|
services.dissent = {
|
||||||
description = "gtkcord4 Discord client";
|
description = "dissent Discord client";
|
||||||
after = [ "graphical-session.target" ];
|
after = [ "graphical-session.target" ];
|
||||||
# partOf = [ "graphical-session.target" ];
|
|
||||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${cfg.package}/bin/gtkcord4";
|
ExecStart = "${cfg.package}/bin/dissent";
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
RestartSec = "20s";
|
RestartSec = "20s";
|
|
@ -59,9 +59,9 @@ in
|
||||||
|
|
||||||
persist.byStore.private = [
|
persist.byStore.private = [
|
||||||
# XXX by default fractal stores its state in ~/.local/share/<build-profile>/<UUID>.
|
# XXX by default fractal stores its state in ~/.local/share/<build-profile>/<UUID>.
|
||||||
".local/share/hack" # for debug-like builds
|
# ".local/share/hack" # for debug-like builds
|
||||||
".local/share/stable" # for normal releases
|
# ".local/share/stable" # for normal releases
|
||||||
".local/share/fractal" # for version 5+, i think?
|
".local/share/fractal" # for version 5+
|
||||||
];
|
];
|
||||||
|
|
||||||
suggestedPrograms = [ "gnome-keyring" ];
|
suggestedPrograms = [ "gnome-keyring" ];
|
||||||
|
|
|
@ -8,7 +8,8 @@ in
|
||||||
sandbox.method = "bwrap";
|
sandbox.method = "bwrap";
|
||||||
sandbox.whitelistDbus = [ "user" ];
|
sandbox.whitelistDbus = [ "user" ];
|
||||||
sandbox.extraRuntimePaths = [
|
sandbox.extraRuntimePaths = [
|
||||||
"keyring/control"
|
"keyring" #< only needs keyring/control, but has to *create* that.
|
||||||
|
# "keyring/control"
|
||||||
];
|
];
|
||||||
sandbox.capabilities = [
|
sandbox.capabilities = [
|
||||||
# ipc_lock: used to `mlock` the secrets so they don't get swapped out.
|
# ipc_lock: used to `mlock` the secrets so they don't get swapped out.
|
||||||
|
@ -54,6 +55,7 @@ in
|
||||||
wantedBy = [ "graphical-session.target" ];
|
wantedBy = [ "graphical-session.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${cfg.package}/bin/gnome-keyring-daemon --start --foreground --components=secrets";
|
ExecStart = "${cfg.package}/bin/gnome-keyring-daemon --start --foreground --components=secrets";
|
||||||
|
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -m 0700 -p %t/keyring";
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
RestartSec = "20s";
|
RestartSec = "20s";
|
||||||
|
|
|
@ -164,7 +164,7 @@ assign [app_id="im.dino.Dino"] workspace number 1
|
||||||
assign [app_id="org.gnome.Fractal"] workspace number 1
|
assign [app_id="org.gnome.Fractal"] workspace number 1
|
||||||
assign [app_id="geary"] workspace number 1
|
assign [app_id="geary"] workspace number 1
|
||||||
assign [app_id="signal"] workspace number 1
|
assign [app_id="signal"] workspace number 1
|
||||||
assign [app_id="so.libdb.gtkcord4"] workspace number 1
|
assign [app_id="so.libdb.dissent"] workspace number 1
|
||||||
assign [app_id="abaddon"] workspace number 1
|
assign [app_id="abaddon"] workspace number 1
|
||||||
|
|
||||||
# window display settings
|
# window display settings
|
||||||
|
|
|
@ -250,7 +250,7 @@ in
|
||||||
|
|
||||||
incoming-im-known-app-name = {
|
incoming-im-known-app-name = {
|
||||||
# trigger notification sound on behalf of these IM clients.
|
# trigger notification sound on behalf of these IM clients.
|
||||||
app-name = "(Chats|Dino|discord|Element|Fractal|gtkcord4)";
|
app-name = "(Chats|Dino|discord|dissent|Element|Fractal)";
|
||||||
body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls
|
body = "^(?!Incoming call).*$"; #< don't match Dino Incoming calls
|
||||||
exec = "${fbcli} --event proxied-message-new-instant";
|
exec = "${fbcli} --event proxied-message-new-instant";
|
||||||
};
|
};
|
||||||
|
@ -403,7 +403,7 @@ in
|
||||||
active = true;
|
active = true;
|
||||||
}
|
}
|
||||||
# ] ++ lib.optionals config.sane.programs.abaddon.enabled [
|
# ] ++ lib.optionals config.sane.programs.abaddon.enabled [
|
||||||
# # XXX: disabled in favor of gtkcord4: abaddon has troubles auto-connecting at start
|
# # XXX: disabled in favor of dissent: abaddon has troubles auto-connecting at start
|
||||||
# {
|
# {
|
||||||
# type = "toggle";
|
# type = "toggle";
|
||||||
# label = ""; # Discord chat client; icons: , 🎮
|
# label = ""; # Discord chat client; icons: , 🎮
|
||||||
|
@ -411,12 +411,12 @@ in
|
||||||
# update-command = "${printIsActive}/bin/print-is-active --user abaddon";
|
# update-command = "${printIsActive}/bin/print-is-active --user abaddon";
|
||||||
# active = true;
|
# active = true;
|
||||||
# }
|
# }
|
||||||
] ++ lib.optionals config.sane.programs.gtkcord4.enabled [
|
] ++ lib.optionals config.sane.programs.dissent.enabled [
|
||||||
{
|
{
|
||||||
type = "toggle";
|
type = "toggle";
|
||||||
label = ""; # Discord chat client; icons: , 🎮
|
label = ""; # Discord chat client; icons: , 🎮
|
||||||
command = "${systemctl-toggle}/bin/systemctl-toggle --user gtkcord4";
|
command = "${systemctl-toggle}/bin/systemctl-toggle --user dissent";
|
||||||
update-command = "${printIsActive}/bin/print-is-active --user gtkcord4";
|
update-command = "${printIsActive}/bin/print-is-active --user dissent";
|
||||||
active = true;
|
active = true;
|
||||||
}
|
}
|
||||||
] ++ lib.optionals config.sane.programs.signal-desktop.enabled [
|
] ++ lib.optionals config.sane.programs.signal-desktop.enabled [
|
||||||
|
|
|
@ -60,6 +60,7 @@ in
|
||||||
"delfin" # Jellyfin client
|
"delfin" # Jellyfin client
|
||||||
"dialect" # language translation
|
"dialect" # language translation
|
||||||
"dino" # XMPP client
|
"dino" # XMPP client
|
||||||
|
"dissent" # Discord client (formerly known as: gtkcord4)
|
||||||
# "emote"
|
# "emote"
|
||||||
# "evince" # PDF viewer
|
# "evince" # PDF viewer
|
||||||
# "flare-signal" # gtk4 signal client
|
# "flare-signal" # gtk4 signal client
|
||||||
|
@ -82,7 +83,6 @@ in
|
||||||
"gnome-frog" # OCR/QR decoder
|
"gnome-frog" # OCR/QR decoder
|
||||||
"gpodder"
|
"gpodder"
|
||||||
# "gthumb"
|
# "gthumb"
|
||||||
"gtkcord4" # Discord client. 2023/11/21: disabled because v0.0.12 leaks memory
|
|
||||||
# "lemoa" # lemmy app
|
# "lemoa" # lemmy app
|
||||||
"libnotify" # for notify-send; debugging
|
"libnotify" # for notify-send; debugging
|
||||||
# "lollypop"
|
# "lollypop"
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
./services
|
./services
|
||||||
./sops.nix
|
./sops.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./users.nix
|
./users
|
||||||
./vpn.nix
|
./vpn.nix
|
||||||
./warnings.nix
|
./warnings.nix
|
||||||
./wowlan.nix
|
./wowlan.nix
|
||||||
|
|
|
@ -255,18 +255,12 @@ let
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
services = mkOption {
|
services = mkOption {
|
||||||
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
|
type = types.attrsOf types.anything; # options.sane.users.value.type;
|
||||||
# type = utils.systemdUtils.types.services;
|
|
||||||
# map to listOf attrs so that we can allow multiple assigners to the same service
|
|
||||||
# w/o worrying about merging at this layer, and defer merging to modules/users instead.
|
|
||||||
type = types.attrsOf (types.coercedTo types.attrs (a: [ a ]) (types.listOf types.attrs));
|
|
||||||
default = {};
|
default = {};
|
||||||
description = ''
|
description = ''
|
||||||
systemd services to define if this package is enabled.
|
user services to define if this package is enabled.
|
||||||
currently only defines USER services -- acts as noop for root-enabled packages.
|
acts as noop for root-enabled packages.
|
||||||
|
see `sane.users.<user>.services` for options;
|
||||||
conventions are similar to `systemd.services` or `sane.users.<user>.services`.
|
|
||||||
the type at this level is obscured only to as to allow passthrough to `sane.users` w/ proper option merging
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
slowToBuild = mkOption {
|
slowToBuild = mkOption {
|
||||||
|
@ -542,8 +536,7 @@ let
|
||||||
|
|
||||||
# conditionally persist relevant user dirs and create files
|
# conditionally persist relevant user dirs and create files
|
||||||
sane.users = lib.mapAttrs (user: en: lib.optionalAttrs (en && p.enabled) {
|
sane.users = lib.mapAttrs (user: en: lib.optionalAttrs (en && p.enabled) {
|
||||||
inherit (p) persist;
|
inherit (p) persist services;
|
||||||
services = lib.mapAttrs (_: lib.mkMerge) p.services;
|
|
||||||
environment = p.env;
|
environment = p.env;
|
||||||
fs = lib.mkMerge [
|
fs = lib.mkMerge [
|
||||||
p.fs
|
p.fs
|
||||||
|
|
|
@ -1,13 +1,103 @@
|
||||||
{ config, lib, options, sane-lib, utils, ... }:
|
{ config, lib, options, sane-lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (builtins) attrValues;
|
|
||||||
inherit (lib) count mapAttrs' mapAttrsToList mkIf mkMerge mkOption types;
|
|
||||||
sane-user-cfg = config.sane.user;
|
sane-user-cfg = config.sane.user;
|
||||||
cfg = config.sane.users;
|
cfg = config.sane.users;
|
||||||
path-lib = sane-lib.path;
|
path-lib = sane-lib.path;
|
||||||
userOptions = {
|
serviceType = with lib; types.submodule {
|
||||||
options = {
|
options = {
|
||||||
|
# these aoptions are mostly copied from systemd. could be improved.
|
||||||
|
description = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
documentation = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
references and links for where to find documentation about this service.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
after = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
bindsTo = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
before = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
wantedBy = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
wants = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
script = mkOption {
|
||||||
|
type = types.nullOr types.lines;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
environment = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
environment variables to set within the service.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
serviceConfig.Type = mkOption {
|
||||||
|
type = types.enum [ "dbus" "oneshot" "simple" ];
|
||||||
|
};
|
||||||
|
serviceConfig.ExecStart = mkOption {
|
||||||
|
type = types.nullOr (types.coercedTo types.package toString types.str);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
serviceConfig.ExecStartPre = mkOption {
|
||||||
|
type = types.nullOr (types.coercedTo types.package toString types.str);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
serviceConfig.ExecStartPost = mkOption {
|
||||||
|
type = types.nullOr (types.coercedTo types.package toString types.str);
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
serviceConfig.ExecStopPost = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
serviceConfig.BusName = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
name of the dbus name this service is expected to register.
|
||||||
|
only once the name is registered will the service be considered "active".
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
serviceConfig.RemainAfterExit = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
serviceConfig.Restart = mkOption {
|
||||||
|
type = types.nullOr (types.enum [ "always" "on-failure" ]);
|
||||||
|
default = null;
|
||||||
|
# N.B.: systemd doesn't allow always/on-failure for Type="oneshot" services
|
||||||
|
};
|
||||||
|
serviceConfig.RestartSec = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "20s";
|
||||||
|
};
|
||||||
|
unitConfig.ConditionEnvironment = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
userOptions = {
|
||||||
|
options = with lib; {
|
||||||
fs = mkOption {
|
fs = mkOption {
|
||||||
# map to listOf attrs so that we can allow multiple assigners to the same path
|
# map to listOf attrs so that we can allow multiple assigners to the same path
|
||||||
# w/o worrying about merging at this layer, and defer merging to modules/fs instead.
|
# w/o worrying about merging at this layer, and defer merging to modules/fs instead.
|
||||||
|
@ -50,16 +140,16 @@ let
|
||||||
# type = utils.systemdUtils.types.services;
|
# type = utils.systemdUtils.types.services;
|
||||||
# `utils.systemdUtils.types.services` is nearly what we want, but remove `stage2ServiceConfig`,
|
# `utils.systemdUtils.types.services` is nearly what we want, but remove `stage2ServiceConfig`,
|
||||||
# as we don't want to force a PATH for every service.
|
# as we don't want to force a PATH for every service.
|
||||||
type = types.attrsOf (types.submodule [ utils.systemdUtils.unitOptions.stage2ServiceOptions utils.systemdUtils.lib.unitConfig ]);
|
type = types.attrsOf serviceType;
|
||||||
default = {};
|
default = {};
|
||||||
description = ''
|
description = ''
|
||||||
systemd user-services to define for this user.
|
services to define for this user.
|
||||||
populates files in ~/.config/systemd.
|
populates files in ~/.config/systemd.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
defaultUserOptions = {
|
defaultUserOptions = with lib; {
|
||||||
options = userOptions.options // {
|
options = userOptions.options // {
|
||||||
services = mkOption {
|
services = mkOption {
|
||||||
# type = utils.systemdUtils.types.services;
|
# type = utils.systemdUtils.types.services;
|
||||||
|
@ -71,7 +161,9 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
userModule = let nixConfig = config; in types.submodule ({ name, config, ... }: {
|
userModule = let
|
||||||
|
nixConfig = config;
|
||||||
|
in with lib; types.submodule ({ name, config, ... }: {
|
||||||
options = userOptions.options // {
|
options = userOptions.options // {
|
||||||
default = mkOption {
|
default = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
|
@ -92,7 +184,7 @@ let
|
||||||
|
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
# if we're the default user, inherit whatever settings were routed to the default user
|
# if we're the default user, inherit whatever settings were routed to the default user
|
||||||
(mkIf config.default {
|
(lib.mkIf config.default {
|
||||||
inherit (sane-user-cfg) fs persist environment;
|
inherit (sane-user-cfg) fs persist environment;
|
||||||
services = lib.mapAttrs (_: lib.mkMerge) sane-user-cfg.services;
|
services = lib.mapAttrs (_: lib.mkMerge) sane-user-cfg.services;
|
||||||
})
|
})
|
||||||
|
@ -126,55 +218,11 @@ let
|
||||||
done
|
done
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
{
|
|
||||||
fs = lib.mkMerge (mapAttrsToList (serviceName: value:
|
|
||||||
let
|
|
||||||
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
|
|
||||||
# see: <repo:nix-community/home-manager:modules/systemd.nix>
|
|
||||||
cleanName = utils.systemdUtils.lib.mkPathSafeName serviceName;
|
|
||||||
generatedUnit = utils.systemdUtils.lib.serviceToUnit serviceName (value // {
|
|
||||||
environment = lib.throwIf (value.path != []) "user service ${serviceName} specifies unsupported 'path' attribute (${builtins.toString value.path})" {
|
|
||||||
# clear PATH to allow inheriting it from environment.
|
|
||||||
# otherwise, nixos would force it to `systemd.globalEnvironment.PATH`, which is mostly tools like sed/find/etc.
|
|
||||||
# clearing PATH here allows user services to inherit whatever PATH the graphical session sets
|
|
||||||
# (see `dbus-update-activation-environment` call in ~/.config/sway/config),
|
|
||||||
# which is critical to making it so user services can see user *programs*/packages.
|
|
||||||
#
|
|
||||||
# note that systemd provides no way to *append* to the PATH, only to override it (or not).
|
|
||||||
# nor do they intend to ever support that:
|
|
||||||
# - <https://github.com/systemd/systemd/issues/1082>
|
|
||||||
PATH = null;
|
|
||||||
} // (value.environment or {});
|
|
||||||
});
|
|
||||||
#^ generatedUnit contains keys:
|
|
||||||
# - text
|
|
||||||
# - aliases (IGNORED)
|
|
||||||
# - wantedBy
|
|
||||||
# - requiredBy
|
|
||||||
# - enabled (IGNORED)
|
|
||||||
# - overrideStrategy (IGNORED)
|
|
||||||
# TODO: error if one of the above ignored fields are set
|
|
||||||
symlinkData = {
|
|
||||||
text = generatedUnit.text;
|
|
||||||
targetName = "${cleanName}.service"; # systemd derives unit name from symlink target
|
|
||||||
};
|
|
||||||
serviceEntry = {
|
|
||||||
".config/systemd/user/${serviceName}.service".symlink = symlinkData;
|
|
||||||
};
|
|
||||||
wants = builtins.map (wantedBy: {
|
|
||||||
".config/systemd/user/${wantedBy}.wants/${serviceName}.service".symlink = symlinkData;
|
|
||||||
}) generatedUnit.wantedBy;
|
|
||||||
requires = builtins.map (requiredBy: {
|
|
||||||
".config/systemd/user/${requiredBy}.requires/${serviceName}.service".symlink = symlinkData;
|
|
||||||
}) generatedUnit.requiredBy;
|
|
||||||
in lib.mkMerge ([ serviceEntry ] ++ wants ++ requires)
|
|
||||||
) config.services);
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
processUser = user: defn:
|
processUser = user: defn:
|
||||||
let
|
let
|
||||||
prefixWithHome = mapAttrs' (path: value: {
|
prefixWithHome = lib.mapAttrs' (path: value: {
|
||||||
name = path-lib.concat [ defn.home path ];
|
name = path-lib.concat [ defn.home path ];
|
||||||
inherit value;
|
inherit value;
|
||||||
});
|
});
|
||||||
|
@ -192,7 +240,12 @@ let
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
imports = [
|
||||||
|
./s6-rc.nix
|
||||||
|
./systemd.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
options = with lib; {
|
||||||
sane.users = mkOption {
|
sane.users = mkOption {
|
||||||
type = types.attrsOf userModule;
|
type = types.attrsOf userModule;
|
||||||
default = {};
|
default = {};
|
||||||
|
@ -224,14 +277,14 @@ in
|
||||||
};
|
};
|
||||||
config =
|
config =
|
||||||
let
|
let
|
||||||
configs = mapAttrsToList processUser cfg;
|
configs = lib.mapAttrsToList processUser cfg;
|
||||||
num-default-users = count (u: u.default) (attrValues cfg);
|
num-default-users = lib.count (u: u.default) (lib.attrValues cfg);
|
||||||
take = f: {
|
take = f: {
|
||||||
sane.fs = f.sane.fs;
|
sane.fs = f.sane.fs;
|
||||||
sane.persist.sys.byPath = f.sane.persist.sys.byPath;
|
sane.persist.sys.byPath = f.sane.persist.sys.byPath;
|
||||||
sane.defaultUser = f.sane.defaultUser;
|
sane.defaultUser = f.sane.defaultUser;
|
||||||
};
|
};
|
||||||
in mkMerge [
|
in lib.mkMerge [
|
||||||
(take (sane-lib.mkTypedMerge take configs))
|
(take (sane-lib.mkTypedMerge take configs))
|
||||||
{
|
{
|
||||||
assertions = [
|
assertions = [
|
|
@ -0,0 +1,137 @@
|
||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
normalizeName = name: lib.removeSuffix ".service" (lib.removeSuffix ".target" name);
|
||||||
|
|
||||||
|
# infers the service type from the arguments and dispatches appropriately
|
||||||
|
genService = { name, run, depends }: if run != null then
|
||||||
|
genService' (normalizeName name) "longrun" depends [
|
||||||
|
(pkgs.writeTextFile {
|
||||||
|
name = "s6-${name}-run";
|
||||||
|
destination = "/${normalizeName name}/run";
|
||||||
|
executable = true;
|
||||||
|
# text = run;
|
||||||
|
text = ''
|
||||||
|
#!/bin/sh
|
||||||
|
${run}
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
]
|
||||||
|
else
|
||||||
|
# TODO: a bundle can totally have dependencies. i can't just map them *all* to contents.
|
||||||
|
# genService' (normalizeName name) "bundle" [] (
|
||||||
|
# (builtins.map
|
||||||
|
# (d: pkgs.writeTextFile {
|
||||||
|
# name = "s6-${name}-contains-${d}";
|
||||||
|
# destination = "/${normalizeName name}/contents.d/${normalizeName d}";
|
||||||
|
# text = "";
|
||||||
|
# })
|
||||||
|
# depends
|
||||||
|
# ) ++ [
|
||||||
|
# # in case the bundle has no contents, ensure `contents.d` still gets made
|
||||||
|
# (pkgs.runCommandLocal "s6-${name}-contains.d" {} ''
|
||||||
|
# mkdir -p $out/"${normalizeName name}"/contents.d
|
||||||
|
# '')
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
genService' (normalizeName name) "bundle" [] [
|
||||||
|
(pkgs.writeTextFile {
|
||||||
|
name = "s6-${name}-contents";
|
||||||
|
destination = "/${normalizeName name}/contents";
|
||||||
|
text = lib.concatStringsSep "\n" (builtins.map normalizeName depends);
|
||||||
|
})
|
||||||
|
]
|
||||||
|
;
|
||||||
|
genService' = name: type: depends: others: pkgs.symlinkJoin {
|
||||||
|
name = "s6-${name}";
|
||||||
|
paths = others ++ [
|
||||||
|
(pkgs.writeTextFile {
|
||||||
|
name = "s6-${name}-type";
|
||||||
|
destination = "/${name}/type";
|
||||||
|
text = type;
|
||||||
|
})
|
||||||
|
] ++ builtins.map
|
||||||
|
(d: pkgs.writeTextFile {
|
||||||
|
name = "s6-${name}-depends-${d}";
|
||||||
|
destination = "/${name}/dependencies.d/${normalizeName d}";
|
||||||
|
text = "";
|
||||||
|
})
|
||||||
|
depends;
|
||||||
|
};
|
||||||
|
|
||||||
|
# create a directory, containing N subdirectories:
|
||||||
|
# - svc-a/
|
||||||
|
# - type
|
||||||
|
# - run
|
||||||
|
# - svc-b/
|
||||||
|
# - type
|
||||||
|
# - run
|
||||||
|
# - ...
|
||||||
|
genServices = svcs: pkgs.symlinkJoin {
|
||||||
|
name = "s6-user-services";
|
||||||
|
paths = builtins.map genService svcs;
|
||||||
|
};
|
||||||
|
|
||||||
|
# output is a directory containing:
|
||||||
|
# - db
|
||||||
|
# - lock
|
||||||
|
# - n
|
||||||
|
# - resolve.cdb
|
||||||
|
# - servicedirs/
|
||||||
|
# - svc-a/
|
||||||
|
# - svc-b/
|
||||||
|
# - ...
|
||||||
|
#
|
||||||
|
# this can then be used by s6-rc-init, like:
|
||||||
|
# s6-svscan scandir &
|
||||||
|
# s6-rc-init -c $compiled -l $PWD/live -d $PWD/scandir
|
||||||
|
# s6-rc -l $PWD/live start svc-a
|
||||||
|
#
|
||||||
|
# N.B.: it seems the $compiled dir needs to be rw, for s6 to write lock files within it.
|
||||||
|
# so `cp` and `chmod -R 600` it, first.
|
||||||
|
compileServices = sources: with pkgs; stdenv.mkDerivation {
|
||||||
|
name = "s6-user-services";
|
||||||
|
src = sources;
|
||||||
|
nativeBuildInputs = [ s6-rc ];
|
||||||
|
buildPhase = ''
|
||||||
|
s6-rc-compile $out $src
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# transform the `user.services` attrset into a s6 services list.
|
||||||
|
s6SvcsFromConfigServices = services: lib.mapAttrsToList
|
||||||
|
(name: service: {
|
||||||
|
inherit name;
|
||||||
|
run = service.serviceConfig.ExecStart;
|
||||||
|
depends = service.wants ++ builtins.attrNames (
|
||||||
|
lib.filterAttrs (_: cfg: lib.elem name cfg.wantedBy) services
|
||||||
|
);
|
||||||
|
})
|
||||||
|
services
|
||||||
|
;
|
||||||
|
|
||||||
|
# in the systemd service management, these targets are implicitly defined and used
|
||||||
|
# to accomplish something like run-levels, or service groups.
|
||||||
|
# map them onto s6 "bundles". their contents are determined via reverse dependency mapping (`wantedBy` of every other service).
|
||||||
|
implicitServices = {
|
||||||
|
"default.target" = {
|
||||||
|
serviceConfig.ExecStart = null;
|
||||||
|
wants = [];
|
||||||
|
wantedBy = [];
|
||||||
|
};
|
||||||
|
"graphical-session.target" = {
|
||||||
|
serviceConfig.ExecStart = null;
|
||||||
|
wants = [];
|
||||||
|
wantedBy = [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.sane.users = with lib; mkOption {
|
||||||
|
type = types.attrsOf (types.submodule ({ config, ...}: let
|
||||||
|
sources = genServices (s6SvcsFromConfigServices (implicitServices // config.services));
|
||||||
|
in {
|
||||||
|
fs.".config/s6/sources".symlink.target = sources;
|
||||||
|
fs.".config/s6/compiled".symlink.target = compileServices sources;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
{ lib, utils, ... }:
|
||||||
|
let
|
||||||
|
# see: <repo:nixos/nixpkgs:nixos/lib/utils.nix>
|
||||||
|
# see: <repo:nix-community/home-manager:modules/systemd.nix>
|
||||||
|
mkUnit = serviceName: value: utils.systemdUtils.lib.serviceToUnit serviceName {
|
||||||
|
inherit (value)
|
||||||
|
script
|
||||||
|
wantedBy
|
||||||
|
;
|
||||||
|
serviceConfig = lib.filterAttrs (_: v: v != null) value.serviceConfig;
|
||||||
|
unitConfig = {
|
||||||
|
inherit (value.unitConfig)
|
||||||
|
ConditionEnvironment
|
||||||
|
;
|
||||||
|
After = value.after;
|
||||||
|
Before = value.before;
|
||||||
|
BindsTo = value.bindsTo;
|
||||||
|
Description = value.description;
|
||||||
|
Documentation = value.documentation;
|
||||||
|
Wants = value.wants;
|
||||||
|
};
|
||||||
|
environment = {
|
||||||
|
# clear PATH to allow inheriting it from environment.
|
||||||
|
# otherwise, nixos would force it to `systemd.globalEnvironment.PATH`, which is mostly tools like sed/find/etc.
|
||||||
|
# clearing PATH here allows user services to inherit whatever PATH the graphical session sets
|
||||||
|
# (see `dbus-update-activation-environment` call in ~/.config/sway/config),
|
||||||
|
# which is critical to making it so user services can see user *programs*/packages.
|
||||||
|
#
|
||||||
|
# note that systemd provides no way to *append* to the PATH, only to override it (or not).
|
||||||
|
# nor do they intend to ever support that:
|
||||||
|
# - <https://github.com/systemd/systemd/issues/1082>
|
||||||
|
PATH = null;
|
||||||
|
} // (value.environment or {});
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# create fs entries for every service, in the systemd user dir.
|
||||||
|
options.sane.users = with lib; mkOption {
|
||||||
|
type = types.attrsOf (types.submodule ({ config, ...}: {
|
||||||
|
fs = lib.concatMapAttrs
|
||||||
|
(serviceName: value: let
|
||||||
|
cleanName = utils.systemdUtils.lib.mkPathSafeName serviceName;
|
||||||
|
generatedUnit = mkUnit serviceName value;
|
||||||
|
#^ generatedUnit contains keys:
|
||||||
|
# - text
|
||||||
|
# - aliases (IGNORED)
|
||||||
|
# - wantedBy
|
||||||
|
# - requiredBy
|
||||||
|
# - enabled (IGNORED)
|
||||||
|
# - overrideStrategy (IGNORED)
|
||||||
|
# TODO: error if one of the above ignored fields are set
|
||||||
|
symlinkData = {
|
||||||
|
text = generatedUnit.text;
|
||||||
|
targetName = "${cleanName}.service"; # systemd derives unit name from symlink target
|
||||||
|
};
|
||||||
|
serviceEntry = {
|
||||||
|
".config/systemd/user/${serviceName}.service".symlink = symlinkData;
|
||||||
|
};
|
||||||
|
wants = builtins.map (wantedBy: {
|
||||||
|
".config/systemd/user/${wantedBy}.wants/${serviceName}.service".symlink = symlinkData;
|
||||||
|
}) generatedUnit.wantedBy;
|
||||||
|
in
|
||||||
|
lib.mergeAttrsList ([ serviceEntry ] ++ wants)
|
||||||
|
)
|
||||||
|
config.services
|
||||||
|
;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue