This page looks best with JavaScript enabled

Change Username Without Separate Session

 ·  ☕ 5 min read

Changing a user’s username on Linux requires no processes be running under that user. This makes sense, but what if we only have that user accessible through a SSh connection? What if we don’t want to allow external access to the root account? What if the root account doesn’t have a password?

Background

I was recently spinning up a bunch of Raspberry Pis running Ubuntu 20.04 and some VPSes also running Ubuntu 20.04. I wanted to change the username on these nodes, but only really had access to the ubuntu (sudo) account. While I know I could use a cloud-init file to create a user exactly how I want (more on that in a future post), I didn’t want to re-flash the nodes and was not able to add a cloud-init file before boot on the VPSes.

The Process

Getting The Commands To Run

So we can’t change the username of a user with running processes, but a SSH session and a bash shell both run under my user whenever I’m connected.

The main problem is executing a command from a user (and sudo-ing to root) while not having that user have a process running.

Using either of the commands below allows a command to be run as the root user which will continue running

1
2
3
4
5
# interactive shell
sudo tmux

# non-interactive command
sudo -s -- sh -c "nohup <command> &"

Now that we can have a command running as root independent of the initiating user, we need to kill everything of the user so we can run usermod commands without difficulty. We kill the processes and wait a couple seconds for them all to terminate. Then we can run whatever commands we need.

1
ps -o pid= -u <current_username> | xargs kill && sleep 2 && <command>
  • ps lists the processes running on the system
    • -o pid= selects only the process ID (pid) and does not create a header for the column (=)
    • -u <username> selects only the processes running under <username>
  • | takes the output of the previous command (ps) and makes it the input of the following command (xargs)
  • xargs takes a line separated list (can change the separator) and turns them into arguments for the following command (-r tells it to do nothing if its input is empty)
  • kill takes a pid (or list of pids) and terminates the process. While kill can send different signals to processes, this uses the default signal (TERM).
  • && runs the following command if the preceding command exited successfully (exit code 0)
  • sleep 2 wait 2 seconds for the killed processes to terminate

Now, we can get to actually changing the username!

Changing The Username

Now that we can run commands as root without our user running processes, we can proceed to change the username and other related tasks.
These commands assume you are running as root. If not, you may need to insert some sudo’s as necessary

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# change the user's username
usermod -l <new_username> <current_username>
# move the user's home directory
usermod -d /home/<new_username> -m <new_username>
# change user's group name
groupmod -n <new_username> <current_username>
# replace username in all sudoers files (DANGER!)
sed -i.bak 's/<current_username>/<new_username>/g' /etc/sudoers
for f in /etc/sudoers.d/*; do
  sed -i.bak 's/<current_username>/<new_username>/g' $f
done

Putting it all together

When we put it all together (with some supporting script), we get change-username.sh as seen below:

 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
#!/bin/bash

currentUser=$1
newUser=$2

if [ $# -lt 2 ]; then
        printf "Usage:\n\t$0 <current_username> <new_username> [new_home_dir_path]\n"
        exit 1
fi

if [ $(id -u) -ne 0 ];then
        echo "Root permission needed for modifying users. Can not continue."
        exit 2
fi

newHome="/home/$newUser"
if [ $# == 3 ];then
        newHome=$3
fi

echo "Changing $currentUser to $newUser"
echo
echo "Running this script has the possibility to break sudo (sudoers file(s)) and WILL kill all processes owned by $currentUser"
echo "$currentUser will be logged out and will need to reconnect as $newUser"
read -n1 -s -r -p $'Continue [Y/n]?\n' key

if [ $key != '' -a $key != 'y' -a $key != 'Y' ]; then
        echo "Stopping; no files changed"
        exit 2
fi


# put the main script in /tmp so the user's home directory can be safely moved
tmpFile=$(mktemp)
cat > $tmpFile << EOF
#!/bin/bash
shopt -s extglob

# terminate (nicely) any process owned by $currentUser
ps -o pid= -u $currentUser | xargs -r kill
# wait for all processes to terminate
sleep 2
# forcibly kill any processes that have not already terminated
ps -o pid= -u $currentUser | xargs -r kill -s KILL


# change the user's username
usermod -l "$newUser" "$currentUser"
# move the user's home directory
usermod -d "$newHome" -m "$newUser"
# change user's group name
groupmod -n "$newUser" "$currentUser"
# replace username in all sudoers files
sed -i.bak 's/'$currentUser'/'$newUser'/g' /etc/sudoers
for f in /etc/sudoers.d/!(*.bak); do
  echo "editing '\$f'"
  sed -i.bak 's/'$currentUser'/'$newUser'/g' \$f
  # TODO fix $f not getting the file path for some reason
done
EOF

echo "Putting script into $tmpFile and running"
chmod 777 $tmpFile
sudo -s -- bash -c "nohup $tmpFile >/dev/null &"

``` <!-- markdownlint-disable-file -->

Command(s) Package
bash bash
ps, kill procps
usermod, groupmod passwd
sed sed
xargs findutils