├── Makefile ├── nixos.nix ├── darwin.nix ├── standalone.nix ├── .editorconfig ├── flake.nix ├── flake.lock ├── module.nix ├── LICENSE ├── activation.nix └── README.md /Makefile: -------------------------------------------------------------------------------- 1 | nixpkgs/update: 2 | @nix flake lock --override-input nixpkgs github:NixOS/nixpkgs/$(rev) 3 | -------------------------------------------------------------------------------- /nixos.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | 3 | let 4 | cfg = config.homini; 5 | in 6 | { 7 | imports = [ ./module.nix ]; 8 | 9 | config = lib.mkIf cfg.enable { 10 | system.userActivationScripts.homini.text = '' 11 | "${cfg.activationPackage}/bin/homini" 12 | ''; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /darwin.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | 3 | let 4 | cfg = config.homini; 5 | in 6 | { 7 | imports = [ ./module.nix ]; 8 | 9 | config = lib.mkIf cfg.enable { 10 | system.activationScripts.postUserActivation.text = '' 11 | "${cfg.activationPackage}/bin/homini" 12 | ''; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /standalone.nix: -------------------------------------------------------------------------------- 1 | { pkgs, dir }: 2 | 3 | let 4 | activation-script = pkgs.callPackage ./activation.nix { }; 5 | in 6 | pkgs.runCommand "homini" { } '' 7 | mkdir -p $out/bin 8 | cp ${activation-script} $out/bin/homini 9 | substituteInPlace $out/bin/homini \ 10 | --subst-var-by OUT $out 11 | ln -s ${dir} $out/homini-dotfiles 12 | '' 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; https://editorconfig.org 2 | root = true 3 | 4 | ; default configuration 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = tab 11 | 12 | [*.nix] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [flake.lock] 17 | indent_style = unset 18 | 19 | [*.md] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Homini - Minimalist Dotfiles Manager using Nix"; 3 | 4 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 5 | 6 | outputs = 7 | { ... }: 8 | { 9 | nixosModules = rec { 10 | homini = import ./nixos.nix; 11 | default = homini; 12 | }; 13 | darwinModules = rec { 14 | homini = import ./darwin.nix; 15 | default = homini; 16 | }; 17 | standalone = import ./standalone.nix; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1748329892, 6 | "narHash": "sha256-1IvXUnfyb7OJVLELBnyZtBuViZ/p0ZEQGo/omGEoB0M=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "fd695452f4da00d010ec3caf700204461c08251d", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /module.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | 8 | let 9 | cfg = config.homini; 10 | in 11 | { 12 | options.homini = { 13 | enable = lib.mkEnableOption ''Enable homini.''; 14 | 15 | dir = lib.mkOption { 16 | type = lib.types.path; 17 | description = '' 18 | The path of the dotfiles directory that will be symlinked to $HOME. 19 | ''; 20 | }; 21 | 22 | activationPackage = lib.mkOption { 23 | internal = true; 24 | type = lib.types.package; 25 | description = '' 26 | The package containing the complete activation script. 27 | ''; 28 | }; 29 | }; 30 | 31 | config = { 32 | homini.activationPackage = 33 | let 34 | activation-script = pkgs.callPackage ./activation.nix { }; 35 | in 36 | pkgs.runCommand "homini" { } '' 37 | mkdir -p $out/bin 38 | cp ${activation-script} $out/bin/homini 39 | substituteInPlace $out/bin/homini \ 40 | --subst-var-by OUT $out 41 | ln -s ${cfg.dir} $out/homini-dotfiles 42 | ''; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Billy Zaelani Malik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /activation.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs }: 2 | 3 | let 4 | activation-bin-path = lib.makeBinPath ( 5 | with pkgs; 6 | [ 7 | nix 8 | bash 9 | coreutils 10 | findutils 11 | ] 12 | ); 13 | 14 | cleanup = pkgs.writeShellScript "cleanup" '' 15 | old_dotfiles=$1 16 | new_dotfiles=$2 17 | shift 18 | shift 19 | for src_path in "$@"; do 20 | rel_path="''${src_path#$old_dotfiles/}" 21 | dst_path="$HOME/$rel_path" 22 | [[ -e "$new_dotfiles/$rel_path" ]] && continue 23 | [[ ! -L "$dst_path" ]] && continue 24 | rm "$dst_path" 25 | done 26 | ''; 27 | 28 | link = pkgs.writeShellScript "link" '' 29 | new_dotfiles=$1 30 | shift 31 | for src_path in "$@"; do 32 | rel_path="''${src_path#$new_dotfiles/}" 33 | dst_path="$HOME/$rel_path" 34 | [[ -L "$dst_path" ]] && collision_opt='f' || collision_opt='b' 35 | mkdir -p "$(dirname "$dst_path")" 36 | ln -s$collision_opt "$src_path" "$dst_path" 37 | done 38 | ''; 39 | in 40 | pkgs.writeShellScript "activation-script" '' 41 | export PATH="${activation-bin-path}" 42 | 43 | log_info() { echo "homini: $@"; } 44 | log_error() { >&2 echo "homini: $@"; } 45 | 46 | xdg_state_home="''${XDG_STATE_HOME:-$HOME/.local/state}" 47 | gcroots="$xdg_state_home/homini/gcroots/homini" 48 | if [[ -e $gcroots ]]; then 49 | old_path="$(readlink -e "$gcroots")" 50 | old_dotfiles=$(readlink -e "$old_path/homini-dotfiles") 51 | fi 52 | new_path="@OUT@" 53 | new_dotfiles=$(readlink -e "$new_path/homini-dotfiles") 54 | 55 | if [[ "$old_dotfiles" == "$new_dotfiles" ]]; then 56 | log_info "no dotfiles changes"; exit 0 57 | fi 58 | 59 | cleanup_old_dotfiles() { 60 | [[ ! -e "$old_dotfiles" ]] && return 61 | find $old_dotfiles -type f -exec bash ${cleanup} $old_dotfiles $new_dotfiles {} + 62 | } 63 | 64 | switch_gcroots() { 65 | [[ "$old_path" == "$new_path" ]] && return 66 | nix-store --realise "$new_path" --add-root "$gcroots" > /dev/null 67 | } 68 | 69 | link_new_dotfiles() { 70 | local new_dotfiles=$(readlink -e $new_path/homini-dotfiles) 71 | find $new_dotfiles -type f -exec bash ${link} $new_dotfiles {} + 72 | } 73 | 74 | cleanup_old_dotfiles 75 | switch_gcroots 76 | link_new_dotfiles 77 | '' 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Homini 2 | 3 | Homini is a **minimalist dotfiles manager** using Nix inspired by 4 | [Home Manager](https://github.com/nix-community/home-manager) and 5 | [GNU Stow](https://www.gnu.org/software/stow/). 6 | 7 | ## How to use Homini? 8 | 9 | Consider the following snipet 10 | 11 | ```nix 12 | homini = { 13 | enable = true; 14 | dir = ./dotfiles; 15 | }; 16 | ``` 17 | 18 | this will link the `dotfiles` directory 19 | 20 | ``` 21 | dotfiles 22 | └── .config 23 | └── git 24 | ├── config 25 | ├── ignore 26 | ├── personal 27 | └── work 28 | ``` 29 | 30 | to your `$HOME` directory. 31 | 32 | ``` 33 | $HOME 34 | └── .config 35 | └── git 36 | ├── config -> /nix/store/xqj0sf5q4q35q1hp19yyhfp8sbp0zrwa-dotfiles/.config/git/config 37 | ├── ignore -> /nix/store/xqj0sf5q4q35q1hp19yyhfp8sbp0zrwa-dotfiles/.config/git/ignore 38 | ├── personal -> /nix/store/xqj0sf5q4q35q1hp19yyhfp8sbp0zrwa-dotfiles/.config/git/personal 39 | └── work -> /nix/store/xqj0sf5q4q35q1hp19yyhfp8sbp0zrwa-dotfiles/.config/git/work 40 | ``` 41 | 42 | ### NixOS 43 | 44 | Rebuild the following flake with `nixos-rebuild switch --flake .#machine`. 45 | 46 | ```nix 47 | { 48 | description = "My NixOS Configurations"; 49 | 50 | inputs = { 51 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 52 | homini.url = "github:smoothprogrammer/homini"; 53 | homini.inputs.nixpkgs.follows = "nixpkgs"; 54 | }; 55 | 56 | outputs = { nixpkgs, homini, ... }: { 57 | nixosConfigurations.machine = nixpkgs.lib.nixosSystem { 58 | system = "x86_64-linux"; 59 | modules = [ 60 | ./configuration.nix 61 | homini.nixosModules.homini { 62 | homini = { 63 | enable = true; 64 | dir = ./dotfiles; 65 | }; 66 | } 67 | ]; 68 | }; 69 | }; 70 | } 71 | ``` 72 | 73 | ### MacOS (nix-darwin) 74 | 75 | Rebuild the following flake with `darwin-rebuild switch --flake .#machine`. 76 | 77 | ```nix 78 | { 79 | description = "My MacOS (nix-darwin) Configurations"; 80 | 81 | inputs = { 82 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 83 | darwin.url = "github:LnL7/nix-darwin"; 84 | darwin.inputs.nixpkgs.follows = "nixpkgs"; 85 | homini.url = "github:smoothprogrammer/homini"; 86 | homini.inputs.nixpkgs.follows = "nixpkgs"; 87 | }; 88 | 89 | outputs = { darwin, homini, ... }: { 90 | darwinConfigurations.machine = darwin.lib.darwinSystem { 91 | system = "x86_64-linux"; 92 | modules = [ 93 | ./configuration.nix 94 | homini.darwinModules.homini { 95 | homini = { 96 | enable = true; 97 | dir = ./dotfiles; 98 | }; 99 | } 100 | ]; 101 | }; 102 | }; 103 | } 104 | ``` 105 | 106 | ### Standalone 107 | 108 | Run the following flake with `nix run`. 109 | 110 | ```nix 111 | { 112 | description = "My dotfiles"; 113 | 114 | inputs = { 115 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 116 | homini.url = "github:smoothprogrammer/homini"; 117 | homini.inputs.nixpkgs.follows = "nixpkgs"; 118 | }; 119 | 120 | outputs = { nixpkgs, homini, ... }: 121 | let 122 | forAllSystems = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed; 123 | in 124 | { 125 | packages = forAllSystems (system: { 126 | default = homini.standalone { 127 | pkgs = nixpkgs.legacyPackages.${system}; 128 | dir = ./dotfiles; 129 | }; 130 | }); 131 | }; 132 | } 133 | ``` 134 | 135 | ## Why Homini when we have Home Manager? 136 | 137 | This [Article](https://www.fbrs.io/nix-hm-reflections) by [Florian Beeres](https://github.com/cideM/) 138 | is what inspired me to write Homini and this quote sums up why 139 | 140 | > At the end of the day I really don’t need the per-user installation of packages 141 | > and elaborate modules that Home Manager gives me. 142 | > I’d be perfectly content with providing a list of packages to install system-wide 143 | > and a few basic primitives to generate configuration files in my home folder. 144 | --------------------------------------------------------------------------------