1
0
Files
dotfiles/pkgs/home-manager/initialFile.nix
Anton 134fe16393 feat: TETR.IO (+ TETR.IO PLUS integration)
This commit does feature a _few_ changes to my general NixOS config
(namely the ideology switch from importing things everywhere to having
my own custom "`mpkgs`").

However most of this effort was all thanks to TETR.IO. The former
maintainer of `pkgs.tetrio-desktop` and `pkgs.tetrio-plus` seems to not
have been playing recently enough to update the packages to v10. Making
them unusuable.

`mpkgs.tetrio.desktop` is a patched `pkgs.tetrio-desktop` that updates
to v10. Alongside this, and a quick discovery that you could make custom
home-manager modules, I took it upon myself to make TETR.IO
configurations (including those of TETR.IO PLUS) fully generated from
Nix.

This effort took way too long, and feels slightly hacky (the way
injecting configurations works is by generating a LevelDB (Chromium
IndexedDB) for the electron instance's Local Storage) and it involves
some custom stuff. (LevelDB deriviation, plus home-manager module for
copying files rather than linking them `home.initialFile`.)

I'm proud of the result, and Tetris is now fully deterministic,
reproducible, and Nix-y.
2026-01-14 21:49:20 +01:00

173 lines
7.3 KiB
Nix

{
pkgs,
lib ? pkgs.lib,
config,
...
}:
let
inherit (lib) mkOption types;
cfg = lib.filterAttrs (n: f: f.enable) config.home.initialFile;
home = config.home.homeDirectory;
in
{
options = {
home.initialFile = mkOption {
type = types.attrsOf (
types.submodule (
{ name, config, ... }:
{
options = {
enable = mkOption {
type = types.bool;
default = true;
};
target = mkOption {
type = types.str;
apply =
path:
let
absPath = if lib.hasPrefix "/" path then path else "${home}/${path}";
in
lib.removePrefix (home + "/") absPath;
description = "Path to target file relative to home directory";
default = "/homeless-shelter/home";
};
text = mkOption {
type = types.nullOr types.lines;
description = "Text of the file, otherwise copies .source file.";
default = null;
};
source = mkOption {
type = types.path;
description = "Path of file whose source to copy";
};
mode = mkOption {
type = types.nullOr types.str;
description = "File mode to apply to target file (sourced from .source / .text if not specified)";
default = null;
};
dir_mode = mkOption {
type = types.str;
description = "File mode to apply to directories";
default = "0755";
};
recursive = mkOption {
type = types.bool;
description = "Whether or not to recursively copy .source as a directory instead of as a file";
default = false;
};
force = mkOption {
type = types.bool;
description = "Whether to unconditionally replace the target file, even if it already exists.";
default = false;
};
};
config = {
target = lib.mkDefault name;
source = lib.mkIf (config.text != null) (
lib.mkDefault (
pkgs.writeTextFile {
inherit (config) text;
name = lib.hm.strings.storeFileName name;
}
)
);
};
}
)
);
description = "Attribute set of files to write into the user home (if they don't already exist).";
default = { };
};
};
config = {
assertions = [
(
let
dups = lib.attrNames (
lib.filterAttrs (n: v: v > 1) (
lib.foldAttrs (acc: v: acc + v) 0 (lib.mapAttrsToList (n: v: { ${v.target} = 1; }) cfg)
)
);
dupsStr = lib.concatStringsSep ", " dups;
in
{
assertion = dups == [ ];
message = ''
Conflicting managed target files: ${dupsStr}
This may happen, for example, if you have a configuration similar to
home.initialFile = {
conflict1 = { source = ./foo.nix; target = "baz"; };
conflict2 = { source = ./bar.nix; target = "baz"; };
}'';
}
)
];
home.activation.copyInitialFiles = lib.hm.dag.entryAfter [ "writeBoundary" ] (
let
homeArg = lib.escapeShellArg home;
in
''
function copyFile() {
local source="$1"
local targetRel="$2"
local mode="$3"
local dirMode="$4"
local recursive="$5"
local force="$6"
local target="${homeArg}/$targetRel"
if [[ -e "$target" && "$force" != "true" ]]; then
verboseEcho "Skipping existing $target"
return 0
fi
run mkdir -p "$(dirname "$target")"
if [[ -d "$source" ]]; then
if [[ "$recursive" != "true" ]]; then
errorEcho "Source '$source' is a directory but recursive=false"
return 1
fi
run rm -rf "$target"
run cp -r "$source" "$target"
else
if [[ -e "$target" && "$force" == "true" ]]; then
run rm -f "$target"
fi
run cp "$source" "$target"
fi
if [[ -n "$mode" ]]; then
if [[ -d "$target" && "$recursive" == "true" ]]; then
run chmod "$dirMode" "$target"
find "$target" -type f -exec chmod "$mode" {} +
else
run chmod "$mode" "$target"
fi
fi
}
''
+ lib.concatMapStrings (
v:
let
src = lib.escapeShellArg (toString v.source);
tgt = lib.escapeShellArg v.target;
mode = if v.mode == null then "''" else lib.escapeShellArg v.mode;
in
''
copyFile ${src} ${tgt} ${mode} ${lib.escapeShellArg v.dir_mode} ${lib.trivial.boolToString v.recursive} ${lib.trivial.boolToString v.force}
''
) (lib.attrValues cfg)
);
};
}