hakurei/nixos.nix
Ophestra c9facb746b
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m10s
Test / Hpkg (push) Successful in 4m5s
Test / Sandbox (race detector) (push) Successful in 4m27s
Test / Hakurei (race detector) (push) Successful in 5m7s
Test / Flake checks (push) Successful in 1m28s
hst/config: remove data field, rename dir to home
There is no reason to give the home directory special treatment, as this behaviour can be quite confusing. The home directory also does not necessarily require its own mount point, it could be provided by a parent or simply be ephemeral.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-08-26 00:56:10 +09:00

349 lines
12 KiB
Nix

packages:
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
lists
attrsets
mkMerge
mkIf
mapAttrs
foldlAttrs
optional
optionals
;
cfg = config.environment.hakurei;
getsubuid = fid: aid: 1000000 + fid * 10000 + aid;
getsubname = fid: aid: "u${toString fid}_a${toString aid}";
getsubhome = fid: aid: "${cfg.stateDir}/u${toString fid}/a${toString aid}";
in
{
imports = [ (import ./options.nix packages) ];
config = mkIf cfg.enable {
assertions = [
(
let
conflictingApps = foldlAttrs (
acc: id: app:
(
acc
++ foldlAttrs (
acc': id': app':
if id == id' || app.shareUid && app'.shareUid || app.identity != app'.identity then acc' else acc' ++ [ id ]
) [ ] cfg.apps
)
) [ ] cfg.apps;
in
{
assertion = (lists.length conflictingApps) == 0;
message = "the following hakurei apps have conflicting identities: " + (builtins.concatStringsSep ", " conflictingApps);
}
)
];
security.wrappers.hsu = {
source = "${cfg.hsuPackage}/bin/hsu";
setuid = true;
owner = "root";
setgid = true;
group = "root";
};
environment.etc.hsurc = {
mode = "0400";
text = foldlAttrs (
acc: username: fid:
"${toString config.users.users.${username}.uid} ${toString fid}\n" + acc
) "" cfg.users;
};
home-manager =
let
privPackages = mapAttrs (username: fid: {
home.packages = foldlAttrs (
acc: id: app:
[
(
let
extendDBusDefault = id: ext: {
filter = true;
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
own = [
"${id}.*"
"org.mpris.MediaPlayer2.${id}.*"
]
++ ext.own;
inherit (ext) call broadcast;
};
dbusConfig =
let
default = {
talk = [ ];
own = [ ];
call = { };
broadcast = { };
};
in
{
session_bus = if app.dbus.session != null then (app.dbus.session (extendDBusDefault id)) else (extendDBusDefault id default);
system_bus = app.dbus.system;
};
command = if app.command == null then app.name else app.command;
script = if app.script == null then ("exec " + command + " $@") else app.script;
isGraphical = if app.gpu != null then app.gpu else app.enablements.wayland || app.enablements.x11;
conf = {
path =
if app.path == null then
pkgs.writeScript "${app.name}-start" ''
#!${pkgs.zsh}${pkgs.zsh.shellPath}
${script}
''
else
app.path;
args = if app.args == null then [ "${app.name}-start" ] else app.args;
inherit id;
inherit (dbusConfig) session_bus system_bus;
direct_wayland = app.insecureWayland;
username = getsubname fid app.identity;
home = getsubhome fid app.identity;
inherit (cfg) shell;
inherit (app) identity groups enablements;
container = {
inherit (app)
wait_delay
devel
userns
device
tty
multiarch
env
;
map_real_uid = app.mapRealUid;
host_net = app.hostNet;
host_abstract = app.hostAbstract;
filesystem =
let
bind = src: {
type = "bind";
inherit src;
};
optBind = src: {
type = "bind";
inherit src;
optional = true;
};
optDevBind = src: {
type = "bind";
inherit src;
dev = true;
optional = true;
};
in
[
(bind "/bin")
(bind "/usr/bin")
(bind "/nix/store")
(optBind "/sys/block")
(optBind "/sys/bus")
(optBind "/sys/class")
(optBind "/sys/dev")
(optBind "/sys/devices")
]
++ optionals app.nix [
(bind "/nix/var")
]
++ optionals isGraphical [
(optDevBind "/dev/dri")
(optDevBind "/dev/nvidiactl")
(optDevBind "/dev/nvidia-modeset")
(optDevBind "/dev/nvidia-uvm")
(optDevBind "/dev/nvidia-uvm-tools")
(optDevBind "/dev/nvidia0")
]
++ optionals app.useCommonPaths cfg.commonPaths
++ app.extraPaths
++ [
{
type = "bind";
dst = "/etc/";
src = "/etc/";
special = true;
}
{
type = "link";
dst = "/run/current-system";
linkname = "/run/current-system";
dereference = true;
}
]
++ optionals (isGraphical && config.hardware.graphics.enable) (
[
{
type = "link";
dst = "/run/opengl-driver";
linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver"."L+".argument;
}
]
++ optionals (app.multiarch && config.hardware.graphics.enable32Bit) [
{
type = "link";
dst = "/run/opengl-driver-32";
linkname = config.systemd.tmpfiles.settings.graphics-driver."/run/opengl-driver-32"."L+".argument;
}
]
)
++ [
{
type = "bind";
src = getsubhome fid app.identity;
write = true;
ensure = true;
}
];
};
};
checkedConfig =
name: value:
let
file = pkgs.writeText name (builtins.toJSON value);
in
pkgs.runCommand "checked-${name}" { nativeBuildInputs = [ cfg.package ]; } ''
ln -vs ${file} "$out"
hakurei show ${file}
'';
in
pkgs.writeShellScriptBin app.name ''
exec hakurei${if app.verbose then " -v" else ""} app ${checkedConfig "hakurei-app-${app.name}.json" conf} $@
''
)
]
++ (
let
pkg = if app.share != null then app.share else pkgs.${app.name};
copy = source: "[ -d '${source}' ] && cp -Lrv '${source}' $out/share || true";
in
optional (app.enablements.wayland || app.enablements.x11) (
pkgs.runCommand "${app.name}-share" { } ''
mkdir -p $out/share
${copy "${pkg}/share/applications"}
${copy "${pkg}/share/pixmaps"}
${copy "${pkg}/share/icons"}
${copy "${pkg}/share/man"}
if test -d "$out/share/applications"; then
substituteInPlace $out/share/applications/* \
--replace-warn '${pkg}/bin/' "" \
--replace-warn '${pkg}/libexec/' ""
fi
''
)
)
++ acc
) [ cfg.package ] cfg.apps;
}) cfg.users;
in
{
useUserPackages = false; # prevent users.users entries from being added
users =
mkMerge
(foldlAttrs
(
acc: _: fid:
foldlAttrs
(
acc: _: app:
(
let
key = getsubname fid app.identity;
in
{
usernames = acc.usernames // {
${key} = true;
};
merge = acc.merge ++ [
{
${key} = mkMerge (
[
app.extraConfig
{ home.packages = app.packages; }
]
++ lib.optional (!attrsets.hasAttrByPath [ key ] acc.usernames) cfg.extraHomeConfig
);
}
];
}
)
)
{
inherit (acc) usernames;
merge = acc.merge ++ [ { ${getsubname fid 0} = cfg.extraHomeConfig; } ];
}
cfg.apps
)
{
usernames = { };
merge = [ privPackages ];
}
cfg.users
).merge;
};
users =
let
getuser = fid: aid: {
isSystemUser = true;
createHome = true;
description = "Hakurei subordinate user ${toString aid} (u${toString fid})";
group = getsubname fid aid;
home = getsubhome fid aid;
uid = getsubuid fid aid;
};
getgroup = fid: aid: { gid = getsubuid fid aid; };
in
{
users = mkMerge (
foldlAttrs (
acc: _: fid:
acc
++ foldlAttrs (
acc': _: app:
acc' ++ [ { ${getsubname fid app.identity} = getuser fid app.identity; } ]
) [ { ${getsubname fid 0} = getuser fid 0; } ] cfg.apps
) [ ] cfg.users
);
groups = mkMerge (
foldlAttrs (
acc: _: fid:
acc
++ foldlAttrs (
acc': _: app:
acc' ++ [ { ${getsubname fid app.identity} = getgroup fid app.identity; } ]
) [ { ${getsubname fid 0} = getgroup fid 0; } ] cfg.apps
) [ ] cfg.users
);
};
};
}