Syntax for home-manager dotfile import

I am new to Nixos and am trying to get started with dotfile management. I was able to install Nixos and enable home-manager as a module. But, as I add configs for apps, can see things getting long in the base configuration.nix. So I separated out my first program’s config (alacritty). This worked pretty well. My config looks like this:

  ###Home-Manager
  home-manager.users.${user} = { pkgs, ... }: {
    home.stateVersion = "24.11";
    home.packages = with pkgs; [ htop ];
    programs.home-manager.enable = true;
    #Alacritty
    home.file = import ./alacritty.nix;
  };

alacritty.nix:

{
".config/alacritty/alacritty.toml".text = ''
[window]

opacity = 0.8

padding.x = 10
padding.y = 10

decorations = "Full"
decorations_theme_variant = "Dark"


[font]
size = 10.0

[font.normal]
family = "DejaVu Sans Mono"
style = "Book"

[font.bold]
family = "DejaVu Sans Mono"
style = "Bold"

[font.italic]
family = "DejaVu Sans Mono"
style = "Oblique"

[font.bold_italic]
family = "DejaVu Sans Mono"
style = "Bold Oblique"

# Colors (Gruvbox dark)

# Default colors
[colors.primary]
# hard contrast background = = '#1d2021'
background = '#282828'
# soft contrast background = = '#32302f'
foreground = '#ebdbb2'

# Normal colors
[colors.normal]
black   = '#282828'
red     = '#cc241d'
green   = '#98971a'
yellow  = '#d79921'
blue    = '#458588'
magenta = '#b16286'
cyan    = '#689d6a'
white   = '#a89984'

# Bright colors
[colors.bright]
black   = '#928374'
red     = '#fb4934'
green   = '#b8bb26'
yellow  = '#fabd2f'
blue    = '#83a598'
magenta = '#d3869b'
cyan    = '#8ec07c'
white   = '#ebdbb2'
'';
}

This worked without an issue. However my next step was to do something similar with hyprland. I first added a second “home.file = import ./hyprland.nix;” but this fails because home.file was already defined. I tried to do various other methods for importing multiple files such as:

home.file = import [ ./alacritty.nix ./hyprland.nix] 

I tried various other syntax but only got errors. I think one was something about coerce a string to a list or something like that.

So I decided just to merge the two config files (it may get long but at least it won’t be in configuration.nix. So I merged the two configs. I removed the curly brackets in between and had the following between the two config texts:

...
white   = '#ebdbb2'
'';

".config/hypr/hyprland.conf".text = ''

Upon doing this it seemed to process ok but I received an error that the home-manager service could not start. I can paste the full configs but basically, I am pretty sure it’s the Hyprland config which was copied from a working system (because it works with just the alacritty section).

I suspect what I’m doing is not ideal but at least I sort of understand what I’m doing with this methodology (though I plan on shoring things up in the future).

How do I either do a “home.file = import” with multiple files defined or get the double-single quote method to work? I know home-manager has some direct config options for Hyprland but I was hoping just to get things going this way. I have spent all weekend digging into this and various other Nixos challenges but with this, I think I’m having trouble making application of the documentation to my specific scenario. I’ll post the full configs but wanted to keep the initial post (relatively) succinct.

Complete configuration.nix (relevant section at end):

# Edit this configuration file to define what should be installed on
# your system.  Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).

{ config, pkgs, ... }:

let
  user="szemlicka";
in

let
  home-manager = builtins.fetchTarball "https://github.com/nix-community/home-manager/archive/release-24.11.tar.gz";
in

{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
      (import "${home-manager}/nixos")
    ];
  
  #Enable Flakes
  nix.settings.experimental-features = [ "nix-command" "flakes" ];

  # Bootloader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  boot ={
    kernelPackages = pkgs.linuxPackages_latest;
    initrd.kernelModules = ["amdgpu"];
  };

  networking.hostName = "dell-g5"; # Define your hostname.
  # networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.

  # Configure network proxy if necessary
  # networking.proxy.default = "http://user:password@proxy:port/";
  # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";

  # Enable networking
  networking.networkmanager.enable = true;

  # Set your time zone.
  time.timeZone = "America/Chicago";

  # Select internationalisation properties.
  i18n.defaultLocale = "en_US.UTF-8";

  i18n.extraLocaleSettings = {
    LC_ADDRESS = "en_US.UTF-8";
    LC_IDENTIFICATION = "en_US.UTF-8";
    LC_MEASUREMENT = "en_US.UTF-8";
    LC_MONETARY = "en_US.UTF-8";
    LC_NAME = "en_US.UTF-8";
    LC_NUMERIC = "en_US.UTF-8";
    LC_PAPER = "en_US.UTF-8";
    LC_TELEPHONE = "en_US.UTF-8";
    LC_TIME = "en_US.UTF-8";
  };

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.${user} = {
    isNormalUser = true;
    description = "Stephen Zemlicka";
    extraGroups = [ "networkmanager" "wheel" "audio" "video" "lp" "scanner" ];
    packages = with pkgs; [];
  };

  # Allow unfree packages
  nixpkgs.config.allowUnfree = true;

  #Display Manager and Desktop Environment
  #LightDM
  services = {
    xserver = {
      enable = true;
      xkb = {
        layout = "us";
        variant = "";
      };
      #Use nvidia drivers if applicable to system
      #videoDrivers = ["nvidia"];
      displayManager = {
        lightdm.enable = true;
      };
    };
    pipewire = {
      enable = true;
      audio.enable = true;
      pulse.enable = true;
      alsa = {
        enable = true;
        support32Bit = true;
      };
    };
    displayManager.defaultSession = "hyprland";
    libinput.enable = true;
  };
  #Hyprland    
  programs = {
    hyprland = {
      enable = true;
      xwayland.enable = true;
    };
  };

  # List packages installed in system profile. To search, run:
  # $ nix search wget
  environment.systemPackages = with pkgs; [
  #  vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
    wget
    nano
    neovim
    kitty
    alacritty
    gtk3
    git
    firefox
    google-chrome
    dejavu_fonts
    xfce.thunar
    xfce.thunar-volman
    xfce.thunar-archive-plugin
    rofi-wayland

];
  
  #Automatic garbage collection
  nix = {
    settings.auto-optimise-store = true;
    gc = {
      automatic = true;
      dates = "weekly";
      options = "--delete-older-than 14d";
    };
  };

  # Some programs need SUID wrappers, can be configured further or are
  # started in user sessions.
  # programs.mtr.enable = true;
  # programs.gnupg.agent = {
  #   enable = true;
  #   enableSSHSupport = true;
  # };

  # List services that you want to enable:

  # Enable the OpenSSH daemon.
  # services.openssh.enable = true;

  # Open ports in the firewall.
  # networking.firewall.allowedTCPPorts = [ ... ];
  # networking.firewall.allowedUDPPorts = [ ... ];
  # Or disable the firewall altogether.
  # networking.firewall.enable = false;

  # This value determines the NixOS release from which the default
  # settings for stateful data, like file locations and database versions
  # on your system were taken. It‘s perfectly fine and recommended to leave
  # this value at the release version of the first install of this system.
  # Before changing this value read the documentation for this option
  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
  system.stateVersion = "24.11"; # Did you read the comment?

  ###Home-Manager
  home-manager.users.${user} = { pkgs, ... }: {
    home.stateVersion = "24.11";
    home.packages = with pkgs; [ htop ];
    programs.home-manager.enable = true;
    #Alacritty
    home.file = import ./env-conf.nix;
  };

}

env-conf.nix:

{
".config/alacritty/alacritty.toml".text = ''
[window]

opacity = 0.8

padding.x = 10
padding.y = 10

decorations = "Full"
decorations_theme_variant = "Dark"


[font]
size = 10.0

[font.normal]
family = "DejaVu Sans Mono"
style = "Book"

[font.bold]
family = "DejaVu Sans Mono"
style = "Bold"

[font.italic]
family = "DejaVu Sans Mono"
style = "Oblique"

[font.bold_italic]
family = "DejaVu Sans Mono"
style = "Bold Oblique"

# Colors (Gruvbox dark)

# Default colors
[colors.primary]
# hard contrast background = = '#1d2021'
background = '#282828'
# soft contrast background = = '#32302f'
foreground = '#ebdbb2'

# Normal colors
[colors.normal]
black   = '#282828'
red     = '#cc241d'
green   = '#98971a'
yellow  = '#d79921'
blue    = '#458588'
magenta = '#b16286'
cyan    = '#689d6a'
white   = '#a89984'

# Bright colors
[colors.bright]
black   = '#928374'
red     = '#fb4934'
green   = '#b8bb26'
yellow  = '#fabd2f'
blue    = '#83a598'
magenta = '#d3869b'
cyan    = '#8ec07c'
white   = '#ebdbb2'
'';

".config/hypr/hyprland.conf".text = ''
# #######################################################################################
# AUTOGENERATED HYPRLAND CONFIG.
# PLEASE USE THE CONFIG PROVIDED IN THE GIT REPO /examples/hyprland.conf AND EDIT IT,
# OR EDIT THIS ONE ACCORDING TO THE WIKI INSTRUCTIONS.
# #######################################################################################

# autogenerated = 1 # remove this line to remove the warning

# This is an example Hyprland config file.
# Refer to the wiki for more information.
# https://wiki.hyprland.org/Configuring/

# Please note not all available settings / options are set here.
# For a full list, see the wiki

# You can split this configuration into multiple files
# Create your files separately and then link them to this file like this:
# source = ~/.config/hypr/myColors.conf


################
### MONITORS ###
################

# See https://wiki.hyprland.org/Configuring/Monitors/
monitor=,preferred,auto,1


###################
### MY PROGRAMS ###
###################

# See https://wiki.hyprland.org/Configuring/Keywords/

# Set programs that you use
$terminal = alacritty
$fileManager = thunar
$menu = rofi -show drun


#################
### AUTOSTART ###
#################

# Autostart necessary processes (like notifications daemons, status bars, etc.)
# Or execute your favorite apps at launch like this:

#exec-once = hyprpaper &
exec-once = $terminal
# exec-once = nm-applet &
# exec-once = waybar & hyprpaper & firefox


#############################
### ENVIRONMENT VARIABLES ###
#############################

# See https://wiki.hyprland.org/Configuring/Environment-variables/

env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24


#####################
### LOOK AND FEEL ###
#####################

# Refer to https://wiki.hyprland.org/Configuring/Variables/

# https://wiki.hyprland.org/Configuring/Variables/#general
general {
    gaps_in = 5
    gaps_out = 20

    border_size = 2

    # https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
    col.active_border = rgba(458588ee) rgba(fe8019ee) 45deg
    col.inactive_border = rgba(504945aa)

    # Set to true enable resizing windows by clicking and dragging on borders and gaps
    resize_on_border = false

    # Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
    allow_tearing = false

    layout = dwindle
}

# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
    rounding = 10
    rounding_power = 2

    # Change transparency of focused and unfocused windows
    active_opacity = 1.0
    inactive_opacity = 1.0

    shadow {
        enabled = true
        range = 4
        render_power = 3
        color = rgba(1a1a1aee)
    }

    # https://wiki.hyprland.org/Configuring/Variables/#blur
    blur {
        enabled = true
        size = 3
        passes = 1

        vibrancy = 0.1696
    }
}

# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
    enabled = yes, please :)

    # Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more

    bezier = easeOutQuint,0.23,1,0.32,1
    bezier = easeInOutCubic,0.65,0.05,0.36,1
    bezier = linear,0,0,1,1
    bezier = almostLinear,0.5,0.5,0.75,1.0
    bezier = quick,0.15,0,0.1,1

    animation = global, 1, 10, default
    animation = border, 1, 5.39, easeOutQuint
    animation = windows, 1, 4.79, easeOutQuint
    animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
    animation = windowsOut, 1, 1.49, linear, popin 87%
    animation = fadeIn, 1, 1.73, almostLinear
    animation = fadeOut, 1, 1.46, almostLinear
    animation = fade, 1, 3.03, quick
    animation = layers, 1, 3.81, easeOutQuint
    animation = layersIn, 1, 4, easeOutQuint, fade
    animation = layersOut, 1, 1.5, linear, fade
    animation = fadeLayersIn, 1, 1.79, almostLinear
    animation = fadeLayersOut, 1, 1.39, almostLinear
    animation = workspaces, 1, 1.94, almostLinear, fade
    animation = workspacesIn, 1, 1.21, almostLinear, fade
    animation = workspacesOut, 1, 1.94, almostLinear, fade
}

# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
# "Smart gaps" / "No gaps when only"
# uncomment all if you wish to use that.
# workspace = w[tv1], gapsout:0, gapsin:0
# workspace = f[1], gapsout:0, gapsin:0
# windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
# windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
# windowrulev2 = rounding 0, floating:0, onworkspace:f[1]

# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
    pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
    preserve_split = true # You probably want this
}

# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
    new_status = master
}

# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
    force_default_wallpaper = 0 # Set to 0 or 1 to disable the anime mascot wallpapers
    disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
}


#############
### INPUT ###
#############

# https://wiki.hyprland.org/Configuring/Variables/#input
input {
    kb_layout = us
    kb_variant =
    kb_model =
    kb_options =
    kb_rules =

    follow_mouse = 1

    sensitivity = 0 # -1.0 - 1.0, 0 means no modification.

    touchpad {
        natural_scroll = true
    }
}

# https://wiki.hyprland.org/Configuring/Variables/#gestures
gestures {
    workspace_swipe = true
}

# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device {
    name = epic-mouse-v1
    sensitivity = -0.5
}


###################
### KEYBINDINGS ###
###################

# See https://wiki.hyprland.org/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier

# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, RETURN, exec, $terminal
bind = $mainMod, Q, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, togglesplit, # dwindle

# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d

# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10

# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10

# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic

# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1

# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow

# Laptop multimedia keys for volume and LCD brightness
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-

# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPause, exec, playerctl play-pause
bindl = , XF86AudioPlay, exec, playerctl play-pause
bindl = , XF86AudioPrev, exec, playerctl previous

##############################
### WINDOWS AND WORKSPACES ###
##############################

# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules

# Example windowrule v1
# windowrule = float, ^(kitty)$

# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$

# Ignore maximize requests from apps. You'll probably like this.
windowrulev2 = suppressevent maximize, class:.*

# Fix some dragging issues with XWayland
windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
'';
}

FWIW, copying the contents between the double-single quotes into the hyprland.conf works (except for one attribute “rounding_power”…but this error does not prevent hyprland from loading the config, it just pops up a message).

Rather than specifying home.file in one place and being careful with importing one file at a time (which you can absolutely do, but the code becomes gnarly), you should be using nix import system to do it for you.

Given files:

default.nix
foo.nix
bar.nix

If you have:

# default.nix
{
  imports = [ ./foo.nix ./bar.nix ]; # Note, it's not _import_, it's _imports_
} 

# foo.nix
{
  home.file.foo.text = "hello";
}

# bar.nix
{
  home.file.bar.text = "world";
}

The final result will neatly merge all options.

Also note that Home Manager has pre-built options for the software you are trying to configure. So, rather than using home.file options, you can use the HM’s built-in options. Compared to just throwing files around, HM options will also configure other things (check declared by link in the option to see the option’s effect).

Side note: import accepts a single file as an argument. You can use it dynamically, but I’d advise against doing so at this point as making the code too dynamic will spike WTFs/minute when it breaks.

Well, I just tried to rebuild and it worked without error with that one attribute commented out. I guess I figured it would build and run as it did when I manually copied the hyprland config (it would run but present the error in hyprland). I guess that’s not the case.

I’d still like to know how to do a home.file = import that references multiple files so I can separate out these configs but at least hyprland is usable now.

I missed your response. Ok, this makes sense. so when calling the import, you use the imported filename without the .nix extension. I think I was missing that. I did see where people referenced using the imports for the files but I was missing on how to call that imported data.

Thank you so much for that help. I think that will help clean things up significantly.

Actually, I don’t think what I said was right. I get how to include the file in the imports. But how do I then apply that imported data to a specific part of the config?

Yeah, the import vs imports has tripped folks up in the past.

import is a Nix language concept that allows importing a Nix expression from a file and getting it in the calling place scope.

imports is a concept from Nix module system

Do I do the imports in place then rather than at the beginning with the other imports?

Maybe I should just read up more on it. Thanks for pointing me in the right direction and for that clarification. It really helps to know that distinction.