nix-community / impermanence

Modules to help you handle persistent state on systems with ephemeral root storage [maintainer=@talyz]
MIT License
1.09k stars 80 forks source link

`systemd` service doesn't start if `home-manager` is not used as a `nixosModule` #52

Closed maydayv7 closed 2 years ago

maydayv7 commented 2 years ago

Hi, I'm pretty new to Nix, and am trying to use the impermanence home-manager module in my configuration (using Flakes), and I have some difficulties understanding how it all works. You can find my dotfiles here

Problem

Every single time the system boots, the home directory is regenerated (because of tmpfs), so all files in ~ are deleted (including the user systemd units to bind-mount the directories), and I need to re-apply the user configuration every time after login to get back all the files, which I hope isn't the desired method of operation. Also, I must run rm -rf ~ before re-applying, else it errors out with this message:

fuse: mountpoint is not empty
fuse: if you are sure this is safe, use the 'nonempty' mount option

and the home-manager configuration is not applied. It is also not possible for me to define a user systemd service to re-apply the configuration on login, so every boot requires a manual rebuild, which is not ideal.

Expected Behaviour

The directories (both the persisted ones and the ones linked using the home-manager option home.file are present on login, w/o any interference from the user.

Important Configuration Files

shortened, for decreasing complexity flake.nix

{
  description = "My Reproducible, Hermetic, Derivational, Portable, Atomic, Multi-PC NixOS Dotfiles";

  ## Package Repositories ##
  inputs =
  {
    ## Main Repositories ##
    # NixOS Stable Release
    nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-21.05";

    # Home Manager
    home-manager =
    {
      url = "github:nix-community/home-manager?ref=release-21.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    # Persistent State Handler
    impermanence.url = "github:nix-community/impermanence";
  };

  ## Output Configuration ##
  outputs = { self, ... } @ inputs:
  let
    ## Variable Declaration ##
    # Architecture
    system = "x86_64-linux";

    # NixOS Version
    version = "21.05";

    # Package Configuration
    pkgs = import inputs.nixpkgs
    {
      inherit system overlays;
      config =
      {
        allowUnfree = true;
        allowBroken = true;
      };
    };

    # System Libraries
    inherit (inputs.nixpkgs) lib;
    inherit (lib) attrValues;

    # Custom Functions
    util = import ./lib { inherit system lib inputs pkgs; };
    inherit (util) user;
    inherit (util) host;
  in
  {
    ## User Specific Configuration ##
    homeManagerConfigurations =
    {
      # User V7
      v7 = user.mkHome
      {
        username = "v7";
        roles = [ "dconf" "discord" "firefox" "git" "zsh" ];
        inherit version;
        persist = true;
      };
    };

    ## Device Specific Configuration ##
    nixosConfigurations =
    {
      # PC - Dell Inspiron 15 5000
      Vortex = host.mkHost
      {
        inherit version;
        name = "Vortex";
        kernelPackage = pkgs.linuxPackages_lqx;
        initrdMods = [ "xhci_pci" "thunderbolt" "nvme" "usb_storage" "sd_mod" ];
        kernelMods = [ "kvm-intel" ];
        kernelParams = [ "quiet" "splash" "rd.systemd.show_status=false" "rd.udev.log_level=3" "udev.log_priority=3" ];
        modprobe = "options kvm_intel nested=1";
        cpuCores = 8;
        filesystem = "btrfs";
        ssd = true;
        roles = [ "android" "fonts" "git" "gnome" "libvirt" "office" "security" "xorg" ];
        users =
        [
          {
            name = "v7";
            description = "V 7";
            groups = [ "wheel" "networkmanager" "audio" "video" "cdrom" "disk" "kvm" "libvirtd" "adbusers" ];
            uid = 1000;
            shell = pkgs.zsh;
          }
        ];
      };
    };
  };
}

filesystem module

{ config, lib, pkgs, ... }:
with lib;
with builtins;
let
  cfg = config.hardware.filesystem;
in rec
{
  options.hardware.filesystem = mkOption
  {
    type = types.enum [ "btrfs" "ext4" ];
    description = "This is the File System to be used by the disk";
    default = "ext4";
  };

  config =
  {
    # Partitions
    fileSystems = (if (cfg == "ext4")
    then
    ## EXT4 File System Configuration ##
    {
      "/" =
      {
        device = "/dev/disk/by-label/System";
        fsType = "ext4";
      };
    }
    else
    ## BTRFS Opt-in State File System Configuration ##
    {
      # ROOT Partition
      # Opt-in State with TMPFS
      "/" =
      {
        device = "tmpfs";
        fsType = "tmpfs";
        options = [ "defaults" "size=3G" "mode=755" ];
      };
      # BTRFS Partition
      "/mnt/btrfs" =
      {
        device = "/dev/disk/by-label/System";
        fsType = "btrfs";
        options = [ "subvolid=5" "compress=zstd" "autodefrag" "noatime" ];
      };
      # NIX Subvolume
      "/nix" =
      {
        device = "/dev/disk/by-label/System";
        fsType = "btrfs";
        options = [ "subvol=nix" ];
      };
      # PERSISTENT Subvolume
      "/persist" =
      {
        device = "/dev/disk/by-label/System";
        fsType = "btrfs";
        options = [ "subvol=persist" ];
        neededForBoot = true;
      };
    });

    # Persisted Files
    environment.persistence."/persist" = (mkIf (cfg == "btrfs")
    {
      directories =
      [
        "/etc/nixos"
        "/etc/NetworkManager/system-connections"
        "/var/log"
        "/var/lib/AccountsService"
        "/var/lib/bluetooth"
        "/var/lib/libvirt"
      ];

      files =
      [
        "/etc/machine-id"
      ];
    });

    # Snapshots
    services.btrbk = (mkIf (cfg == "btrfs")
    {
      instances =
      {
        persist =
        {
          onCalendar = "daily";
          settings =
          {
            timestamp_format = "long";
            snapshot_preserve = "31d";
            snapshot_preserve_min = "7d";
            volume."/mnt/btrfs".subvolume =
            {
              persist.snapshot_create = "always";
            };
          };
        };
      };
    });
  };
}

lib/user.nix

{ system, lib, inputs, pkgs, ... }:
with builtins;
{
  ## User Home Configuration ##
  mkHome = { username, roles, version, persist ? false, ... }:
  inputs.home-manager.lib.homeManagerConfiguration
  {
    inherit system username pkgs;
    stateVersion = version;
    homeDirectory = "/home/${username}";
    configuration =
    let
      # Module Import Function
      mkRole = name: import (../roles/user + "/${name}");
      user_roles = map (r: mkRole r) roles;

      # Extra Configuration Modules
      extra_modules =
      [
        ../modules/user
        "${inputs.impermanence}/home-manager.nix"
      ];
    in
    {
      _module.args =
      {
        inherit inputs pkgs username;
      };

      # Modulated Configuration Imports
      imports = shared_roles ++ user_roles ++ extra_modules;

      # Home Manager Configuration
      home.username = username;
      programs.home-manager.enable = true;
      systemd.user.startServices = true;
      home.persist = persist;
    };
  };
}

persist module

{ config, lib, pkgs, username, ... }:
with lib;
with builtins;
let
  cfg = config.home.persist;
in rec
{
  options.home.persist = mkOption
  {
    description = "Persist User Folders";
    type = types.bool;
    default = false;
  };

  config = mkIf (cfg == true)
  {
    # Persist files in Home Directory
    home.persistence."/persist/home/${username}" =
    {
      directories =
      [
        "Desktop"
        "Documents"
        "Downloads"
        "Music"
        "Pictures"
        "Templates"
        "Videos"
        ".config/dconf"
        ".config/discord"
        ".config/evolution"
        ".config/geary"
        ".config/gnome-boxes"
        ".config/goa-1.0"
        ".config/google-chrome"
        ".config/libvirt"
        ".config/Microsoft"
        ".local/share/evolution"
        ".local/share/gnome-boxes"
        ".local/share/keyrings"
        ".mozilla"
      ];

      files =
      [
        ".git-credentials"
        ".zsh_history"
      ];

      allowOther = false;
    };
  };
}

Command for applying user configuration (since, I'm using home-manager as a separate function instead of conventionally importing the module)

nix build /etc/nixos#homeManagerConfigurations.$USER.activationPackage
./result/activate
rm -rf ./result

Also, if this behaviour is irregular, please point out what is being done wrong in my configuration

talyz commented 2 years ago

Hi! To automatically set up home-manager when using impermanence, it's easiest to use home-manager's NixOS module (it's in the nixos directory of the home-manager repo). It will activate the configuration in a systemd service on boot before you log in and therefore also solve the conflict problem you're seeing. I guess this info really should be in the readme.. Sorry about that!

maydayv7 commented 2 years ago

Okay, thanks a lot!