main
1{
2 lib,
3 config,
4 ...
5}: {
6 options.programs.bash = {
7 enable = lib.mkOption {
8 type = lib.types.bool;
9 description = ''
10 Whenever to configure Bash as an interactive shell.
11 '';
12 default = true;
13 };
14 shellInit = lib.mkOption {
15 type = lib.types.lines;
16 description = ''
17 Shell script code called during bash shell initialisation.
18 '';
19 default = "";
20 };
21 interactiveShellInit = lib.mkOption {
22 type = lib.types.lines;
23 description = ''
24 Shell script code called during interactive bash shell initialisation.
25 '';
26 default = "";
27 };
28 shellAliases = lib.mkOption {
29 type = with lib.types; attrsOf (nullOr (either str path));
30 description = ''
31 Set of aliases for bash shell
32 '';
33 example = {
34 ll = "ls -l";
35 ".." = "cd ..";
36 };
37 default = {};
38 };
39 shellOptions = lib.mkOption {
40 type = lib.types.listOf lib.types.str;
41 default = [
42 # Append to history file rather than replacing it.
43 "histappend"
44
45 # Extended globbing.
46 "extglob"
47 "globstar"
48
49 # Warn if closing shell with running jobs.
50 "checkjobs"
51 ];
52 example = [
53 "extglob"
54 "-cdspell"
55 ];
56 description = ''
57 Shell options to set. Prefix an option with
58 "`-`" to unset.
59 '';
60 };
61 history = {
62 options = lib.mkOption {
63 type = lib.types.listOf (
64 lib.types.enum [
65 "erasedups"
66 "ignoredups"
67 "ignorespace"
68 "ignoreboth"
69 ]
70 );
71 default = [];
72 description = "Controlling how commands are saved on the history list.";
73 };
74 size = lib.mkOption {
75 type = lib.types.nullOr lib.types.int;
76 default = 100000;
77 description = "Number of history lines to keep on memory.";
78 };
79 ignore = lib.mkOption {
80 type = lib.types.listOf lib.types.str;
81 default = [];
82 example = [
83 "ls"
84 "cd"
85 "exit"
86 ];
87 description = "List of commands that should not be saved to the history list.";
88 };
89 file = {
90 path = lib.mkOption {
91 type = lib.types.nullOr lib.types.str;
92 default = null;
93 description = "Location of the bash history file.";
94 };
95 size = lib.mkOption {
96 type = lib.types.nullOr lib.types.int;
97 default = 100000;
98 description = "Number of history lines to keep on file.";
99 };
100 };
101 };
102 };
103
104 config = let
105 cfg = config.programs.bash;
106
107 historyLines = (
108 lib.concatStringsSep "\n" (
109 lib.mapAttrsToList (n: v: "${n}=${v}") (
110 lib.optionalAttrs (cfg.history.file.size != null) {
111 HISTFILESIZE = toString cfg.history.file.size;
112 }
113 // lib.optionalAttrs (cfg.history.size != null) {
114 HISTSIZE = toString cfg.history.size;
115 }
116 // lib.optionalAttrs (cfg.history.file.path != null) {
117 HISTFILE = ''"${cfg.history.file.path}"'';
118 }
119 // lib.optionalAttrs (cfg.history.options != []) {
120 HISTCONTROL = lib.concatStringsSep ":" cfg.history.options;
121 }
122 // lib.optionalAttrs (cfg.history.ignore != []) {
123 HISTIGNORE = lib.escapeShellArg (lib.concatStringsSep ":" cfg.history.ignore);
124 }
125 )
126 ++ lib.optional (cfg.history.file.path != null) ''mkdir -p "$(dirname "$HISTFILE")"''
127 )
128 );
129
130 shoptsLines = let
131 switch = v:
132 if lib.hasPrefix "-" v
133 then "-u"
134 else "-s";
135 in
136 lib.concatStringsSep "\n" (map (v: "shopt ${switch v} ${lib.removePrefix "-" v}") cfg.shellOptions);
137
138 aliasesLines = builtins.concatStringsSep "\n" (
139 lib.mapAttrsToList (k: v: "alias -- ${k}=${lib.escapeShellArg v}") (
140 lib.filterAttrs (k: v: v != null) cfg.shellAliases
141 )
142 );
143 in {
144 files.".bash_profile".text = ''
145 # load session variables
146 . ${config.environment.loadEnv}
147
148 ${cfg.shellInit}
149
150 # include .bashrc if it exists
151 [[ -f ~/.bashrc ]] && . ~/.bashrc
152 '';
153
154 files.".bashrc".text = ''
155 # Commands that should be applied only for interactive shells.
156 [[ $- == *i* ]] || return
157
158 ${historyLines}
159
160 ${shoptsLines}
161
162 ${aliasesLines}
163
164 ${cfg.interactiveShellInit}
165 '';
166 };
167}