Skip to content

Instantly share code, notes, and snippets.

@js6pak
Forked from piousdeer/example.nix
Created May 7, 2025 13:24
Show Gist options
  • Save js6pak/d17a317de6a76ba9dac0d110adc651ed to your computer and use it in GitHub Desktop.
Save js6pak/d17a317de6a76ba9dac0d110adc651ed to your computer and use it in GitHub Desktop.
Create mutable files with home-manager and Nix
{
home.file."test-file" = {
text = "Hello world";
force = true;
mutable = true;
};
}
# This module extends home.file, xdg.configFile and xdg.dataFile with the `mutable` option.
{ config, lib, ... }:
let
fileOptionAttrPaths =
[ [ "home" "file" ] [ "xdg" "configFile" ] [ "xdg" "dataFile" ] ];
in {
options = let
mergeAttrsList = builtins.foldl' (lib.mergeAttrs) { };
fileAttrsType = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
options.mutable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to copy the file without the read-only attribute instead of
symlinking. If you set this to `true`, you must also set `force` to
`true`. Mutable files are not removed when you remove them from your
configuration.
This option is useful for programs that don't have a very good
support for read-only configurations.
'';
};
}));
in mergeAttrsList (map (attrPath:
lib.setAttrByPath attrPath (lib.mkOption { type = fileAttrsType; }))
fileOptionAttrPaths);
config = {
home.activation.mutableFileGeneration = let
allFiles = (builtins.concatLists (map
(attrPath: builtins.attrValues (lib.getAttrFromPath attrPath config))
fileOptionAttrPaths));
filterMutableFiles = builtins.filter (file:
(file.mutable or false) && lib.assertMsg file.force
"if you specify `mutable` to `true` on a file, you must also set `force` to `true`");
mutableFiles = filterMutableFiles allFiles;
toCommand = (file:
let
source = lib.escapeShellArg file.source;
target = lib.escapeShellArg file.target;
in ''
$VERBOSE_ECHO "${source} -> ${target}"
$DRY_RUN_CMD cp --remove-destination --no-preserve=mode ${source} ${target}
'');
command = ''
echo "Copying mutable home files for $HOME"
'' + lib.concatLines (map toCommand mutableFiles);
in (lib.hm.dag.entryAfter [ "linkGeneration" ] command);
};
}
{
config,
pkgs,
lib,
...
}:
let
# Path logic from:
# https://github.com/nix-community/home-manager/blob/35535345be0be7dbae2e9b787c6cf790f8c893d5/modules/programs/vscode.nix
cfg = config.programs.vscode;
vscodePname = cfg.package.pname;
configDir =
{
"vscode" = "Code";
"vscode-insiders" = "Code - Insiders";
"vscodium" = "VSCodium";
"openvscode-server" = "OpenVSCode Server";
"windsurf" = "Windsurf";
"cursor" = "Cursor";
}
.${vscodePname};
userDir =
if pkgs.stdenv.hostPlatform.isDarwin then
"Library/Application Support/${configDir}/User"
else
"${config.xdg.configHome}/${configDir}/User";
configFilePath =
name: "${userDir}/${lib.optionalString (name != "default") "profiles/${name}/"}settings.json";
tasksFilePath =
name: "${userDir}/${lib.optionalString (name != "default") "profiles/${name}/"}tasks.json";
keybindingsFilePath =
name: "${userDir}/${lib.optionalString (name != "default") "profiles/${name}/"}keybindings.json";
snippetDir =
name: "${userDir}/${lib.optionalString (name != "default") "profiles/${name}/"}snippets";
pathsToMakeWritable = lib.flatten [
(lib.mapAttrsToList (n: v: [
(lib.optional (
v.userSettings != { } || v.enableUpdateCheck != null || v.enableExtensionUpdateCheck != null
) (configFilePath n))
(lib.optional (v.userTasks != { }) (tasksFilePath n))
(lib.optional (v.keybindings != [ ]) (keybindingsFilePath n))
(lib.mapAttrsToList (language: _: "${snippetDir n}/${language}.json") v.languageSnippets)
(lib.optional (v.globalSnippets != { }) "${snippetDir n}/global.code-snippets")
]) cfg.profiles)
];
in
{
home.file = lib.genAttrs pathsToMakeWritable (_: {
force = true;
mutable = true;
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment