Skip Navigation

Assignment 8: Create a User Account

For this assignment, you will create a regular user to account to use instead of logging in as root. In addition, you will install a friendlier shell to use with this account, add Neovim for working with UTF-8 characters, and set up doas to be able to run commands as root.

Page Contents

Background

Up to this point, we have been running as the root user in our Alpine Linux installation. While using the root account is appropriate for the initial system setup process, we really don’t want to use this account on a day-to-day basis. Any command we mistype as root can destroy the entire system, and any malicious code we encounter has the ability to compromise the entire system. For these reasons, we need to create a regular user account.

When we create a regular user account, we’re really doing several things:

  1. Allocating a new user ID number (uid) on the system.
  2. Mapping the new uid to a symbolic, human-readable name.
  3. Associating the new uid with a group ID number (gid) that indicates the user’s primary group. On most Linux systems today (Alpine included) creating a user also creates a group with a new gid at the same time. A new user will have its primary group set to this newly created group by default, and that group will be private to the newly created user.
  4. Creating and setting the location of the home directory associated with the new user account.
  5. Specifying which shell the user will run when logging into the system.

It’s important to remember that Linux operates using uid and gid numbers for Discretionary Access Control (DAC). These numbers are associated with the permissions that a given user (or group member) has to access files and perform other operations on the system. User names are for the benefit of the humans sitting at the keyboard, and they need to mapped to uid numbers. On our installation, this mapping is performed by the /etc/passwd file.

The Shell

Whenever you log into a Linux machine on the command line, you’re used to seeing a prompt at which you can type commands. The program that produces this prompt is called the shell, and multiple different shells are available. Each shell has its own unique features, and shells often operate a bit differently from one another.

So far, we have been using the Almquist Shell that is provided by BusyBox. On Alpine Linux, the Almquist Shell is the default, and it is located at /bin/ash (which is a symlink to the BusyBox binary). This shell is an improved variant of the original Bourne Shell, which was developed at Bell Labs in the late 1970s. Since Ash uses a superset of the original Bourne shell syntax, the /bin/sh (Bourne Shell program) is also linked to the BusyBox binary. Ash is capable of running scripts designed for the original Bourne shell. Another variant of the Bourne shell is the KornShell, which was also developed at Bell Labs.

Most mainstream distributions these days ship the GNU Bourne Again SHell (Bash) as the default shell. One side-effect of this choice is that a lot of otherwise Bourne-compatible shell scripts contain “Bashisms,” or specialized syntax that only really works with Bash. Bash is also a much slower shell than Ash when executing shell scripts, which is why the Debian distribution ships their own version of Ash – the Debian Almquist shell (Dash) – for running scripts.

In addition to the Bourne shell and its derivatives (such as Ash, Bash, and Dash), there are entirely different classes of shell available, which have little to no compatibility with the original Bourne shell. Most of these shells are based on the C Shell, which was developed at the University of California, Berkeley. C shell (/bin/csh) and its variants (like /bin/tcsh) are often found in commercial UNIX systems and BSD systems.

The Z Shell

For our regular user account, we’re going to use the Z shell (Zsh), which was originally created at Princeton University. Zsh is a Bourne shell derivative and is therefore capable of running Bourne shell scripts. However, it adds a number of features from other shells (including KornShell and Bash) while retaining good performance. Zsh is also highly programmable and customizable. While we won’t necessarily be using it in our system, there is even an add-on for this shell called Oh My Zsh to aid in customization.

Neovim

So far, we’ve been using the vi editor, which is actually provided by the BusyBox multicall binary on Alpine Linux. While vi gets the job done for basic setup tasks, it does have several limitations. First, this version of vi only supports the most basic vi commands. There is an improved version of vi – known as Vim for Vi Improved – that offers more commands and functionality. However, Vim has grown quite bloated in terms of its code base, and it must be configured using the somewhat arcane Vimscript language.

The Neovim project has reimplemented Vim with a cleaner design that makes it easier to extend and reuse. In addition, Neovim can be configured with Lua in addition to (or instead of) Vimscript. Some of you may have encountered Lua before, as it is frequently used for modifying video games (Roblox, World of Warcraft, and Dota 2 are just a few examples).

doas

On most Linux distributions, including Raspberry Pi OS, it’s possible for a regular user with the right permissions to execute commands as root by using the sudo command. Figure 1 shows an example.

Xkcd comic demonstrating the sudo command

Figure 1: Use of the sudo command. Credit: xkcd [CC-BY-NC license]

Unfortunately, the implementation of the sudo command has become quite bloated, as sudo contains a number of features that relatively few people will ever use. As a general rule, larger code bases invite a greater number of potential bugs, and these bugs can lead to security vulnerabilities. In 2021, there was a major buffer overflow vulnerability (CVE-2021-3156) in sudo. Some of these risks were known as early as 2015, when the OpenBSD project created the doas command as a replacement for sudo. As of 2021, doas had less than 1/100th as many lines of code as sudo.

Alpine Linux officially recommends doas over sudo to enable a user account to obtain root privileges. The sudo package is still available in the community repository, but doas is found in the official documentation (and some of the newer wiki entries). We’re going to follow this recommendation and use doas instead of sudo, since the former is much lighter and easier to configure properly.

Procedure

This section is structured as a tutorial. Please read slowly and carefully enter commands.

Step 0. Read the Background Section

If you skipped down to this point, go read the Background section above, and also read the pages linked from the background material. It’s important to understand why we’re making the choices we’re making in this assignment.

Step 1: Update the System

Update all the system packages first, since we’re going to be installing additional packages. The commands to update are:

apk update
apk upgrade

If you see the linux-rpi5-teal package get upgraded, then you also need to reboot onto the new kernel.

In the event that you get any errors at this step, run the nmtui command to troubleshoot your network connection. If you’re using a wired connection, be sure you plugged in the network cable.

Step 2: Install Packages

We can install Zsh, Neovim, a spellcheker, and doas all in one step:

apk add zsh neovim tree-sitter-lua hunspell hunspell-en-us doas

Step 3: Configure doas

Edit /etc/doas.conf by running:

vi /etc/doas.conf

Move the cursor to the # at the beginning of the line that allows the wheel group to become root, and press the X key to remove it and the following space. The line should look like the following with NO leading space:

permit persist :wheel

Save and exit vi.

Step 4: Create a User Account

Now we need to create our new user. Choose the same username as your CCU username, which is the part of your CCU email address before the @ sign. Since my username is mmurphy2, I’m using it in the following commands. Substitute your own username for mine in both the -h option and at the end!

adduser -h /home/mmurphy2 -s /bin/zsh mmurphy2

Set the user’s password when prompted. You won’t see any characters displayed while you’re doing this step. If you make a mistake in the confirmation and see an error message, simply run (again, replacing my username with yours) the following command and set it again:

passwd mmurphy2

We can check that the user was created successfully by running:

id mmurphy2

The result will look something like:

uid=1000(mmurphy2) gid=1000(mmurphy2) groups=1000(mmurphy2)

Since this is the first regular user account we created on this system, both the uid and private gid numbers default to 1000.

Note that we need to be members of a few extra groups to be able to do useful things with our system. The first of these groups is the wheel group, which we configured to allow use of the doas command in the previous step. Alpine Linux has a neat shortcut for adding the user account to this group:

adduser mmurphy2 wheel

Since we’re the only user on this Raspberry Pi system, it is convenient to have access to some other groups:

adduser mmurphy2 tty
adduser mmurphy2 lp
adduser mmurphy2 audio
adduser mmurphy2 dialout
adduser mmurphy2 input
adduser mmurphy2 video
adduser mmurphy2 kvm
adduser mmurphy2 games

We will need to be members of the audio, input, and video groups to get our graphical environment working properly in later assignments, as these groups enable access to hardware devices used in our desktop environments. The tty and dialout groups allow our user account to have direct access to various other hardware devices. Membership in the lp group allows for printing if we set up a printer later. The kvm group allows for the use of virtualization, while the games group is used for certain video game packages (mostly for shared high score records).

Step 5: Switch to the New Account

Now that we have our regular user account, let’s stop running as root. Run the command:

exit

to log out. Now log in using your CCU username and the password you set in the previous step. Check that you have the right shell running with the command:

echo $SHELL

The output should show /bin/zsh, indicating that you have started Zsh. You can verify that you’re running as your user instead of the root user by running:

whoami

Step 6: Configure Neovim

Before we can configure and run Neovim for the first time, we need to create the directory that will hold the configuration file. We’re going to configure Neovim only for our user account, not system-wide, since it isn’t a great practice to customize the system-wide configuration of an editor too much. One issue with a system-wide configuration on a multiuser system is that different users have different preferences. Another issue is that too much customization can have security implications, and we really don’t want to create security problems if the root account starts Neovim for any reason.

To create the user configuration directory and start editing the configuration file, run:

mkdir -p $HOME/.config/nvim
nvim $HOME/.config/nvim/init.lua

Go into insert mode (i key) and enter the following 3 lines:

vim.opt.number = true
vim.opt.cursorline = true
vim.opt.showmode = true

Press the Escape key. Save the file with :w, but do NOT exit (NO q).

You can now reload the lua configuration by typing:

:luafile %

and pressing Enter.

Magically, you should see line numbers appear on the left side, and the line number containing the cursor should be highlighted. Move to the bottom line if the cursor isn’t already there. Press o to open a new line below it, press Enter to leave a blank line, and then add these two lines to make searching case-insensitive by default:

vim.opt.ignorecase = true
vim.opt.smartcase = true

Save and reload the lua configuration to test it. You won’t see much change this time. Add another blank line and add some lines to enable mouse support once we get to the graphical environment:

vim.opt.clipboard = 'unnamedplus'
vim.opt.completeopt = {'menu', 'menuone', 'noselect'}
vim.opt.mouse = 'a'

Again, save and reload the configuration to be sure you didn’t make a mistake. Add another blank line and configure automatic indentation to use 4 spaces, following the standard used in Python:

vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true

Save and reload the configuration, then enable spellcheck by adding a blank line followed by:

vim.opt.spelllang = 'en_us'
vim.opt.spell = true

Save and reload the configuration. You won’t see any indication of misspelled words just yet, but this is expected. Add a blank line and enable syntax highlighting like so:

vim.cmd('syntax enable')
vim.cmd('filetype plugin indent on')

Save and reload the configuration. Again, there will be no changes. Finally, leave a blank line and then add some color settings and a Lua comment with some misspelled words in it:

vim.cmd('colorscheme elflord')
vim.cmd('hi SpellBad ctermbg=red')

-- this iz teh end!

Save and reload the configuration. Your colors should now be a lot more vivid, and you should have working syntax highlighting of your Lua code. The misspelled words in your Lua comment at the bottom should be highlighted in red.

Check that your file looks like the following, with all the pieces put together:

vim.opt.number = true
vim.opt.cursorline = true
vim.opt.showmode = true

vim.opt.ignorecase = true
vim.opt.smartcase = true

vim.opt.clipboard = 'unnamedplus'
vim.opt.completeopt = {'menu', 'menuone', 'noselect'}
vim.opt.mouse = 'a'

vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true

vim.opt.spelllang = 'en_us'
vim.opt.spell = true

vim.cmd('syntax enable')
vim.cmd('filetype plugin indent on')

vim.cmd('colorscheme elflord')
vim.cmd('hi SpellBad ctermbg=red')

-- this iz teh end!

You can exit Neovim with :q when finished. See Step 10 for color customization, among other options.

Step 7: Configure Zsh

Now that we have a basic configuration for Neovim set up, let’s go ahead and edit our Zsh configuration file. This file lives at $HOME/.zshrc, which makes it a hidden file (starts with a dot) at the root of our user’s home directory. We would typically pronounce the name of this file as “dot zee shark” or just “the zee shark.” The rc ending is an ancient convention from the Multics shell of the 1960s, in which commands that should be run every time the shell started would be included in the RUNCOM macro. Start editing by running:

nvim $HOME/.zshrc

Let’s begin by allowing Zsh to save command history between sessions, which is useful for system administration purposes, since it allows you to go back later and see which commands you typed previously. Zsh uses several environment variables for this purpose, which we can set at the top of the file to save our last 1000 commands to $HOME/.histfile:

HISTFILE=~/.histfile
HISTSIZE=1000
SAVEHIST=1000

Now let’s add some more lines to turn on useful features, such as Extended Globbing and the Completion System:

setopt extendedglob
autoload -Uz compinit
compinit

To make command lines easier to edit, we need to enable Emacs-like editing mode and bind our Home and End keyboard keys to do what we expect. Note that the Home and End keys produce escape sequences that can be rebound using the bindkey directive in the Zsh configuration, so the syntax looks a little odd:

bindkey -e
bindkey '^[[H' beginning-of-line
bindkey '^[[F' end-of-line

Now let’s set a shell alias for the ls command to enable colorized output. We can also set our PAGER variable to the less command. Programs that understand PAGER and produce too much output to fit on the screen will be run through less so that we can scroll up and down through the output. We can also set our default editor to Neovim.

alias ls='ls --color=auto'
export PAGER=less
export EDITOR=nvim

Finally, let’s make a cool command prompt:

PROMPT=$'%(?.%F{green}\U221a.%F{red}%?)%f %B[%n@%m:%~]%#%b '

That prompt is a bit to unpack, but:

  1. The $’ indicates the start of a string that needs to interpret escape sequences. This string runs to the closing single quote (‘).
  2. The %(?. . ) is an if-then-else that uses the exit status of the previous command (?) to select a piece of the prompt. If the last exit status was zero, indicating a success, then the part after the first period (.) is used. Otherwise, if the exit status was non-zero, indicating an error, then the part after the second period is used.
  3. The %F{green} changes the foreground color to green.
  4. \U221a is a Unicode escape sequence that represents the square root sign (√). Since we turned on escape sequences with the initial $’, the √ will be displayed if this branch of the if-then-else is selected.
  5. On the else side of the if-then-else, the %F{red} changes the foreground color to red.
  6. The %? displays the last command exit status code (which is a number).
  7. After the end of the if-then-else, the %f restores the foreground color to its default setting.
  8. The space leaves a literal space.
  9. The %B changes the font weight to bold.
  10. The [ displays a literal [ symbol.
  11. %n displays the current user’s username.
  12. @ displays the literal @ sign.
  13. %m displays the hostname of the system.
  14. The : displays a literal colon.
  15. %~ displays the current working directory with the part that consists of the user’s home directory replaced by a tilde (~).
  16. The ] displays a literal closing bracket (]).
  17. The %# displays either a % sign for a regular user or a # sign for the root user.
  18. The %b removes the bold font weight and restores the normal font.
  19. The prompt ends with a literal space in order to leave a space between the prompt and the spot where commands are typed.

Putting the whole file together, our $HOME/.zshrc file should look something like this (I put blank lines between the sections):

HISTFILE=~/.histfile
HISTSIZE=1000
SAVEHIST=1000

setopt extendedglob
autoload -Uz compinit
compinit

bindkey -e
bindkey '^[[H' beginning-of-line
bindkey '^[[F' end-of-line

alias ls='ls --color=auto'
export PAGER=less
export EDITOR=nvim

PROMPT=$'%(?.%F{green}\U221a.%F{red}%?)%f %B[%n@%m:%~]%#%b '

Step 8: Test the Result

To test your Zsh configuration, save and exit from Neovim (the same way as you did from vi, by going into Normal Mode with Esc and then :wq). Back at the command prompt, run (note that the command is a single dot!):

. $HOME/.zshrc

If you did everything correctly, your prompt should now begin with a green square root sign. To show the red error code for unsuccessful commands, you can run a command that always returns nonzero status:

false

The prompt that follows should have a red 1 at the beginning. This prompt setup is quite useful, since it explicitly shows when a command has failed to run properly. The prompt configuration I supplied is the one I use on my own systems.

Step 9. Optionally Customize the Shell

This step is OPTIONAL. If you like my prompt, then you can keep it. However, if you’d like different colors, perhaps a different success symbol, etc., then please feel free to customize the prompt to your liking. For the purpose of this assignment, the only requirement is that the prompt needs to show whether or not the previous command succeeded. See the following resources for ideas:

You can also enable other useful shell features by installing additional packages. For example, completions for various programs can be added by running:

doas apk add zsh-completions

Other packages can be found by using the Alpine Package Index and searching for zsh*. Be sure to change the version from Edge to v3.20 in the first dropdown, and set the architecture to aarch64 in the third dropdown. Click on the package name to see a short description and a link to the upstream project.

Step 10. Optionally Customize Neovim

This step is OPTIONAL. However, I suspect at least one of you will want to go and tweak Neovim.

Perhaps the easiest first tweak is to find a color scheme that you like. A quick way to test color schemes is to run:

nvim

Run the following command in Normal Mode to see a list of available color schemes:

:r!apk info -L neovim | grep 'runtime/colors' | awk -F '/' '{print $NF}'

Everything that ends in .vim is a possible color scheme. To test another color scheme, use the :colorscheme command. For example, there is (ironically) a color scheme named “murphy.vim” (not mine). I can switch to it using:

:colorscheme murphy

Try out the other color schemes by simply changing the “murphy” to the name of another color scheme from the list (minus the .vim ending). Once you have found a color scheme you like, edit your init.lua file and change the line:

vim.cmd('colorscheme elflord')

to the name of your desired color scheme.

There are many, many more things you can do with Neovim. Quite a few websites explain how to install package managers that let you download preconfigured Neovim configurations and plugins. Some of these resources include:

Submission Checklist

While logged in as your newly created, regular user account, run the following commands:

hostname
date
whoami
echo $SHELL
apk info | grep neovim
doas grep wheel /etc/doas.conf | grep -v '#'

How to Check Your Work

To verify that you have everything working properly, look at the output of the above commands:

  1. Be sure that your CCU username is part of your hostname.
  2. Be sure that the date shows a time during the period for this assignment this semester.
  3. Check that the output of whoami matches your CCU username.
  4. Verify that your SHELL environment variable (echo $SHELL) is /bin/zsh.
  5. Your apk info command should list neovim as installed. You might also have neovim-doc or other additional neovim-related packages, which is fine.
  6. The final command will likely prompt you for your regular account password. After giving the correct password, it should display the “permit persist :wheel” line from your /etc/doas.conf file. If you get an error message, then either you didn’t type your password correctly, or /etc/doas.conf isn’t configured correctly.

For the curious, I’m having you pipe the final two commands through grep to trim the output. The apk info command by itself lists every installed package on the system. We only care about (and have space on the screen for) the neovim package. As shipped, the /etc/doas.conf file contains a comment that contains the word “wheel”. We invert the match (-v) with grep to filter out that comment. Since the # character is also the comment symbol in the Zsh shell, we need to put quotes around it.

Final Steps

Use your phone to take a picture of the screen. Upload the picture to Moodle as the submission for this assignment.

When you’re finished with the assignment and are ready to power off the Pi, you need to run:

doas poweroff

Since you’re now running as a regular user, you’ll probably find that you don’t have permissions for the poweroff command.

ABET Assessment

Successful completion of this assignment satisfies the following performance indicator: