From e8399f7c9fdb0869042698d31d45f47444a4c66d Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Sun, 19 Oct 2014 19:28:45 +0200 Subject: [PATCH 1/3] Start a configuration management tool based on Nix to manage User Profiles. --- nixup/default.nix | 17 +++++ nixup/lib/eval-config.nix | 24 +++++++ nixup/lib/from-env.nix | 4 ++ nixup/modules/module-list.nix | 4 ++ nixup/modules/nixpkgs.nix | 96 ++++++++++++++++++++++++++++ nixup/modules/user/activation.nix | 102 ++++++++++++++++++++++++++++++ nixup/modules/user/build.nix | 50 +++++++++++++++ 7 files changed, 297 insertions(+) create mode 100644 nixup/default.nix create mode 100644 nixup/lib/eval-config.nix create mode 100644 nixup/lib/from-env.nix create mode 100644 nixup/modules/module-list.nix create mode 100644 nixup/modules/nixpkgs.nix create mode 100644 nixup/modules/user/activation.nix create mode 100644 nixup/modules/user/build.nix diff --git a/nixup/default.nix b/nixup/default.nix new file mode 100644 index 0000000000000..6808abc21ee3a --- /dev/null +++ b/nixup/default.nix @@ -0,0 +1,17 @@ +{ configuration ? import ./lib/from-env.nix "NIX_USER_CONFIG" +, system ? builtins.currentSystem +}: + +let + eval = import ./lib/eval-config.nix { + modules = [ configuration { nixpkgs.system = system; } ]; + }; + + inherit (eval) pkgs; +in + +{ + inherit (eval) config options; + + profile = eval.config.user.build.profile; +} diff --git a/nixup/lib/eval-config.nix b/nixup/lib/eval-config.nix new file mode 100644 index 0000000000000..71c884c977014 --- /dev/null +++ b/nixup/lib/eval-config.nix @@ -0,0 +1,24 @@ +# From an end-user configuration file (`configuration'), build a NixUP +# configuration object (`config') from which we can retrieve option +# values. + +{ lib ? import +, baseModules ? import ../modules/module-list.nix +, modules +, extraArgs ? {} +, check ? true +, prefix ? [] +}: + +rec { + + # Merge the option definitions in all modules, forming the full + # system configuration. + inherit (lib.evalModules { + inherit prefix; + modules = modules ++ baseModules; + args = extraArgs; + check = check; + }) config options; + +} diff --git a/nixup/lib/from-env.nix b/nixup/lib/from-env.nix new file mode 100644 index 0000000000000..6bd71e40e9a1a --- /dev/null +++ b/nixup/lib/from-env.nix @@ -0,0 +1,4 @@ +# TODO: remove this file. There is lib.maybeEnv now +name: default: +let value = builtins.getEnv name; in +if value == "" then default else value diff --git a/nixup/modules/module-list.nix b/nixup/modules/module-list.nix new file mode 100644 index 0000000000000..337c6771c17ed --- /dev/null +++ b/nixup/modules/module-list.nix @@ -0,0 +1,4 @@ +[ ./user/activation.nix + ./user/build.nix + ./nixpkgs.nix +] \ No newline at end of file diff --git a/nixup/modules/nixpkgs.nix b/nixup/modules/nixpkgs.nix new file mode 100644 index 0000000000000..66b71e3c38ebe --- /dev/null +++ b/nixup/modules/nixpkgs.nix @@ -0,0 +1,96 @@ +{ config, lib, ... }: + +with lib; + +let + + isConfig = x: + builtins.isAttrs x || builtins.isFunction x; + + optCall = f: x: + if builtins.isFunction f + then f x + else f; + + mergeConfig = lhs_: rhs_: + let + lhs = optCall lhs_ { inherit pkgs; }; + rhs = optCall rhs_ { inherit pkgs; }; + in + lhs // rhs // + optionalAttrs (lhs ? packageOverrides) { + packageOverrides = pkgs: + optCall lhs.packageOverrides pkgs // + optCall (attrByPath ["packageOverrides"] ({}) rhs) pkgs; + }; + + configType = mkOptionType { + name = "nixpkgs config"; + check = traceValIfNot isConfig; + merge = args: fold (def: mergeConfig def.value) {}; + }; + +in + +{ + options = { + nixpkgs.path = mkOption { + type = types.path; + example = "/home/user/dev/nixpkgs"; + description = '' + Location of the collection of packages which is used for building + the current user profile. This is also useful for building profile + against either bleeding-edge recipes or archived version of the + recipes. + ''; + }; + + nixpkgs.config = mkOption { + default = {}; + example = literalExample + '' + { firefox.enableGeckoMediaPlayer = true; + packageOverrides = pkgs: { + firefox60Pkgs = pkgs.firefox60Pkgs.override { + enableOfficialBranding = true; + }; + }; + } + ''; + type = configType; + description = '' + The configuration of the Nix Packages collection. (For + details, see the Nixpkgs documentation.) It allows you to set + package configuration options, and to override packages + globally through the packageOverrides + option. The latter is a function that takes as an argument + the original Nixpkgs, and must evaluate + to a set of new or overridden packages. + ''; + }; + + nixpkgs.system = mkOption { + type = types.str; + description = '' + Specifies the Nix platform type for which NixOS should be built. + If unset, it defaults to the platform type of your host system. + Specifying this option is useful when doing distributed + multi-platform deployment, or when building virtual machines. + ''; + }; + + nixpkgs.pkgs = mkOption { + type = types.attrs; + internal = true; + description = '' + Attribute set containing the collection of packages imported from + the . + ''; + }; + }; + + config = mkDefault { + nixpkgs.path = ; + nixpkgs.pkgs = import config.nixpkgs.path { inherit (config.nixpkgs) system config; }; + }; +} diff --git a/nixup/modules/user/activation.nix b/nixup/modules/user/activation.nix new file mode 100644 index 0000000000000..bcb6ba34fa092 --- /dev/null +++ b/nixup/modules/user/activation.nix @@ -0,0 +1,102 @@ +{ config, lib, ... }: + +with lib; + +let + pkgs = config.nixpkgs.pkgs; + + addAttributeName = mapAttrs (a: v: v // { + text = '' + #### Activation script snippet ${a}: + ${v.text} + ''; + }); + + path = [ pkgs.coreutils ]; + + activateScript = pkgs.writeScript "user-profile-activate" config.user.activationScripts; +in + +{ + + options = { + + user.activationScripts = mkOption { + default = {}; + + example = { + aliasIfconfig = { + text = '' + # Some local configuration + if ifconfig > /dev/null 2>&1; then + : + elif test -x /sbin/ifconfig; then + ln -s /sbin/ifconfig ~/.usr/bin/ifconfig + fi + ''; + deps = [ "setupLocalUsr" ]; + }; + }; + + description = '' + A set of shell script fragments that are executed when a NixOS + system configuration is activated. Examples are updating + ~/., creating accounts, and so on. Since these + are executed every time you boot the system or run + nixos-rebuild, it's important that they are + idempotent and fast. + ''; + + type = types.attrsOf types.unspecified; # FIXME + + apply = set: + '' + #! ${pkgs.stdenv.shell} + + userConfig=@out@ + profileLink=$HOME/.nixup/profile + + export PATH=/empty + for i in ${toString path}; do + PATH=$PATH:$i/bin:$i/sbin + done + + _status=0 + trap "_status=1" ERR + + # Ensure a consistent umask. + umask 0022 + + ${ + let + set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set; + withHeadlines = addAttributeName set'; + in textClosureMap id (withHeadlines) (attrNames withHeadlines) + } + + # Make this configuration the current configuration. + # The readlink is there to ensure that when $systemConfig = /system + # (which is a symlink to the store), /run/current-system is still + # used as a garbage collection root. + ln -sfn "$(readlink -f "$userConfig")" $profileLink + + # Prevent the current configuration from being garbage-collected. + mkdir -p /nix/var/nix/gcroots/per-user/$USER/$HOME + ln -sfn $profileLink /nix/var/nix/gcroots/per-user/$USER/$HOME/profile + + exit $_status + ''; + + }; + + }; + + config = { + user.buildCommands = '' + cp ${toString activateScript} $out/activate + substituteInPlace $out/activate --subst-var out + chmod u+x $out/activate + unset activationScript + ''; + }; +} diff --git a/nixup/modules/user/build.nix b/nixup/modules/user/build.nix new file mode 100644 index 0000000000000..23b670809563e --- /dev/null +++ b/nixup/modules/user/build.nix @@ -0,0 +1,50 @@ +{ config, lib, ... }: + +with lib; + +let + pkgs = config.nixpkgs.pkgs; + + profile = pkgs.stdenv.mkDerivation { + name = "nixup-profile"; + preferLocalBuild = true; + buildCommand = '' + mkdir $out + + ${config.user.buildCommands} + ''; + }; + +in + +{ + options = { + user.build = mkOption { + internal = true; + type = types.attrsOf types.package; + default = {}; + description = '' + Attribute set of derivations used to setup the system. + ''; + }; + + user.buildCommands = mkOption { + internal = true; + type = types.lines; + default = []; + example = literalExample '' + "ln -s ${pkgs.firefox} $out/firefox-stable" + ''; + description = '' + List of commands to build and install the content of the profile + directory. + ''; + }; + }; + + config = { + + user.build.profile = profile; + + }; +} From 850aeed25310fbce23454636b0a2c58e970429c6 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Mon, 20 Oct 2014 22:48:59 +0200 Subject: [PATCH 2/3] Add user.resourceFiles to create files in the home directory. --- nixup/modules/module-list.nix | 1 + nixup/modules/user/resourceFiles.nix | 94 ++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 nixup/modules/user/resourceFiles.nix diff --git a/nixup/modules/module-list.nix b/nixup/modules/module-list.nix index 337c6771c17ed..e1ff51d9a629d 100644 --- a/nixup/modules/module-list.nix +++ b/nixup/modules/module-list.nix @@ -1,4 +1,5 @@ [ ./user/activation.nix ./user/build.nix + ./user/resourceFiles.nix ./nixpkgs.nix ] \ No newline at end of file diff --git a/nixup/modules/user/resourceFiles.nix b/nixup/modules/user/resourceFiles.nix new file mode 100644 index 0000000000000..aafe402654d46 --- /dev/null +++ b/nixup/modules/user/resourceFiles.nix @@ -0,0 +1,94 @@ +{ config, lib, ... }: + +with lib; + +let + pkgs = config.nixpkgs.pkgs; +in + +{ + options = { + user.resourceFiles = mkOption rec { + default = {}; + type = types.loaOf (types.submodule options); + example = literalExample '' + [ { target = ".ssh/config"; + text = " + Host home.my-domain.name + User seti + "; + } + { target = ".gitconfig"; + source = ./gitconfig; + } + ] + ''; + description = '' + Set of files that have to be linked in the home directory. + ''; + + options = { name, config, ... }: { + options = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether this ~/. file should be generated. This + option allows specific ~/. files to be disabled. + ''; + }; + + target = mkOption { + type = types.str; + description = '' + Name of symlink (relative to ~/.). + Defaults to the attribute name. + ''; + }; + + text = mkOption { + default = null; + type = types.nullOr types.lines; + description = "Text of the file."; + }; + + source = mkOption { + type = types.path; + description = "Path of the source file."; + }; + + setupScript = mkOption { + type = types.lines; + internal = true; + description = "Shell script code to produce the resource file."; + }; + }; + + config = { + target = mkDefault name; + source = mkIf (config.text != null) + (mkDefault (config.pkgs.writeText "homeDir-${name}" config.text)); + + setupScript = mkDefault '' + target="$HOME/${config.target}" + mkdir -p $(dirname $target) + if test -e $target -a \! -L $target; then + mv $target $target.backup + fi + ln -sf "${config.source}" $target + ''; + }; + }; + + }; + }; + + config = { + + user.activationScripts.resourceFiles = '' + echo "Setting up resource files ..." + + ${concatMapStringsSep "\n" (rc: rc.setupScript) (attrValues config.user.resourceFiles)} + ''; + }; +} From 1e50b2adebc998327fe9b8d1c18c7eeaa503d2c5 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Tue, 21 Oct 2014 23:05:13 +0200 Subject: [PATCH 3/3] Add user.packages to list packages to install in the profile. --- nixup/modules/module-list.nix | 3 +- nixup/modules/user/packages.nix | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 nixup/modules/user/packages.nix diff --git a/nixup/modules/module-list.nix b/nixup/modules/module-list.nix index e1ff51d9a629d..b98d1b386f5a4 100644 --- a/nixup/modules/module-list.nix +++ b/nixup/modules/module-list.nix @@ -1,5 +1,6 @@ [ ./user/activation.nix ./user/build.nix + ./user/packages.nix ./user/resourceFiles.nix ./nixpkgs.nix -] \ No newline at end of file +] diff --git a/nixup/modules/user/packages.nix b/nixup/modules/user/packages.nix new file mode 100644 index 0000000000000..247f5b3ee85c1 --- /dev/null +++ b/nixup/modules/user/packages.nix @@ -0,0 +1,56 @@ +{ config, lib, ... }: + +with lib; + +let + pkgs = config.nixpkgs.pkgs; + + userPath = pkgs.buildEnv { + name = "user-path"; + paths = config.user.packages; + pathsToLink = [ "/" ]; + ignoreCollisions = true; + # !!! Hacky, should modularise. + postBuild = + '' + if [ -x $out/bin/update-mime-database -a -w $out/share/mime/packages ]; then + XDG_DATA_DIRS=$out/share $out/bin/update-mime-database -V $out/share/mime > /dev/null + fi + + if [ -x $out/bin/gtk-update-icon-cache -a -f $out/share/icons/hicolor/index.theme ]; then + $out/bin/gtk-update-icon-cache $out/share/icons/hicolor + fi + + if [ -x $out/bin/glib-compile-schemas -a -w $out/share/glib-2.0/schemas ]; then + $out/bin/glib-compile-schemas $out/share/glib-2.0/schemas + fi + + if [ -x $out/bin/update-desktop-database -a -w $out/share/applications ]; then + $out/bin/update-desktop-database $out/share/applications + fi + ''; + }; + +in + +{ + options = { + user.packages = mkOption { + default = []; + type = types.listOf types.path; + example = literalExample "with config.nixpkgs.pkgs; [ firefox thunderbird ]"; + description = '' + The set of packages that appear in + $HOME/.nixup/profile/sw. These packages are + are automatically updated every time you rebuild the user profile. + ''; + }; + }; + + config = { + user.build.userPath = userPath; + user.buildCommands = '' + ln -s ${userPath} $out/sw + ''; + }; +}