summary refs log tree commit diff
path: root/nixos/modules/system/activation/activation-script.nix
blob: ddfd1af4a3190cacfabe051fc3b2b7ee6c43e444 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# generate the script used to activate the configuration.
{ config, lib, pkgs, ... }:

with lib;

let

  addAttributeName = mapAttrs (a: v: v // {
    text = ''
      #### Activation script snippet ${a}:
      _localstatus=0
      ${v.text}

      if (( _localstatus > 0 )); then
        printf "Activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus"
      fi
    '';
  });

  path = with pkgs; map getBin
    [ coreutils
      gnugrep
      findutils
      getent
      stdenv.cc.libc # nscd in update-users-groups.pl
      shadow
      nettools # needed for hostname
      utillinux # needed for mount and mountpoint
    ];

in

{

  ###### interface

  options = {

    system.activationScripts = mkOption {
      default = {};

      example = literalExample ''
        { stdio = {
            text = '''
              # Needed by some programs.
              ln -sfn /proc/self/fd /dev/fd
              ln -sfn /proc/self/fd/0 /dev/stdin
              ln -sfn /proc/self/fd/1 /dev/stdout
              ln -sfn /proc/self/fd/2 /dev/stderr
            ''';
            deps = [];
          };
        }
      '';

      description = ''
        A set of shell script fragments that are executed when a NixOS
        system configuration is activated.  Examples are updating
        /etc, creating accounts, and so on.  Since these are executed
        every time you boot the system or run
        <command>nixos-rebuild</command>, it's important that they are
        idempotent and fast.
      '';

      type = types.attrsOf types.unspecified; # FIXME

      apply = set: {
        script =
          ''
            #! ${pkgs.runtimeShell}

            systemConfig=@out@

            export PATH=/empty
            for i in ${toString path}; do
                PATH=$PATH:$i/bin:$i/sbin
            done

            _status=0
            trap "_status=1 _localstatus=\$?" 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 "$systemConfig")" /run/current-system

            # Prevent the current configuration from being garbage-collected.
            ln -sfn /run/current-system /nix/var/nix/gcroots/current-system

            exit $_status
          '';
      };
    };

    system.userActivationScripts = mkOption {
      default = {};

      example = literalExample ''
        { plasmaSetup = {
            text = '''
              ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
            ''';
            deps = [];
          };
        }
      '';

      description = ''
        A set of shell script fragments that are executed by a systemd user
        service when a NixOS system configuration is activated. Examples are
        rebuilding the .desktop file cache for showing applications in the menu.
        Since these are executed every time you run
        <command>nixos-rebuild</command>, it's important that they are
        idempotent and fast.
      '';

      type = types.attrsOf types.unspecified;

      apply = set: {
        script = ''
          unset PATH
          for i in ${toString path}; do
            PATH=$PATH:$i/bin:$i/sbin
          done

          _status=0
          trap "_status=1 _localstatus=\$?" ERR

          ${
            let
              set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
              withHeadlines = addAttributeName set';
            in textClosureMap id (withHeadlines) (attrNames withHeadlines)
          }

          exit $_status
        '';
      };

    };

    environment.usrbinenv = mkOption {
      default = "${pkgs.coreutils}/bin/env";
      example = literalExample ''
        "''${pkgs.busybox}/bin/env"
      '';
      type = types.nullOr types.path;
      visible = false;
      description = ''
        The env(1) executable that is linked system-wide to
        <literal>/usr/bin/env</literal>.
      '';
    };
  };


  ###### implementation

  config = {

    system.activationScripts.stdio = ""; # obsolete

    system.activationScripts.var =
      ''
        # Various log/runtime directories.

        mkdir -m 1777 -p /var/tmp

        # Empty, immutable home directory of many system accounts.
        mkdir -p /var/empty
        # Make sure it's really empty
        ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true
        find /var/empty -mindepth 1 -delete
        chmod 0555 /var/empty
        chown root:root /var/empty
        ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true
      '';

    system.activationScripts.usrbinenv = if config.environment.usrbinenv != null
      then ''
        mkdir -m 0755 -p /usr/bin
        ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp
        mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
      ''
      else ''
        rm -f /usr/bin/env
        rmdir --ignore-fail-on-non-empty /usr/bin /usr
      '';

    system.activationScripts.specialfs =
      ''
        specialMount() {
          local device="$1"
          local mountPoint="$2"
          local options="$3"
          local fsType="$4"

          if mountpoint -q "$mountPoint"; then
            local options="remount,$options"
          else
            mkdir -m 0755 -p "$mountPoint"
          fi
          mount -t "$fsType" -o "$options" "$device" "$mountPoint"
        }
        source ${config.system.build.earlyMountScript}
      '';

    systemd.user = {
      services.nixos-activation = {
        description = "Run user-specific NixOS activation";
        script = config.system.userActivationScripts.script;
        unitConfig.ConditionUser = "!@system";
        serviceConfig.Type = "oneshot";
      };
    };
  };

}