解决 NixOS + Home Manager 的 VSCode settings.json 写入问题
更新日志
2025-08-15
- 更新为更完善的版本
2025-06-16
- 发布文章。
如果使用 Home Manager 配置 VSCode 的设置的话,因为 VSCode 启动时会写入 settings.json,导致每次打开 VSCode 都会弹出一个 settings.json 无法保存的弹窗。最简单的解决方案是直接不用 Nix 配置 VSCode。好处是简单粗暴,但是这样就需要用其他方法管理 VSCode 的配置了,和 Nix 的哲学不符。
通过搜索,我找到了一个 21 年的相关 issue #1800,里面获赞最多的是一个 gist,给出了一个模块,可以让 home.file 引入的文件变得可写。但是我更想使用 Home Manager 提供的 profile 选项来配置我的 VSCode,于是我注意到了该 issue 下还有一个比较简短的评论,给出了第三种解决方案的可能。
显然,一条 21 年的评论里的代码是没法直接用的,于是我顺藤摸瓜找到了评论者的 Nix 配置仓库(顺带一提,这位也是一个相当好用的 Nix 格式化软件 Alejandra 的作者),在里面找到了他最新的 VSCode配置代码。简单总结一下,就是改了一下 VSCode 的配置和扩展文件存储位置,再在开机时清理掉该位置的文件,把 Home Manager 生成的配置和扩展文件复制过去。这种方法既可以避免弹窗,还快速调试 VSCode 的配置,而不需要改一两行配置就要 nix-rebuild;同时也保证了 Nix 配置的稳定性。
但是由于用到了这位作者自己定义的一些库,并且他也不是为了 Home Manager 管理的配置编写的,这个配置代码也是不能直接用的。我这里给一个我的 NixOS 25.05 版本的 Home Manager 模块配置,以供参考:
第一版
# ./home/linux/gui/vscode.nix{ config, lib, pkgs, ...}: let inherit (lib.lists) concatLists; homeDir = config.home.homeDirectory; userDataDir = "${homeDir}/.data/vscode/data"; extensionsDir = "${homeDir}/.data/vscode/extensions"; pkg = pkgs.vscode.override { commandLineArgs = concatLists [ ["--extensions-dir" extensionsDir] ["--user-data-dir" userDataDir] ]; };in { imports = [./profiles]; programs.vscode = { enable = true; package = pkg; mutableExtensionsDir = false; }; # To solve VSCode wants to write settings.json # VSCode will reset per reboot/rebuild. systemd.user.services.vscode-setup = { Unit = { Description = "VSCode Setup service"; After = ["graphical-session-pre.target"]; Wants = ["graphical-session-pre.target"]; }; Install.WantedBy = ["graphical-session.target"]; Service = { Type = "oneshot"; ExecStart = lib.getExe (pkgs.writeShellApplication { name = "vscode-setup"; text = '' rm -rf "${userDataDir}/User" rm -rf "${extensionsDir}" mkdir -p "${userDataDir}" mkdir -p "${extensionsDir}" cp -r --dereference --no-preserve=mode,ownership \ "${homeDir}/.config/Code/User" "${userDataDir}/User" cp -r --dereference --no-preserve=mode,ownership \ "${homeDir}/.vscode/extensions/." "${extensionsDir}" ''; }); }; };}第二版
第一版缺陷很多,所以我编写了第二版:
# ./home/linux/gui/vscode.nix{ config, lib, pkgs, ...}: let homeDir = config.home.homeDirectory; dataDir = config.xdg.dataHome; userDataDir = "${dataDir}/vscode/data"; extensionsDir = "${dataDir}/vscode/extensions";in { programs.vscode = { enable = true; mutableExtensionsDir = false; }; # To solve VSCode wants to write settings.json # VSCode will reset per reboot/rebuild. systemd.user.services.vscode-setup = { Unit = { Description = "VSCode Setup service"; After = ["graphical-session-pre.target"]; Wants = ["graphical-session-pre.target"]; }; Install.WantedBy = ["graphical-session.target"]; Service = { Type = "oneshot"; UMask = "0022"; ExecStart = lib.getExe (pkgs.writeShellApplication { name = "vscode-setup"; runtimeInputs = with pkgs; [coreutils gnutar jq]; text = let userSrc = "${homeDir}/.config/Code/User"; userDst = "${userDataDir}/User"; extSrc = "${homeDir}/.vscode/extensions"; extDst = extensionsDir; dirsToPreserve = [ "workspaceStorage" "History" ]; backupCmds = builtins.concatStringsSep "\n" (map (dir: '' if [ -e "${userDst}/${dir}" ]; then echo "Backing up data/User/${dir}..." mv "${userDst}/${dir}" "/tmp/vscode-${dir}-$$" fi '') (dirsToPreserve ++ ["globalStorage"])); restoreCmds = builtins.concatStringsSep "\n" (map (dir: '' if [ -e "/tmp/vscode-${dir}-$$" ]; then echo "Restoring data/User/${dir}..." mv "/tmp/vscode-${dir}-$$" "${userDst}/${dir}" fi '') dirsToPreserve); in '' set -eu ${backupCmds} echo "Cleaning old directories..." rm -rf "${userDst}" rm -rf "${extDst}" mkdir -p "${userDataDir}" mkdir -p "${extDst}" echo "Copying user settings from ${userSrc}..." cp -r --dereference --no-preserve=mode,ownership ${userSrc} "${userDst}" echo "Copying extensions from ${extSrc}..." tar -h -C "${extSrc}" -cf - . | tar -C "${extDst}" -xf - --no-same-owner --no-same-permissions --mode='u=rX,go=rX' chmod u+w -R "${extDst}" 2>/dev/null || true ${restoreCmds} echo "Restoring and merging data/User/globalStorage..." if [ -e "/tmp/vscode-globalStorage-$$" ]; then cp -rT "/tmp/vscode-globalStorage-$$" "${userDst}/globalStorage" src_storage_json="${userSrc}/globalStorage/storage.json" dst_storage_json="${userDst}/globalStorage/storage.json" if [ -f "$src_storage_json" ] && [ -f "$dst_storage_json" ]; then echo "Merging data/globalStorage/storage.json with new data..." merged_json=$(mktemp) jq -s '.[0] * .[1]' "$dst_storage_json" "$src_storage_json" > "$merged_json" mv "$merged_json" "$dst_storage_json" echo "Merge complete." else echo "Skipping storage.json merge: one or both files do not exist." fi else echo "No backed-up globalStorage found to restore." fi echo "VSCode setup complete." ''; }); }; };}你也可以直接查看我的 Nix 配置文件仓库。