-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathbash-setup
More file actions
executable file
·261 lines (244 loc) · 12.4 KB
/
bash-setup
File metadata and controls
executable file
·261 lines (244 loc) · 12.4 KB
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#!/bin/sh
#
# Configure generic bash shell environment.
#
# In WSL environment, make sure prompt and terminal title contains the WSL
# distro name instead of the hostname, which is that of the WSL host.
# Uses the default prompt from Arch and Fedora as starting point.
# Also configure shell options like history, add some aliases etc.
# Do it for all users with a home directory, including root user.
# Assuming running as root, cannot change only for some "current user".
#
# Note: Updates the .bashrc file in individual existing users profile, not the
# global default!
#
# Verify running as root (not relying on sudo which may not be installed yet).
if [ ${EUID:-$(id -u)} -ne 0 ]; then
echo 'Please run this script as root' >&2
exit 1
fi
# Verify not running with sudo.
# This would not work, since the environment variables are then stripped to a
# minimum and specifically the WSL_DISTRO_NAME will not be available.
if [ -n "$SUDO_UID" ]; then
echo 'Please run this script as actual root and not just sudo' >&2
exit 1
fi
if ! command -v bash > /dev/null 2>&1; then
if [ "$1" != '--force' ] && [ "$1" != '-f' ]; then
echo "Bash is not installed" >&2
echo "To proceed anyway run with argument -f or --force" >&2
exit 1
fi
echo "WARNING: Bash is not installed but proceeding anyway due to argument $1"
fi
# Make sure there is a .bashrc in every existing home directory with some
# proper configuration, as well as a .bash_profile to load it for interactive
# login shells. Also make sure to set the owner, to the same as the home
# directory itself. Normally these will exist, e.g. creating users with
# "useradd --create-home" will put a default .bashrc in place, but there may
# be a home directory created manually that does not contain a .bashrc, and
# e.g. on Alpine bash may be installed when users have already been created.
# Note: Assuming every subdirectory of /home is really a user home, and also
# that the name of the subdirectory always matches the username.
# OLD:
#find /home -mindepth 1 -maxdepth 1 -type d -exec cp --no-clobber /etc/skel/.bashrc {} \; -exec chown --reference={} {}/.bashrc \;
# Make sure root user home directory also has a default .bashrc file,
# as well as .bash_profile to load it for interactive login shells. Normally it
# does not! Assuming we are running as root, current owner should be correct.
#cp --no-clobber /etc/skel/.bash_profile /etc/skel/.bashrc /root
#(find /home -mindepth 2 -maxdepth 2 -type f -name .bashrc -print0 && find /root -mindepth 1 -maxdepth 1 -type f -name .bashrc -print0) | while read -d $'\0' file; do
# ....
(find /home -mindepth 1 -maxdepth 1 -type d -print0 && printf '%s\0' /root) | while read -d $'\0' dir; do
user=$(basename "$dir")
echo "Checking user profile ${user}..."
echo "Checking .bash_profile..."
file="$dir/.bash_profile"
if [ ! -f "$file" ]; then
echo "No existing file"
if [ -f "/etc/skel/.bash_profile" ]; then
echo "Copying from skeleton file"
cp "/etc/skel/.bash_profile" "$dir"
else
echo "Creating with hard coded default content"
# Creating with exact content from /etc/skel/.bash_profile in Arch.
cat > "$file" << 'EOF'
#
# ~/.bash_profile
#
[[ -f ~/.bashrc ]] && . ~/.bashrc
EOF
fi
# Note: The following assumes name of home directory always matches
# the username. Using chown --reference="$dir" would be safer, but it
# does not work with BusyBox chown.
chown "${user}:${user}" "$file"
else
echo "Found existing file"
echo "Skipping: Not checking the existing file and will just assume it is correct"
fi
echo "Checking .bashrc..."
file="$dir/.bashrc"
if [ ! -f "$file" ]; then
echo "No existing file"
if [ -f "/etc/skel/.bashrc" ]; then
echo "Copying from skeleton file"
cp /etc/skel/.bashrc "$dir"
else
echo "Creating with hard coded default content"
# Creating with exact content from /etc/skel/.bashrc in Arch.
# We could have merged in all changes below to get the complete
# file immediately, but we do it to easier keep track of the origin.
cat > "$file" << 'EOF'
#
# ~/.bashrc
#
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
alias ls='ls --color=auto'
alias grep='grep --color=auto'
PS1='[\u@\h \W]\$ '
EOF
fi
# Note: Same as for .bash_profile above!
chown "${user}:${user}" "$file"
else
echo "Found existing file"
fi
# Configure shell prompt for WSL
# Commenting out any existing assignment of PS1 variable in .bashrc, and
# adds a new where the WSL distro name is included in the prompt.
if [ -z "$WSL_DISTRO_NAME" ]; then
echo "Skipping shell prompt for WSL: Not in a WSL environment"
else
# Alternative 1: Default prompt with just \h replaced by ${WSL_DISTRO_NAME}
# Assuming default is:
# PS1='[\u@\h \W]\$ '
# Creating a new value where ${WSL_DISTRO_NAME} instead of \h:
# PS1='[\u@${WSL_DISTRO_NAME} \W]\$ '
# And then also modify terminal title correspondingly, from something like
# this which seems to be default, where syntax is "ESC]0;stringBEL" to set
# window title to string but not icon (change from value 0 to 1 for only icon,
# 2 for only title, 0 means both, although don't seem to make any difference),
# with lowercase \w for showing current dir full path, while shell prompt as
# specified above uses uppercase \W to show only the basename:
# \[\e]0;\u@\h:\w\a\]
# To this:
# \[\e]0;\u@${WSL_DISTRO_NAME}:\w\a\]
#prompt="PS1='\\[\\e]0;\\u@\${WSL_DISTRO_NAME}:\\w\\a\\][\\u@\${WSL_DISTRO_NAME} \\W]\\$ '"
# Alternative 2: More customized prompt with colors:
if [ "$user" = "root" ]; then
# The prompt string to insert. Could also be used to match as fixed
# string, with 'grep --quiet --line-regexp --fixed-strings "$prompt"',
# but that is not valid in BusyBox grep.
prompt="PS1='\\[\\e]0;\\u@\${WSL_DISTRO_NAME}:\\w\\a\\]\\[\\e[01;36m\\]\\u\\[\\e[01;30m\\]@\\[\\e[01;32m\\]\${WSL_DISTRO_NAME} \\[\\e[01;34m\\]\\W \\[\\e[01;31m\\]\\$\\[\\e[00m\\] '"
# Regex to match any existing, in basic (BRE) syntax, which works
# with 'grep -q "$prompt"', and therefore also supported with
# BusyBox grep. The combination of regex escaping necessary with
# this syntax, and shell double-quote escaping on top of a string
# that already contains many special characters makes it very
# complex:
# 1: Sequences like \e, \u, \w, \a are not valid escape sequences.
# grep's behavior is undefined — some versions treat \e as literal
# e (ignoring the backslash), others warn and ignore it. So you
# cannot use \\ in a double-quoted string to match a literal \ when
# followed by these letters, because the shell reduces \\ to \, and
# grep then warns about and ignores the \ in \e, matching only e.
# The solution is to use a character class [\\] in the double-
# quoted string, which the shell reduces to [\], and inside a
# character class \ is just a literal character with no special
# meaning, so it reliably matches a literal \.
# 2: Every [ in the pattern — whether it's meant to start a
# character class or match a literal bracket — is interpreted as
# opening a character class. So [01;36m] is not "literal [01;36m]",
# it's a character class containing 0, 1, ;, 3, 6, m followed by a
# literal ]. The solution is to write every literal [ as [\[] — a
# character class containing only [, which matches a literal [.
# Also $ has the same problem — it's an end-of-line anchor outside
# a character class, so literal \$ in the file needs [\\][$] in the
# double-quoted string ([\\] becomes [\] matching \, and [$] matches
# literal $).
prompt_regex="^PS1='[\\][\\[][\\]e]0;[\\]u@\${WSL_DISTRO_NAME}:[\\]w[\\]a[\\]][\\][\\[][\\]e[\\[]01;36m[\\]][\\]u[\\][\\[][\\]e[\\[]01;30m[\\]]@[\\][\\[][\\]e[\\[]01;32m[\\]]\${WSL_DISTRO_NAME} [\\][\\[][\\]e[\\[]01;34m[\\]][\\]W [\\][\\[][\\]e[\\[]01;31m[\\]][\\][$][\\][\\[][\\]e[\\[]00m[\\]] '\$"
#echo "$prompt" | grep "$prompt_regex"
#printf '%s\n' $prompt_regex
else
prompt="PS1='\\[\\e]0;\\u@\${WSL_DISTRO_NAME}:\\w\\a\\]\\[\\e[01;36m\\]\\u\\[\\e[01;30m\\]@\\[\\e[01;32m\\]\${WSL_DISTRO_NAME} \\[\\e[01;34m\\]\\W \\[\\e[01;33m\\]\\$\\[\\e[00m\\] '"
prompt_regex="^PS1='[\\][\\[][\\]e]0;[\\]u@\${WSL_DISTRO_NAME}:[\\]w[\\]a[\\]][\\][\\[][\\]e[\\[]01;36m[\\]][\\]u[\\][\\[][\\]e[\\[]01;30m[\\]]@[\\][\\[][\\]e[\\[]01;32m[\\]]\${WSL_DISTRO_NAME} [\\][\\[][\\]e[\\[]01;34m[\\]][\\]W [\\][\\[][\\]e[\\[]01;33m[\\]][\\][$][\\][\\[][\\]e[\\[]00m[\\]] '\$"
fi
if grep -q "$prompt_regex" "$file"; then
echo "Skipping shell prompt for WSL: Already configured"
else
echo "Updating shell prompt for WSL"
sed -i -r 's/^(\s*PS1=.*)$/#\1/' "$file"
printf '%s\n' "$prompt" >> "$file"
fi
unset prompt prompt_regex
fi
# Configure shell history
# - Append local history to file instead of overwriting, to keep history from multiple parallel sessions.
# - Do not record repeated commands more than once, and do not record commands prefixed with space.
# - Record max 1000 entries in memory (bash default is 500 if not overridden by global config)
# - Read/write max 2000 entries from/to file (bash default is to match history size if not overridden).
# Note: Originally used 'grep --quiet --line-regexp --fixed-strings "pattern"' for matching, but this is not compatible
# with BusyBox, so changed it to 'grep -q "^HISTCONTROL=ignoreboth$"' which should be equivalent and work anywhere.
if grep -q "^shopt -s histappend$" "$file"; then
echo "Skipping histappend shell option: Already configured"
else
echo "Adding histappend shell option"
echo "shopt -s histappend" >> "$file"
fi
# Define variables
for def in \
"HISTCONTROL=ignoreboth" \
"HISTSIZE=1000" \
"HISTFILESIZE=2000"
do
name="${def%%=*}" # %%=* strips from the first = to the end (the name)
#value="${def#*=}" # #*= strips up to and including the first = (the value)
if grep -q "^${def}$" "$file"; then
echo "Skipping variable ${def}: Already defined"
else
old=$(grep "^${name}=" "$file")
if [ -n "$old" ]; then
echo "Commenting out existing variable ${old}"
sed -i -r "s/^(\\s*(export\\s+)?${name}=.*)$/#\\1/" "$file"
fi
unset old
echo "Adding variable ${def}"
echo "$def" >> "$file"
fi
done
unset def name # Currently unused: value line
# Define aliases
# - ls alias for automatic colored output by default (already exists by default in Arch)
# - grep alias for automatic colored output by default (does not exist by default)
# TODO:
# - pacman alias for automatic colored output by default (does not exist by default)
# - Automatic colored output can also be enabled by option Color in /etc/pacman.conf
# - diff alias for automatic colored output by default (does not exist by default)
# - This assumes package diffutils has been installed, which is not by default.
#
for def in \
"ls=ls --color=auto" \
"grep=grep --color=auto"
do
name="${def%%=*}" # %%=* strips from the first = to the end (the name)
value="${def#*=}" # #*= strips up to and including the first = (the value)
line="alias ${name}='${value}'"
if grep -q "^${line}$" "$file"; then
echo "Skipping ${line}: Already defined"
else
old=$(grep "^alias ${name}=" "$file")
if [ -n "$old" ]; then
echo "Commenting out existing ${old}"
sed -i -r "s/^(\\s*alias\\s+${name}=.*)$/#\\1/" "$file"
fi
unset old
echo "Adding ${line}"
printf '%s\n' "${line}" >> "$file"
fi
done
unset def name value line
unset user file
done
unset dir