mirror of
https://github.com/RGBCube/hjem
synced 2025-10-13 13:12:16 +00:00
commit
ee5c671eeb
5 changed files with 390 additions and 16 deletions
|
@ -46,6 +46,8 @@
|
|||
hjem-basic = import ./tests/basic.nix checkArgs;
|
||||
hjem-special-args = import ./tests/special-args.nix checkArgs;
|
||||
hjem-linker = import ./tests/linker.nix checkArgs;
|
||||
hjem-xdg = import ./tests/xdg.nix checkArgs;
|
||||
hjem-xdg-linker = import ./tests/xdg-linker.nix checkArgs;
|
||||
});
|
||||
|
||||
devShells = forAllSystems (system: let
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# be avoided here.
|
||||
{
|
||||
config,
|
||||
options,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
|
@ -189,6 +190,92 @@ in {
|
|||
description = "Files to be managed by Hjem";
|
||||
};
|
||||
|
||||
xdg = {
|
||||
cache = {
|
||||
directory = mkOption {
|
||||
type = path;
|
||||
default = "${cfg.directory}/.cache";
|
||||
defaultText = "$HOME/.cache";
|
||||
description = ''
|
||||
The XDG cache directory for the user, to which files configured in
|
||||
{option}`hjem.users.<name>.xdg.cache.files` will be relative to by default.
|
||||
|
||||
Adds {env}`XDG_CACHE_HOME` to {option}`environment.sessionVariables` for
|
||||
this user if changed.
|
||||
'';
|
||||
};
|
||||
files = mkOption {
|
||||
default = {};
|
||||
type = attrsOf (fileType cfg.xdg.cache.directory);
|
||||
example = {"foo.txt".source = "Hello World";};
|
||||
description = "Cache files to be managed by Hjem";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
directory = mkOption {
|
||||
type = path;
|
||||
default = "${cfg.directory}/.config";
|
||||
defaultText = "$HOME/.config";
|
||||
description = ''
|
||||
The XDG config directory for the user, to which files configured in
|
||||
{option}`hjem.users.<name>.xdg.config.files` will be relative to by default.
|
||||
|
||||
Adds {env}`XDG_CONFIG_HOME` to {option}`environment.sessionVariables` for
|
||||
this user if changed.
|
||||
'';
|
||||
};
|
||||
files = mkOption {
|
||||
default = {};
|
||||
type = attrsOf (fileType cfg.xdg.config.directory);
|
||||
example = {"foo.txt".source = "Hello World";};
|
||||
description = "Config files to be managed by Hjem";
|
||||
};
|
||||
};
|
||||
|
||||
data = {
|
||||
directory = mkOption {
|
||||
type = path;
|
||||
default = "${cfg.directory}/.local/share";
|
||||
defaultText = "$HOME/.local/share";
|
||||
description = ''
|
||||
The XDG data directory for the user, to which files configured in
|
||||
{option}`hjem.users.<name>.xdg.data.files` will be relative to by default.
|
||||
|
||||
Adds {env}`XDG_DATA_HOME` to {option}`environment.sessionVariables` for
|
||||
this user if changed.
|
||||
'';
|
||||
};
|
||||
files = mkOption {
|
||||
default = {};
|
||||
type = attrsOf (fileType cfg.xdg.data.directory);
|
||||
example = {"foo.txt".source = "Hello World";};
|
||||
description = "data files to be managed by Hjem";
|
||||
};
|
||||
};
|
||||
|
||||
state = {
|
||||
directory = mkOption {
|
||||
type = path;
|
||||
default = "${cfg.directory}/.local/state";
|
||||
defaultText = "$HOME/.local/share";
|
||||
description = ''
|
||||
The XDG state directory for the user, to which files configured in
|
||||
{option}`hjem.users.<name>.xdg.state.files` will be relative to by default.
|
||||
|
||||
Adds {env}`XDG_STATE_HOME` to {option}`environment.sessionVariables` for
|
||||
this user if changed.
|
||||
'';
|
||||
};
|
||||
files = mkOption {
|
||||
default = {};
|
||||
type = attrsOf (fileType cfg.xdg.state.directory);
|
||||
example = {"foo.txt".source = "Hello World";};
|
||||
description = "state files to be managed by Hjem";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
packages = mkOption {
|
||||
type = listOf package;
|
||||
default = [];
|
||||
|
@ -225,18 +312,25 @@ in {
|
|||
};
|
||||
|
||||
config = {
|
||||
environment.loadEnv = let
|
||||
toEnv = env:
|
||||
if isList env
|
||||
then concatMapStringsSep ":" toString env
|
||||
else toString env;
|
||||
in
|
||||
lib.pipe cfg.environment.sessionVariables [
|
||||
(mapAttrsToList (name: value: "export ${name}=\"${toEnv value}\""))
|
||||
concatLines
|
||||
(pkgs.writeShellScript "load-env")
|
||||
];
|
||||
|
||||
environment = {
|
||||
sessionVariables = {
|
||||
XDG_CACHE_HOME = mkIf (cfg.xdg.cache.directory != options.xdg.cache.directory.default) cfg.xdg.cache.directory;
|
||||
XDG_CONFIG_HOME = mkIf (cfg.xdg.config.directory != options.xdg.config.directory.default) cfg.xdg.config.directory;
|
||||
XDG_DATA_HOME = mkIf (cfg.xdg.data.directory != options.xdg.data.directory.default) cfg.xdg.data.directory;
|
||||
XDG_STATE_HOME = mkIf (cfg.xdg.state.directory != options.xdg.state.directory.default) cfg.xdg.state.directory;
|
||||
};
|
||||
loadEnv = let
|
||||
toEnv = env:
|
||||
if isList env
|
||||
then concatMapStringsSep ":" toString env
|
||||
else toString env;
|
||||
in
|
||||
lib.pipe cfg.environment.sessionVariables [
|
||||
(mapAttrsToList (name: value: "export ${name}=\"${toEnv value}\""))
|
||||
concatLines
|
||||
(pkgs.writeShellScript "load-env")
|
||||
];
|
||||
};
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.user != "";
|
||||
|
|
|
@ -18,10 +18,18 @@
|
|||
enabledUsers = filterAttrs (_: u: u.enable) cfg.users;
|
||||
disabledUsers = filterAttrs (_: u: !u.enable) cfg.users;
|
||||
|
||||
userFiles = user: [
|
||||
user.files
|
||||
user.xdg.cache.files
|
||||
user.xdg.config.files
|
||||
user.xdg.data.files
|
||||
user.xdg.state.files
|
||||
];
|
||||
|
||||
linker = getExe cfg.linker;
|
||||
|
||||
manifests = let
|
||||
mapFiles = _: files:
|
||||
mapFiles = files:
|
||||
lib.attrsets.foldlAttrs (
|
||||
accum: _: value:
|
||||
if value.enable -> value.source == null
|
||||
|
@ -44,7 +52,9 @@
|
|||
text = builtins.toJSON {
|
||||
clobber_by_default = cfg.users."${username}".clobberFiles;
|
||||
version = 1;
|
||||
files = mapFiles username cfg.users."${username}".files;
|
||||
files = concatMap mapFiles (
|
||||
userFiles cfg.users."${username}"
|
||||
);
|
||||
};
|
||||
checkPhase = ''
|
||||
set -e
|
||||
|
@ -210,8 +220,8 @@ in {
|
|||
|
||||
systemd.user.tmpfiles.users =
|
||||
mapAttrs (_: u: {
|
||||
rules = pipe u.files [
|
||||
attrValues
|
||||
rules = pipe (userFiles u) [
|
||||
(concatMap attrValues)
|
||||
(filter (f: f.enable && f.source != null))
|
||||
(map (
|
||||
file:
|
||||
|
|
163
tests/xdg-linker.nix
Normal file
163
tests/xdg-linker.nix
Normal file
|
@ -0,0 +1,163 @@
|
|||
let
|
||||
userHome = "/home/alice";
|
||||
in
|
||||
(import ./lib) {
|
||||
name = "hjem-xdg-linker";
|
||||
nodes = {
|
||||
node1 = {
|
||||
self,
|
||||
pkgs,
|
||||
inputs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.modules) mkIf;
|
||||
inherit (lib.strings) optionalString;
|
||||
|
||||
xdg = {
|
||||
clobber,
|
||||
altLocation,
|
||||
}: {
|
||||
cache = {
|
||||
directory = mkIf altLocation (userHome + "/customCacheDirectory");
|
||||
files = {
|
||||
"foo" = {
|
||||
text = "Hello ${optionalString clobber "new "}world!";
|
||||
inherit clobber;
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
directory = mkIf altLocation (userHome + "/customConfigDirectory");
|
||||
files = {
|
||||
"bar.json" = {
|
||||
generator = lib.generators.toJSON {};
|
||||
value = {bar = "Hello ${optionalString clobber "new "}second world!";};
|
||||
inherit clobber;
|
||||
};
|
||||
};
|
||||
};
|
||||
data = {
|
||||
directory = mkIf altLocation (userHome + "/customDataDirectory");
|
||||
files = {
|
||||
"baz.toml" = {
|
||||
generator = (pkgs.formats.toml {}).generate "baz.toml";
|
||||
value = {baz = "Hello ${optionalString clobber "new "}third world!";};
|
||||
inherit clobber;
|
||||
};
|
||||
};
|
||||
};
|
||||
state = {
|
||||
directory = mkIf altLocation (userHome + "/customStateDirectory");
|
||||
files = {
|
||||
"foo" = {
|
||||
source = pkgs.writeText "file-bar" "Hello ${optionalString clobber "new "}fourth world!";
|
||||
inherit clobber;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
imports = [self.nixosModules.hjem];
|
||||
|
||||
system.switch.enable = true;
|
||||
|
||||
users.groups.alice = {};
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
home = userHome;
|
||||
password = "";
|
||||
};
|
||||
|
||||
hjem = {
|
||||
linker = inputs.smfh.packages.${pkgs.system}.default;
|
||||
users = {
|
||||
alice = {
|
||||
enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
specialisation = {
|
||||
defaultFilesGetLinked.configuration = {
|
||||
hjem.users.alice = {
|
||||
xdg = xdg {
|
||||
clobber = false;
|
||||
altLocation = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
altFilesGetLinked.configuration = {
|
||||
hjem.users.alice = {
|
||||
files.".config/foo".text = "Hello world!";
|
||||
xdg = xdg {
|
||||
clobber = false;
|
||||
altLocation = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
altFilesGetOverwritten.configuration = {
|
||||
hjem.users.alice = {
|
||||
files.".config/foo" = {
|
||||
text = "Hello new world!";
|
||||
clobber = true;
|
||||
};
|
||||
xdg = xdg {
|
||||
clobber = true;
|
||||
altLocation = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = {nodes, ...}: let
|
||||
baseSystem = nodes.node1.system.build.toplevel;
|
||||
specialisations = "${baseSystem}/specialisation";
|
||||
in
|
||||
# py
|
||||
''
|
||||
node1.succeed("loginctl enable-linger alice")
|
||||
|
||||
with subtest("Default file locations get liked"):
|
||||
node1.succeed("${specialisations}/defaultFilesGetLinked/bin/switch-to-configuration test")
|
||||
node1.succeed("test -L ${userHome}/.cache/foo")
|
||||
node1.succeed("grep \"Hello world!\" ~alice/.cache/foo")
|
||||
node1.succeed("test -L ${userHome}/.config/bar.json")
|
||||
node1.succeed("grep \"Hello second world!\" ~alice/.config/bar.json")
|
||||
node1.succeed("test -L ${userHome}/.local/share/baz.toml")
|
||||
node1.succeed("grep \"Hello third world!\" ~alice/.local/share/baz.toml")
|
||||
node1.succeed("test -L ${userHome}/.local/state/foo")
|
||||
node1.succeed("grep \"Hello fourth world!\" ~alice/.local/state/foo")
|
||||
|
||||
with subtest("Alternate file locations get linked"):
|
||||
node1.succeed("${specialisations}/altFilesGetLinked/bin/switch-to-configuration test")
|
||||
node1.succeed("test -L ${userHome}/customCacheDirectory/foo")
|
||||
node1.succeed("grep \"Hello world!\" ~alice/customCacheDirectory/foo")
|
||||
node1.succeed("test -L ${userHome}/customConfigDirectory/bar.json")
|
||||
node1.succeed("grep \"Hello second world!\" ~alice/customConfigDirectory/bar.json")
|
||||
node1.succeed("test -L ${userHome}/customDataDirectory/baz.toml")
|
||||
node1.succeed("grep \"Hello third world!\" ~alice/customDataDirectory/baz.toml")
|
||||
node1.succeed("test -L ${userHome}/customStateDirectory/foo")
|
||||
node1.succeed("grep \"Hello fourth world!\" ~alice/customStateDirectory/foo")
|
||||
# Same name as config test file to verify proper merging
|
||||
node1.succeed("test -L ${userHome}/.config/foo")
|
||||
node1.succeed("grep \"Hello world!\" ~alice/.config/foo")
|
||||
|
||||
with subtest("Alternate file locations get overwritten when changed"):
|
||||
node1.succeed("${specialisations}/altFilesGetLinked/bin/switch-to-configuration test")
|
||||
node1.succeed("${specialisations}/altFilesGetOverwritten/bin/switch-to-configuration test")
|
||||
node1.succeed("test -L ${userHome}/customCacheDirectory/foo")
|
||||
node1.succeed("grep \"Hello new world!\" ~alice/customCacheDirectory/foo")
|
||||
node1.succeed("test -L ${userHome}/customConfigDirectory/bar.json")
|
||||
node1.succeed("grep \"Hello new second world!\" ~alice/customConfigDirectory/bar.json")
|
||||
node1.succeed("test -L ${userHome}/customDataDirectory/baz.toml")
|
||||
node1.succeed("grep \"Hello new third world!\" ~alice/customDataDirectory/baz.toml")
|
||||
node1.succeed("test -L ${userHome}/customStateDirectory/foo")
|
||||
node1.succeed("grep \"Hello new fourth world!\" ~alice/customStateDirectory/foo")
|
||||
# Same name as config test file to verify proper merging
|
||||
node1.succeed("test -L ${userHome}/.config/foo")
|
||||
node1.succeed("grep \"Hello new world!\" ~alice/.config/foo")
|
||||
'';
|
||||
}
|
105
tests/xdg.nix
Normal file
105
tests/xdg.nix
Normal file
|
@ -0,0 +1,105 @@
|
|||
let
|
||||
userHome = "/home/alice";
|
||||
in
|
||||
(import ./lib) {
|
||||
name = "hjem-xdg";
|
||||
nodes = {
|
||||
node1 = {
|
||||
self,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
imports = [self.nixosModules.hjem];
|
||||
|
||||
users.groups.alice = {};
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
home = userHome;
|
||||
password = "";
|
||||
};
|
||||
|
||||
hjem.users = {
|
||||
alice = {
|
||||
enable = true;
|
||||
files = {
|
||||
"foo" = {
|
||||
text = "Hello world!";
|
||||
};
|
||||
};
|
||||
xdg = {
|
||||
cache = {
|
||||
directory = userHome + "/customCacheDirectory";
|
||||
files = {
|
||||
"foo" = {
|
||||
text = "Hello world!";
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
directory = userHome + "/customConfigDirectory";
|
||||
files = {
|
||||
"bar.json" = {
|
||||
generator = lib.generators.toJSON {};
|
||||
value = {bar = "Hello second world!";};
|
||||
};
|
||||
};
|
||||
};
|
||||
data = {
|
||||
directory = userHome + "/customDataDirectory";
|
||||
files = {
|
||||
"baz.toml" = {
|
||||
generator = (pkgs.formats.toml {}).generate "baz.toml";
|
||||
value = {baz = "Hello third world!";};
|
||||
};
|
||||
};
|
||||
};
|
||||
state = {
|
||||
directory = userHome + "/customStateDirectory";
|
||||
files = {
|
||||
"foo" = {
|
||||
source = pkgs.writeText "file-bar" "Hello fourth world!";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Also test systemd-tmpfiles internally
|
||||
systemd.user.tmpfiles = {
|
||||
rules = [
|
||||
"d %h/user_tmpfiles_created"
|
||||
];
|
||||
|
||||
users.alice.rules = [
|
||||
"d %h/only_alice"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.succeed("loginctl enable-linger alice")
|
||||
machine.wait_until_succeeds("systemctl --user --machine=alice@ is-active systemd-tmpfiles-setup.service")
|
||||
|
||||
# Test XDG files created by Hjem
|
||||
with subtest("XDG files created by Hjem"):
|
||||
machine.succeed("[ -L ~alice/customCacheDirectory/foo ]")
|
||||
machine.succeed("grep \"Hello world!\" ~alice/customCacheDirectory/foo")
|
||||
machine.succeed("[ -L ~alice/customConfigDirectory/bar.json ]")
|
||||
machine.succeed("grep \"Hello second world!\" ~alice/customConfigDirectory/bar.json")
|
||||
machine.succeed("[ -L ~alice/customDataDirectory/baz.toml ]")
|
||||
machine.succeed("grep \"Hello third world!\" ~alice/customDataDirectory/baz.toml")
|
||||
# Same name as config test file to verify proper merging
|
||||
machine.succeed("[ -L ~alice/customStateDirectory/foo ]")
|
||||
machine.succeed("grep \"Hello fourth world!\" ~alice/customStateDirectory/foo")
|
||||
|
||||
with subtest("Basic test file for Hjem"):
|
||||
machine.succeed("[ -L ~alice/foo ]") # Same name as cache test file to verify proper merging
|
||||
machine.succeed("grep \"Hello world!\" ~alice/foo")
|
||||
# Test regular files, created by systemd-tmpfiles
|
||||
machine.succeed("[ -d ~alice/user_tmpfiles_created ]")
|
||||
machine.succeed("[ -d ~alice/only_alice ]")
|
||||
'';
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue