You are on page 1of 280

Introduction to Linux

Administration
Jason W. Eckert
Dean of Technology
February 2022

Contents:
Chapter 1: Introduction to the Linux Operating System
Chapter 2: Navigating the System & Working with Text Files
Chapter 3: Managing Linux Files
Chapter 4: Working with the Shell
Chapter 5: System Administration
Chapter 6: Network Administration
Chapter 7: Cloud Technologies
Appendix A: Working with macOS
Appendix B: Working with FreeBSD
-2-

CHAPTER

Introduction to the Linux


Operating System
This chapter introduces you to the features and history of the Linux
operating system, as well as the different methods that can be used to host
the Linux operating system. After installing Linux within a virtual machine,
you’ll also explore the different terminals present on a local Linux system
and enter basic shell commands.

1.1 Linux, UNIX, and macOS


1.2 Linux Resources and Certification
1.3 Linux Installation Types
1.4 Installing Linux in a Virtual Machine
1.5 Logging into Linux
1.6 Entering Shell Commands
1.7 Obtaining Command Help
1.8 Shutting Down Linux
-3-

1.1 Linux, UNIX, and macOS


Linux is an open source operating system based on UNIX (sometimes spelled Unix).
macOS is a flavour of UNIX. All three are very, very similar.

UNIX
UNIX was created by Dennis Ritchie and Ken Thompson of AT&T Bell Labs in 1969
and was rewritten in the C programming language (created by Dennis Ritchie) shortly
thereafter. It is considered the “grandfather” of all operating systems and led to the
creation of nearly all other modern technologies, including the Internet.

When UNIX was created, AT&T was restricted by a federal court order preventing them
from marketing it. As a result, AT&T sold the UNIX source code off to several
companies, which developed their own flavours of UNIX, as well as gave free copies out
to several universities including the University of California at Berkeley from which we
have BSD (Berkeley Software Distribution) UNIX. These vendors and universities are
largely responsible for the mainstream development of UNIX since the late 1970s. Some
common flavours of UNIX that arose in the 1980s onwards include SunOS, Solaris,
OpenSolaris, IBM AIX, Apple AUX, DEC Ultrix, True64 UNIX, SGI IRIX, SCO UNIX,
Novell UnixWare (later SCO), NEC UNIX, Xenix, HP-UX, QNX, MINIX, FreeBSD,
NetBSD, OpenBSD, and NeXTSTEP.

macOS
macOS (formerly called Mac OS X) is also a UNIX flavour. It is based on NeXTSTEP
UNIX, which Apple obtained in the late 1990s following their purchase of NeXT
Computer. Similarly, the iOS and iPadOS variants of macOS are also UNIX flavours.

FSF, GNU, and Open Source


Linux finds its roots in the UNIX community; this community was originally known as
the hacker culture (the term hacker now refers to those that use technology maliciously).
In 1983, one famous hacker named Richard Stallman started the Free Software
Foundation (FSF) to encourage computer science students and freelance software
developers worldwide to create and improve free software for the UNIX platform.
Richard had seen success because of collaboration between software developers in the
Artificial Intelligence Labs at the Massachusetts Institute of Technology (MIT) earlier
and believed that the sharing of ideas would result in faster software development.

During the mid 1980s, the collaboration seen by UNIX vendors was starting to diminish;
thus, the FSF quickly bloomed into the GNU Project. The GNU Project aimed to create a
free UNIX-like operating system (GNU stands for GNUs Not UNIX). And while the
GNU project never came to fruition, several great products came from the GNU
-4-

community, including the GNU emacs editor. More importantly, the GNU Project led to
the release of the GNU Public License (GPL), which is often referred to as a copyleft
software license. If you develop software under the GPL, you must make the source code
for the program freely accessible to anyone. Moreover, anyone else who modifies (and
improves) your source code must also make that source code freely accessible to anyone.
In short, the GPL ensures that source code is freely available, forever.

Today, there are many different software licenses that you can use to publish software
under. Some are strict copyleft licenses like the GPL. Others allow others more flexibility
with how the source code can be used and are called permissive licenses as a result. Since
both copyleft and permissive licenses allow anyone to obtain and modify the source code
for a program, they are collectively called open source software licenses, and software
created under these licenses are referred to as open source software.

Linux
In 1991, a Finnish student named Linus Torvalds created an improved version of MINIX
UNIX called Linux that he released under the GPL. Since then, thousands of open source
software developers around the globe have transformed Linux into a very sophisticated
operating system, as reflected in this IBM commercial (2000): youtu.be/fJA9eiUktcA.
Linus and his team coordinate the core development of the Linux operating system
kernel (the core component that operates the hardware and allocates resources to
programs), while other open source developers worldwide create the software packages
that provide for the remainder of the operating system (graphical desktop, compilers,
editors, services, and so on.). You may obtain different distributions (or distros) of Linux
as a result. All Linux distributions share the same kernel and core libraries, yet have
different software packaged with the kernel.

There are hundreds of Linux distributions available; some common ones include Red
Hat, Fedora, SuSE, Arch, Debian, Ubuntu and Alpine. Google’s Android operating
system is also a Linux distribution, as are many other highly-customized Linux
distributions used in embedded devices, routers, wearables, and smart appliances. Due to
its open source nature, Linux can be customized to run on nearly any hardware platform
today. Hardware manufacturers do not need to worry about licensing an existing
operating system for the hardware they create.

Most emerging technologies also rely on open source software for rapid development,
and these technologies are almost exclusively developed on the Linux platform. For
example, security appliances run Linux exclusively, and nearly all cybersecurity tools
were created to run on Linux. Blockchain technologies, whose widely-adopted standard
is Hyperledger from The Linux Foundation, are developed on the Linux platform before
reaching mainstream usage.

In 2018, Gartner estimated that over 90% of the world population was a Linux user in
some way, and that Linux comprised 78% of embedded devices, 100% of
supercomputers, 83% of smartphones (Android), 75% of tablets (Android), 48% of
-5-

servers (96% of cloud servers, and 12% of on-premises servers), 93% of network
appliances (routers, WAPs, next generation firewalls, etc.) and 7% of desktops (mostly
developer workstations and Chromebooks). In short, Linux boasts the most interfaces and
software for any computer or mobile device and provides the foundation for the largest
systems and organizations in the world.

To learn more about the history of GNU and Linux, you can watch the documentary
REVOLUTION OS online at youtu.be/jw8K460vx1c. To learn more about open source,
read the book The Cathedral & the Bazaar by Eric S. Raymond, which is also available
online at www.catb.org/~esr/writings/cathedral-bazaar/. To learn more about the
importance of Linux in today’s IT landscape, read the following CompTIA blogs:
• 5 Linux Skills You Must Master to Be a Cybersecurity Professional
(goo.gl/S8FfX5)
• Linux, Open Source and Security: Table Stakes for the Cybersecurity Professional
(goo.gl/hnr793)
• Why You Should View Linux as a Core IT Skill: 6 Areas of IT That Use Linux
(goo.gl/wsyLnU)

Linux and The Cloud


The physical computer network we call the Internet started as the US government-funded
ARPA/NSFnet. In the early 1990s, it renamed the Internet and sold to commercial
organizations (called Internet Service Providers, or ISPs). ISPs sold access to this
network so that people could connect to Web servers around the world that hosted HTML
webpages. The Internet was the physical network, and the Web server was the “killer
app” that led to the popularization of the Internet. The worldwide collection of Web
servers hosting webpages was called the World Wide Web (WWW), and you only needed
an Internet connection to access it. The open source Linux operating system, alongside
the open source Apache Web server allowed anyone with a computer to run a Web
-6-

server, leading to an explosion of interest surrounding the Internet and WWW. Most
believe that the rapid growth of the WWW would not have been possible without Linux
or Apache. Can you imagine how many Web servers would have been installed in the
1990s if you had to purchase an expensive Windows NT Server license to run one?

Today, Web servers host complex Web apps instead of webpages, and the worldwide
collection of servers hosting Web apps is called the cloud. Since the cloud is simply an
evolution of the WWW, it should be no surprise that Linux alongside open source
software and frameworks had a major part in its evolution and continues to guide its
direction.

Of course, Web apps need to be written by developers and sent regularly to servers in the
cloud for testing before they are copied to production servers in the cloud. The software
and processes used to get new Web app builds to the cloud is called a devops (developer
operations) workflow and the IT administrators that create this workflow are called
devops. New versions of popular Web apps, such as Google, Facebook, Netflix, and
Twitter are often pushed dozens of times a day to the cloud using a devops workflow to
test new features or bug fixes before they are implemented.
-7-

1.2 Linux Resources and Certification


Information related to specific Linux procedures is often gathered into a common form
called a HOWTO; these documents are step-by-step manuals on a certain topic, including
the specific use of certain commands. For the impatient, there also exist Mini-HOWTOs
that give quick steps on how to perform a certain task. You can also find FAQs
(Frequently Asked Questions) and README files (syntax and prerequisite information
for certain tasks) on the Internet for many Linux topics. Additionally, most cities have a
Linux User Group (LUG) that holds regular meetings where those who are new to Linux
(called newbies) gain information from those with more experience (called gurus).

Following are some websites that you may find useful as you learn and use Linux:
• Command Line Fu – User-submitted Linux command-line tricks.
www.commandlinefu.com

• ExplainShell.com – Breaks down and explains large commands you submit.


explainshell.com

• HowtoForge – Hosts easy-to-read tutorials for different Linux-related topics.


www.howtoforge.com

• Kernel Newbies – Summarizes the latest Linux kernel features.


kernelnewbies.org/LinuxChanges

• Phoronix – Linux hardware-related news, resources, and benchmarks.


www.phoronix.com

• The Linux Documentation Project – A central repository of HOWTOs.


www.tldp.org

Twitter is also an excellent resource for obtaining Linux information, especially related to
new features and technologies related to your Linux distribution. For example, if you use
Fedora and Ubuntu Linux, you could follow @Fedora and @Ubuntu, as well as generic
Linux-related Twitter accounts, such as @LinuxQuestions.

As Linux became more and more popular in the late 1990s, the need for skilled Linux
administrators, developers and users escalated for most companies, and Linux
certification became a sought-after benchmark for hiring. There are many organizations
that offer Linux certification today, including Red Hat, SUSE, Linux Professional
Institute (LPI), The Linux Foundation, and the Computing Technology Industry
Association (CompTIA).

This book is geared towards the CompTIA Linux+ certification. In addition to having
excellent brand recognition in the industry, CompTIA Linux+ is a popular vendor-neutral
certification that tests the general concepts and skills needed by any Linux administrator.
-8-

1.3 Linux Installation Types


There are four main ways that you can deploy Linux:

1. Install Linux directly onto a computer (e.g. laptop, desktop, server). This method
is often called a bare metal Linux installation. Many servers within an
organization or in the cloud are bare metal installations of Linux that host virtual
machines or containers (discussed in 3. and 4.), and IT administrators are
responsible for installing, managing, and securing them. Developers who create
Linux software often install Linux directly on their laptop or desktop, and then
install their developer tools on Linux. Similarly, IT administrators who primarily
manage Linux systems do the same. This is why both Lenovo and Dell sell
laptops preinstalled with Linux (Dell calls their Linux XPS laptop line the
“Developer Edition”).

2. Use Windows alongside the Windows Subsystem for Linux (WSL) version 2.
Windows 10 Professional and later allow you to run an entire Linux operating
system (including a real Linux kernel) alongside Windows for developing and
testing Linux apps, or for easily managing Linux servers from a Windows system.
IT administrators can use WSL to centrally manage Linux servers within their
organization or the cloud, as well as perform ethical hacking and vulnerability
assessments for the networks and systems they manage. Several Linux
distributions for WSL are available in the Microsoft Store, and the integration
between Linux and Windows is seamless (graphical Linux apps run with full
hardware acceleration). To how to install and configure WSL, visit
https://docs.microsoft.com/en-us/windows/wsl/install-win10.

3. Install Linux within a virtual machine on an existing Windows, Linux or macOS


system. Virtual machines allow you to run multiple operating systems
simultaneously on your computer via hypervisor software that manages access to
the underlying hardware resources. There are several hypervisors available for
Windows, Linux and macOS, including Hyper-V, VMWare, KVM/Qemu, Oracle
Virtualbox, Parallels, and UTM. Because virtualization can be used to maximize
the utilization of hardware resources, most Linux servers within an organization
and in the cloud are run within virtual machines on server hardware. IT
administrators often run Linux virtual machines on their laptop or desktop to
manage other Linux systems or to experiment with technologies before deploying
them. Software developers that wish to use Windows or macOS developer tools to
create Linux apps often run Linux in a virtual machine to test their apps.

4. Run Linux containers on an existing Windows or macOS system. Containers are


like virtual machines, but do not contain a full operating system, and lack an
operating system kernel. As a result, containers must use the kernel on an
underlying operating system. Linux containers only have enough filesystem,
software libraries and supporting programs to run a particular Linux app, which
-9-

makes them very small and ideal for running in the cloud (cloud servers can run
thousands of small containers). To run Linux in a container, you must install a
container system such as Docker. Developers that create Linux cloud apps often
install Docker Desktop on Windows or macOS to run Linux containers. On
macOS, Docker Desktop uses a Linux virtual machine (with a Linux kernel) to
run Linux containers. On Windows, Docker Desktop leverages the Linux kernel
in WSL to run Linux containers. IT administrators are responsible for deploying
and managing containers that are created by developers within their organization
or in the cloud. Moreover, many third-party software packages that IT
administrators must deploy (e.g. ticketing systems, network & security
monitoring) are often provided within a container today and must be deployed on
a system running Docker.

1.4 Installing Linux in a Virtual Machine


To explore the different aspects of the Linux operating system, we’ll install a Linux
workstation in a virtual machine on an existing Windows or macOS system. Later in this
book, we’ll install a two additional Linux virtual machines that we will configure as
servers. At the end of this book, we’ll configure Linux containers using Docker.

In the following exercise, you install the Fedora Workstation Linux distribution (with a
graphical desktop) and download some sample files for use in the following chapters. In
Chapter 5, you install the Ubuntu Server Linux distribution (without a graphical desktop).

Exercise
1. On your Windows or macOS computer, download the latest ISO image for Fedora
Workstation from getfedora.org.

2. Install a free hypervisor on your Windows or macOS system. For macOS on the Apple
M1 platform, you can use UTM/Qemu (mac.getutm.app). For Windows or macOS on the
x64 platform, Oracle Virtualbox (www.virtualbox.org/wiki/Downloads) is recommended,
but you can instead use the native Hyper-V hypervisor on Windows. Refer to the free
documentation on the hypervisor website to learn how to create a virtual machine using
the hypervisor software.

3. In your hypervisor software, create a new virtual machine called Fedora Workstation
that has 4GB of RAM, 64GB of virtual hard disk storage, and a connection to the Internet
via the network interface in your computer. Insert the ISO image for Fedora into the
virtual DVD of this virtual machine.

4. Start and then connect to your Fedora Workstation virtual machine. Press Enter to test
your media and boot Fedora Live. Once the graphical desktop has loaded, select the
option Install to Hard Drive from the bar at the bottom of the screen.

5. Select English (United States) and press Continue.


- 10 -

6. If the time zone shown is incorrect, click Time & Date, choose the correct time zone
(e.g. Americas/Toronto) and press Done.

7. Click Installation Destination. You should see that your 64GB virtual disk is already
selected and called sda (or vda if using UTM/Qemu). Select Custom and press Done
when finished. This will allow you to manually configure your partitions.

8. Choose a partitioning scheme of Standard Partition from the drop-down menu and
click Click here to create them automatically. This will create several partitions.

9. Highlight the /home (sda5) partition, reduce the Desired Capacity to 10GB and click
Update Settings. This will leave some free unpartitioned space on our first disk for a
later exercise. Press Done and then click Accept Changes when prompted.

10. Click Begin Installation. When the installation has finished, click Finish
Installation. Click the icon in the upper right corner and choose Power Off / Log Out >
Power Off and click Power Off to shut down your Fedora Live installation image.

11. In the settings for your virtual machine, remove the Fedora ISO image from the
virtual DVD and start your newly-installed Fedora Workstation virtual machine.

12. On the first boot after installation, you must complete a Welcome wizard. Click Start
Setup at this wizard and make the following selections when prompted:
a) Disable Location Services and Automatic Problem Reporting
b) Skip associating your user with online accounts
c) Create a regular user called woot with a password of Secret555

13. At the Welcome to GNOME screen, click No Thanks when finished, and close the
Getting Started window.

14. Open a terminal app by navigating to Activities > Show Applications (icon with 9
circles on bar at the bottom of the screen) > Terminal.

15. Type sudo passwd root at the command prompt and press Enter. Next, enter the
password for your woot user account (Secret555) and then enter a password of
Secret555 for the root user when prompted (twice).

16. Type su - root and press Enter. Supply the root user’s password of Secret555 when
prompted. Next, type git clone https://github.com/jasoneckert/classfiles.git and press
Enter. When finished, type poweroff to shut down your Fedora virtual machine.
- 11 -

1.5 Logging into Linux


Linux can have several different users logged in simultaneously, either locally or from
across the network (e.g. using Secure Shell). Furthermore, there is a special user account
used for system administration; this account is called root and has all special privileges
on the system.

To log in, a user must connect to a terminal. A terminal is simply a unique pathway into
a Linux system. There are six default terminals that you can access locally (while seated
at the Linux system) by pressing a key combination:

tty1 [Ctrl]-[Alt]-F1 tty3 [Ctrl]-[Alt]-F3 tty5 [Ctrl]-[Alt]-F5


tty2 [Ctrl]-[Alt]-F2 tty4 [Ctrl]-[Alt]-F4 tty6 [Ctrl]-[Alt]-F6

On Fedora Linux, the terminal tty1 contains a graphical login program called the
GNOME Display Manager (gdm), as shown below:

If you log into this terminal, you’ll obtain a graphical desktop, or Graphical User
Interface (GUI), on tty2. If you switch to tty1 and log into the gdm again as a different
user, you’ll get another graphical desktop on tty3, and so on. Normally, only one
graphical desktop is necessary on any Linux system. Consequently, the terminals tty3-
tty6 contain a command line login program by default, as shown below for tty3.

Fedora 34 (Workstation Edition)


Kernel 5.11.12-300.fc34.x86_64 on an x86_64 (tty3)

localhost login:

Most Linux distributions prevent you from logging into the gdm as the root user since
graphical programs should not be run as root for security reasons. However, you can log
into a command line terminal as root without issue.

NOTE: There are many display managers available. For example, some Linux
distributions use the KDE Display Manager (kdm) instead of gdm.
- 12 -

In addition to local command line terminals (tty3-tty6), there are an unlimited number of
command line terminals that you can obtain from across a network (these are called
pseudo terminals and have names prefixed by the characters pty). All command line
terminals use an interface called a shell, and the standard shell used in Linux is the
Bourne Again Shell (bash). When the root user logs into the system, the bash shell
provides a # prompt:

Fedora 34 (Workstation Edition)


Kernel 5.11.12-300.fc34.x86_64 on an x86_64 (tty3)

localhost login: root


Password:
[root@localhost ~]# _

Alternatively, regular users receive a $ prompt from the bash shell after login:

[woot@localhost ~]$ _

NOTE: You can use the who command in Linux to view which users are logged into the
system and the terminals that they are using. Graphical terminals are often shown using a
desktop environment display number (e.g. :1, :2 and so on.).

When you use a Linux GUI, X Windows software handles the drawing of graphics on the
terminal screen. The original X Windows software is called X.org, but is fast being
replaced by a newer, faster version called Wayland. To provide a standard look and feel
for X Windows, a desktop environment with an associated window manager is also
loaded. There are two mainstream desktop environments for Linux:
• GNOME (GNU Network Object Model Environment) which uses a top-down
navigation system like macOS:
- 13 -

• KDE (K Desktop Environment) Plasma Workspaces which uses a Start-button


style navigation system like Windows:

Fedora uses the GNOME desktop environment by default. To access a command line
bash shell from GNOME, you can click on the Activities menu in the upper left corner
and then navigate to Show Applications (icon with 9 circles) > Terminal. In KDE Plasma
Workspaces, you can click the Start button in the lower left corner and navigate to
Applications > System > Terminal. You can start an unlimited number of Terminal apps
(and associated bash shells) within a desktop environment.

You can also run multiple bash shells within a single terminal using either the screen
or tmux command if your system does not have a desktop environment installed
(common on Linux servers). The following tmux example runs three bash shells:
- 14 -

Recall that you cannot log into the gdm and obtain GNOME or KDE desktop as the root
user by default. However, after opening a command line terminal within a desktop
environment as a regular user, you can switch to the root user to perform any
administrative tasks. One way to do this is by using the su (switch user) command. To
switch to the root user and load the root user’s environment variables ( – ), you can run
the su – root command and supply the root user’s password when prompted.
Alternatively, you can use the su –c "command" root command or the sudo
"command" command to run a single command as the root user.

If you do not specify the username when using the su or sudo command, the root user is
assumed. Additionally, the root user can use the su command to switch to any other user
account without specifying a password.

Exercise
1. Power on your Linux system. When finished, you are taken to the tty1 terminal, which
displays the gdm. Switch between the other local command line terminals by using the
appropriate key combinations ([Ctrl]+[Alt]+F2 through F6).

2. Switch to tty3 and log in as the user root with the password Secret555 (note that you
receive a # prompt). Next, switch to tty4 and log in as the user woot with the password
Secret555 (note that you receive a $ prompt). Now, switch back to tty3 and note that you
are still logged in as the root user on this terminal.

3. Switch back to tty1 and log into the gdm as the woot user to start the GNOME desktop
environment. Open a command line terminal by clicking Activities and typing Terminal
(which is faster than navigating to it).

4. Run the who command to display a list of logged on users and note that woot is logged
into tty2 and tty4, while root is logged into tty3. Next, run the su - root command (enter
the root user’s password of Secret555) to switch to the root user.

5. Switch to tty3. Attempt to run the screen command and press y to install it when
prompted (twice). Now that is has been installed, run the screen command, and note that
your screen is cleared to represent that you are now running bash within a screen session.
Next, press the [Ctrl]+a key combination and then press c to create a new screen. Run
the who command on this new screen and observe the output. Note that you are in a
subset of tty3. Next, press the [Ctrl]+a key combination and then press p to return to the
previous screen. Next, press the [Ctrl]+a key combination and then press n to go to the
next screen. Run exit to kill your current bash shell, returning you to your first screen.
Finally, run exit again to kill your first screen and the screen session.

6. Run the tmux command and note that your screen now has a green status bar to
represent that you are now running bash within a tmux session. Next, press the [Ctrl]+b
key combination and then type % to split your screen. Next, press the [Ctrl]+b key
combination and then type “ to split your screen in the opposite direction. Run the who
- 15 -

command and note that tmux runs all screens within the tty3 terminal. Next, practice
switching between these three screens by pressing the [Ctrl]+b key combination
followed by a cursor key (up down left right). When finished, type exit within each of
your screens to kill each bash shell, returning you to your original bash shell.

6. Run the dnf groupinstall “KDE Plasma Workspaces” command (press y when
prompted) to download and install the KDE Plasma Workspaces desktop environment
(which will take some time). Once it has completed, run the reboot command to reboot
your system and choose the Settings (cog wheel icon) menu in the gdm to select it for
your session (it is usually called Plasma). Use the KDE start menu to locate the Terminal
app to obtain a bash shell and run the who command. When finished, log out of the KDE
desktop environment.

1.6 Entering Shell Commands


Once logged into the system, you can enter commands to tell the Linux kernel what to do.
Each command may have options (which are typically prefixed by a dash character), and
arguments that tell the command what to analyze. For example, the command ls –l
/etc has one option (-l) and one argument (/etc). All characters entered into a bash
shell are case-sensitive; thus the command ls is not equivalent to the command LS.
Moreover, you can use the up cursor key on your keyboard to scroll through previously-
entered commands or cancel a command that you started typing by pressing the
[Ctrl]+c key combination. As shown with the who command below, not all
commands need to have options or arguments, and whether options are entered before
arguments and vice versa depends on the actual command itself.

[root@localhost ~]# who


root tty3 2019-07-20 20:31
root pts/0 2019-07-20 14:11 (10.3.101.22)
root tty2 2019-07-20 10:06
[root@localhost ~]# _

Similarly, you may use the date command with the –u or --universal option to
print the date in universal time format:

[root@localhost ~]# date -u


Sun Jul 20 02:17:35 UTC 2019
[root@localhost ~]# date --universal
Sun Jul 20 02:17:37 UTC 2019
[root@localhost ~]# _

Options, that use two dash characters are called POSIX options and are typically
followed by an entire word specifying the option instead of a single character. If the
output of a certain command is too large to fit on the screen, you can use the
[Shift]+[PageUp] and [Shift]+[PageDown] key combinations to scroll up
and down terminal screens. Alternatively, you can append the characters | more to the
- 16 -

end of the command; this is referred to as a pipe and will be discussed in a later chapter.
Table 1-1 lists some common basic Linux commands.

Table 1-1 Basic Linux Commands


who Displays who is on the system
date Displays the current date and time
finger username Displays information about the username (run dnf
install finger on Fedora to download and install it)
w Displays who is on the system and what they are doing
whoami Displays your username
exit Exits out of the shell (logout)
id Print the User ID (UID) and Group IDs (GIDs) associated
with the current user account
echo argument Displays the argument to the terminal screen
passwd Changes your password (the root user can change other
user's passwords by specifying a username as an argument)
clear Clears the screen
uname option Displays system information specified by the option.
The –a option specifies all information.

The bash shell interprets several characters with special meaning; these characters are
referred to as shell metacharacters and are used to alter the execution of commands. For
example, the ; metacharacter is a command terminator and can be used to run several
commands in turn. For example, to run the date command followed by the id
command followed by the who command, you could run:

[root@localhost ~]# date;id;who


Sun Jul 20 22:02:26 EDT 2019
uid=0(root)gid=0(root)groups=0(root),1(bin),2(daemon),3(sys),4(adm)
root tty3 2019-07-20 21:56
[root@localhost ~]# _

The $ metacharacter accesses the contents of a variable stored in memory. For example,
to display the contents of the SHELL variable, you can use the echo command and
prefix the variable name with the $ metacharacter:

[root@localhost ~]# echo We use the $SHELL shell


We use the /bin/bash shell
[root@localhost ~]# _

The backquote metacharacter ( ` ) performs command substitution:

[root@localhost ~]# echo Today is `date`


Today is Sun Jul 20 22:13:18 EDT 2018
[root@localhost ~]# _
- 17 -

There are over 30 metacharacters that are interpreted by the bash shell; most of these will
be examined throughout this book. There are times, however, that the special meaning of
a shell metacharacter is not desired. Consider the following command:

[root@localhost ~]# echo You owe me $4.50


You owe me .50
[root@localhost ~]# _

The $ metacharacter in the above example told the shell to look for a variable called 4 in
memory (a . is not a valid part of a variable name). Since there was no such variable, the
shell printed nothing in place of the $4 in the echo command.

There are 3 ways to protect shell metacharacters from shell interpretation:


1. Enclose the metacharacters within single quotes (which protect all characters
within from shell interpretation)
2. Enclose the metacharacters within double quotes (which protect most characters
from shell interpretation; exceptions include the $ and ` characters)
3. Prefix the character with a \ character (which protects the next character from
shell interpretation)

Consequently, only method 1 and 3 will work to protect the $ character in our example:

[root@localhost ~]# echo 'You owe me $4.50'


You owe me $4.50
[root@localhost ~]# echo "You owe me $4.50"
You owe me .50
[root@localhost ~]# echo You owe me \$4.50
You owe me $4.50
[root@localhost ~]# _

There may also be times when the terminal that you are using becomes scrambled. When
this happens, you can use the reset command to reset your terminal to default settings.
Alternatively, you can press [Ctrl]+j and type stty sane followed by [Ctrl]+j
again (you won’t see stty sane on the screen as it is being typed).

Exercise
1. Log into tty3 as the root user and practice the sample commands listed in Table 1-1.

2. Scramble your terminal by running the stty raw -echo command. Next, run the reset
command to fix your terminal (you won’t see the characters as you are typing it).

3. Run each of the following commands in your shell (note what each one does):
echo "My shell is $SHELL"
echo My name is `whoami`
echo This is good; echo This is great
- 18 -

1.7 Obtaining Command Help


In addition to the Internet resources discussed earlier, Linux provides a set of local
documentation called manual pages for most commands and files on the system. For
example, to obtain help on the who command, you can use the man command and
specify who as the argument:

[root@localhost ~]# man who


WHO(1) User Commands WHO(1)

NAME
who - show who is logged on

SYNOPSIS
who [OPTION]... [ FILE | ARG1 ARG2 ]

DESCRIPTION
-a, --all
same as -b -d --login -p -r -t -T -u

-b, --boot
time of last system boot

-d, --dead
print dead processes

-H, --heading
print line of column headings
q
[root@localhost ~]# _

Since manual pages are typically several pages long, they are displayed page-by-page.
You can press the h key to view a help screen or the q key to quit. Moreover, manual
pages are divided into different sections. WHO(1) in the output above indicates that any
user may execute it, since it is in section 1 of the manual pages. A list of standard
manual page sections is shown in Table 1-2.

Table 1-2 Manual Page Sections


1 User commands (can be executed by any user)
2 System calls
3 Library routines
4 Special device files
5 Configuration file formats
6 Games
7 Miscellaneous
8 Administrative commands (can be executed by
the root user only)
9 Kernel routines
- 19 -

Sometimes, a certain user command (section 1) may have the same name as a system call
(section 2) or a configuration file (section 5). In this case, the man command will return
the manual page from section 1 only since manual pages are searched starting from
section 1. To obtain the manual page from a specific section, specify the section number
as an argument to the man command. For example, to obtain the manual page for the
who kernel routine, you could use the command man 9 who (which tells the man
command to search the manual pages starting from section 9).

Each manual page has a description header that can be viewed separately using the
whatis command. For example, to view the description of the who command, you
could execute the following:

[root@localhost ~]# whatis who


who (1) - show who is logged on
[root@localhost ~]# _

NOTE: If the whatis command produces no output, first run the mandb command to
index the manual pages database (or the makewhatis command on older systems).

You can also search manual page headers for a keyword that represents the task that
needs to be performed by using the –k option to the man command. For example, if you
were unaware of the who command and wished to find a command that shows a list of
logged in users, you can search the manual pages using the keyword logged:

[root@localhost ~]# man –k logged


last (1) - show listing of last logged in users
lastb (1) - show listing of last logged in users
users (1) - print the user names of users currently logged in
w (1) - Show who is logged on and what they are doing
who (1) - show who is logged on
[root@localhost ~]# _

NOTE: You can use the apropos command in place of man –k. If no output is
shown by either command, run the mandb command to index the manual page database.

While the manual pages have persisted as the main method for obtaining help on UNIX
and Linux systems, there also exist info pages for many commands that can be viewed
using the info command. As with man, you can press the h key to view a help screen or
the q key to quit. For example, to view the info page for the who command, you can run:

[root@localhost ~]# info who


File: coreutils.info, Node: who invocation, Prev: users invocation,
Up: User information

20.6 'who': Print who is currently logged in


============================================

'who' prints information about users who are currently logged on.
- 20 -

Synopsis:

'who' [OPTION] [FILE] [am i]

If given no non-option arguments, 'who' prints the following


information for each user currently logged on: login name, terminal
line, login time, and remote hostname or X display.

If given one non-option argument, 'who' uses that instead of


'/var/run/utmp' as the name of the file containing the record of users
logged on. If given two non-option arguments, 'who' prints only the
entry for the user running it (determined from its standard input),
preceded by the hostname. Traditionally, the two arguments given are
'am i', as in 'who am i'.

The program accepts the following options.


'-a'
'--all'
Same as '-b -d --login -p -r -t -T -u'.

'-b'
'--boot'
Print the date and time of last system boot.
[root@localhost ~]# _

Some commands are merely functions that are part of the bash shell itself. For these shell
built-in commands, you may read the manual page for the bash shell or use the help
command. For example, to obtain help on the echo function of the bash shell, you can
run the help echo command. To obtain a list of all shell built-in commands, run the
help command with no arguments.

Exercise
1. Log into tty3 as the root user and run the whatis crontab command to display the
description of the crontab command (discussed in a later chapter).

2. Run the man crontab command to view the crontab manual page, and note it is in
section 1. Find the line that mentions the cron(8) daemon (a daemon that can only be run
as root because it is in section 8). A daemon is a system service in Linux (equivalent to a
service on Windows). Press h to view a help screen. Next, press q to quit.

3. Run the man 5 crontab command to display the manual page for the crontab
configuration file, and then press q to quit. Finally, run the man -k cron command to
view all entries in the manual pages that have the word cron in their name or description.

3. Run the info crontab command to view the crontab info page. Press q to quit.

4. Run the help | more command to view all built-in shell commands, pressing q to quit
when finished. Next, run the help echo command to view echo usage information. Next,
run the help help command prompt to view help usage information.
- 21 -

1.8 Shutting Down Linux


There are many different ways to shut down or reboot a Linux system. For example, to
halt or power off a Linux system in 5 minutes, you can run the shutdown –h +5
minutes or shutdown –P +5 minutes command. On modern Linux systems,
halt and power off are equivalent.

Alternatively, to reboot a Linux system in 5 minutes, you can use the shutdown –r
+5 minutes command.

To power off a Linux system immediately, you can use either the shutdown –P now
command or the poweroff command (which is much simpler). Alternatively, to reboot
a Linux system immediately, you can use either the shutdown –r now command or
the reboot command.

Before rebooting or powering off a server system, you should always warn any other
logged in users using the wall (warn all) command. For example, wall "The
system is being rebooted in 5 minutes – please log off
before then." will warn all users that the system is about to be rebooted.

Exercise
1. Log into tty3 as the root user and run the wall “The system is about to be rebooted!”
command.

2. Next, run the poweroff command to shut down your Linux virtual machine.
- 22 -

CHAPTER

Navigating the System &


Working with Text Files
This chapter introduces you to the Linux filesystem structure as well as the
types of files available on a typical Linux filesystem. In addition, this
chapter explores common utilities used to view and edit the contents of text
files.

2.1 The Linux Directory Structure


2.2 Viewing Directory Contents
2.3 Viewing the Contents of Files
2.4 Common Text Tools
2.5 Editing Text Files using nano
2.6 Editing Text Files using vi
2.7 Editing Text Files using Emacs
- 23 -

2.1 The Linux Directory Structure


The Linux directory structure is fundamentally different than the directory structure in
other operating systems such as Windows as it is not storage device-based. Instead, the
directory structure in Linux has one root directory (represented by the / character), and
all storage devices such as SSDs, hard disks, USB flash drives, and DVDs are mounted
to a directory underneath this / directory.

Absolute and relative pathnames in Linux are similar to those specified at a command
prompt in Windows, however the default name delimiter is a forward slash (/) instead of
a backslash (\). Following is a sample Windows directory structure with 3 storage
devices:

The absolute pathnames for these directories are:


C:\Windows\system32
C:\inetpub
D:\public
E:\data
E:\programs

The equivalent structure in Linux for the same 3 storage devices would be:

The absolute pathnames for these directories are:


/Windows/system32
/inetpub
/public
/data
/programs

Changing from the /public directory to the /programs directory will involve
changing storage devices (just like changing from D:\ to E:\ on Windows), however
this change is transparent to the user.
- 24 -

A more realistic Linux directory structure follows the UNIX Filesystem Hierarchy
Standard (FHS) available at www.pathname.com/fhs/:

Short descriptions of each directory shown above are provided in Table 2-1.

Table 2-1 Common Linux Directories


/tmp Stores temporary files used by programs
/root The home directory for the root user
/etc Contains most system configuration
/etc/sysconfig Contains device and service specific system configuration
/var Contains log files, spools and content that varies between systems
/bin Contains binary programs that any user may execute (it is usually
a shortcut to the /usr/bin directory)
/usr Contains software packages that users can run
/opt Contains extra software packages (not provided by the distro)
/dev Contains pointers to device drivers in the kernel
/home Contains all regular user home directories (e.g. /home/bob)
/sbin Contains binary programs that only the superuser (root) can run
(it is usually a shortcut to the /usr/sbin directory)
/lib Contains program libraries
/mnt or /media An empty directory typically used for mounting DVD, USB, and
other removable media devices
/proc A special (pseudo) filesystem in RAM that stores kernel,
hardware, and process information
/sys A special (pseudo) filesystem in RAM that stores device
configuration used by the Linux kernel

When you log into a Linux system, you are placed in your home directory. For the root
user, this is the /root directory, and for other users this is /home/username.
Following this, you can change your current directory with the cd (change directory)
command and display your current directory with the pwd (print working directory)
command. To change the current directory, you can specify an absolute or relative
pathname as an argument to the cd command. Absolute pathnames are the full pathname
starting from the / directory. Relative pathnames do not start from the / directory and
are instead relative to your current directory. Each directory has two special hidden files
that are commonly used in relative pathnames: . refers to the current directory, and
.. refers to the parent directory
- 25 -

Thus, if the current directory is /home/user1, you can change your directory to
/home by typing the command cd .. at a command prompt (ensure there is a space
after the cd command). Similarly, if the current directory is /home, you can move to the
/home/bob directory by typing the command cd bob at a command prompt (if the
pathname does not start with the / character, the system assumes that the bob directory
is underneath the current directory).

Say for example, that the current directory is /etc/sysconfig. You can use the
command cd / to change to the / directory using an absolute pathname. To do the same
with a relative pathname, the command would be cd ../.. at a command prompt.

Similarly, if the current directory is /usr/local, you can change to the /usr/bin
directory by typing cd /usr/bin (absolute pathname) or cd ../bin (relative
pathname) at a command prompt.

Since most pathnames in Linux are fairly long, relative pathnames may save a great deal
of typing. In addition, the [Tab] completion feature of the bash shell saves having to
type long pathnames. If there is a directory called /supercalifragilistic, you
can simply type cd /super and press the [Tab] key to allow the shell to fill in the
remaining characters if it is the only directory underneath the / directory that starts with
the name super. If there were two or more directories underneath the / directory that
started with the word super, the [Tab] key will alert the user with a beep. Pressing the
[Tab] key again after this beep will display a list of names to choose from.

Without arguments, the cd command returns you to your home directory. Alternatively,
you may use the cd ~ command to return to your home directory; the ~ metacharacter
represents the home directory of the current user. This metacharacter can also be used to
specify other users' home directories as well. To change to the home directory for the
user named woot, you could type the command cd ~woot at a command prompt.

Exercise
1. Log into tty3 as the root user and run the following commands:
pwd (you should be in /root)
cd /etc/sysconfig (switches directory using absolute pathname)
pwd (you should be in /etc/sysconfig)
cd ../.. (switches to / using relative pathname)
cd root (switches to /root using relative pathname)
cd ../etc/sysconfig (switches to /etc/sysconfig using relative pathname)
cd ~woot (takes you to woot’s home directory)
pwd (you should be in /home/woot)
cd (takes you to your home directory, /root)

2. Switch to the /etc/sysconfig/network-scripts directory using the [Tab] completion


feature of your shell. Next, run the cd command to return to your home directory.
- 26 -

2.2 Viewing Directory Contents


Types of Files
There are many different types of files that may be present on a typical Linux filesystem.
These file types are described in Table 2-2.

Table 2-2 Common Linux File Types


binary data files Contain data in binary form
text files Files which contain ASCII text
block device files Files which represent a device driver in the Linux kernel that
transfers information block by block to a filesystem
character (raw) Files which represent a device driver in the Linux kernel that
device files transfers information character by character (serial)
executable files Files which have execute permission – they may be binary
compiled programs, or shell scripts (batch files)
linked files Files that are linked to other files on the filesystem; they may be
shortcuts to other files (called symbolic links), or copies of other
files (hard links). Linking will be discussed later in the text.
directories Directories are merely special files that contain a list of files and
subdirectories within them
named pipes Files that allow one process to save to them while another process
reads from them
sockets Named pipes that write/read data from across a network

Although filenames in Linux may have extensions like Windows files, such as .txt and
.gif, they typically do not. Linux filenames can be up to 255 characters in length and
can contain upper and lowercase letters, numbers, as well as the underscore ( _ ) and dash
( - ) characters. It is good form to avoid using any other character in a filename since
those characters may be interpreted specially by the shell. For example, the filename
this$file will cause problems with certain commands since the bash shell will treat
the $ metacharacter as a variable identifier.

Viewing File Types


To list files in a certain directory, you can specify the directory to list as an argument to
the ls (list) command. Without arguments, it lists files in your current directory:

[root@localhost classfiles]# ls
bigfile Hidden Lab3.1.2 proposal1 SAII text.err
bin INTRO letter proposal2 small_town text.fxd
empty issue Miscellaneous README.GENERAL stuff timecal
greeting Lab3.1.1 Poems SAI termcap what_am_i
[root@localhost classfiles]# _
- 27 -

The ls command usually indicates file types with different colors; however the colors
used change from distribution to distribution and terminal to terminal. To append a
character to a file listing indicating the file type, you can use the -F option to ls:

[root@localhost classfiles]# ls -F
bigfile Hidden/ Lab3.1.2/ proposal1 SAII/ text.err
bin/ INTRO/ letter proposal2 small_town text.fxd
empty issue Miscellaneous/ README.GENERAL stuff/ timecal*
greeting Lab3.1.1/ Poems/ SAI/ termcap what_am_i
[root@localhost classfiles]# _

The -F option to the ls command appends:


a / character to directories
a * to executable files
a @ to symbolically linked files (discussed later)
a | to a named pipe
a = to a socket
nothing to all other file types

You can also use the -l (long listing) option with ls to indicate file types:

[root@localhost classfiles]# ls -l
total 828
-rw------- 2 root bin 13649 May 26 1998 bigfile
drwxr-xr-x 2 root bin 4096 Apr 8 1999 bin
-rw------- 1 root bin 0 May 26 1998 empty
-rw------- 1 root bin 111 May 26 1998 greeting
d--------- 2 root bin 4096 Apr 8 1999 Hidden
-rw-r--r-- 1 root bin 121 May 26 1998 issue
-rw------- 2 root bin 1044 May 26 1998 letter
drwxr-xr-x 2 root bin 4096 Jan 5 16:13 Miscellaneous
drwxr-xr-x 5 root bin 4096 Apr 8 1999 Poems
-rw------- 1 root bin 134 May 26 1998 proposal1
-rw------- 1 root bin 2175 May 26 1998 proposal2
-rw------- 1 root bin 1580 May 26 1998 small_town
drwxr-xr-x 2 root root 4096 Aug 14 2002 stuff
-rw-r--r-- 1 root root 737535 Jan 4 18:38 termcap
-rw------- 1 root bin 250 May 26 1998 text.err
-rw------- 1 root bin 231 May 26 1998 text.fxd
-r-x------ 1 root bin 270 May 26 1998 timecal
-rw------- 1 root bin 352 May 26 1998 what_am_i
[root@localhost classfiles]# _

The first character in each line indicates the type of file:


– represents a regular file (may contain binary or ASCII data)
d represents a directory
b and c represent block and character device files, respectively
n represents a named pipe
s represents a socket
l represents a symbolic link
- 28 -

The other fields are labeled below and will be discussed in more detail later on:

permissions owner group size date filename


(mode) owner

-rw------- 2 root bin 13649 May 26 1998 bigfile

file type link count

In Fedora Linux, the ll command is an alias to the ls -l command. To see a list of


aliases in your shell, type alias at a command prompt. To view additional file
information, you can use the stat command (e.g. stat bigfile).

NOTE: You may also notice a . character at the end of the permissions (mode). This
indicates that the file has an SELinux security context (discussed later in Chapter 6).

We can see from the outputs of ls –F and ls –l earlier that Poems is a directory and
that proposal1 and proposal2 are regular files. Unfortunately we do not know if
the files proposal1 and proposal2 contain text or binary data. To find out, you can
use the file command:

[root@localhost classfiles]# file proposal1 proposal2


proposal1: ASCII text
proposal2: ASCII text
[root@localhost classfiles]# _

Wildcards
Linux wildcard metacharacters may be used to specify multiple files to a command (also
known as file globbing). These wildcards are listed in Table 2-3.

Table 2-3 Shell Wildcards


* Specifies zero or more characters let* matches let, let5,
letter and so on
? Specifies one character let? matches let1, let2, let3,
(any character) letA and so on
[1238] Specifies one character let[1238] matches let1, let2,
(1,2,3 or 8 only) let3, and let8
[!1238] Specifies one character let[!1238] matches let4,
(anything except 1,2,3 or 8 only) let5, letA and so on
[a-zA-Z] Specifies one character let[a-zA-Z] matches leta,
(an alphabetical character only) letA, letb, letB and so on
[0-9] Specifies one character let[0-9] matches let1, let2,
(a numeric character only) let3 and so on
- 29 -

For example, to see the file type for files that start with the letters pro, you can use the
following command:

[root@localhost classfiles]# file pro*


proposal1: ASCII text
proposal2: ASCII text
[root@localhost classfiles]# _

Listing Files Recursively


Another common option to the ls command is –R (recursive). The word recursive refers
to all files and subdirectories underneath a certain part of the filesystem structure. Thus,
the command ls –R will list everything underneath a certain directory. To list the files
and subdirectories recursively for the Poems directory, you can use the following:

[root@localhost classfiles]# ls -R Poems


Poems/:
Blake nursery Shakespeare twister Yeats

Poems/Blake:
jerusalem tiger

Poems/Shakespeare:
sonnet1 sonnet15 sonnet2 sonnet3 sonnet4 sonnet7
sonnet10 sonnet17 sonnet26 sonnet35 sonnet5 sonnet9

Poems/Yeats:
mooncat old whitebirds
[root@localhost classfiles]# _

Options may be combined; thus to obtain a long listing of all files recursively, you can
use the command ls –lR.

NOTE: You can also use the tree command to generate a list of files recursively. For
example, tree Poems would display the same information as ls -R Poems but use
lines to display the relationships between directories, subdirectories and files.

Listing Hidden Files


Hidden files have files that start with a period ( . ) and are not displayed by the ls
command by default. To display hidden files, you can use the –a (all) option to the ls
command (which also displays the . and .. directory references):

[root@localhost ~]# ls -a
. .bash_history .bashrc .config .local
.. .bash_logout .cache .cshrc .tcshrc
anaconda-ks.cfg .bash_profile classfiles
[root@localhost ~]# _
- 30 -

Exercise
1. Log into tty3 as the root user. Run the cd classfiles command to switch to the classfiles
subdirectory.

2. Run the ls -F, ls -l and file * commands to list the files in the current directory, noting
their file types (including the files empty and what_am_i).

3. Run the ls -l /dev | less command and note that most files in the /dev directory are
block or character device files. Next, run the ls -l /run/systemd/journal/dev-log
command and note that it is a socket file. Finally, run the stat /run/systemd/journal/dev-
log command to view additional information about this socket file.

4. Run the cd ; ls -a command and note the hidden files in your home directory.

5. Run the ls -l classfiles/Poems/Shakespeare command (use the [Tab] completion


feature of the bash shell to fill out the directory name) and note the sonnet files within.
Next, run the ls -l classfiles/Poems/Shakespeare/sonnet[15] command to list only
sonnet1 and sonnet5.

6. Run the ls -R classfiles/Poems and tree classfiles/Poems commands to list the


filenames recursively within the Poems directory.

2.3 Viewing the Contents of Files


So far, we have examined commands that view the names of files as well as their types.
Most files on a Linux system are text files. As a result, there are many utilities that can be
used to view their contents. By far the easiest method for viewing the contents of a text
file is by using the cat (concatenate) command followed by the names of the files to
view. To view the file called proposal1, you can use the following command:

[root@localhost classfiles]# cat proposal1


On my honor,
I will try to do my duty,
to God and my country.
To help other people at all times,
and to obey the Girl Scout laws.
[root@localhost classfiles]# _

The cat command displays the entire file to the screen. If the file is too large to be
displayed to the terminal screen, the beginning of the file will not be displayed. To solve
this problem, recall from the last chapter that you can use the [Shift]+[PgUp] and
[Shift]+[PgDn] key combinations to scroll up and down terminal screens.

Another method for viewing large files is to view them page-by-page. One command that
can be used to do this is the more command, as shown below for the letter file:
- 31 -

[root@localhost classfiles]# more letter


Hello Mother! Hello Father!

Here I am at Camp Granada. Things are very entertaining,


and they say we'll have some fun when it stops raining.

All the counselors hate the waiters, and the lake has
alligators. You remember Leonard Skinner? He got
ptomaine poisoning last night after dinner.

Now I don't want this to scare you, but my bunk mate has
malaria. You remember Jeffrey Hardy? Their about to
organize a searching party.

Take me home, oh Mother, Father, take me home! I hate Granada.


Don't leave me out in the forest where I might get eaten
by a bear! Take me home, I promise that I won't make noise,
or mess the house with other boys, oh please don't make me
stay -- I've been here one whole day.

Dearest Father, darling Mother, how's my precious little


brother? I will come home if you miss me. I will even
let Aunt Bertha hug and kiss me!
--More--(82%)

The more command indicates that 82% of the file is displayed. Pressing the [Enter]
key moves forward one line and pressing the [Spacebar] moves forward one whole
page. You can press h for a help screen, or q to quit. Thus, the more command does
more than the cat command. Unfortunately, the cursor keys do not allow a user to scroll
up and down through a document. To do this, you must use the less utility (less is more
than more, more or less). To view the file letter with the less utility, you can run the
following command:

[root@localhost classfiles]# less letter


Hello Mother! Hello Father!

Here I am at Camp Granada. Things are very entertaining,


and they say we'll have some fun when it stops raining.

All the counselors hate the waiters, and the lake has
alligators. You remember Leonard Skinner? He got
ptomaine poisoning last night after dinner.

Now I don't want this to scare you, but my bunk mate has
malaria. You remember Jeffrey Hardy? Their about to
organize a searching party.

Take me home, oh Mother, Father, take me home! I hate Granada.


Don't leave me out in the forest where I might get eaten
by a bear! Take me home, I promise that I won't make noise,
or mess the house with other boys, oh please don't make me
stay -- I've been here one whole day.
letter
- 32 -

Like more, you can obtain a help screen in the less utility by pressing h or quit at any
time by pressing the q key.

Some other useful commands include tac (reverse cat), as well as the head and tail
utilities that display the first 10 lines and last 10 lines of a file by default, respectively.
You can instead specify the number of lines to display as an option to head and tail.
For example, to display the first 2 lines of proposal1 and the last 6 lines of letter,
you can run the following commands:

[root@localhost classfiles]# head -2 proposal1


On my honor,
I will try to do my duty,
[root@localhost classfiles]# tail -6 letter

Wait a minute! It's stopped hailing! Guys are swimming!


Guys are sailing! Playing baseball, gee that's better!
Mother, Father, kindly disregard this letter.

Alan Sherman
[root@localhost classfiles]# _

The tail command can also be used to continuously display the end of a file while the
file is being written to by other programs. To do this, specify the –f option to the tail
command. This is very useful for log files since you can view entries as they are added
while troubleshooting a problem.

The utilities discussed earlier only work on files that contain text. If you use them on a
binary data file, you will likely scramble your terminal. To safely view binary data files,
you can instead use the strings command (which displays known ASCII text patterns
within the file), or the od command (which displays a file in octal format, or in
hexadecimal format with the -x option).

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
cat bigfile
more bigfile (type h for help, q to quit)
less bigfile (type h for help, q to quit)
cat letter
tail -8 letter
strings letter
od -x letter
cat /etc/hosts
cat Poems/Yeats/mooncat
cat Poems/Shakespeare/* | less (views all sonnets page-by-page)
cat Poems/Blake/tiger
head -1 Poems/Blake/tiger
- 33 -

2.4 Common Text Tools


Linux shares many utilities with the UNIX operating system; many of these utilities were
used to modify text for large databases in one particular way. These utilities are known as
text tools and there are over 300 of them available. When specifying text within files
using these tools, you can use regular expressions to match multiple words just as shell
wildcards can be used to match multiple filenames. Regular expressions use similar
metacharacters compared to wildcards, yet their meanings are very different. Some
common regular expression metacharacters are listed in Table 2-4.

Table 2-4 Common Regular Expressions


* Specifies zero or more of the let* matches le, let, lett,
previous character lettt and so on
? Specifies zero or one of the let? matches le and let
previous character
+ Specifies one or more of the let+ matches let, lett, lettt
previous character and so on
. Specifies one character let. matches let1, let2, let3,
(any character) letA and so on
[1238] Specifies one character let[1238] matches let1, let2,
(1,2,3 or 8 only) let3, and let8
[^1238] Specifies one character let[^1238] matches let4,
(anything except 1,2,3 or 8 only) let5, letA and so on
[a-zA-Z] Specifies one character let[a-zA-Z] matches leta,
(an alphabetical character only) letA, letb, letB and so on
[0-9] Specifies one character let[0-9] matches let0, let1,
(a numeric character only) let2, let3, let4, let5, let6,
let7, let8, and let9
{3} Specifies three occurrences of let{3} matches lettt
the previous character
{1,3} Specifies one to three let{1-3} matches let, lett
occurrences of the previous and lettt
character
^hello Matches the word hello if it is
at the beginning of the line
hello$ Matches the word hello if it is
at the end of the line
(the|end) Matches the word "the" or "end"

The grep (global regular expression print) command is one of the most commonly used
text tools. It displays only those lines of a text file that match a text pattern. For example,
to display all of the lines that have the word Mother in the file letter, you can use the
following command:
- 34 -

[root@localhost classfiles]# grep Mother letter


Hello Mother! Hello Father!
Take me home, oh Mother, Father, take me home! I hate Granada.
Dearest Father, darling Mother, how's my precious little
Mother, Father, kindly disregard this letter.
[root@localhost classfiles]# _

Alternatively, you could use the –c option to the grep command to display the number
of times the word Mother appears in the file letter:

[root@localhost classfiles]# grep –c Mother letter


4
[root@localhost classfiles]# _

Like all Linux commands, grep is case sensitive. To search for text case-insensitive,
you can use the –i option to the grep command. Alternatively, you can search for all
lines that do not have the specified word by using the –v option to the grep command.

The first argument to grep supports regular expressions. Thus, to find all lines in the file
letter that end with the word Sherman, you can use the following command:

[root@localhost classfiles]# grep 'Sherman$' letter


Alan Sherman
[root@localhost classfiles]# _

Similarly, to view all lines that have the word toe, tie, and the, you can use the
following regular expression with the grep command:

[root@localhost classfiles]# grep t.e letter


Hello Mother! Hello Father!
and they say we'll have some fun when it stops raining.
All the counselors hate the waiters, and the lake has
Take me home, oh Mother, Father, take me home! I hate Granada.
Don't leave me out in the forest where I might get eaten
or mess the house with other boys, oh please don't make me
Dearest Father, darling Mother, how's my precious little
brother? I will come home if you miss me. I will even
Guys are sailing! Playing baseball, gee that's better!
Mother, Father, kindly disregard this letter.
[root@localhost classfiles]# _

Notice from the previous output that grep also printed lines that have the word Mother
and Father since it contained the pattern the. To search for only those lines that have
the words toe, tie, or the separated by spaces, you can use the following command:

[root@localhost classfiles]# grep " t.e " letter


All the counselors hate the waiters, and the lake has
Don't leave me out in the forest where I might get eaten
or mess the house with other boys, oh please don't make me
[root@localhost classfiles]# _
- 35 -

The double quotes used in the last example protect the space characters around the t.e
from shell interpretation (so that grep can interpret them). While grep recognizes most
regular expressions, the |, + and ? symbols are extended regular expressions and not
interpreted by default. To force grep to recognize them, you must specify the –E option
to the grep command or use the egrep command:

[root@localhost classfiles]# egrep "(Mother|Father)" letter


Hello Mother! Hello Father!
Take me home, oh Mother, Father, take me home! I hate Granada.
Dearest Father, darling Mother, how's my precious little
Mother, Father, kindly disregard this letter.
[root@localhost classfiles]# _

Alternatively, you can use the grep command without specifying regular expressions.
In this case, it is much faster to use the –F option to the grep command or the fgrep
command since these commands will not need to spend time searching for regular
expressions.

Another text tool that is commonly used is the wc (word count) command. By default,
the wc command counts the number of lines, words and characters in a file, but you can
use the –l, -w or –c options to display only the lines, words or characters, respectively.
The following command displays only the number of lines in the file bigfile:

[root@localhost classfiles]# wc -l bigfile


361 bigfile
[root@localhost classfiles]# _

The sort command can sort a text file alphabetically (or reverse alphabetically using the
–r option) by the first letter in each line, as shown below for proposal1:

[root@localhost classfiles]# sort proposal1


and to obey the Girl Scout laws.
I will try to do my duty,
On my honor,
to God and my country.
To help other people at all times,
[root@localhost classfiles]# _

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
grep "I " letter
grep -v "I " letter
grep -i love Poems/Shakespeare/* (note the number of lines returned)
wc -l Poems/Shakespeare/* (note the total number of lines in all sonnets... was
Shakespeare truly a romantic?)
- 36 -

2.5 Editing Text Files using nano


Most Linux configuration is performed by making the appropriate changes to a text file
on the system. Linux also allows you to create shell scripts that contain commands and
programming constructs for later execution.

A simple text editor that can be used on many Linux systems is nano, which uses
[Ctrl] key combinations for performing functions such as saving and quitting. If you
type nano letter to edit the letter file, the bottom of the screen lists all the
[Ctrl] key combinations (^X is the same as[Ctrl]+x):

GNU nano 2.9.8 File: letter

Hello Mother! Hello Father!

Here I am at Camp Granada. Things are very entertaining,


and they say we'll have some fun when it stops raining.

All the counselors hate the waiters, and the lake has
alligators. You remember Leonard Skinner? He got
ptomaine poisoning last night after dinner.

Now I don't want this to scare you, but my bunk mate has
malaria. You remember Jeffrey Hardy? Their about to
organize a searching party.

[ Read 28 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Page ^K Cut Text ^C CurPos
^X Exit ^J Justify ^W Where Is ^V Next Page ^U UnCut Txt ^T Spell

Exercise
1. Log into tty3 as the root user. Run the cd classfiles command to switch to the classfiles
subdirectory.

2. Run the nano letter command to edit the letter file. Add a few lines of your choice and
then quit without saving your changes.

3. Run the nano sample.txt command and add a few lines of your choice. Save your
changes and quit when finished. Next, run the cat sample.txt command to verify the
contents of the file.
- 37 -

2.6 Editing Text Files using vi


The most common text editor implemented on Linux systems is the vi (visual) editor. It
is a very powerful text editor and a vi-able skill for any IT administrator to learn as a
result. The version of vi used in Linux is called vim (vi improved); it is an open source
version of the original UNIX vi editor. To edit a new file, you can type vi filename
or vim filename at a command prompt and receive the following interactive screen:

~
~
~
~
~
~
~
~
~
~
~

~
"filename" [New File]

The ~ characters represent the end of the file and are placeholders only. The cursor is
automatically placed at the beginning of the file. The easiest way to navigate the file is to
use the cursor (arrow) keys, or [PageUp]/[PageDown]. You’ll know when you reach
the end of the file, because vi displays ~ (tilde) characters at the bottom.

Before you edit or add text, you must first understand that vi has two modes. When you
first open the vi editor, you are placed in COMMAND MODE, where every key and key
combination on your keyboard represents a function (e.g. pressing the x key will delete
the character your cursor is on). A handful of these functions take you to INSERT
MODE, where the keys on your keyboard are actually used to type/edit text (e.g. pressing
the x key will insert the letter x in your document). To return back to COMMAND
MODE after inserting/editing text, you can press the [Esc] key.

There are many ways to switch to INPUT MODE when in COMMAND MODE; the
most common are listed below:

COMMAND MODE

[Esc] a, i, o, A, I, O, c, r

INPUT MODE
- 38 -

The common functions that enter INPUT MODE are described below:
a starts appending text after current character
i starts inserting text before current character
o opens a new line underneath the cursor to insert text
A starts appending text after current line
I starts inserting text at the beginning of the current line
O opens a new line above the cursor to insert text
c starts inserting text on the character that you are on
r replaces one character only

Some useful commands you can use when in COMMAND MODE include:
yy yanks a line of text to the buffer
y3y or 3yy yanks 3 lines of text to the buffer
y3w 3yw yanks 3 words of text to the buffer
p pastes the contents of the buffer below the current line
P pastes the contents of the buffer above the current line
dd deletes the current line
d3d or 3dd deletes 3 lines
d5w 5dw deletes 5 words
x deletes one character
3x deletes 3 characters (starting with the current character)
J joins lines (join line below you to current line)
u undoes the previous change
[Ctrl]+g shows current line stats
: takes you to the interactive : prompt (called ex mode)
:wq saves and quit
:w lala saves as file lala
:q! quits, throwing away any changes
:set all shows all environment parameters
:set number sets auto line numbering
:set nonumber unsets auto line numbering
ZZ saves and quits (same as :wq)

Additional navigation commands (beyond [PageUp]/[PageDown] and the cursor


keys) that are useful in COMMAND MODE include:
h j k l alternatives to the cursor keys for navigation
1G goes to line 1
23G goes to line 23
G goes to the last line
^ goes to beginning of the current line
$ goes to beginning of the current line
d$ deletes from cursor to end of the current line
d^ deletes from cursor to the beginning of the current line
- 39 -

Any set options in ex mode can be made permanent for all users by editing the
/usr/share/vim/vimrc file, or for just your user account by creating a .exrc or
.vimrc file in your home directory. The .vimrc file is only used if you have the full
vim package installed (most distributions have the vim-minimal package installed by
default). The most common options that are placed in this file turn on line numbering
(number), cursor position (ruler), highlighting of brackets (showmatch), tabs that
are equivalent to 4 spaces (ts=4 since the default is 8, which is too big), and
programming language highlights (syntax):
set number ruler showmatch ts=4
syntax on

The vi editor also supports several functions that you can use in COMMAND MODE.
The most common of these include:
/Mother searches for Mother (n = next occurrence, N = previous occurrence)
?Mother does the same search, but in the reverse direction (earlier lines)
~ switches case for current letter
gUU turns entire line to uppercase
ddp swaps current line with the next one
zf5j folds the next 5 lines (zo = open/expand, zc = close, zd = delete)

Moreover, ex mode (the : prompt) supports several additional commands that can be
used to perform powerful text processing. Following are some useful commands that you
can run in ex mode:
:% s/the/THE/g searches for the and replace with THE in the whole file
:1,7 s/the/THE/g same as previous, but only on lines 1 through 7
:ab LR Linux Rocks creates an abbreviation
When you type LR in INSERT MODE, it will replace it with Linux Rocks (this
can be put into your .vimrc or .exrc file to make permanent)

:r proposal1 inserts the contents of the proposal1 file under the current line
:r !date inserts the output of the date command under the current line
:help p displays help for functions that start with p
:help holy-grail displays help for all ex mode commands
:e proposal1 edits a new file (proposal1) instead of current file
:split proposal1 edits proposal1 in a new split screen (horizontal)
:vsplit proposal1 edits proposal1 in a new split screen (vertical)
You can use [Ctrl]+ww to move between screens, [Ctrl]+w, _ to minimize
the current screen, or [Ctrl]+w, = to restore current screen to original size

:tabe lala creates a new tab called lala


:tabs shows tabs
:tabn moves to next tab
:tabp moves to previous tab
:set rightleft a fun prank (especially if you add it to someone else’s .vimrc)
- 40 -

Exercise
1. Log into tty3 as the root user and run the dnf install vim command to install the full
vim package. Next, run cd classfiles to switch to the classfiles subdirectory.

2. Run the vi letter command to edit the letter file using the vi editor. Practice many of
the commands discussed earlier in both COMMAND MODE and INPUT MODE. When
finished, quit without saving your changes.

3. Run the vi small_town command to edit the small_town file using the vi editor. Note
that this file is full of mistakes, repeated lines and improper word wrap. Use your
knowledge of the vi editor to fix these errors. When finished, save your changes and quit
the vi editor.

4. Run the vi ~/.vimrc command to create a new vi runtime configuration file in your
home directory. Add the line set number to this file. When finished, save your changes
and quit the vi editor. Next, run the vi letter command and note that line numbering is
automatically enabled.

5. (Optional) Run the vi tutorial by typing vimtutor at a command prompt and follow the
directions. This exercise should take about 30 minutes.

2.7 Editing Text Files using Emacs


The most powerful text editor aside from vi is the GNU Emacs (Editor Macros) editor.
It boasts similar functionality to vi and some Linux users prefer Emacs to vi when editing
files because of its rich macro programming environment using the LISP (List
Processing) artificial intelligence programming language. Like vi, Emacs has a steep
learning curve. To get started using Emacs, however, you only need to know some basic
[Ctrl]key combinations for navigation and functionality:

[Ctrl]+a moves the cursor to the beginning of the line


[Ctrl]+e moves the cursor to the end of the line
[Ctrl]+h displays help screen
[Ctrl]+d deletes the current character
[Ctrl]+k deletes from the cursor to the end of the line
[Esc]+d deletes the current word
[Ctrl]+x + [Ctrl]+c exits the Emacs editor
[Ctrl]+x + [Ctrl]+s saves the current document
[Ctrl]+x + [Ctrl]+w saves as a new filename
[Ctrl]+x + u undoes the previous change

Emacs is not installed by default on most Linux distributions. To install it on Fedora


Linux, you can run the dnf install emacs command. Next, to edit the letter file
using Emacs, you can run the emacs letter command:
- 41 -

File Edit Options Buffers Tools Help


Hello Mother! Hello Father!

Here I am at Camp Granada. Things are very entertaining,


and they say we'll have some fun when it stops raining.

All the counselors hate the waiters, and the lake has
alligators. You remember Leonard Skinner? He got
ptomaine poisoning last night after dinner.

Now I don't want this to scare you, but my bunk mate has
malaria. You remember Jeffrey Hardy? Their about to
organize a searching party.

-uu-:---F1 letter (Fundamental)--L1--Top----------------------------


For information about the GNU Project and its goals, type C-h C-a.

When run in a desktop environment, Emacs runs as a graphical app with drop-down
menus but supports the same [Ctrl]key combinations.

Exercise
1. Log into tty3 as the root user. Run the cd classfiles command to switch to the classfiles
subdirectory.

2. Run the dnf install emacs command and press y when prompted to download and
install the Emacs editor and supporting files.

3. Run the emacs sample.txt command. Add some lines, as well as modify some of the
lines you created with nano. Save your changes and quit when finished. Next, run the cat
sample.txt command to verify the contents of the file.
- 42 -

CHAPTER

Managing Linux Files


In previous chapters, you have navigated the Linux filesystem structure,
viewed and edited files. This chapter focuses on managing those same files
using commands to copy, move, remove, find, and link them. Additionally,
you’ll learn how to change file ownership, permissions and attributes.

3.1 Manipulating Files & Directories


3.2 Finding Files on the Filesystem
3.3 Linking Files & Directories
3.4 File & Directory Ownership
3.5 File & Directory Permissions
3.6 Default Permissions
3.7 Special Permissions
3.8 Beyond User, Group & Other
3.9 Linux File Attributes
- 43 -

3.1 Manipulating Files & Directories


Copying Files and Directories
The cp command can be used to make a copy of a file and requires two arguments: the
source file and the destination directory. To copy the file letter in the current
directory to the /etc/sysconfig directory, you can run the following commands:

[root@localhost ~]# cp letter /etc/sysconfig


[root@localhost ~]# ls /etc/sysconfig
amd console hwconf kudzu ntpd squid
apmd desktop i18n letter pcmcia static
apm-scripts dhcpd identd mouse radvd syslog
arpwatch firewall init named rawdevices tux
authconfig firewall~ ipchains network rhn ups
cbq gpm irda networking samba vnc
clock harddisks keyboard network-scripts sendmail xinetd
[root@localhost ~]# _

If the second argument specifies an existing file, the source file will overwrite the
destination file. If the second argument specifies a non-existent file, then the source file
will be copied and renamed the destination filename. For example, to copy the file
letter from the current directory to the /etc/sysconfig directory and rename it
letter2, you can run the following commands:

[root@localhost ~]# cp letter /etc/sysconfig/letter2


[root@localhost ~]# ls /etc/sysconfig
amd desktop identd mouse rawdevices ups
apmd dhcpd init named rhn vnc
apm-scripts firewall ipchains network samba xinetd
arpwatch firewall~ irda networking sendmail
authconfig gpm keyboard network-scripts squid
cbq harddisks kudzu ntpd static
clock hwconf letter pcmcia syslog
console i18n letter2 radvd tux
[root@localhost ~]# _

If we performed the same command again, we would be prompted to overwrite


/etc/sysconfig/letter2 since it already exists.

[root@localhost ~]# cp letter /etc/sysconfig/letter2


cp: overwrite `/etc/sysconfig/letter2'? n
[root@localhost ~]# _

This is because there are aliases configured by default in Fedora Linux that override
common commands, including one called cp that automatically runs the cp –i
command (the –i option means interactively prompt for confirmation). Thus each time
you type cp in Fedora Linux, you are actually typing the cp –i command instead.
- 44 -

To view aliases that are configured in your shell, run the alias command:

[root@localhost ~]# alias


alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias mv='mv -i'
alias rm='rm -i'
[root@localhost ~]# _

To prevent the cp command from interactively prompting you to overwrite files, specify
the –f (force) option to the cp command:

[root@localhost ~]# cp –f letter /etc/sysconfig/letter2


[root@localhost ~]# _

When making a copy of a directory (and all of its contents), ensure that you use the –r
(recursive) option to the cp command. The recursive option is used with many
commands (as we saw in Chapter 2) and is typically –r or –R.

Moving/Renaming Files and Directories


Instead of copying a file, you can specify to move it from one directory to another with
the mv command, hence renaming its location. As with the cp command, the mv
command takes two arguments: the source file and the destination directory. If the second
argument specifies an existing file, the source file will overwrite the destination file (after
prompting you, since there is a mv alias that runs mv –i). If the second argument
specifies a non-existent file, then the source file will be renamed the destination filename.

The following example renames issue to issue2 in the current directory:

[root@localhost ~]# ls
bigfile Hidden Lab3.1.2 proposal1 SAII text.err
bin INTRO letter proposal2 small_town text.fxd
empty issue Miscellaneous README.GENERAL stuff timecal
greeting Lab3.1.1 Poems SAI termcap
what_am_i
[root@localhost ~]# mv issue issue2
[root@localhost ~]# ls
bigfile Hidden Lab3.1.2 proposal1 SAII text.err
bin INTRO letter proposal2 small_town text.fxd
empty issue2 Miscellaneous README.GENERAL stuff timecal
greeting Lab3.1.1 Poems SAI termcap
what_am_i
[root@localhost ~]# _
- 45 -

NOTE: Although mv is typically used when renaming files and directories, you can
instead use the rename command to do the same.

Creating/Deleting Files and Directories


Often the target directory that you wish to copy or move a file to does not exist. To create
a new directory, you can use the mkdir command, and to remove a directory, you can
use the rmdir command, as shown in the following example:

[root@localhost ~]# mkdir sample


[root@localhost ~]# ls
bigfile INTRO Miscellaneous SAI termcap
bin issue2 Poems SAII text.err
empty Lab3.1.1 proposal1 sample text.fxd
greeting Lab3.1.2 proposal2 small_town timecal
Hidden letter README.GENERAL stuff what_am_i
[root@localhost ~]# rmdir sample
[root@localhost ~]# ls
bigfile Hidden Lab3.1.2 proposal1 SAII text.err
bin INTRO letter proposal2 small_town text.fxd
empty issue2 Miscellaneous README.GENERAL stuff timecal
greeting Lab3.1.1 Poems SAI termcap
what_am_i
[root@localhost ~]# _

You may also use the –p option to the mkdir command to create a directory and any
parent directories that do not exist. For example, the mkdir –p /dir1/dir2
command will create the dir2 directory and also create the dir1 directory if it does not
already exist. Similarly, the rmdir –p /dir1/dir2 command will remove the
dir1 and dir2 directories.

It is important to note that the rmdir command will only remove a directory that is
empty. To remove files that are in a directory, you must use the rm command:

[root@localhost ~]# rm file1


rm: remove `file1'? y
[root@localhost ~]# _

Since there is a default rm alias that runs the rm –i command, you are prompted for
each deletion. To avoid this, use the –f option to the rm command:

[root@localhost ~]# rm –f file2


[root@localhost ~]# _

You can remove a directory and all of its contents recursively using the rm command.
Simply specify the recursive option (–r or –R) alongside the –f option to ensure you are
not prompted for each deletion:
- 46 -

[root@localhost ~]# rm –Rf dir1


[root@localhost ~]# _

NOTE: You can also use the unlink command to remove files on Linux systems.

If you wish to create an empty file for practice use, use the touch command. The
touch command was designed to update the timestamp on a file but will create the file
if it does not exist. For example, to create an empty file called testfile, you can use
the touch testfile command.

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
cp proposal1 /var
ls /var
cp proposal1 /var (note that you are prompted to overwrite the file)
cp proposal1 /var/newproposal1
ls /var
mv /var/newproposal1 new1
ls
cp -R Poems Poems2
ls
mv Poems2 Poems3
ls
rm -f new1
rm -Rf Poems3
ls

3.2 Finding Files on the Filesystem


Linux systems often have many thousand directories and files, and there are several
commands you can use to locate them. By far the fastest method to locate files is to use
the locate command. For example, to find all files that have the word issue in their
absolute pathname, you could run the following command:

[root@localhost ~]# locate issue


/etc/issue.net
/etc/issue
/usr/share/doc/wine-20010822/wine-doc/portability-issues.html
/usr/share/man/man5/issue.net.5.gz
/usr/share/man/man5/issue.5.gz
/usr/share/ssl/misc/c_issuer
/lib/security/pam_issue.so
/root/issue
[root@localhost ~]# _
- 47 -

The locate command returns the results very quickly because it searches a pre-indexed
database of all files on the filesystem (/var/lib/mlocate/mlocate.db). This
file is updated daily, however, if new programs or files are added to a system, it is good
form to manually update this database with the updatedb command. You can exclude
certain file types and directories from being indexed by updatedb if you edit the
appropriate PRUNE lines within /etc/updatedb.conf.

Linux also has a PATH variable that contains a list of directories to search for executable
files. Unlike Windows, Linux does not look in the current directory for executable files
before it searches the PATH variable. Instead, when you execute a program on a Linux
system, the system immediately searches the PATH variable if you did not specify a
proper absolute or relative pathname to the command. Thus, if you wish to execute a
program in your current directory and your current directory is not listed in the PATH
variable, use ./programname (a forced relative pathname). Because most commands
that you execute are listed in directories in the PATH variable, you can search for these
programs using the which command (which only searches PATH for files that are
executable). For example, to list the pathname to the vi command, you can run:

[root@localhost ~]# which vi


/usr/bin/vi
[root@localhost ~]# _

NOTE: You can also use the whereis command instead of the which command to
locate program files. The whereis command also displays the location of source files
and manual pages for program files.

If you need to search for files based on specific criteria, you can use the find command.
For example, to find all files underneath the /var directory that have a size greater than
1024 kilobytes, you could use the following command:

[root@localhost ~]# find /var -size +1024k


/var/lib/rpm/Packages
/var/lib/rpm/Basenames
/var/lib/slocate/slocate.db
/var/log/lastlog
/var/log/pacct
/var/cache/man/whatis
/var/ftp/lib/libc-2.2.4.so
[root@localhost ~]# _

Alternatively, you could use the find /home –name "let*" command to find all
files starting from /home recursively that have a name starting with the characters "let",
or the find /home –user bozo command to find all files starting from /home
recursively that are owned by bozo. A more complex example is find / –name
"core.[1-9]*" –exec 'rm -i' {} \; -print which locates core dump
files (named core.number) and prompts you to remove them. Table 3-1 lists some
common criteria that you can use with the find command.
- 48 -

Table 3-1 Common find command options


-atime -n Searches for files that were accessed less than n days ago
-atime +n Searches for files that were accessed more than n days ago
-group n Searches for files who are owned by a certain group or GID (n)
-inum n Searches for files who have an inode number of n
-ls May be used with other search options to perform a long file listing
for files and directories found during the search
-mtime -n Searches for files that were modified less than n days ago
-mtime +n Searches for files that were modified more than n days ago
-name n Searches for a certain filename n (n may contain wildcards)
-perm n Searches for files and directories that have a certain permission set
of n (e.g. 600 or u=rw as discussed later in this chapter)
-perm -n Searches for files and directories that contain at least the
permissions described by n.
-perm +n Searches for files and directories that contain any of the permissions
described by n.
-regexp n Searches for certain filenames using regular expressions instead of
wildcard metacharacters
-size -n Searches for files with a size less than n
-size n Searches for files with a size of n
-size +n Searches for files with a size greater than n
-type n Searches for files of type n where n is
b for block files, c for character files, d for directory files,
p for named pipes, f for regular files, s for sockets, or
l for symbolic links (shortcuts, discussed later in this chapter)
-user n Searches for files owned by a certain user or UID (n)

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
touch newfile1
locate newfile1 (no results in the locate database for this new file)
updatedb
locate newfile1 (it works now because you updated the locate database)
echo $PATH (note the directories in PATH)
which cp (note that cp is in a directory in PATH)
which newfile1 (no results are shown because newfile1 is not in PATH)
cp newfile1 /usr/bin
chmod +x /usr/bin/newfile1 (this gives newfile1 execute permission,
which is discussed later in this chapter)
which newfile1 (the newfile1 executable program is now shown!)
find / -name “*hosts*”
find /usr -type d
- 49 -

3.3 Linking Files & Directories


There are two methods you can use to link files on a Linux filesystem. But before we
discuss those methods, we must first examine the structure of files and directories. Every
file and directory has two components: an inode (information node) and a data section
(also called the data blocks). The data section of a file contains the filename and the data
that exists in the file, whereas the inode contains all other information such as ownership,
permissions, modification date, size, and so on. This is illustrated below:

INODE DATA BLOCKS

inode number filename: letter


ownership Hello Mother! Hello Father!
size
location of data blocks Here I am at Camp Granada. Things are very
modification date entertaining,and they say we'll have some fun when it
permissions (mode) stops raining.
owner
group owner All the counselors hate the waiters, and the lake has
etc. alligators. You remember Leonard Skinner? He got
ptomaine poisoning last night after dinner.

It is important to note that each inode


Nowon a filesystem
I don't want thishas
to ascare
unique inode
you, number.
but my bunk Also,
mate
directories are just special files that
hashave an inode listing the other files they contain.

The first type of link that you can create between files is called a hard link. Hard links
share the same inode (and inode number) as well as the same contents. When one hard
linked file is changed, all other hard linked files are changed as well. The only restriction
to hard links is that they must reside on the same filesystem; thus you cannot hard link a
file on a filesystem that resides on a hard disk to a file that exists on a filesystem on a
USB flash drive. The reason for this is that inode numbers are unique on each filesystem,
but not across filesystems. The root user can also hard link directories together; when a
file is added to one directory, it is added to all other hard linked directories as well.

Each time a hard link is created, the link count for that file increments by 1. For all
normal files, the link count is 1; however a link count of 3 indicates that there are 2 other
files on the filesystem which are hard linked to it as shown below:

permissions owner group size date filename


(mode) owner

-rw------- 3 root bin 13649 May 26 1998 bigfile

file type link count


- 50 -

To create a hard link, you can use the ln command and specify the original and new files
as arguments. To verify that hard links have been created successfully, you can view their
inode numbers using the –i option to the ls command; all hard links must share the
same inode number. For example, to create a hard link to proposal1 called
hardlinkfile, you could run the following commands:

[root@localhost ~]# ln proposal1 hardlinkfile


[root@localhost ~]# ll -i proposal1
587627 -rw------- 2 root bin 134 May 26 1998 proposal1
[root@localhost ~]# ll -i hardlinkfile
587627 -rw------- 2 root bin 134 May 26 1998 hardlinkfile
[root@localhost ~]# cat proposal1
On my honor,
I will try to do my duty,
to God and my country.
To help other people at all times,
and to obey the Girl Scout laws.
[root@localhost ~]# cat hardlinkfile
On my honor,
I will try to do my duty,
to God and my country.
To help other people at all times,
and to obey the Girl Scout laws.
[root@localhost ~]# _

To remove a hard link, you can use the rm command. Since you remove a file by name
(not inode number), removing one hard linked file will not remove all others.

The second type of link is called a soft or symbolic link (symlink). Symlinks are shortcuts
to other files. Both files have different sizes and inode numbers, and the data blocks of
the symlink only contains the pathname of the target file. When you open or edit a
symlink, you are actually working with the target file. A long listing of a symlink
indicates the target file with an arrow (-->) and displays an l file type:

permissions owner group size date filename


(mode) owner

lrw------- 1 root bin 39 May 26 1998 symlink --> letter

file type link count

You can use the ln command with the –s option to create symlinks. For example, to
create a symlink to proposal2 called symlinkfile, you could run:

[root@localhost ~]# ln -s proposal2 symlinkfile


[root@localhost ~]# ll symlinkfile
lrwxrwxrwx 1 root root 9 Sep 11 01:41 symlinkfile -> proposal2
[root@localhost ~]# _
- 51 -

Unlike hard links, symlinks are commonly made to directories to simplify navigation
around the filesystem and need not reside on the same filesystem because they do not
share inode numbers.

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
ll letter (note that it is not hard linked or symlinked)
ln letter hardletter
ll -i letter hardletter (note the link count of 2 and duplicate inode number)
rm -f hardletter
ll -i letter (note that the link count has returned to 1)
ln -s letter symletter
ll (note the file type and target pointer)
rm -f symletter

3.4 File & Directory Ownership


As we shall see in the next section, the owner and group owner of a file or directory is
important in determining how permissions are applied. On modern UNIX and Linux
systems, only the root user can change file or directory ownership.

To change the ownership of a file or directory, you can use the chown command
followed by the new owner and the file or directory name. Similarly to change the group
ownership of a file or directory, you can use the chgrp command followed by the new
group and the file or directory name. An example of changing the ownership and group
ownership of the file called letter is seen below:

[root@localhost ~]# ll letter


-rwxrw-r-- 2 root bin 1044 May 26 1998 letter
[root@localhost ~]# chown bozo letter
[root@localhost ~]# chgrp sys letter
[root@localhost ~]# ll letter
-rwxrw-r-- 2 bozo sys 1044 May 26 1998 letter
[root@localhost ~]# _

You can also use the chown command to change the owner and group ownership of a
file at the same time by using the notation owner.group or owner:group as the first
argument to the chown command:

[root@localhost ~]# chown root:bin letter


[root@localhost ~]# ll letter
-rwxrw-r-- 2 root bin 1044 May 26 1998 letter
[root@localhost ~]# _
- 52 -

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
ll proposal1 (note the user and group owner)
chown nobody proposal1
chgrp nobody proposal1
ll proposal1 (note the nobody user and nobody group owner)
chown root.bin proposal1
ll proposal1 (note the root user and bin group owner)

3.5 File & Directory Permissions


Understanding Permissions
Linux uses 3 permissions by default: read (r), write (w), and execute (x), in that order.
If a permission is to be denied, a dash (-) is used to represent the revoked permission.
For example, if the read and execute permissions are to be granted but the write
permission denied, the permission would be seen as r-x.

Moreover, there are 3 sets of permissions in each permission string (mode):


rwxrwxrwx. The first set of permissions belong to the user (owner) of the file, the
second set belongs to the group owner of the file and the third to everyone else (other).

rwxrwxrwx

u g o
s r t
e o h
r u e
p r

permissions owner group


(mode) owner

-rwxrwxrwx 1 root bin 39 May 26 1998 letter

Thus, there are:


3 permissions in Linux = rwx
3 sets of permissions in a mode = rwxrwxrwx
3 targets for the permissions = user, group and other
- 53 -

Although files and directories are treated equally with respect to assigning and revoking
permissions, r, w and x have different meanings for each, as shown in Table 3-2.

Table 3-2 Standard Linux Permissions


Permission: Function when set on files: Function when set on directories:
r Allows you to open and read the Allows you to list files in the
file directory (e.g. with ls)
w Allows you to open, read and Allows you to create, delete and
edit the file rename files in the directory
x Allows you to execute the file Allows you to access the directory
(e.g. program or shell script)

To set permissions on a file or directory, the owner of that file or directory (or root) can
use the chmod (change mode) command. The chmod command can operate in one of
two modes: symbolic mode and absolute mode.

Changing Permissions using Symbolic Mode


Symbolic mode uses the letters u, g, and o to represent user, group and other, and the
letter a to represent all three. The + symbol will add a permission, the - symbol will take
one away, and the = symbol makes the permission equal to a certain set.

Say for example, that the Poems directory has the mode rwxrwxrwx. In order to take
the write permission away from other, we can use the chmod o-w Poems command.
The resulting mode would be rwxrwxr-x. Similarly if the letter file had the mode
r-xrw-r--, we could give the write permission to the user using the chmod u+w
letter command. The resulting mode would be rwxrw-r--.

Several permissions can be added or taken away at one time using the chmod command.
For example, if the Poems directory had the r--rw-r-x mode, running the chmod
u+w,g-w+x,o+w-x Poems command would change the mode to rw-r-xrw-.
Similarly, to give user, group and other all permissions (rwxrwxrwx) to the Poems
directory you can run chmod a=rwx Poems command.

Changing Permissions using Absolute Mode


The absolute mode of the chmod command involves using numeric values to alter
permissions of a file or directory. Each permission is assigned a numeric value:
• Read permission has a value of 4.
• Write permission has a value of 2.
• Execute permission has a value of 1.
• If a permission is not to be assigned, the given value is 0.

When we add these values, we can say that a full set of permissions has a combined value
of 7 for user, group or other: rwx=(4+2+1)=7
- 54 -

A mode that grants all permissions to user, group, and other is represented by 777:
rwxrwxrwx=(4+2+1)(4+2+1)(4+2+1)=777

Thus, if we wanted to change the permissions on letter to allow the user read and
write (a combined value of 6), members of the group read permission (a value of 4), and
nothing to everyone else (a value of 0), we could run the chmod 640 letter
command. The resulting mode of letter would be rw-r-----, which is the same as
(4+2)(4)(0)=640.

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
chmod u=r,g=rwx,o=w letter
ll letter (note the mode is r--rwx-w-)
chmod g-wx,o-w letter
ll letter (note the mode is r--r-----)
chmod 000 letter
ll letter (note the mode is ---------)
chmod 777 letter
ll letter (note the mode is rwxrwxrwx)
chmod 664 letter
ll letter (note the mode is rw-rw-r--)

3.6 Default Permissions


When you create a new file, the system assigns a mode of 666 (rw-rw-rw-), and when
you create a new directory, the system assigns a mode of 777 (rwxrwxrwx). Since this
is not very secure, the system contains a umask (user mask) variable that automatically
removes some of these permissions during file or directory creation. The default umask is
022 on most Linux systems. This means that no permissions (0) are removed for the user,
but that the write permission (2) is removed for group and other.

To view your umask, you can type the umask command without arguments; to change
your umask, specify the new umask as an argument to the umask command.

For example, if you change your umask to 027 using the umask command, new files and
directories will have no permissions (0) removed from the user, the write (2) permission
removed from the group, and all permissions (7) removed from other.

Following this, new directories would get rwxrwxrwx from the system, less the umask
(027), leaving the mode rwxr-x---. New files would get rw-rw-rw- from the
system, less the umask (027), leaving the mode rw-r-----.
- 55 -

Exercise
1. Log into tty3 as the root user and run the following commands:
umask (note the default umask of 0022, which is 022)
mkdir sampledir
ll -d sampledir (note the default permissions of rwx-r-xr-x)
touch samplefile
ll samplefile (note the default permissions of rw--r--r--)
umask 077
mkdir sampledir2
ll -d sampledir2 (note the default permissions of rwx-------)
touch samplefile2
ll samplefile2 (note the default permissions of rw--------)

3.7 Special Permissions


There are 3 hidden special permissions that provide additional administrative
functionality: the Set User ID (SUID) which replaces rwxrwxrwx with rwsrwxrwx,
the Set Group ID (SGID) which replaces rwxrwxrwx with rwxrwsrwx, and the sticky
bit which replaces rwxrwxrwx with rwxrwxrwt.

These bits are assumed by placing a number at the beginning of the bit set. For example,
chmod 7777 letter gives all 3 permissions (rwsrwsrwt): the SUID (4), SGID (2)
and sticky bit (1). Alternatively, you could run the chmod u+s,g+s,o+t letter
command to do the same using symbolic mode.

SUID and SGID Bits


If the SUID is set on a file, when you execute that file you temporarily become the user
(owner) of the file while it is executing. Similarly, if you have the SGID set on a file, you
temporarily become a member of the group the file is owned by while it is executing. In
short, SUID and SGID are the Linux equivalent of the RunAs command (or Start menu
option) on Windows systems. Take the passwd command as an example:

[root@localhost ~]# ll /usr/bin/passwd


-rwsr-xr-x 1 root root 28792 Feb 8 2011 /usr/bin/passwd
[root@localhost ~]# _

We see from above that regular users become the root user when they change their own
password. This is because the only user allowed to change any passwords on UNIX and
Linux systems is the root user; thus regular users must temporarily become root when
changing their own password.

NOTE: If you have the SGID bit set on a directory, then you are temporarily that
primary group when creating files or directories within that directory. If you create
subdirectories within that directory, the SGID is also placed on that as well.
- 56 -

NOTE: The Linux kernel prevents shell scripts from running with SUID and SGID
privileges for security reasons. Thus, SUID and SGID bits should only be set on binary
compiled programs (e.g. grep, vi, and so on).

The Sticky Bit


The sticky bit is fundamentally different than the SUID and SGID as it only has a useful
function on directories. If the sticky bit is set on a directory, you may add files (if you
have w permission) but can only delete files within the directory that you own (i.e. the
ones that you have added). This is useful for creating a directory that gives w permission
to many different users, as it ensures that those users can only delete their own files, and
files that were added by others). The /tmp and /var/tmp directories have the sticky
bit set since they are public directories used by various programs on the system.

Because the SUID and SGID normally apply to executable programs and the sticky bit
applies to directories, special permissions rely on the x permission to function. Thus, if
you take away x permission, the system alerts you to this by capitalizing the output:

[root@localhost ~]# chmod 7777 samplefile


[root@localhost ~]# ll samplefile
-rwsrwsrwt 1 root root 22520 2012-02-26 09:56 samplefile
[root@localhost ~]# chmod 7666 samplefile
[root@localhost ~]# ll samplefile
-rwSrwSrwT 1 root root 22520 2012-02-26 09:56 samplefile
[root@localhost ~]# _

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
chmod 4666 /bin/false
ll /bin/false (note the capitalization indicating absence of x)
chmod 4777 /bin/false
ll /bin/false (users who execute false become the owner!)
mkdir /public
chmod 1777 /public (sets the sticky bit)
touch /public/rootfile
ll /public/rootfile (note that the owner is root)
exit

2. Log into tty3 as the woot user and run the following commands:
touch /public/wootfile
ll /public/wootfile (note that the owner is woot)
rm -f /public/wootfile
rm -f /public/rootfile (note the error message, even though you have w
permission on the directory)
- 57 -

3.8 Beyond User, Group & Other


UNIX is all about simplicity. And since Linux was born from UNIX, it shares the same
focus. When it comes to permissions, almost everything can be achieved by assigning
permissions to the owner of the file (user), the group owner (group) and everyone else
(other) if the file and directory structure is well designed. However, there may be
exceptions. Take the following file, for example. The owner of the file is woot (which has
rw), the group of the file is acctg (which has rw), and everyone else has no access:

[root@localhost ~]# ll proposal1


-rw-rw---- 1 woot acctg 134 May 26 1998 proposal1
[root@localhost ~]# _

If you need to allow the user bob access to read the file (but not edit it), the default
permission structure will not allow that. Luckily, you can add use the setfacl (set file
ACL) command to add a special section to the mode of the file for bob only:

[root@localhost ~]# setfacl -m u:bob:r-- proposal1


[root@localhost ~]# _

You can add multiple users or groups in addition to user, group and other in the mode of
the file. The list of users and groups that have access to a file or directory is called the
Access Control List (ACL) and is now comprised of user, group, other, and bob. The –m
option in the setfacl command above modifies the ACL, and you can use g instead of
u to add an additional group to the ACL.

Now, when you perform a long listing of the file, you will see a + symbol next to the
mode to indicate that there are additional members in the ACL. To see these additional
members, you can use the getfacl command:

[root@localhost ~]# ll proposal1


-rw-rw----+ 1 woot acctg 134 May 26 1998 proposal1
[root@localhost ~]# getfacl proposal1
# file: proposal1
# owner: woot
# group: acctg
user::rw-
user:bob:r--
group::rw-
mask::rw-
other::---
[root@localhost ~]# _

Note the extra node in the output of the setfacl command for mask. The mask is
compared to all additional entries in the ACL; if the mask is more restrictive, it takes
precedence when it comes to permissions. For example, if the mask is set to r-- and the
user bob has rw-, then the user bob actually gets r-- to the file. When you run the
- 58 -

setfacl command, the mask is always made equal to the least restrictive permission
assigned so that it does not affect additional ACL entries. However, if you copy the file to
another filesystem, the mask becomes ---, which negates all additional ACL entries
made on the file. In other words, the mask is a safety precaution that prevents additional
ACL entries from being used if the file is copied.

To remove all additional entries in the mode, you can use the -b option to setfacl:

[root@localhost ~]# setfacl -b proposal1


[root@localhost ~]# ll proposal1
-rw-rw---- 1 woot acctg 134 May 26 1998 proposal1
[root@localhost ~]# getfacl proposal1
# file: proposal1
# owner: woot
# group: acctg
user::rw-
group::rw-
other::---
[root@localhost ~]# _

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
setfacl -m u:woot:rw- letter
ll letter (note the + symbol next to the mode)
getfacl letter (note the mask and entry for woot)
setfacl -b letter
ll letter (note the + symbol is no longer present)
getfacl letter (note the absence of additional ACL entries)

3.9 Linux File Attributes


Like Windows and DOS, Linux has file and directory attributes. However, these
attributes typically provide for low-level filesystem functionality only. To see the
filesystem attributes that are currently assigned to a file or directory, you can use the
lsattr (list attributes) command:

[root@localhost ~]# lsattr proposal1


-------------e---- proposal1
[root@localhost ~]# _

By default, all files and directories have the e attribute by default, which writes to the file
in “extent” blocks (rather than immediately in a byte-by-byte fashion). If you would like
to add or remove attributes, you can use the chattr (change attributes) command. The
following example adds the immutable attribute (i) to the same file:
- 59 -

[root@localhost ~]# chattr +i proposal1


[root@localhost ~]# lsattr proposal1
----i--------e---- proposal1
[root@localhost ~]# _

The immutable attribute (i) is the most commonly used attribute as it prevents the file
from being modified in any way (even by the root user). The definitions of the other
attributes can be found by viewing the manual page for the chattr command. To
remove this attribute, you can specify the –i option to the chattr command:

[root@localhost ~]# chattr -i proposal1


[root@localhost ~]# lsattr proposal1
-------------e---- proposal1
[root@localhost ~]# _

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
lsattr letter
chattr +i letter
lsattr letter (note the immutable attribute is set)
vi letter (make a change and attempt to save the file – when
you are denied, exit vi discarding changes)
chattr -i letter
lsattr letter
man chattr (note the description of other attributes)
- 60 -

CHAPTER

Working with the Shell


This chapter dives into the bash shell in more depth. You’ll learn about shell
redirection, piping and variables, as well as how to create shell scripts.
Additionally, this chapter introduces the use of Git for version control and
ends with an introduction to the Z shell.

4.1 Standard Input, Output & Error


4.2 Redirecting stdin, stdout & stderr
4.3 Piping
4.4 Shell Variables
4.5 Shell Scripts
4.6 Text Tools, sed & awk
4.7 Using Git
4.8 The Z Shell
- 61 -

4.1 Standard Input, Output & Error


Information that a user types on their keyboard while a program is running is called
Standard Input (stdin). The output that a program displays on the terminal screen as a
result of normal activity is called Standard Output (stdout), and any error messages that
are displayed are called Standard Error (stderr). Stdin, stdout and stderr are commonly
called file descriptors and are typically represented by a number:
• stdin = 0
• stdout = 1
• stderr = 2

Not all commands use all three file descriptors. For example, some commands do not
take information from stdin, whereas others do not send information to stdout.

4.2 Redirecting stdin, stdout & stderr


You can force the bash shell to redirect the stdout and stderr from their original location
(the terminal screen) to a file. You can also send a file to the bash shell as stdin. To do
either of these, you must use the redirection symbols (< or >) at the end of a command.
Take the who command as an example:

[root@localhost ~]# who


root tty2 2019-07-29 09:45
root pts/0 2019-07-29 09:45 (3.0.0.2)
[root@localhost ~]# _

The who command did not prompt the user for input (no stdin), gave the desired output
to the terminal screen (stdout) and did not display any error messages to the terminal
screen (stderr). If you wish to redirect the stdout of the who command to a file called
file1 in the current directory, you could use the file descriptor 1 and the > redirection
symbol as follows:

[root@localhost ~]# who 1>file1


[root@localhost ~]# _

Notice that no output from the who command was displayed on the terminal screen since
it was redirected to the file1 file. Instead, it was redirected to the file1 file:

[root@localhost ~]# cat file1


root tty2 2019-07-29 09:45
root pts/0 2019-07-29 09:45 (3.0.0.2)
[root@localhost ~]# _

NOTE: If the file you redirect stdout to exists, the shell will first clear its contents before
information is redirected into the file. If the file does not exist, the shell will create it.
- 62 -

In a similar fashion, you can redirect stderr (file descriptor 2) to a file:

[root@localhost ~]# who -z


who: invalid option -- z
Try `who --help' for more information.
[root@localhost ~]# who -z 2>file1
[root@localhost ~]# cat file1
who: invalid option -- z
Try `who --help' for more information.
[root@localhost ~]# _

You may also redirect stdout and stderr at the same time. The following example
attempts to perform a long listing on the file /etc/passwd (which exists) and
/etc/password (which does not exist and produces an error):

[root@localhost ~]# ll /etc/passwd /etc/password


ls: cannot access /etc/password: No such file or directory
-rw-r--r-- 1 root root 1705 2019-04-22 04:25 /etc/passwd
[root@localhost ~]# ll /etc/passwd /etc/password 1>file1 2>file2
[root@localhost ~]# cat file1
-rw-r--r-- 1 root root 1705 2019-04-22 04:25 /etc/passwd
[root@localhost ~]# cat file2
ls: cannot access /etc/password: No such file or directory
[root@localhost ~]# _

Notice from the previous example that the stdout and stderr were redirected to two
separate files to avoid one overwriting the other; if you wish to redirect stdout and stderr
to the same file, you must use special notation:

[root@localhost ~]# ll /etc/passwd /etc/password 1>file1 2>&1


[root@localhost ~]# cat file1
ls: cannot access /etc/password: No such file or directory
-rw-r--r-- 1 root root 1705 2019-04-22 04:25 /etc/passwd
[root@localhost ~]# _

NOTE: To discard error messages entirely, you can redirect stderr to /dev/null
(which is a special device file that represents nothing) using the syntax 2>/dev/null.

Since stdout redirection is more common than stderr redirection, it is assumed if you do
not specify a file descriptor. The following output demonstrates this:

[root@localhost ~]# date 1>file1


[root@localhost ~]# cat file1
Sun Oct 8 02:00:38 EDT 2019
[root@localhost ~]# date >file1
[root@localhost ~]# cat file1
Sun Oct 8 02:00:44 EDT 2019
[root@localhost ~]# _
- 63 -

To avoid overwriting a file, you can use two redirection symbols. For example, to append
the stdout of the date command to file1 you can use the following commands:

[root@localhost ~]# date >file1


[root@localhost ~]# date >>file1
[root@localhost ~]# cat file1
Sun Oct 8 02:02:31 EDT 2019
Sun Oct 8 02:02:35 EDT 2019
[root@localhost ~]# _

So far, we have discussed the redirection of stdout and stderr. However, you can also
redirect a file as stdin to a command by specifying file descriptor 0 followed by the <
redirection symbol (if you don’t specify 0, it assumes 0 anyways):

[root@localhost ~]# cat file1


This is a sample file.
[root@localhost ~]# cat <file1
This is a sample file.
[root@localhost ~]# _

There is no difference between the commands above since the cat command will accept
the file to display as an argument or from stdin. However there are a handful of Linux
commands that will not accept the file to use as an argument. For these commands, you
must redirect the file to the command as stdin. An example of this is the tr (transliterate)
command that changes one character to another:

[root@localhost ~]# tr a A file1


tr: extra operand *file1*
Try `tr --help' for more information.
[root@localhost ~]# tr a A <file1
This is A sAmple file.
[root@localhost ~]# _

Following is a summary of common redirection uses:


command >file
command 2>file
command >file 2>file2
command >file 2>&1
command >>file
command 2>>file
command <file

NOTE: You can also use <<LABEL syntax; however this is not file redirection, but
instead called a here document. For example, echo <<LABEL will allow you to enter
as many lines of input as you like on the command line until you enter a line that reads
LABEL. At that time, the lines of input are then sent to the echo command and displayed
on the terminal.
- 64 -

Exercise
1. Log into tty3 as the root user and run the following commands:
df -hT >diskspace (we examine the df command in a later chapter)
date >>diskspace
cat diskspace
ls letter lett >good 2>bad
cat good
cat bad
cp /etc/hosts ~
cat hosts
sort hosts >hosts
cat hosts (the file is empty because the shell clears an existing file
before writing stdout to it with the > symbol)
tr a A <letter >newletter
cat newletter
ls <letter (ls runs normally because it does not accept stdin)

4.3 Piping
In the previous section, we discussed sending stdout and stderr to a file and taking stdin
from a file. You can also send the stdout of one command to another command that
accepts the information it receives as stdin. That command can then manipulate the
information and send its stdout to yet another command, and so on. To do this, you must
use a pipe ( | ) symbol:

stdout stdin stdout stdin stdout stdin

command 1 | command2 | command3 | command4

NOTE: If a command can accept information from stdin and produce stdout, it is called a
filter command because it can exist within a pipe.

If a command that displays too much output to the terminal screen, you can send the
stdout of the command to more or less using a pipe to display it page-by-page, as
shown below using the who command:

[root@localhost ~]# who | less


root pts/0 2019-07-29 08:55
root pts/122 2019-07-29 09:45
root pts/5 2019-07-29 10:11
root pts/9 2019-07-29 10:19
root pts/44 2019-07-29 11:11
root pts/29 2019-07-29 13:00
root pts/137 2019-07-29 13:59
root pts/71 2019-07-29 14:41
root pts/62 2019-07-29 14:57
- 65 -

bob pts/33 2019-07-29 15:33


bob pts/7 2019-07-29 16:05
: q
[root@localhost ~]# _

Alternatively, you could send the output of the who command to grep in order to
display only lines that contain the user bob:

[root@localhost ~]# who | grep bob


bob pts/33 2019-07-29 15:33
bob pts/7 2019-07-29 16:05
[root@localhost ~]# _

Say, for example, you wanted to reverse sort a file (sort -r), then change all d
characters to D characters (tr d D), double-space the output for printing (pr -d) and
display it to the screen page-by-page (less). There is no command in Linux that will do
this, but you can use the following pipe to achieve the desired result for file1:

cat file1 | sort –r | tr d D | pr –d | less

If you wish to save the information in the previous example to a file called newfile1
instead of viewing it page-by-page, you could combine piping and redirection:

cat file1 | sort –r | tr d D | pr –d > newfile1

Another useful filter command that was designed specifically for use within pipes is the
tee command. The tee command is a filter command that accepts stdin and saves it to
both a file and stdout. To view the information used in the previous example as well as
retain a saved copy for later use, you can use the following pipe:

cat file1 | sort –r | tr d D | pr –d | tee file1 | less

The tee command clears file1 before writing to it. To instead append stdout to file1
in the example above, you can add the –a option to the tee command. Two other
commands that are common within pipes include uniq (omits duplicate lines) and
xargs (converts stdin to command line arguments for the next command in the pipe).

Exercise
1. Log into tty3 as the root user and run the following commands:
cd classfiles
grep -i mother letter | wc -l
cat letter | pr -d | less
cat proposal1 | tr a W | sort | tee newfile
cat newfile
cat Poems/Shakespeare/sonnet* | wc -l
grep -i love Poems/Shakespeare/sonnet* | wc -l
- 66 -

4.4 Shell Variables


Your shell stores key configuration information in special areas of memory called
environment variables. Environment variables are loaded from files when the shell is
first started and may contain information that is used by programs, or configuration
information for the shell itself. In short, environment variables define the working
environment that is used by both users and programs.

To list the environment variables in your current shell, you can use the set command:

[root@localhost ~]# set


BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fig
nore:interactive_comments:login_shell:progcomp:promptvars:sourcepath
BASH_VERSINFO=([0]="4" [1]="2" [2]="10" [3]="1" [4]="release" [5]="x64-
redhat-linux-gnu")
BASH_VERSION='4.2.10(1)-release'
HISTFILE=/root/.bash_history
HISTFILESIZE=1000
HISTSIZE=1000
HOME=/root
HOSTNAME=localhost.localdomain
HOSTTYPE=x64
IFS=$' \t\n'
LANG=en_US.UTF-8
LESSOPEN='||/usr/bin/lesspipe.sh %s'
LINES=24
LOGNAME=root
MACHTYPE=x64-redhat-linux-gnu
MAIL=/var/spool/mail/root
MAILCHECK=60
OSTYPE=linux-gnu
PATH=/usr/lib/ccache:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbi
n:/usr/bin:/root/bin
PIPESTATUS=([0]="1" [1]="1")
PPID=2858
PS1='[\u@\h \W]\$ '
PS2='> '
PS4='+ '
PWD=/root
SHELL=/bin/bash
SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-
comments:monitor
TERM=xterm
UID=0
USER=root
[root@localhost ~]# _

Each variable stores one or more values used by programs or the shell. For example,
PATH stores the list of directories with executable program (discussed in the previous
chapter), whereas HOME stores the user’s home directory location (same as ~), and PS1
- 67 -

stores the format of the shell prompt (\u = username, \h = host name, \W = current
directory). Some other common variables include:
BASH (pathname to the BASH shell executable on the filesystem)
BASH_VERSION (version of the BASH shell)
EUID (the effective UID (User ID) of the user that started the bash shell)
HISTFILE (pathname to the file that stores previously-entered commands)
HISTFILESIZE (the number of previously-entered commands stored in HISTFILE
when the user logs out of the shell)
HISTSIZE (the number of previously-entered commands stored in memory and
accessed using the cursor keys, the history command, or ! syntax – e.g.
!! recalls the previous command, and !457 recalls command 457)
HOSTNAME (the system's host name)
LOGNAME (the login name of the current user)
MAIL (pathname to the mailbox file where email is stored)
OSTYPE (the operating system used)
PWD (the current working directory)
RANDOM (a special variable that creates a random number when accessed)
SHELL (pathname of the current shell on the filesystem)
TERM (the terminal settings used by the shell, from the system terminal database)

To access the information within an environment variable, you must precede the variable
name by a $ metacharacter. Thus, to see what is in the LOGNAME variable, you could run
the following command:

[root@localhost ~]# echo $LOGNAME


root
[root@localhost ~]# _

To change an existing environment variable or create a new one, specify the name
immediately followed by = and the new value. Environment variable names can be up to
256 characters in length and may contain uppercase letters, lowercase letters, numbers
(cannot start with a number), the underscore (_) and dash (-) characters only. It is
standard form to keep environment variable names uppercase for easy identification.

[root@localhost ~]# MYVAR=cool


[root@localhost ~]# echo $MYVAR
cool
[root@localhost ~]# _

Environment variables created in this way are available only to the current shell; if the
shell starts other programs (such as the vi editor), those programs typically run in
another shell (discussed in Chapter 5) and are unable to access the environment variable
in the current shell. Exporting an environment variable makes it available to all other
shells that are started by your shell and the programs that they run. A list of exported
environment variables can be obtained by running the env command:
- 68 -

[root@localhost ~]# env | grep MYVAR


[root@localhost ~]# export MYVAR
[root@localhost ~]# env | grep MYVAR
MYVAR=cool
[root@localhost ~]# _

You can also create an environment variable and export it in a single command:

[root@localhost ~]# export MYVAR2=neat


[root@localhost ~]# env | grep MYVAR2
MYVAR2=neat
[root@localhost ~]# _

To remove MYVAR2 from memory, you can use unset MYVAR2 command.

The umask variable discussed in the previous chapter is also a variable. However, it must
be set with a special command (umask). As a result, it is called a special variable.
Aliases (also discussed in the previous chapter) are another special variable; they can
contain one or more commands that will be run when the alias is executed. For example,
to create and use an alias called ddf that runs the date command followed by the df
command, you can run the following commands:

[root@localhost ~]# alias ddf='date;df'


[root@localhost ~]# ddf
Mon Nov 28 09:34:37 EST 2019
Filesystem 1K-blocks Used Available Use% Mounted on
rootfs 50395844 7698724 40137120 17% /
devtmpfs 763256 0 763256 0% /dev
tmpfs 771516 160 771356 1% /dev/shm
tmpfs 771516 40116 731400 6% /run
/dev/sda2 50395844 7698724 40137120 17% /
[root@localhost ~]# _

To remove the ddf alias from memory, you can use unalias ddf command.

Environment variables and special variables (umask and aliases) are stored in memory
and destroyed when the bash shell is exited. To ensure that they are available when a new
shell is started, you must add them to an environment file. When a new bash shell is
started, it first executes any commands and variables listed in /etc/profile and in
files under the /etc/profile.d directory. It then proceeds to execute the contents of
the following files (in order, if they exist) in your home directory: ~/.bash_profile,
~/.bash_login, ~/.profile, and ~/.bashrc. The ~/.bashrc file is also
executed each time you execute additional bash shells within your bash shell. This makes
it ideal for specifying alias special variables (which are not exported otherwise).

NOTE: After modifying an environment file, you can log out and into your shell to
execute it, or run the source command. For example, source ~/.bash_profile
will execute the contents of ~/.bash_profile in the current bash shell.
- 69 -

NOTE: While not an environment file, any commands you list in your
~/.bash_logout file will be executed when you exit your shell.

NOTE: You may start additional bash shells within your current bash shell by running
the bash command. The bash –r (or --restricted) command can be used to
start a restricted bash shell that does not allow changing directories, creating variables,
running programs using absolute pathnames, or using redirection and pipe symbols.

Exercise
1. Log into tty3 as the root user and run the following commands:
VAR1="Hello World"
echo $VAR1
set | grep VAR1
env | grep VAR1 (VAR1 is not shown, as it was not exported)
export VAR1
env | grep VAR1
PS1='C:${PWD////\\\\}> ' (note that your prompt is now Windows-style)
cat ~/.bash_profile (on Fedora, it runs .bashrc first if it exists)
cat ~/.bashrc (note the aliases for rm, cp and mv)
exit

2. Log into tty3 again as the root user and run the echo $VAR1 command to verify that it
is no longer available. Next, edit the ~/.bashrc file (e.g. vi ~/.bashrc), add the following
lines, and save your changes when finished. Next, run exit to log out of your shell.
export VAR1="Hello World"
alias bye="echo Goodbye ; sleep 5 ; exit"

3. Log into tty3 again as the root user and see if the echo $VAR1 and bye commands
work as expected.

4.5 Shell Scripts


Shell scripts are text files that contain a list of commands and functions that are run when
the shell script is executed. The following is an example of a basic shell script (# symbols
denote comments; any text to the right of a # symbol is ignored):

[root@localhost ~]# cat myscript.sh


#this is a comment line
echo Today is:
date
echo The users on the system are: #this is a line comment
who
[root@localhost ~]# _
- 70 -

If you have read and execute permission to this shell script, you can execute it like any
other command (use ./myscript.sh if the current directory is not in the PATH
variable). Alternatively, if you have read permission only, you can open a new bash shell
to execute it (bash myscript.sh):

[root@localhost ~]# ./myscript.sh


Today is:
Sun Jul 29 22:02:26 EDT 2018
The users on the system are:
root tty2 2018-07-29 08:55
[root@localhost ~]# _

To ensure that your shell script always spawns a bash shell to run the commands within,
you can add a shebang (also called a hashpling) at the beginning of the shell script.
Shebangs must be the first line of a shell script and start with #!, as shown below:

[root@localhost ~]# cat myscript


#!/bin/bash
#this is a comment line
echo Today is:
date
echo The users on the system are: #this is a line comment
who
[root@localhost ~]# _

The echo command is very useful in shell scripts since it can easily produce stdout.
You can also specify escape sequences if you add the –e option. Escape sequences allow
you to insert or omit formatting characters. Some common escape sequences include:
\c print without newline (must be at end of line)
\n newline
\t tab
\n where n represents an ASCII code (e.g. \007)

Thus, to prevent the new line characters from interfering with the output shown earlier,
we could use the following shell script:

[root@localhost ~]# cat myscript


#!/bin/bash
#this is a comment line
echo –e "Today is: \c"
date
echo –e "The users on the system are: \c" #this is a line comment
who
[root@localhost ~]# ./myscript
Today is: Sun Jul 29 22:02:26 EDT 2018
The users on the system are: root tty2 2018-07-29 08:55
[root@localhost ~]# _
- 71 -

Reading Data and Using Special Variables


Most shell scripts need input in order for them to run in a variety of different situations.
This input may be from stdin or via command arguments. You can obtain stdin from
users and place this stdin into a variable using the read command:

[root@localhost ~]# cat myscript


#!/bin/bash
echo -e "What is your name? --> \c"
read NAME
echo "Hi $NAME"
[root@localhost ~]# ./myscript
What is your name? --> Jason
Hi Jason
[root@localhost ~]# _

Instead of obtaining stdin from a user during execution, you may require that the user
who executes the shell script provide arguments on the command line at execution.
These arguments are called positional parameters and may be referenced using the
special variables $1, $2, $3,...${10}, ${11}, ${12}, and so on. Following is an
example of using positional parameters:

[root@localhost ~]# cat myscript


#!/bin/bash
echo "This shell script prints your last name followed by your first."
echo "Last Name: $2 First Name: $1"
[root@localhost ~]# ./myscript
This shell script prints your last name followed by your first.
Last Name: First Name:
[root@localhost ~]# _
[root@localhost ~]# ./myscript Jason Eckert
This shell script prints your last name followed by your first.
Last Name: Eckert First Name: Jason
[root@localhost ~]# _

Following is a summary of how positional parameters are generated:

./myscript one two three four ...

$1 $2 $3 $4 . . . ${10} ${11}

NOTE: You can also define default positional parameters using the set command.

In addition to positional parameters, there are several special shell variables that are
useful in shell scripts:
$0 command name
$# number of positional parameters
$* all positional parameters
- 72 -

$@ all positional parameters (same as $* in bash)


$? exit status of the last foreground process
$$ PID of current shell
$! PID of last background process
$- flags set in the current shell

Thus, we could modify our previous script to ensure that if users do not supply 2
positional parameters, they get an error message, and the default name will be John Doe:

[root@localhost ~]# cat myscript


#!/bin/bash
if [ $# -ne 2 ]
then
echo "You must enter at least two arguments. Name set to John Doe."
set John Doe
fi
echo "This shell script prints your last name followed by your first."
echo "Last Name: $2 First Name: $1"
[root@localhost ~]# ./myscript
You must enter at least two arguments. Name set to John Doe.
This shell script prints your last name followed by your first.
Last Name: Doe First Name: John
[root@localhost ~]# _
[root@localhost ~]# ./myscript Jason Eckert
This shell script prints your last name followed by your first.
Last Name: Eckert First Name: Jason
[root@localhost ~]# _

Exit Status and Decision Constructs


Notice from the previous example, that the shell script only gave the error message and
set the name to John Doe if a certain condition was met. This is called a decision
construct. For a decision construct to work, there must be a condition that returns true or
false. True and false are represented by an exit status number; an exit status of 0 is true,
whereas an exit status of 1-255 is false. When a command finishes successfully, it returns
a 0 exit status. If a command fails, it will return an exit status between 1 and 255 (this
allows the command to return different numbers for different types of errors).

You can view the exit status of the previous command using the special variable $?. By
default, all shell scripts exit with a true exit status, but this can be changed using the
exit command within the shell script:

[root@localhost ~]# cat myscript


#!/bin/bash
if [ $# -ne 2 ]
then
echo You must enter at least two arguments.
exit 255
fi
echo "This shell script prints your last name followed by your first."
- 73 -

echo "Last Name: $2 First Name: $1"


[root@localhost ~]# ./myscript
You must enter at least two arguments.
[root@localhost ~]# echo $?
255
[root@localhost ~]# _
[root@localhost ~]# ./myscript Jason Eckert
This shell script prints your last name followed by your first.
Last Name: Eckert First Name: Jason
[root@localhost ~]# echo $?
0
[root@localhost ~]# _

NOTE: The /bin/true (or : character) and /bin/false commands in Linux may
be used to generate a true and false exit status, respectively.

In order to create a condition that returns a true or false exit status as in our example
earlier, you can use the test command (which can alternatively use [ ] brackets).

Using two arguments, you use the following syntax:


test arg1 option arg2 or [ arg1 option arg2 ]
The option returns true if:
-eq arg1 is numerically equal to arg2
-ne arg1 is numerically not equal to arg2
-lt arg1 is numerically less than arg2
-le arg1 is numerically less than or equal to arg2
-gt arg1 is numerically greater than arg2
-ge arg1 is numerically greater than or equal to arg2
= arg1 and arg2 have identical string contents
!= arg1 and arg2 do not have identical string contents

Using one argument, you use the following syntax:


test option arg or [ option arg ]

The option returns true if:


-z length of arg is zero
-n length of arg is non-zero (same as no option)
-r arg is a file/directory that exists and has read permission
-w arg is a file/directory that exists and has write permission
-x arg is a file/directory that exists and has execute permission
-f arg is a file that exists
-d arg is a directory that exists
-h or -L arg is a symbolic link
-s arg is a file that exists and has a size > 0 bytes
- 74 -

You can also combine test expressions. For example, to see whether a certain file is
readable and writable, you could use the –a option between tests:
test –r myfile –a –w myfile or [ -r myfile –a –w myfile ]

To see if $# has the value 4 or 5, you could use the –o option between tests:
test $# –eq 4 –o $# –eq 5 or [ $# –eq 4 –o $# –eq 5 ]

To reverse the meaning of a condition, you can also use the ! symbol:
test !$VAR –eq 4 or [ ! $VAR –eq 4 ]

Now that you can build test statements, you can use these test statements in a decision
construct to modify the flow of your shell script. There are two main decision constructs,
the if construct, and the case construct. In addition to these, there are two useful
decision operators && and ||.

Following is the syntax of the if construct:


if this command list returns true
then
do this
elif this command list returns true
then
do this
else
do this
fi

Both elif and else statements are optional, and you may have as many elif
statements as you wish. Following is an example use of the if construct:

echo –e "What would you like to do? (Quit/Continue) -->\c"


read CHOICE
if [ $CHOICE = "quit" –o $CHOICE = "Quit" ]
then
rm /tmp/* #remove temporary files
exit 0
fi

You can also set up redirection for the entire if statement. Output redirection comes
after the if and piping can occur just before the if. For example, to redirect stdout and
stderr for an entire if statement, you could use the following syntax:
- 75 -

if [ $ANSWER –eq 42 ]
then
echo –e "We found the answer!"
exit 0
fi >file1 2>file2

You can also nest if statements within each other:

echo "What text do you want to search for? --> \c"


read TEXT
if [ -n $TEXT ]
then
echo "What file do you want to search? --> \c"
read FILE
if [ -r $FILE ]
then
echo "Should it be case insensitive? (y/n) \c"
read ANS
if [ $ANS = "y" -o $ANS = "Y" ]
then
grep –i $TEXT $FILE
else
grep $TEXT $FILE
fi
fi
fi

Conditional AND (&&) and conditional OR (||) operators may be used instead of an if
construct. For example, to execute enter the /public directory only if it was created
successfully, you could use the mkdir /public && cd /public command.
Alternatively, you could echo a warning message only if the /public directory could
not be created by running the mkdir /public || echo "The directory
could not be created" command.

The && operator executes the left statement, but only executes the right statement if the
left statement returned a true exit status (i.e. completed successfully). The || operator
executes the left statement, but only executes the right statement if the left statement
returned a false exit status (i.e. completed unsuccessfully). You can combine test
statements with these constructs as well. For example, to create the directory /public
only if the value of the $VAR is 5, you could use one of the following statements:
[ $VAR –eq 5 ] && mkdir /public or
test $VAR –eq 5 && mkdir /public

The case decision construct simplifies using several if statements. The syntax of this
construct, as well as an example illustrating its use are shown next:
- 76 -

case word in
pattern1 ) command list
;;
pattern2 ) command list
;;
pattern3 ) command list
;;
esac

echo "Program Menu


===================
What would you like to do?
V)iew the company policies and procedures
P)rovide feedback to the Human Resources department
L)earn the vi editor
H)ear a duck quack
Enter your choice = = > \c"
read ANS
case $ANS in
v | V ) less /public/company_policies.txt
;;
p | P ) mail hr_admin
;;
l | L ) vimtutor
;;
h | H ) echo "Go to the park."
;;
* ) echo "You must select V, P, L or H!"
exit 1
;;
esac

Looping Constructs
You may need a portion of a shell script to execute continuously until some condition is
met. To do this, you can use a looping construct alongside a calculation that eventually
makes a condition false. This calculation usually takes a variable (called a counter
variable) and mathematically increments or decrements it until the condition is met. You
can use the expr command to perform mathematical calculations in your bash shell:

[root@localhost ~]# expr 4 + 5


9
[root@localhost ~]# expr 7 \* 7
49
[root@localhost ~]# _
- 77 -

There are three main looping constructs in Linux: the while construct, the until
construct, and the for construct. Following is the syntax of while and until:
while this command list returns true
do
this stuff
done

until this command list returns true


do
this stuff
done

You can also setup I/O redirection for the entire while/until statement. Output
redirection comes after the done and piping can occur just before the while/until.
Below is an example of using the while construct:

COUNTER=0
While [ $COUNTER –lt 7 ]
do
echo “All work and no play makes Jack a dull boy” >>redrum
COUNTER=`expr $COUNTER + 1`
done
cat redrum

NOTE: As an alternative to the backquote (`) for command substitution shown in the
previous example, you may also see $(command) syntax, which is easier to read in a
shell script. For example, $(expr $COUNTER + 1). Make sure you use regular
braces, since curly braces have a different meaning. For example, the ${!pattern}
syntax returns variable names that match a pattern, and the ${VAR:=value} syntax
creates a VAR variable and sets its value at the same time.

The most common loop construct used in shell scripts is for, as it can process a list of
items easily. Following is the syntax of the for loop construct:
for name in string1 string2 string3 … …
do
this stuff
done

Below are some examples of using the for construct:

for NAME in root bozo bob sue mary jane


do
echo "Hi $NAME"
done
- 78 -

for FILE in *
do
mv $FILE $FILE.jpg
done

You can also use the break and continue keywords within any looping construct to
stop executing the loop or to skip all remaining lines in the loop and continue
immediately to the next loop.

for ARG in $@
do
echo "Which extension should $ARG have?"
echo "(n = next file, q = quit)"
read ANS

if [ $ANS = "n" –o $ANS = "N" ]


then
continue
elif [ $ANS = "q" –o $ANS = "Q" ]
then
break
else
mv $ARG $ARG.$ANS
fi
done

Shell Functions
Many shell scripts have a section of code that is used in various places throughout the
shell script. To save time typing this code each time it is needed, you can create a shell
function for it and execute the shell function each time you need to execute the code.
Like aliases, shell functions are special variables that contain code that is executed when
the shell function is executed. The bash shell has several shell functions built into it that
we have used throughout the course including cd, exit and help.

To create a function called hello that welcomes the current user and displays users on
the system, you could add the following lines near the beginning of a shell script:
hello () {
echo "Hello $LOGNAME, the users on the system are: ";
who;
}

You can instead use a single line when creating a function or prefix the function name
with the function command. For example, the function hello () { echo
"Hello $LOGNAME, the users on the system are: "; who; }
command will also create the hello function described earlier.
- 79 -

To execute a function, you can run it like any other command in your shell script (e.g.
hello). Since commands can take arguments (positional parameters), so can functions.
The following example sets a function and then executes it using the first argument:

[root@localhost ~]# cat myscript


#!/bin/bash
hello () { echo "Hello $1, the users on the system are: "; who; }
hello bob
[root@localhost ~]# ./myscript
Hello bob, the users on the system are:
root tty2 2018-07-29 08:55
[root@localhost ~]# _

As with variables, to view the functions in your shell, you can use the set or declare
-f command. To make a function available to other shells that you start, you can export
it using the export –f functionname command and later view it using the env
command.

Several functions can be added to a file called a function library that can be imported
into other shell scripts (under the shebang line) using either the source command or the
dot ( . ) function, as shown below:

[root@localhost ~]# cat functionlibrary


hello () { echo "Hello $1, the users on the system are: "; who; }
[root@localhost ~]# cat myscript
#!/bin/bash
. /root/functionlibrary

echo This script runs the hello function several times


hello bob
hello mary
hello sue
[root@localhost ~]# _

Exercise
1. Log into tty3 as the root user and run the following command to obtain 5 sample shell
scripts for system automation: git clone https://github.com/jasoneckert/shellscripts.git

2. Run the cd shellscripts command to switch to the shellscripts directory. Next, view the
contents of script1.sh, script2.sh, script3.sh and script5.sh, tracing the syntax and usage
(script4.sh is used in the next exercise). These scripts leverage commands that we’ll
discuss in Chapter 5 to view storage configuration and perform backups (using tar).

3. Using your current knowledge of Linux commands and shell script constructs, create a
new shell script that performs some tasks of your choice. Execute your shell script when
finished to test its functionality.
- 80 -

4.6 Text Tools, sed & awk


Key to creating successful shell scripts is an understanding of common text utilities that
you can use within a shell script, including the powerful sed and awk tools.

Common Text Tools


cut –d: -f3 /etc/passwd Cuts the 3rd field from the colon-delimited
passwd file and displays the results.
cut –d: -f3 /etc/passwd > passfile
cut –d: -f6 /etc/passwd > passfile2
paste passfile passfile2 Pastes columns of info together.
pr –d letter > letter2 Double spaces letter, and formats it for
printing with a header, saving stdout.
pr –d –l20 letter > letter2 As above but with 20 lines per page.
fmt –30 letter Formats each line to be 30 characters long.
nl letter Numbers the lines.
printf "Hello %s! \nYour shell is: %s \n" $LOGNAME $SHELL
Displays the following (each %s refers to
each of the two string arguments supplied):
Hello root
Your shell is: /bin/bash
cat users departments Displays the following contents:
1 bob
2 sue

1 sales
2 support
join users departments Displays results joined with common field:
1 bob sales
2 sue support
cat file1 Displays the following contents:
This is neat.

expand –t1 file1 Converts each tab to one space:


This is neat.
diff file1 file2 Displays the lines that are different only.
split –l4 /etc/passwd Splits the file every 4 lines, creating the files
xaa, xab, xac…(each are 4 lines long).
- 81 -

Using sed
The Stream Editor (sed) is a powerful and versatile text manipulation tool. It takes two
arguments at minimum: the sed edit command (or alternatively a script file that contains
those commands) and the filename to perform those sed edit commands on:
sed edit command [or –f script] filename

NOTE: sed returns the lines modified as well as all other lines (use the –n option to
suppress the other lines)

The sed edit commands usually search for text (target) and replace it with other text
(replacement) using the sed s/target/replacement/ syntax. Normally, sed
stops when it finds the first occurrence of the target in each line processed, unless you use
the sed s/target/replacement/g syntax. Like many text tools, you may use
regular expressions when specifying the target.

The Stream Editor can also narrow down a search and replace operation by focusing on
certain lines only; this is called sed addressing. The following examples using the
letter file in the classfiles directory:

sed /Mother/s/the/BILLY/ letter (on lines that contain the word


Mother, searches for the and
replaces it with BILLY )

sed 1s/Mother/Grandma/ letter (on the first line only, search for
Mother and replace with Grandma)

sed ‘$s/Alan/Mike/’ letter (on the last line only, search for Alan
and replace with Mike)

sed 1,8s/the/WOWEE/ letter (on lines 1 to 8 only, search for the


and replace with WOWEE)

Recall that you may instead place several sed edit commands in a script that can be
specified using the –f option to the sed command. For example, to replace oh and
home with OH and HOME on lines that contain the word Mother, you could make the
following script file and then run the sed –f myscript letter command.

myscript:
/Mother/{
s/oh/OH/g
s/home/HOME/g
}
- 82 -

Sed edit commands use the / character to delimit different sections by default, but you
can specify any delimiter following the search (s) option other than a Space or Tab. For
example, sed s:/bin/bash:/HOLY/MOLY:g /etc/passwd will search for
/bin/bash and replace it with /HOLY/MOLY globally in the passwd file.

The character at the end of a sed edit command is called the flag. We have already seen
the g (global) flag. The following examples demonstrate other sed edit command flags:

sed s/the/TEST/2 letter (replace the 2nd occurrence of the


with TEST only in each line)

sed 's/the/THE/w newfile' letter (prints the edited text to a file called
newfile)

sed /the/d letter (deletes lines with the in them)

sed '/Mother/r /etc/hosts' letter (output the contents of the file


/etc/hosts immediately after
each line that has the word Mother)

NOTE: The sed command doesn’t modify the contents of the file specified unless you
use the -i option (it just sends the modified results to stdout).

Using awk
The awk command (named for its creators: Aho, Weinberger & Kernighan) is probably
the most advanced text tool that ships with Linux; as a result it is often referred to as the
awk programming language. It uses a series of pattern-action statements to manipulate or
format text information, as shown below. Like sed, it can take these statements from a
script file by using the -f option:

awk 'pattern {action}


pattern {action}
pattern {action}' filename

awk -f scriptfile filename

NOTE: You may omit pattern (which processes the entire file) or action (the default
action is to print the results).

On its most basic level, you can use awk to print data from a file to the screen. Each file
is treated like a database file that is tab or space delimited and each field is given a
variable name $1, $2, $3, and so on. Following are some examples:
- 83 -

awk '/Mother/ {print}' letter (prints lines that contain the


word Mother)

awk '/Mother/ {print $1, $4}' letter (prints 1st and 4th field
of lines that contain Mother)

awk '/Mother/ {print $1 $4}' letter (same as above but no space


is used to separate the field
output)

awk '/Mother/ {print $1 "\t" $4}' letter (same as before but a Tab
is used to separate the field
output)

awk –F: '/root/ {print $3, $5}' /etc/passwd


(specifies a : field delimiter
when printing the 3rd and 5th
fields)

The awk command can also include summary sections (similar to a header and footer)
that contain custom variables that tally the total the number of records printed, and so on:

awk ‘BEGIN {action}


pattern {action}
pattern {action}
END {action} ’ inputfile

awk ‘BEGIN {count = 1}


pattern {count = count + 1}
pattern {count = count + 1}
END {print count} ’ inputfile

As well, awk has several hundred built in variables that you can use to print specific
information. For example, awk ‘/Mother/ {print FNR}’ letter prints the
record number for all lines that contain the word Mother). Some other variables that are
commonly used with awk include ARGC (number of command line arguments),
FILENAME (name of current input file), NF (number of fields in current record), and
RLENGTH (length of string matched by match function).

Another useful feature of awk is its ability to use several different comparison and
arithmetic operators. These are shown below and demonstrated next:
== < > ~
!= <= >= !~
+ - * / % ^
- 84 -

awk '$1 ~ /Mother/ {print}' letter (if the first field is Mother, then the
line will be printed)

awk '$1 >= "M" {print}' letter (if the first field starts with the letter
M or later in the ASCII order, then it
will be printed)

awk 'BEGIN {value = 8; divideby = 2}


END {print value / divideby}' letter (prints the number 4)

As with sed, awk can use regular expressions:

awk '$2 ~ /^M/ {print}' letter (print only if 2nd field starts with M)

awk '$2 ~ /^[MF]/ {print}' letter (print only if 2nd field starts with
M or F)

awk '$2 ~ /^[^MF]/ {print}' letter (print only if 2nd field does not start
with M or F)

One of the most useful features of awk is its ability to format data using virtually any
criteria. Say, for example, you have the following file:

sales:
lala Q1 44509 938
lala Q2 387 987
poe Q1 398746 87
poe Q2 98708 789
noonoo Q1 10987 9876
noonoo Q2 4326 3254
tinky Q1 20981 2987
tinky Q2 47621 3738

Running the following command:

awk '$2 ~ /Q1/ {printf "%10s %9d.00 %9d.00\n", $1, $3, $4}' sales

will produce the following output:

lala 4509.00 938.00


poe 398746.00 87.00
noonoo 10987.00 9876.00
tinky 20981.00 2987.00
---------- --------- ---------
10 9 .00 9 .00
- 85 -

Exercise
1. Log into tty3 as the root user and run the cd classfiles command to switch to the
classfiles directory. Next, execute many of the common text tool examples shown in this
section, as well as many of the sed and awk examples.

2. Run the cd ../shellscripts command to switch to the shellscripts directory. Next, view
the contents of script4.sh, noting how sed was used to replace "/" characters with "-"
characters in the backup file that was generated. Following this, modify the script that
you created in the final step of the previous exercise and add some of the utilities
introduced in this section where appropriate.

3. As a challenge, execute and then observe the contents of the /root/classfiles/bin/treed


shell script (which gives a tree directory listing – e.g. ./treed /etc). Use the sed manual
page to identify the search and replace functions performed.

4. For an extra challenge, create a shell script that works as a binary clock using some of
the text tools in this section to reduce the number of lines necessary. You can research the
usage of a binary clock at www.anelace.com. Essentially, a binary clock uses 1 for the
first row, 2 for the second row, 4 for the third row and 8 for the fourth row. Thus, the
time 8:56pm (53 seconds) could be represented as:
0 0 1
0 1 1 0 0
0 1 0 0 1 0
0 1 1 0 0 1

An example binary clock shell script that does not use text tools to reduce the number of
lines necessary is available at http://triosdevelopers.com/jason.eckert/trios/binclock.sh.
- 86 -

4.7 Using Git


Whether you are creating shell scripts to automate system tasks or writing code for a Web
app, it can be challenging to coordinate and keep track of all of the different source code
files. You can use Git to centrally store a master copy of your shell scripts and other
source code, as well as organize the changes that you and your fellow IT administrators
make to these files across multiple servers.

Git is a version control system that can be used to keep track of the changes that you
make to files over time. It allows you to revert files to a previous state or see who made
changes to a file at different times if several people are working with those files
collaboratively. It was originally developed in 2005 by Linus Torvalds (the creator of
Linux) to aid in making changes to the Linux kernel but has since become the most
common open source version control system used in the world. You can run Git on most
platforms, including Windows, Linux and macOS. Although people primarily use it today
for version controlling software projects, you can use it to provide version control for any
files on your system. In this section, we’ll use it to provide version control and central
access for Linux shell scripts.

Using Git for Local Version Control


Git essentially takes snapshots (called commits) of the files that you have within a
particular folder (called a repository, or repo) on your system. Each commit contains the
changes you’ve made to the files since the last commit, so that you can easily rollback
those changes (much like a Windows System Restore point). Before you use Git to create
commits, you must first tell Git about yourself using the git config command, since
that information must be added to each commit that you create:
[jason@localhost ~]$ git config --global user.name "Jason Eckert"
[jason@localhost ~]$ git config --global user.email "jason@trios.com"
[jason@localhost ~]$ _

To turn an existing folder into a Git repo, you can use the git init command. For
example, if you are in the myscripts directory under your home directory on a Linux
system, you could run the following commands to turn that directory into a Git repo (this
will also create a hidden .git folder underneath the myscripts directory):

[jason@localhost ~]$ pwd


/home/jason
[jason@localhost ~]$ cd myscripts/
[jason@localhost myscripts]$ pwd
/home/jason/myscripts
[jason@localhost myscripts]$ ls
chownscript.sh filemaintain.sh newuserscript.sh
seccheck.sh sysusage.sh
[jason@localhost myscripts]$ git init
Initialized empty Git repository in /home/jason/myscripts/.git/
- 87 -

[jason@localhost myscripts]$ git status


On branch master

Initial commit

Untracked files:
(use "git add <file>..." to include in what will be committed)

chownscript.sh
filemaintain.sh
newuserscript.sh
seccheck.sh
sysusage.sh

nothing added but untracked files present (use "git add" to track)
[jason@localhost myscripts]$ _

Notice that the git status command above listed the files in the myscripts directory
but said that they were “untracked.” This is normal because Git doesn’t assume you want
everything to be version controlled. After creating a Git repo, you have to tell Git which
files you want to version control by staging them with the git add command. Staging
adds the files to an index that sets the files that Git can take a snapshot (commit) of.

After the files have been staged, you can take snapshots of them using the git commit
command. The following commands stage all the files in the current ( . ) folder, shows
that they are ready for committing, and then creates a new commit with the description
“My first commit”:

[jason@localhost myscripts]$ git add .


[jason@localhost myscripts]$ git status
On branch master

Initial commit

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: chownscript.sh


new file: filemaintain.sh
new file: newuserscript.sh
new file: seccheck.sh
new file: sysusage.sh

[jason@localhost myscripts]$ git commit -m "My first commit"


[master (root-commit) 53f9566] My first commit
5 files changed, 60 insertions(+)
create mode 100755 chownscript.sh
create mode 100644 filemaintain.sh
create mode 100755 newuserscript.sh
create mode 100644 seccheck.sh
create mode 100644 sysusage.sh
[jason@localhost myscripts]$ _
- 88 -

NOTE: You can exclude files from git add by adding their names (or wildcard patterns)
to the .gitignore file in the root of your Git repo.

Next, let’s modify the filemaintain.sh shell script using the vi editor, see that Git
detected the modification, stage the files in our repo again, and create a new commit
using an appropriate description of the changes that we made (in this example, I added
XFS checking to the script):

[jason@localhost myscripts]$ vi filemaintain.sh


[jason@localhost myscripts]$ git status
On branch master

Changes not staged for commit:


(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working
directory)

modified: filemaintain.sh

no changes added to commit (use "git add" and/or "git commit -a")
[jason@localhost myscripts]$ git add .
[jason@localhost myscripts]$ git commit -m "Added XFS checking to
filemaintain.sh"
[master 08e7f90] Added XFS checking to filemaintain.sh
1 file changed, 1 insertion(+)
[jason@localhost myscripts]$ _

To see a list of all commits (and who made them), you can use the git log command,
and to roll back to a previous version of the file, you can use the git reset --hard
command. Say, for example, that I didn’t like the XFS checking addition I made to
filemaintain.sh and wanted to roll it back to the previous version. To see all the
changes I made to the files in my repo, and rollback the change to “My first commit”, I
could use the following commands (HEAD is simply a reference to the most recent
commit):

[jason@localhost myscripts]$ git log


commit 08e7f90fd4c1820eab77968ee98c8a7682c43aa8
Author: Jason Eckert <jason@trios.com>
Date: Mon Aug 13 14:54:20 2018 -0400

Added XFS checking to filemaintain.sh

commit 53f95663c4a9f5b53f5ee8b86b91024fd9e1fc9a
Author: Jason Eckert <jason@trios.com>
Date: Mon Aug 13 14:53:05 2018 -0400

My first commit
[jason@localhost myscripts]$ git reset --hard 53f9566
HEAD is now at 53f9566 My first commit
[jason@localhost myscripts]$ git log
commit 53f95663c4a9f5b53f5ee8b86b91024fd9e1fc9a
Author: Jason Eckert <jason@trios.com>
- 89 -

Date: Mon Aug 13 14:53:05 2018 -0400

My first commit
[jason@localhost myscripts]$ _

Now, if I view the filemaintain.sh script, the XFS edits will no longer be there.

Also note that you had to specify the commit ID when using the git reset --hard
command. Since it is very long (53f95663c4a9f5b53f5ee8b86b91024fd9e1fc9a), you
only need to specify the first seven characters in the command itself (53f9566). Instead of
working with commit IDs, you can tag each commit you create with a friendlier name
and optional description. For example, after creating a commit, you can run git tag
v1.0 to give the commit a tag of v1.0 (if you want to add a description to your tag, you
can instead use git tag -a v1.0 -m "Example description"). Following
this, you could use git reset –hard v1.0 to revert the state of your repo to that
commit instead of specifyin the commit ID.

Tags are also useful for viewing the contents of a commit using git show. For
example, git show v1.0 could be used instead of git show commitID. You
can use the git tag command to view the available tags in your repo.

Following is a summary of Git commands that you should find useful at this point:
git config Sets general Git parameters like username and email
git init Creates a Git repo within the current directory (creates .git folder)
git add filenames Adds the filenames to the Git index (called staging)
git rm filenames Removes the specified filenames from the Git index
git commit -m description Creates a snapshot with a specified description
git tag label Adds a friendly label to your previous commit
git tag -a label -m description As above, but with a description
git status Views the status of a repo
git log Views the commit history of a repo
git show commitID_or_tag Views the commit history of a repo
git reset --hard commitID_or_tag Reverts files to a previous commit

Using Git Collaboratively


While we just saw how Git can perform version control for your local files, other users
can download (or clone) copies of your Git repos on the same computer, or across a
network or the Internet. Those users can then create commits periodically after making
changes to the files in their cloned repo, and then push those changes back to your
original repo. Any computer running Git can clone a Git repo from any other computer
running Git, regardless of the operating system used, and there are many websites that
you can freely use to host Git repos online, including GitHub and GitLab.
- 90 -

Look back at the output of the git status command earlier and you will notice that it
mentions that you are “On branch master.” A branch is simply a section of your Git repo,
much like the different partitions on a hard disk. Any changes you make to an original or
cloned Git repo are part of the master branch by default (future versions of Git will call
this the main branch). You can create as many other branches as you like to store
changes that you may want to experiment with. Once you are satisfied that the changes
work as you expected, you can merge the changes you made in your branch with the files
in the master branch.

Normally, you maintain an original repo on your computer that other users download
(clone). Rather than modifying the master branch on their cloned copy, the other users
would normally create separate branches on their cloned copy to test their modifications
and perform commits as necessary. Once the modifications are working well, they can
upload (push) the branch to the original repo on your computer where you can view the
changes and merge them into the master branch. After there’s an updated master branch
on the original repo, others can then download (pull) a fresh copy of the master branch to
get the new changes in their local copy of the repo.

Let’s experiment with this using another user (root) on the same computer. The only
requirement is that the other user have read/write access to your repo folder.

The following commands run by the root user (the other user) create a cloned copy of the
myscripts repo (/home/jason/myscripts) within the root user’s home directory
(/root) using the git clone command:

[root@localhost myscripts]# pwd


/root
[root@localhost ~]# git clone /home/jason/myscripts/
Cloning into 'myscripts'...
done.
[root@localhost ~]# cd myscripts/
[root@localhost myscripts]# pwd
/root/myscripts
[root@localhost myscripts]# ls
chownscript.sh filemaintain.sh newuserscript.sh seccheck.sh
sysusage.sh
[root@localhost myscripts]# _

If you were cloning this from another computer, you’d have to use git clone
username@hostname:/path instead. For example, to clone this repo on
triosdevelopers.com to your local computer, you’d have to use the git clone
root@triosdevelopers.com:/home/jason/myscripts/ command and
supply the root user’s password when prompted.

Now, let’s make a branch called AddSIEM that we can use to test out adding SIEM
functionality to our seccheck.sh script and view our branch when finished:
- 91 -

[root@localhost myscripts]# git checkout -b AddSIEM


Switched to a new branch 'AddSIEM'
[root@localhost myscripts]# git branch
* AddSIEM
master
[root@localhost myscripts]# _

Notice that the git branch command indicates that AddSIEM is our current branch
(*). Now, let’s modify the script, view the modification, stage and commit it:

[root@localhost myscripts]# vi seccheck.sh


[root@localhost myscripts]# git status
On branch AddSIEM
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working
directory)

modified: seccheck.sh

no changes added to commit (use "git add" and/or "git commit -a")
[root@localhost myscripts]# git add *
[root@localhost myscripts]# git commit -m "Added SIEM to seccheck.sh"
[AddSIEM e94d34e] Added SIEM to seccheck.sh
1 file changed, 1 insertion(+), 1 deletion(-)
[root@localhost myscripts]# _

At this point, you’ve modified the seccheck.sh script in the AddSIEM branch only. If
you switched back to the master branch in your cloned repo using the git checkout
master command and viewed the seccheck.sh file, you’d see that your changes are
not there. That’s because they are only shown in the AddSIEM branch.

Now, let’s push our branch to the original repo. Luckily, you don’t have to remember the
location of the original repo, because after you clone a repo, Git remembers the original
location and allows you to use the word “origin” to refer to it:

[root@localhost myscripts]# git push origin AddSIEM


Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 293 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
To /home/jason/myscripts/
* [new branch] AddSIEM -> AddSIEM
[root@localhost myscripts]# _

This uploaded the AddSIEM branch from the cloned repo (in /root/myscripts) to
the original repo (in /home/jason/myscripts). Let’s switch back to the jason user
and see if the branch was successfully uploaded to the original repo with the git
branch command, and then merge the changes in the AddSIEM branch with our current
branch (master) using the git merge command:
- 92 -

[jason@localhost myscripts]$ git branch


AddSIEM
* master
[jason@localhost myscripts]$ git merge AddSIEM
Updating 53f9566..e94d34e
Fast-forward
seccheck.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
[jason@localhost myscripts]$ _

Now, other users who have a cloned copy of the original repo can run git pull
origin master to download an updated (merged) copy of the master branch from
the original location that has the new SIEM feature added to the seccheck.sh script.
Let’s switch back to the cloned repo in the root user’s home directory and do this:

[root@localhost myscripts]# git pull origin master


From /home/jason/myscripts
* branch master -> FETCH_HEAD
53f9566..e94d34e master -> origin/master
Already up-to-date.
[root@localhost myscripts]# _

For all new modifications made by other users we simply repeat this process. Now,
imagine that you have a repository on a server that contains a central copy of all the shell
scripts that can be used on other Linux servers within your organization:
• You can clone the repo on the other Linux servers (or pull an updated copy of the
master branch), create a new branch to store any modifications to the shell scripts,
and perform commits in this branch as necessary.
• Once you are happy with the changes, you can push the branch to the original
repo, where it can be viewed by others and merged into the master branch.

NOTE: Developers use this same process to coordinate and version control all other
software source code (JavaScript, Swift, Java, PHP, and so on). However, they typically
create a repo on an online Git repository such as GitHub.com (discussed later in this
section) as the origin of the repo. These online Git repositories are the cornerstone of
open source software development – if the source code repo for an open source software
project is on GitHub, other developers who want to contribute useful fixes/features to the
software can clone the repo, create a local branch to make their contributions, and then
request that the branch be added to the repo on GitHub (GitHub calls this a pull request)
for others to view, comment on, and merge into the master branch if approved.

If you are working on a branch for a long period of time, you should use git rebase
periodically to move your branch to the HEAD of the master branch (this makes git
log output much easier to read too). For example, if you are in the AddSIEM branch and
want to rebase it to the end of the master branch, you could run git rebase
AddSIEM master.
- 93 -

Before rebasing the AddSIEM (purple) branch with the master (blue) branch:

After rebasing the AddSIEM (purple) branch with the master (blue) branch:

Following is a summary of git commands that you should find useful at this point:
git clone /path Clones a local Git repo, locally to the current directory
git clone username@hostname:/path Clones a remote repo locally
git checkout -b branchname Creates a new branch, and switches to it
git checkout branchname Switches to a different branch
git branch Views branches in the repo
git branch -d branchname Deletes a branch
git merge branchname Merges a branch into the current branch
git rebase branchname Rebases the current branch to the end of another one
git push origin branchname Pushes a branch to the original repo location
git pull origin branchname Pulls a branch from the original repo location

Using Online Repositories to Centrally Store Files


There are several online repositories that you can use to store your source code. The most
common is GitHub. If you create a free account on GitHub.com, you can create a new
public repository called myscripts to host your shell scripts as shown here:
- 94 -

Next, on your local computer, you can run the following commands to push the contents
of your repo to GitHub and set GitHub as the original repo (which turns your local
computer’s repo into a cloned copy from that point onwards):

cd /path/to/repo
git remote add origin
https://github.com/accountname/nameofGitHubrepo.git

You don’t have to remember these commands ,because when you create a new repo
within GitHub, it gives you the commands at the bottom of the screen:

Following this, you can go to any Linux server and run the command git clone
https://github.com/jasoneckert/myscripts.git to clone the original
repo to obtain the central copy of the shell scripts. Then, you can optionally make
branches, do commits, push those commits back to GitHub, and merge them into the
master branch on GitHub.

Exercise
1. Log into tty3 as the root user, create a /scripts folder and move all of the scripts that
you have created in this chapter to it using the following commands:
mkdir /scripts
cp scriptname scriptname scriptname /scripts
cd /scripts

2. Next, run the following commands to set your Git user information, turn the /scripts
folder into a Git repo, as well as create and view a commit of your files:
git config --global user.name "name"
git config --global user.email "email"
git init
git status
git add .
git commit -m "My first snapshot"
git log

3. Run these commands to create a new branch called sample and switch to this branch:
git checkout -b sample
git branch

4. Run these commands to modify one of your shell scripts and create a new commit:
vi scriptname
git add .
git commit -m "Description"
- 95 -

5. Run these commands to switch back to your master branch and view your shell script
(the modification is not visible because it was done in the sample branch only):
git checkout master
cat scriptname

6. Run these commands to merge your sample branch into your master branch and view
your shell script (the modification is now visible following the merge):
git merge sample
cat scriptname

7. Create a free account on GitHub and create a public scripts repo. Use the appropriate
commands (shown on GitHub after you create a new repo) to push your /scripts repo to
GitHub and set GitHub to be the original copy (which converts your /scripts repo into a
cloned copy).

When you push your /scripts repo to GitHub, you will be prompted to log in with your
GitHub username and a personal access token (very long password) that is used in place
of your real GitHub password for pushing. You can generate a personal access token on
the GitHub website by clicking your user icon > Settings > Developer settings >
Personal access tokens. When generating a personal access token, you must supply a
description, expiration and select the repo scope for it to work:

Next, you can copy and paste the personal access token into your VM when prompted for
your GitHub password!
- 96 -

4.8 The Z Shell


Throughout this book, we have focused on the bash shell, as it is the most common shell
on UNIX and Linux systems. However, there are many different shells available for you
to use, including the Z shell (zsh), which is the second most popular shell today. macOS
and a few Linux distributions (e.g. Kali Linux, which is used for cybersecurity
penetration testing) use the Z shell by default instead of bash.

On the surface, the Z shell is nearly identical to bash. It has the same command line and
shell scripting features but displays a % prompt instead of a $ prompt (the root user still
receives a # prompt). As a bash shell user, this means that you can immediately start
working within a Z shell. Moreover, you can create shell scripts exactly like you would in
the bash shell. However, you would use a #!/bin/zsh shebang in place of a
#!/bin/bash shebang on the first line of the shell script.

The Z shell also has a similar environment file structure. The ~/.zshrc file works just
like ~/.bashrc in that it is executed for each new Z shell you create, and the
/etc/zshprofile and ~/.zshprofile environment files are used in place of
/etc/bash_profile and ~/.bash_profile, respectively.

The Z shell implements all of the same bash features that we have discussed but includes
a few more that we’ll examine in this section. Nearly all Linux systems have the Z shell
installed by default. You can start a new Z shell by running the zsh command within an
existing bash shell. If you decide to use the Z shell as your default shell instead of bash,
you can run the chsh -s /bin/zsh command.

Flexible Redirection and Piping


Using the Z shell, you can redirect standard input, output and error to multiple files, as
well as use standard output redirection within a pipe (eliminating the need for the tee
command). For example, the following command sends the standard output of the who
command to both file1.txt and file2.txt:

[jason@localhost ~]% who >file1.txt >file2.txt


[jason@localhost ~]% _

You could instead send the standard output to file1.txt as well as pipe it to the grep
command to extract lines with the word “bob” in them, and save them to file2.txt:

[jason@localhost ~]% who >file1.txt | grep bob >file2.txt


[jason@localhost ~]% _

The following will merge and sort bigfile1.txt and bigfile2.txt, saving the
combined sorted output to output.txt:
- 97 -

[jason@localhost ~]% sort <bigfile1.txt <bigfile2.txt >output.txt


[jason@localhost ~]% _

Another neat Z shell trick is to use input redirection to view files. For example, instead of
running the less proposal1 command, you could just run <proposal1 at a Z
shell prompt.

Z Shell Options
By setting Z shell options, you can control nearly every aspect of the shell, including
auto-completion, file globbing, command history, input/output processing, shell
emulation, function handling, and more.

Two common options are autocd and correct. The easiest way to set these two
options is to add the line setopt autocd correct to your ~/.zshrc file. After
doing this, you’ll need to start a new Z shell (or run source ~/.zshrc to reload
~/.zshrc in your current shell).

The autocd option allows you to change to a directory by specifying the directory name
only (without the cd command). For example:

[jason@localhost ~]% pwd


/home/jason
[jason@localhost ~]% Desktop
[jason@localhost ~]% pwd
/home/jason/Desktop
[jason@localhost ~]% _
The correct option allows your shell to auto-correct misspelled commands. For
example, if you type the cal (calendar) command incorrectly:

[jason@localhost ~]% cla


zsh: correct 'cla' to 'cal' [nyae]? y
November 2020
Su Mo Tu We Th Fr Sa
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
[jason@localhost ~]% _

For a full list of options available, visit zsh.sourceforge.net/Doc/Release/Options.html


- 98 -

Z Shell Modules
The built-in commands in the Z shell are modular; you can choose which ones are
available by loading the appropriate modules. To view the default modules loaded in your
Z shell, run the zmodload command without arguments:

[jason@localhost ~]% zmodload


zsh/complete
zsh/main
zsh/parameter
zsh/regex
zsh/terminfo
zsh/zle
zsh/zutil
[jason@localhost ~]% _

To get additional built-in commands in your Z shell, you can choose to load additional
modules. For example, to use the strftime built-in command to obtain detailed time
and date information, you must load the zsh/datetime module, as shown below:

[jason@localhost ~]% zmodload zsh/datetime


[jason@localhost ~]% strftime "%a, %d %b %y %T %z"
Sun, 01 Nov 20 08:57:08 -0500
[jason@localhost ~]% _

To ensure that the datetime module is loaded each time you start a Z shell, add the line
zmodload zsh/datetime to your ~/.zshrc file.
Using built-in commands in Z shell scripts makes them more robust and portable. While
the date command on one system may not support the same options as on another
system, the strftime built-in command will always work the same way on any system
running the Z shell. Similarly, by loading the zsh/mathfunc module, you can use
mathematical functions such as sqrt (square root) within a Z shell script without having
to rely on other programs installed on the underlying UNIX or Linux system. For a full
list of modules available, visit zsh.sourceforge.net/Doc/Release/Zsh-Modules.html

Z Shell Functions
The Z shell also supports the loading of additional functions from directories specified in
the $fpath variable. These directories contain many useful functions by default, and
programs you install may add even more.

One commonly-used function is compinit, which extends the built-in [Tab] key
completion feature for commands and filenames to include command options and
arguments as well. To ensure that this function is loaded each time you start a new Z
shell, add the line autoload -U compinit && compinit to your ~/.zshrc
file. Following this, when you type the who command, you can type - and press the
[Tab] key to see the available options:
- 99 -

[jason@localhost ~]% who -


-H -- print line of column headings
-T -- show user's message acceptance status as +, - or ?
-a -- print all entries
-b -- print time of last system boot
-d -- print dead processes
-l -- print system login processes
-m -- print information about current terminal
-p -- print active processes spawned by init
-q -- print only login names and number of users logged on
-r -- print current runlevel
-t -- print last system clock change
-u -- show idle time
[jason@localhost ~]% _

If the available options are POSIX options (e.g.--universal), you can also use the
[Tab] key to autocomplete the option name after typing the first few letters. For
complex commands that require specific option combinations, you’ll be prompted to
select required options before selecting secondary options. Additionally, for commands
that accept a predefined list of arguments, you can use the [Tab] key to autocomplete
those arguments as well.

Z Shell Themes and Plugins


Perhaps the most attractive feature of the Z shell is its support for plugins and themes.
There are currently over 250 plugins that provide a wide range of additional functionality,
from integrating Git features into the Z shell prompt to displaying the current weather in
your terminal using Unicode symbols. If you’d like to modify the look and feel of your Z
shell, there are over 150 themes available for you to add. To search for and install Z shell
plugins and themes, follow the instructions online at github.com/ohmyzsh/ohmyzsh

As with other Z shell configuration, you specify the theme and plugins you wish to use in
your ~/.zshrc file. For example, the following ~/.zshrc lines apply the agnoster
theme as well as load the git and dotenv plugins:
ZSH_THEME="agnoster"
plugins=(
git
dotenv
)
- 100 -

Exercise
1. Log into tty3 as the root user and run the dnf install zsh command to install the Z
shell. Next, run the zsh command to start a new Z shell. In your new Z shell, run the
following commands:
cd classfiles
grep -i mother letter >file1.txt >file2.txt
cat file1.txt
cat file2.txt
grep -i mother letter >file1.txt | grep -i granada >file2.txt
cat file1.txt
cat file2.txt
sort <proposal1 <greeting >sortedresults.txt
cat sortedresults.txt
setopt autocd correct
grp -i mother letter (note that it prompts you to correct the command to grep)
autoload -U compinit
compinit
date - (then press [Tab] to see the available date command options)

2. Edit your ~/.zshrc (e.g. vi ~/.zshrc) and add the following lines to ensure that the Z
shell options and function we used earlier are always available when you start a new Z
shell. Save your changes when finished.
setopt autocd correct
autoload -U compinit
compinit
- 101 -

CHAPTER

System Administration
This chapter examines the core administration topics that you need to know
in order to manage the Linux operating system, including users, processes,
printers, storage, bootloaders, daemons, locales, file compression, backup,
software, logs, performance, devices and system rescue. We’ll also discuss
Linux server deployment and storage configuration, as well as install two
additional Ubuntu Server distributions (modern & legacy).

5.1 Users & Groups


5.2 Processes
5.3 Printing
5.4 Storage
5.5 Server Installation
5.6 Server Storage
5.7 System Startup & Daemons
5.8 Localization
5.9 Compression
5.10 Backup
5.11 Software
5.12 Logs
5.13 Performance
5.14 Devices
5.15 System Rescue
- 102 -

5.1 Users & Groups


Users and groups are identified using User IDs (UIDs) and Group IDs (GIDs), and the
root user always has a UID of 0 (which gives it full access to a system). You can use the
following commands to see your username, group memberships, UIDs and GIDs:

[root@localhost ~]# whoami


root
[root@localhost ~]# groups
root bin daemon sys adm disk wheel
[root@localhost ~]# id
uid=0(root) gid=0(root)
groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
[root@localhost ~]# _

The first group listed by the groups and id commands above is your primary group
(the one that becomes the group owner of new files and directories that you create).

Each Linux system stores user account data in two files: /etc/passwd (user
information) and /etc/shadow (password information). As well, /etc/group stores
a list of valid groups on the system and their members. Each of these files is colon ( : )
delimited and has a line for each user or group account on the system.

Lines in /etc/passwd have the following format:

name:passwd:UID:GID:GECOS:homedir:shell

• name is the login name used during authentication


• passwd represents the password used during authentication (if the
/etc/shadow file is used to store the password, then there is an x in this field)
• UID is the User ID of the user account
• GID is the primary Group ID of the user account
• GECOS (General Electric Comprehensive OS) is a comment field (optional)
• homedir is the user's home directory
• shell is the user's shell

NOTE: Legacy systems stored the encrypted password in the passwd field of
/etc/passwd, instead of in /etc/shadow, which was a security hazard. You may
convert /etc/passwd to legacy format by using the pwunconv command or convert
a legacy system to use /etc/shadow for passwords using the pwconv command.

Lines in /etc/shadow have the following format:

name:passwd:lastchange:min:max:warn:inactive:expiry:unused
- 103 -

• name is the login name used during authentication


• passwd represents the encrypted password used during authentication (daemon
accounts typically do not have a password – they usually contain !! in this field)
• lastchange represents the date of the last password change in UNIX epoch
format (measured in the number of days since Jan 1st 1970, or the UNIX epoch)
• min represents the minimum number of days that a user must wait before
changing a password again after it has been changed
• max represents the maximum number of days before a password expires
• warn represents the number of days before password expiry that a user is warned
to change the password
• inactive represents the number of days after a password expires that an
account is disabled (prevented from logging in again)
• expiry represents that number of days since Jan 1st 1970 that the account is
disabled
• unused is not used (reserved for future use)

NOTE: You can visit www.epochconverter.com to convert a UNIX epoch date to a


regular date and vice versa.

Lines in /etc/group have the following format:

groupname:passwd:GID:membership

• groupname is the name of the group


• passwd represents the encrypted group password used by the newgrp
command (discussed later) – while group passwords are rarely used today, they
are typically stored in /etc/gshadow
• GID is the Group ID
• membership lists the members of the group separated by , characters

NOTE: You can also use the getent command to view the entries within the user,
password and group database files; for example, getent passwd is equivalent to cat
/etc/passwd.

Adding User Accounts


To add a user account to the system, you can use the useradd command. For example,
to create bozo and make the home directory /home/bozo, you can use the following
command:

[root@localhost ~]# useradd –m bozo


[root@localhost ~]# _
- 104 -

The useradd command accepts options that can be used to specify the shell (-s
/bin/bash), UID (-u UID), as well as other user account information. If this
information is not provided by these options when you create a user, default values are
taken from the files /etc/login.defs and /etc/default/useradd:

[root@localhost ~]# cat /etc/login.defs


# Password aging controls:
# PASS_MAX_DAYS Maximum number of days a password may be used.
# PASS_MIN_DAYS Minimum number of days between password changes.
# PASS_MIN_LEN Minimum acceptable password length.
# PASS_WARN_AGE Number of days warning given before password expiry.
PASS_MAX_DAYS 99999
PASS_MIN_DAYS 0
PASS_MIN_LEN 5
PASS_WARN_AGE 7

# Min/max values for automatic uid selection in useradd


UID_MIN 1000
UID_MAX 60000
# System accounts
SYS_UID_MIN 201
SYS_UID_MAX 999

# Min/max values for automatic gid selection in groupadd


GID_MIN 500
GID_MAX 60000
# System accounts
SYS_GID_MIN 201
SYS_GID_MAX 999

# If useradd should create home directories for users by default


# On RH systems, we do. This option is overridden with the -m flag on
# useradd command line.
CREATE_HOME yes

# The permission mask is initialized to this value. If not specified,


# the permission mask will be initialized to 022.
UMASK 077

# Use SHA512 to encrypt password.


ENCRYPT_METHOD SHA512

[root@localhost ~]# cat /etc/default/useradd


# useradd defaults file
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/bash
SKEL=/etc/skel
CREATE_MAIL_SPOOL=yes
[root@localhost ~]# _
- 105 -

The second last line in /etc/default/useradd (SKEL=/etc/skel) refers to the


skeleton directory. When a new user is created with a home directory, it copies files from
the skeleton directory to the new user's home directory. Thus, if you wish to ensure that
all new users receive a certain file in their home directory, simply place that file in
/etc/skel and it will be copied over during account creation. The following output
indicates that the files in /etc/skel were copied over to /home/bozo when the user
account was created:

[root@localhost ~]# ls -a /home/bozo


. .bash_logout .bashrc .gnome2 .zshrc
.. .bash_profile .emacs .mozilla
[root@localhost ~]# ls -a /etc/skel
. .bash_logout .bashrc .gnome2 .zshrc
.. .bash_profile .emacs .mozilla
[root@localhost ~]# _

After creating a user, they cannot log into the system until you assign them a password.
To do this, you can use the passwd command:

[root@localhost ~]# passwd bozo


Changing password for user bozo
New password: **********
Retype new password: **********
passwd: all authentication tokens updated successfully
[root@localhost ~]# _

Deleting User Accounts


To remove the bozo user account from the system, you can use the userdel command.
The –r option to the userdel command can be used to remove the home directory for
that user as well, however this is seldom done since that user may have important files
needed by other users. Any files that the deleted user owned on the filesystem become
owned by the deleted user's old UID. If you add another user with that same UID, then
the new user becomes the owner of those files automatically, as shown in the following
example:

[root@localhost ~]# ls -la /home/bozo


total 36
drwx------ 3 bozo bozo 4096 Oct 22 03:00 .
drwxr-xr-x 6 root root 4096 Oct 22 03:00 ..
-rw-r--r-- 1 bozo bozo 191 Oct 22 03:00 .bash_profile
-rw-r--r-- 1 bozo bozo 124 Oct 22 03:00 .bashrc
-rw-r--r-- 1 bozo bozo 3511 Oct 22 03:00 .zshrc
[root@localhost ~]# userdel bozo
[root@localhost ~]# ls -la /home/bozo
total 36
drwx------ 3 1003 1003 4096 Oct 22 03:00 .
drwxr-xr-x 6 root root 4096 Oct 22 03:00 ..
-rw-r--r-- 1 1003 1003 191 Oct 22 03:00 .bash_profile
-rw-r--r-- 1 1003 1003 124 Oct 22 03:00 .bashrc
- 106 -

-rw-r--r-- 1 1003 1003 3511 Oct 22 03:00 .zshrc


[root@localhost ~]# useradd -m -u 1003 bozoette
[root@localhost ~]# ls -la /home/bozo
total 36
drwx------ 3 bozoette bozoette 4096 Oct 22 03:00 .
drwxr-xr-x 7 root root 4096 Oct 22 03:08 ..
-rw-r--r-- 1 bozoette bozoette 191 Oct 22 03:00 .bash_profile
-rw-r--r-- 1 bozoette bozoette 124 Oct 22 03:00 .bashrc
-rw-r--r-- 1 bozoette bozoette 3511 Oct 22 03:00 .zshrc
[root@localhost ~]# _

The useradd –m –u 1003 bozoette command gave bozoette her own home
directory (/home/bozoette). This is good form; she may then go into /home/bozo
when she needs to access bozo's old files only.

Modifying User Accounts


There are several commands that can be used to modify the user account information
stored in /etc/passwd and /etc/shadow. The most common include:
usermod –l bozoette bozo (changes the login name of bozo to bozoette)
usermod –u 500 bozo (changes bozo’s UID to 500)
usermod –e ”01/01/2025” bozo (expires bozo’s account on Jan 1st 2025)
chage –m 10 bozo (forces bozo to wait 10 days between password changes)
chage –M 20 bozo (bozo’s new password expires in 20 days)
chage –W 5 bozo (bozo is warned 5 days before his password expires)
chage –l bozo (display password aging parameters for bozo)
chsh –s /bin/zsh bozo (changes bozo’s shell to the Z Shell)
chfn bozo (changes bozo’s GECOS field, viewable using finger bozo)
passwd -l bozo (locks bozo’s user account)
passwd -u bozo (unlocks bozo’s user account)

NOTE: The passwd -l command locks a user account by adding !! to the beginning
of the passwd field in /etc/shadow (changing the password). The passwd -u
command removes the !! from this field to unlock the account. Other methods that you
can use to lock a user account including changing the user’s password or changing the
user’s shell to /bin/false to prevent the user from obtaining a shell (they just get
logged out if they attempt to log in).

Working with Groups


There are several different commands you can use to manage groups:
groupadd production (adds the production group to the system)
groupmod -n prod production (renames the production group to prod)
usermod -aG prod bozo (adds bozo to the prod group)
usermod -g prod bozo (makes prod the primary group for bozo)
groupdel prod (deletes the prod group)
- 107 -

Remember that your primary group (the one that gets attached to files and directories you
create) is listed in /etc/passwd, and all other groups that you are a member of are
listed in /etc/group. To change your primary group membership temporarily to
another group to which you are a member of you can use the newgrp command:

[root@localhost ~]# touch file1 && ls -l file1


-rw-r--r-- 1 root root 0 Oct 22 05:57 file1
[root@localhost ~]# newgrp sys
[root@localhost ~]# touch file2 && ls -l file2
-rw-r--r-- 1 root sys 0 Oct 22 05:58 file2
[root@localhost ~]# _

The newgrp command affects the current shell only. If you exit your shell and start a
new one, your original primary group is used.

Privilege Escalation
It is good form on a Linux system to log in as a regular user account to perform day-to-
day tasks and only switch to the root account to perform system administration as needed.
As discussed in Chapter 1, you can use the su – command to open a new shell as the
root user, or the su –c "command" command to run a single command as the root
user (if no user is specified with su the root user is assumed).

Alternatively, you can log into a Linux system as a regular user and use privilege
escalation to run certain commands as the root user. For example, sudo command will
run a command as the root user, and sudoedit textfile will edit a text file as the
root user. For you to use the sudo command to run root commands, your user account
must be granted access to those commands via a line in the /etc/sudoers file. By
default, a line exists in this file that grants users who are a member of the sudo or
wheel (short for “big wheel”) group the ability to run any commands as the root user via
the sudo command. The first regular user that you create during installation is added to
one of these groups to allow them to perform any administrative tasks (including setting
the root user’s password), but you can add others afterwards. For example, to allow bozo
to run root commands via sudo on Fedora Linux, you can use the usermod -aG
wheel bozo command. The /etc/sudoers file is read only for all users, but the
root user can modify it using the visudo command (view the comments within the
/etc/sudoers file or the sudoers manual page for syntax examples).

You can also use the Polkit framework (formerly PolicyKit) for priviledge escalation. By
default, Polkit allows members of the wheel group to execute commands as the root
user by running pkexec command. You can also configure Polkit rules and policies
that provide granular access to specific users and groups for priviledged tasks. This
involves creating .rules, .conf or .pkla files with the appropriate configuration
under the /etc/polkit-1 directory (view the polkit manual page for detailed
syntax and location information).
- 108 -

Exercise
1. Log into tty3 as the root user and run the following commands:
less /etc/login.defs
cat /etc/default/useradd
touch /etc/skel/policies.txt
su - woot (switches to a woot session – no password needed!)
sudo useradd -m larry (enter woot’s password of Secret555)
sudo useradd -m curly (sudo caches your previous authentication during the session)
pkexec useradd -m moe (enter woot’s password of Secret555)
exit (returns you to your root session)
passwd larry (enter Secret555)
passwd curly (enter Secret555)
passwd moe (enter Secret555)
groupadd stooges
usermod -aG stooges larry curly moe
grep larry /etc/passwd (interpret the fields shown)
grep larry /etc/shadow (interpret the fields shown)
grep stooges /etc/group (interpret the fields shown)
passwd -l larry
grep larry /etc/shadow
passwd -u larry
grep larry /etc/shadow
chfn larry (supply some suitable values of your choice)
grep larry /etc/passwd
dnf install finger
finger larry
ls -a /etc/skel
ls -a /home/larry (note the contents are the same as /etc/skel and include policies.txt)

2. Log into tty4 as the woot user and run the following commands:
groups (note that you are a member of the wheel group)
sudo userdel larry (supply your password of Secret555 when prompted to confirm)
su - (supply the root user password of Secret555 when prompted)
ls -la /home/larry (note that larry’s old UID owns existing files)
useradd -m -u UID shemp (where UID is larry’s old UID)
ls -la /home/larry /home/shemp (note that shemp owns larry’s old files)

3. Return to tty3 and run the following commands:


id (note your UID & GID and that root is your primary group)
touch firstnewfile
ls -l firstnewfile (note the owner of root, and group owner of root)
newgrp sys
id (note your UID & GID and that sys is your primary group)
touch secondnewfile
ls -l secondnewfile (note the owner of root, and group owner of sys)
- 109 -

5.2 Processes
A program consists of executable files (and supporting data files) on a filesystem. When
a program is executed, it is called a process from that point on. System services are called
daemon processes, while other processes are called user processes. Moreover, each
process is started by one other process (called a parent process), except for Systemd/init,
which is the first process on the system and started directly by the Linux kernel.
Furthermore, each process may start an unlimited number of other processes (called child
processes). Because of this, each process is identified by a unique process ID (PID), yet
also has a parent process ID (PPID) identifying the process that started it. Systemd/init
always has a PID=1 and PPID=0.

When a process has finished executing, it releases its resources (including memory
address and PID) to the system such that they may be reused by other processes. Some
processes encounter problems that prevent these resources from being released. A process
that is caught in a continuous loop of activity is said to be a rogue (or runaway) process
whereas a process that does not release its PID to the system for reuse is called a zombie
process. Both rogue and zombie processes typically occur on heavily-loaded Linux
systems and should be killed to allow the system to function normally.

Viewing Processes
The standard method to view processes in Linux is the ps (process) command. Without
any options, it displays the processes in the current terminal shell only. To view processes
on the entire system (-e) and display a full list of description columns (-f), you can run
the following command (piped to the head -5 command to reduce output displayed):

[root@localhost ~]# ps –ef | head -5


UID PID PPID C STIME TTY TIME CMD
root 1 0 0 01:29 ? 00:00:04 /sbin/init
root 2 1 0 01:29 ? 00:00:00 [keventd]
root 3 1 0 01:29 ? 00:00:00 [kapm-idled]
root 4 0 0 01:29 ? 00:00:00 [ksoftirqd_CPU0]

The UID (user), PID, PPID, TTY (terminal) and CMD (command) columns are self-
explanatory. The C column lists the number of processor cycles, the STIME column lists
the time the process was started, and the TIME column indicates how much time the
process has spent executing on the system. Notice from the above output that most
daemon processes have a ? in the TTY field. This is because daemons are not associated
with a terminal since they are rarely started by users that are logged into a terminal.

You can also use the –l (long) option to see a long list of description columns, including
F (special process flags), S (state: S=sleeping, R=running, Z=zombie), PRI (priority), NI
(nice value), ADDR (memory address), SZ (size in memory), and WCHAN (what the
process is waiting for):
- 110 -

[root@localhost ~]# ps -el | head -5


F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
100 S 0 1 0 0 68 0 - 353 do_sel ? 00:00:04 systemd
040 S 0 2 1 0 69 0 - 0 contex ? 00:00:00 keventd
040 S 0 3 1 0 69 0 - 0 apm_ma ? 00:00:00 kapm-idle
040 S 0 4 0 0 79 19 - 0 ksofti ? 00:00:00 ksoftirqd

Two other useful ps options include -t (or --tty), which lists processes by terminal
(e.g. ps –t /dev/tty3), and -u (or --user), which lists processes started by a
certain user (e.g. ps –u bozo). BSD UNIX-style options also work with the ps
command. These options do not start with a dash character and display information in a
slightly different format. The following command shows all processes (a) sorted by user
(u) across all terminals (x):

[root@localhost ~]# ps aux | head -5


USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 1412 520 ? S 01:29 0:04 init [5]
root 2 0.0 0.0 0 0 ? SW 01:29 0:00 [keventd]
root 3 0.0 0.0 0 0 ? SW 01:29 0:00 [kapm-idled]
root 4 0.0 0.0 0 0 ? SWN 01:29 0:00 [ksoftirqd_CPU0]

You can also use the pstree command to quickly determine the parent-child
relationships between processes, or the interactive top utility to display processes by top
processor and memory usage. When in the top utility, you can press h for help, k to kill
a process, r to change the priority of a process, or q to quit.

Alternatively, you can install htop (dnf install htop) to obtain a colour version
of top with a resource graph and function key navigation:
- 111 -

Killing Processes
To terminate a process, you can send it a kill signal. The most common kill signals are
shown in Table 5-2, but you can run the kill -l command to see a complete list. To
kill several process, you can send a kill signal to the parent process that started them,
since parent processes kill their child processes before killing themselves. You can also
detach a child process from its parent process using the nohup command. This is often
useful when running graphical programs from a terminal within a desktop environment; if
you close your terminal window, it won’t kill the graphical program that is running as a
child process.

Table 5-2 Kill Signals


Number Name Description
1 SIGHUP Hang-up – used to restart a process only
2 SIGINT Interrupt – weak, but often works (same as [Ctrl]+c)
3 SIGQUIT Core Dump – stronger than 2 (same as [Ctrl]+\)
15 SIGTERM Termination – stronger than 3 (the default kill signal if
none is specified)
9 SIGKILL Absolute Kill – strongest kill signal

To send a process a kill signal, use the kill command and specify the kill signal and
PID of the process to be killed. For example, to kill the process with PID=8116 using a
kill signal 2 (SIGINT), you can use either the kill -2 8116 or kill -SIGINT
8116 command. You should always try a kill signal 2 first in order to allow the process
to properly clean up resources. Many rogue and zombie processes do not respond to
weaker kill signals (this is known as trapping a signal); thus you may need to try stronger
kill signals in sequence. No process can trap a kill signal 9.

NOTE: Before using a SIGKILL you should see which files the process has open, as
those files may become corrupted following the SIGKILL; you can do this with the
lsof –p PID command.

To quickly find a PID to use with kill, you can use the ps or pidof command. For
example, ps -ef|grep firefox or pidof firefox can be used to identify the
PIDs for the firefox processes running on the system. You can then locate the correct PID
to kill. Instead, you can kill processes by name using the killall command, but this
will kill all processes with the same name. For example, killall -9 firefox
would send a kill signal 9 (SIGKILL) to every firefox process on the system.

The fuser command can also be used to kill processes associated with a particular
terminal. For example, fuser –k /dev/tty3 will send a SIGKILL signal to all
processes on the tty3 terminal. You can also use the pgrep and pkill commands to
locate and kill processes using a wide variety of different criteria, including regular
expressions. For example, the pgrep –u woot "^apache" command will return
- 112 -

the PIDs for all processes started by the user woot that have a name starting with http,
and the pkill -u woot -9 "^apache" would send a SIGKILL to them.

Running Processes in the Background


When you run commands in your shell, your shell starts a child shell to execute the
processes. For example, if you type the ls command, your shell starts a child shell
(called forking) that directly executes the ls command and waits for the results. Once
the ls command has completed executing, the child shell kills itself, and the parent shell
presents you with another shell prompt so that you can run another command. This is
illustrated in the following flowchart:

bash (PID=182)
fork()

bash (PID=354, PPID=182)


wait()
exec() ls
exit()
bash (PID=182)

If the command that you wish to run will take a long time to execute, you may omit the
wait() function in the above flowchart and receive your shell prompt back immediately
after starting the command. To do this, place a & character at the end of the command to
run it as a background process. For example, to update the locate database (which
takes a long time) and receive your shell prompt back immediately after running the
command, you run the following command:

[root@localhost ~]# updatedb &


[1] 26208
[root@localhost ~]# _

The shell will immediately return the PID of the process (26208) as well as a background
job ID (1). You may later use the PID to kill the process, and the background job ID to
manipulate or kill the process. To see a list of all background jobs in your shell, you can
use the jobs command.

[root@localhost ~]# jobs


[1]+ Running updatedb &
[root@localhost ~]# _

The fg command can be used to transfer a background job to the foreground (job IDs
must be prefixed with a % character). To pause a process such that it may be started again
in the background, press [Ctrl]+z and enter the command bg %jobID. For example,
to bring updatedb to the foreground, pause it and run it in the background again, you
could run the following commands:
- 113 -

[root@localhost ~]# fg %1
updatedb
[Ctrl]+z
[1]+ Stopped updatedb
[root@localhost ~]# bg %1
[1]+ updatedb &
[root@localhost ~]# _

To kill a background process, you may either use the name as an argument to the
killall or pkill command, or the PID or background job ID as an argument to the
kill command. For example, the following command sends a SIGTERM to the
updatedb background job:

[root@localhost ~]# kill -15 %1


[1]+ Terminated updatedb
[root@localhost ~]# _

Modifying Process Priority


Each process is assigned a priority (the PRI column from the ps –el command shown
earlier) by the kernel during execution. This priority ranges from 0 (high priority) to 127
(low priority). You cannot directly affect this priority, but you can indirectly influence it
by assigning a nice value that ranges from -20 (higher priority) to 19 (lower priority).

New processes are assigned a nice value of 0 by default (background processes start with
4), but all users can start a process with a lower priority nice value (being nice to other
users of the system). Only the root user can start a process with a higher priority nice
value. Say, for example, that you need to execute a large simulation program called
cathena that takes about 4 hours to run and monopolizes the processor during that
time. To be nice to your fellow co-workers using the same system, you could lower the
priority of cathena. It will take longer to execute (e.g. 8 hours), but not monopolize the
processor. To start cathena in the background with the lowest priority nice value (19),
you can use the nice command:

[root@localhost ~]# nice –n 19 cathena &


[1] 8677
[root@localhost ~]# _

If you instead started the cathena process with the default priority nice value (0), you
can change the nice value to 19 while it is running using the renice command:

[root@localhost ~]# cathena &


[1] 8677
[root@localhost ~]# renice +19 8677
8677 (process ID) old priority 0, new priority +19
[root@localhost ~]# _
- 114 -

Scheduling Processes
The simplest way to schedule a process is using the watch command, which is often
used for scheduling tasks to run repetitively for short periods of time. For example, the
watch –n 2 date command will run the date command every two seconds until
you press the [Ctrl]+c key combination. To schedule a process to run at a later time,
you must instead use a daemon.
To schedule a command or shell script to execute at a single time in the future using the
at daemon, you can use the at command. For example, to schedule the date and who
commands to run at 11:30pm on Dec 11 you can use the following command (press
[Ctrl]+d when finished, which sends at an EOF, or end-of-file signal):

[root@localhost ~]# at 11:30pm December 11


at> date >/results
at> who >>/results
at> [Ctrl]+d
job 1 at Thu Dec 11 23:30:00 2021
[root@localhost ~]# _

Following this, you can use the at –l (or atq) command to see a list of jobs scheduled
by the at daemon (each is given a job ID), the at -c jobID command to view the
contents of a scheduled at job, or the at –d jobID (or atrm jobID) command to
remove a schedule at job. It is important to redirect the output of any scheduled
commands to a file since they will not be sent to a terminal screen when executed
otherwise.

The time format you specify for your at daemon job is very flexible. Following are some
examples of various time formats supported by the at command:
at 9:15pm July 15
at 9:15pm 04/06/2022
at 9:15pm 04062022
at 9:15pm 04.06.2022
at noon July 15
at midnight
at teatime
at tomorrow
at now + 1 minute
at now + 1 hour
at now + 1 day
at now + 1 week

At daemon jobs are stored under the /var/spool/at directory, and the
/etc/at.allow and /etc/ad.deny files can be used to restrict which regular users
can schedule jobs (root is always allowed). If /etc/at.allow exists, then users listed
in it are allowed to use the at daemon only. If /etc/at.allow does not exist, and
/etc/at.deny exists, then users not listed in it are allowed to use the at daemon only.
- 115 -

On most Linux distributions, there is only a blank /etc/at.deny file to ensure that all
users have the ability to schedule jobs using the at daemon.

Alternatively, to run a command or shell script repetitively using the cron daemon, you
can use the crontab -e command to edit your cron table using the default editor. For
example to schedule /home/woot/script1.sh to run at 2:20pm and 2:40pm
Monday to Friday, you could add the following line to your cron table, then save your
changes and quit:

20,40 14 * * 1-5 /home/woot/script1.sh

minute past hour hour day of week


(0-59) (0-23) (0=Sunday,6=Saturday,7=Sunday)

day of month month of year


(1-31) (1-12)

You can add as many lines to your cron table as you like to schedule commands and shell
scripts. They will be run as your user account at the times you specify. You can also use
the crontab –l command to list your cron table, or the crontab –r command to
remove all entries from it.

Cron daemon jobs are stored under the /var/spool/cron directory, and the
/etc/cron.allow and /etc/cron.deny files can be used to restrict which regular
users can schedule jobs (root is always allowed). If /etc/cron.allow exists, then
users listed in it are allowed to use the cron daemon only. If /etc/cron.allow does
not exist, and /etc/cron.deny exists, then users not listed in it are allowed to use the
at daemon only. On most Linux distributions, there is only a blank /etc/cron.deny
file to ensure that all users have the ability to schedule jobs using the cron daemon.

As the root user, you can instead use a text editor to add lines to the system cron table
(/etc/crontab) that schedule commands or shell scripts. The comments at the
beginning of this file identify the format of entries (note that you must also supply the
name of the user to run the task as before the command).

[root@localhost ~]# cat /etc/crontab


# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,
# | | | | |
# * * * * * user-name command to be executed
30 1 * * 1-5 root /scripts/script1.sh
[root@localhost ~]# _
- 116 -

Alternatively, to schedule a shell script to run periodically, you can place the shell script
in one of the following directories to have it automatically run hourly, daily, weekly or
monthly:
/etc/cron.hourly
/etc/cron.daily
/etc/cron.weekly
/etc/cron.monthly

Software packages often add scripts to these directories to schedule maintenance tasks on
the system. For shell scripts that can’t be schedules hourly/daily/weekly/monthly, you
can place a custom cron table in the /etc/cron.d directory with the appropriate
information (software packages may do this as well). The following example schedules
the /usr/lib/sa/sa1 -d 1 1 command to run every 10 minutes:

[root@localhost ~]# head -2 /etc/cron.d/sysstat


# Run system activity accounting tool every 10 minutes
*/10 * * * * root /usr/lib/sa/sa1 -d 1 1
[root@localhost ~]# _

NOTE: On Fedora, the cron daemon runs the /etc/cron.d/0hourly script, which
executes the contents of the /etc/cron.hourly directory 1 minute past the hour,
every hour on the hour. The /etc/cron.hourly/0anacron file starts the anacron
daemon, which then executes the contents of the /etc/cron.daily,
/etc/cron.weekly and /etc/cron.monthy directories at the times specified in
/etc/anacrontab. The anacron daemon is functionally equivalent to the cron
daemon with one difference: if the computer is turned off during the time a task is
scheduled, the anacron daemon will execute that task at the next available time, whereas
the cron daemon will not. This makes the anacron daemon well-suited to executing tasks
that do not occur very often (e.g. daily, weekly, and monthly).

The Special /proc Filesystem


Most system information (including the information displayed by ps) is obtained from a
special filesystem in RAM that is mounted to the /proc directory. You can use the cat,
more or less commands to view the contents of files within /proc if you need current
information about your system. A sample listing of /proc is shown below:

[root@localhost ~]# ls –F /proc


1/ 1227/ 13/ 615/ 928/ driver/ irq/ misc slabinfo
1156/ 1228/ 132/ 643/ 947/ execdomains isapnp modules stat
11643/ 1236/ 2/ 7/ 965/ fb kcore mounts swaps
1192/ 1237/ 3/ 755/ apm filesystems kmsg mtrr sys/
1222/ 1243/ 4/ 8/ bus/ fs/ ksyms net/ sysvipc/
1223/ 1244/ 5/ 827/ cmdline ide/ loadavg partitions tty/
1224/ 1245/ 590/ 860/ cpuinfo interrupts locks pci uptime
1225/ 1262/ 595/ 9/ devices iomem mdstat scsi/ version
1226/ 1263/ 6/ 900/ dma ioports meminfo self@
[root@localhost ~]# _
- 117 -

The numbered directories shown under /proc match the PIDs currently on the system,
and the files match exported system information. For example, to see a list of IRQs
used, simply view the interrupts file:

[root@localhost ~]# head -7 /proc/interrupts


CPU0 CPU1
0: 332 0 IO-APIC-edge timer
1: 2 0 IO-APIC-edge i8042
8: 21 0 IO-APIC-edge rtc0
9: 0 0 IO-APIC-fasteoi acpi
12: 4 0 IO-APIC-edge i8042
16: 510 0 IO-APIC-fasteoi uhci_hcd:usb3, HDA Intel, eth1
[root@localhost ~]# _

The /proc/sys directory also allows the root user to change kernel parameters while
the kernel is running. Many of the files under this directory are turned on (contain the
number 1) or off (contain the number 0). For example, to enable routing in the Linux
kernel, you can run the following command:

[root@localhost ~]# echo 1 > /proc/sys/net/ipv4/ip_forward


[root@localhost ~]# cat /proc/sys/net/ipv4/ip_forward
1
[root@localhost ~]# _

Exercise
1. Log into tty3 as the root user and run the following commands:
ps -ef | less (view the processes on your system)
ps -el | grep Z (do you have any zombie processes on your system?)
pstree (use [Shift]+[PageUp] and [Shift]+[PageDown] to navigate)
top (press q to quit when finished)
dnf install htop
htop (press F10 to quit when finished)
ps (note the PID of your bash shell for the following commands)
kill -2 PID (note that bash trapped the signal)
kill -3 PID (note that bash trapped the signal)
kill -15 PID (note that bash trapped the signal)
kill -9 PID (note that the process was killed successfully)

2. Log back into tty3 as the root user and run the following commands:
sleep 10 (note that you do not receive your prompt until sleep terminates)
sleep 5000000& ; sleep 5000000& ; sleep 5000000&
jobs
fg %3
[Ctrl]+c (this sends a 2/SIGINT kill signal to the foreground process)
killall -9 sleep
nice -n 19 ps -l (note the priority and nice values)
nice -n -20 ps -l (note the priority and nice values)
nice -n 11 sleep 50000& (note the PID for the next command)
- 118 -

renice -5 PID
exec ls (you were logged out after this command was run because you
directed your shell to not fork() and instead directly execute the ls
command using exec(), which is always followed by exit())

3. Log back into tty3 as the root user and run the at now + 1 minute command. At the
at> prompt, type echo Hello World >>/testfile and press Enter. At the next at> prompt,
press [Ctrl]+d. In about one minute, run the command cat /testfile to test your results.

4. Run the crontab -e command to edit your cron table using nano. Enter the following
lines to schedule the /bin/false command (which prints a false exit status and is harmless
to schedule for practice) to run at 6:15am and 9:20pm on the first Tuesday of the month,
regardless of the month:
15 6 1-6 * 2 /bin/false
20 21 1-6 * 2 /bin/false

Save your changes and quit the vi editor when finished. Next, type crontab -l to list your
cron table. Normally, you would schedule a shell script instead of /bin/false (e.g. the
backup shell script we examined in the previous chapter).

5. Run the following commands to examine the contents of the /proc directory:
ls -F /proc/1 (this is the PID for the first daemon on the system)
cat /proc/interrupts
cat /proc/devices
cat /proc/ioports
cat /proc/cpuinfo
cat /proc/meminfo
cat /proc/bus/* (e.g. /proc/bus/usb/devices)
cat /proc/uptime
cat /proc/sys/net/ipv4/tcp_syncookies (note that this kernel setting is enabled – it is
used to prevent Denial of Service attacks)

5.3 Printing
Print jobs on Linux systems today are printed using the lp (line printer) command.
Before print jobs are sent to the actual printer they are first saved to a print queue
directory (a process called spooling) provided the printer is in an accepting state. When
the actual printer is ready to accept the print job, the print job is sent to the printer
provided the printer is in an enabled state. On modern Linux systems, the Common
UNIX Printing Service (CUPS) daemon created by Apple manages the entire process
uses a print queue directory of /var/spool/cups:

accept enable
lp Print Queue Printer
reject (/var/spool/cups) disable
- 119 -

The CUPS daemon is based on the original AT&T UNIX printing system but adds
modern printer driver support and the Internet Printing Protocol (IPP) commonly used
to print documents across networks such as the Internet using HTTP or HTTPS.

Creating a Printer
The main CUPS configuration files are /etc/cups/cupsd.conf and
/etc/cups/printers.conf. Since the format of these files is rigid, you should use
the lpadmin command or a graphical tool to configure new printers, such as the
GNOME Printers tool (Activities > Show Applications > Settings > Printers):

By default, when you plug in a USB printer, the CUPS daemon will automatically create
a printer device for it with the model name of the printer as the printer name. If other
printers are detected (e.g. network shared printers, or printers on a parallel or serial port),
you can add them in the Printers tool by clicking on the + symbol and supplying the
appropriate information during the wizard (location of the printer, printer name, printer
driver, and so on.). There are many types of printer locations that you can configure in
this wizard:
• Locally-connected printers (Parallel, USB, Serial, etc.). These ports for these
printers are normally auto-detected but can be manually added if you specify the
correct URI (Universal Resource Identifier). For example, the URI for a parallel
port is parallel:///dev/lp0 (or usb:///dev/lp0 if it supports USB
translation).
• Shared printers on the network using the Internet Printing Protocol (IPP).
• Shared printers on the network using the UNIX Line Printer Daemon (LPD)
protocol.
• Shared printers on the network using the Windows Server Message Blocks
(SMB) or Common Internet File System (CIFS) protocol.
• Network-connected printers using the Appsocket or HP Jetdirect protocol (TCP
port 9100).
- 120 -

However, the most comprehensive way to create and manage printers on any Linux
system is by using the CUPS Web Administration Tool. To access this tool, type
http://localhost:631/admin in a web browser and log in as the root user:

You can easily add printers easily, as well as printer classes for groups of printers that
you’d like to manage as a single unit. When you click Add Printer, a wizard will prompt
you for the necessary information regarding your printer. If you enable printer sharing,
hosts that support IPP (Windows, Linux, macOS) can then print to your shared printer
using the URL http://hostname/printers/printername.

Managing Printers
Once a printer has been created, you can create and manage printers with the CUPS Web
Administration Tool by navigating to the Printers tab, as well as view printer statistics
and control the printer (accept, reject, enable, disable) using the following commands:

lpstat -t (prints the status of all printers on the system)


cupsaccept p1 (accepts the printer called p1)
cupsreject –r "down" p1 (rejects the printer p1 with the reason “down”)
cupsenable p1 (enables the printer p1)
cupsdisable –r "down" p1 (disables the printer p1 with the reason “down”)

Managing Print Jobs


To print from a graphical app, you can choose the Print option from the File menu and
select your printer. Following this, you can manage or cancel your print job by navigating
- 121 -

to the Jobs tab of the CUPS Web Administration Tool. Alternatively, to print a file from
the command line, you can use the lp command followed by any options and the names
of the files to be printed. Following are some common options to the lp command:
-n 5 (prints 5 copies of the file)
-t "text" (gives the job a name of “text” which prints on the banner page)
-d p1 (prints to the destination printer called p1)
-q 100 (specifies a print job priority of 100 – print job priorities range
from 0(low) to 100(high))
-o land (prints in landscape orientation)

To print the file /etc/hosts to the printer named p1, you can run the following:

[root@localhost ~]# lp –d p1 /etc/hosts


request id is p1-5 (1 file(s))
[root@localhost ~]# _

To view the print jobs in the print queue for p1, you can use the following command:

[root@localhost ~]# lpstat p1


p1-2 root 1024 Thu 01 Dec 2011 09:36:35 AM EST
p1-3 root 1024 Thu 01 Dec 2011 09:36:41 AM EST
p1-4 root 1024 Thu 01 Dec 2011 09:36:42 AM EST
p1-5 root 1024 Thu 01 Dec 2011 09:36:59 AM EST
[root@localhost ~]# _

If you wish to remove the last print job from the print queue, you can use the cancel
command as shown below. The root user can run the command cancel –a to cancel
all pending print jobs when troubleshooting a printer issue.

[root@localhost ~]# cancel p1-5


[root@localhost ~]# _

If you don’t specify the –d option to the lp command, the system default printer is
assumed. To set the system default printer to p1, you either run the lpoptions –d p1
command, add the line default p1 to your ~/.lpoptions file, or add the line
export PRINTER=p1 to an environment file (e.g. ~/.bash_profile).

The BSD UNIX Printing System


While the CUPS printing system is common in modern Linux distributions, it is
backwards-compatible to the legacy BSD UNIX printing system (which uses the lpr
command to print files using the BSD UNIX Line Printer Daemon (LPD), the lpc
command to control the print process, and the lprm command to remove print jobs). For
example, to print 5 copies of the file /etc/hosts to the printer p1, you could use the
following command:
- 122 -

[root@localhost ~]# lpr –P p1 -# 5 /etc/hosts


[root@localhost ~]# _

To view the print jobs in the print queue for the printer p1, you can run the following:

[root@localhost ~]# lpq –P p1


p1 is not ready
Rank Owner Job File(s) Total Size
1st root 1 hosts 1024 bytes
2nd root 2 hosts 1024 bytes
3rd root 3 hosts 1024 bytes
[root@localhost ~]# _

To remove the last print job from the print queue, you can run the following:

[root@localhost ~]# lprm 5


[root@localhost ~]# lpq –P p1
p1 is not ready
Rank Owner Job File(s) Total Size
1st root 1 hosts 1024 bytes
2nd root 2 hosts 1024 bytes
[root@localhost ~]# _

To see the status of each printer on the system, you can use the following:

[root@localhost ~]# lpc status


p1:
printer is on device 'parallel' speed -1
queuing is enabled
printing is disabled
no entries
daemon present
[root@localhost ~]# _

Note from the lpc status output that BSD uses different names to identify the
printing process. When queuing is enabled, the printer accepts jobs into the print queue,
and when printing is enabled, jobs are sent from the print queue to the printer.

Exercise
1. Log into tty3 as the root user and run the following commands:
lpadmin -p p1 -E -v /dev/null -m raw (creates a fake printer that prints to /dev/null)
lpadmin -d p1 (sets the system default printer to p1)
cupsaccept p1
cupsdisable p1 (this holds print jobs in the print queue)
lpstat -t
less /etc/cups/printers.conf
lp /etc/issue
lpstat
ls /var/spool/cups
- 123 -

lpr /etc/issue
lpq
ls /var/spool/cups
lpc status
lpstat
cancel p1-1 p1-2
lpstat

2. Log into the GNOME desktop environment as the woot user and explore the graphical
Printers tool as well as the CUPS Web Administration Tool.

5.4 Storage
On Windows systems, the filesystem (e.g. NTFS, FAT32, exFAT) on a storage device is
mounted to a drive letter starting with C: (A: and B: are reserved for the two floppy
drives on the first IBM PC). Most Windows PCs have a single storage device that
contains one large filesystem mounted to C:, and the root directory on this filesystem is
referred to as C:\ (\ is the root directory on C:). To access the filesystem on a DVD or
USB flash drive, you’d access a different drive letter (e.g. D:\).

However, on UNIX and Linux systems, the filesystem on a storage device (e.g. ext4, xfs,
vfat, exfat) is instead mounted to a directory under the root directory (/), and there is
only one root directory per system. Thus, to access the filesystem on a DVD or USB flash
drive, you must mount it to a directory under the root directory (/) and then access that
directory to see what is on the DVD or USB flash drive.

Referring to Storage Devices using Device Files


Every storage device in Linux is represented by a block device file in the /dev directory.
For example, /dev/sda refers to your first hard disk, SSD, or USB flash drive, while
/dev/nvme0 refers to your first NVMe SSD. Since NVMe SSDs support advanced
namespace divisions, you’ll likely need to refer to your first NVMe SSD as
/dev/nvme0n1 (the first namespace on the first NVMe SSD). Following is a summary
of common block device files for storage devices:
/dev/sda First SCSI/SATA hard disk drive, SSD, or USB flash drive
/dev/sdb Second SCSI/SATA hard disk drive, SSD, or USB flash drive
/dev/sdc Third SCSI/SATA hard disk drive, SSD, or USB flash drive
/dev/nvme0n1 First namespace division on the first NVMe SSD
/dev/nvme1n1 First namespace division on the second NVMe SSD
/dev/scd0 First SCSI/SATA CD/DVD drive
/dev/sr0 First SCSI/SATA CD/DVD drive with recording ability
/dev/cdrom A shortcut to the device file for the CD/DVD drive in your system
/dev/loop0 The first loopback interface (used for mounting ISO image files)
- 124 -

Except for DVDs (whose contents must be written using DVD burning software), you
must first create at least one partition on a storage device in order to format it with a
filesystem. On legacy hard disks, SSDs and USB flash drives under 2TB in size that use a
Master Boot Record (MBR) to store partition information, there may be up to 4 primary
partitions, but one of these primary partitions can be labelled as an extended partition
and further subdivided into logical drives. For /dev/sda, these partitions would be:
/dev/sda1 1st primary partition
/dev/sda2 2nd primary partition
/dev/sda3 3rd primary partition
/dev/sda4 4th primary partition (labelled as an extended partition)
/dev/sda5 1st logical drive in the extended partition
/dev/sda6 2nd logical drive in the extended partition
/dev/sda7 3rd logical drive in the extended partition
and so on…

On modern hard disks, SSDs, and USB flash drives that use a GUID Partition Table
(GPT) to store partition information, you can have up to 128 primary partitions
(/dev/sda1 to /dev/sda128). Consequently, there is no need for extended
partitions or logical drives. Most users only create one partition on a storage device, thus
if you have a single hard disk (/dev/sda) and you insert a USB flash drive into your
computer, the filesystem on it will be on the first partition, /dev/sdb1.

MORE INFORMATION ABOUT DEVICE FILES:


Device files are used to represent device drivers that are compiled into the Linux kernel
and are created automatically when the udev daemon detects new devices at system
startup, or when they are inserted into the system (e.g. USB devices).

Block device files are used to represent storage devices (that can transfer information
block-by-block to a filesystem) whereas character device files represent all other device
types, including /dev/lp0 (the first parallel port on the system), /dev/ttyS0 (the
first serial port on the system), /dev/tty1 (the first terminal on the system),
/dev/bus/usb/* (USB-connected devices), /dev/null (which deletes anything
sent to it), /dev/zero (produces a continuous stream of NULL/blank output), and
/dev/urandom (generates random numbers).

Both block and character device files consist only of inodes (they have no data blocks).
As a result, a long listing (ls -l) of a device file shows major number,minor
number in place of the file size. The major number locates the device driver in the
Linux kernel, whereas the minor number is the instance of the device. For example, a
long listing of /dev/sda1 will show 8,1 in place of the file size. This indicates a
major number of 8 (8th device driver in the kernel) and a minor number of 1 (first
partition on the device). Similarly, a long listing of /dev/sda2 will show 8,2 (8th
device driver in the kernel, second partition on the device).
- 125 -

Linux Filesystem Types


Table 5-3 lists the most common filesystems used on Linux systems.

Table 5-3 Common Linux Filesystems


btrfs B-tree File System – similar to zfs, but still in development.
exfat Extended FAT filesystem – often used on large removeable storage
devices such as USB hard drives and USB flash drives.
ext4 Fourth extended filesystem – the default filesystem used on most
Linux systems today, and the successor of ext3 and ext2.
iso9660, These filesystem are used for mounting CDs and DVDs, as well as
udf ISO image files that contain a CD or DVD filesystem.
vfat The DOS FAT filesystem with long file name support (it can
support up to FAT32). It is typically used only on USB flash drives.
xfs The X File System – a high-performance filesystem originally
created by Silicon Graphics that is very common on Linux systems.
zfs The Zettabyte File System (ZFS) – a very high-performance,
feature-rich filesystem commonly used on large Linux systems.

All of the filesystems listed in Table 5-3 (except for exfat, vfat and ext2) are journaling
filesystems. A journaling filesystem records filesystem changes to a circular log on the
filesystem itself before performing them. Thus, if the power fails during a file copy, at the
next startup the system will know what happened just before the power failed and
complete the copy operation.

Viewing and Mounting Filesystems


To see which filesystems are currently mounted on your system, you can use the df (disk
free space by mounted filesystem) command with the -h (human-readable) and -T
(show filesystem type) options:

[root@localhost ~]# df -hT


Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 930M 0 930M 0% /dev
tmpfs tmpfs 950M 0 950M 0% /dev/shm
tmpfs tmpfs 380M 596K 380M 1% /run
/dev/sda4 xfs 50G 9.3G 41G 19% /
tmpfs tmpfs 950M 0 950M 0% /tmp
/dev/sda3 xfs 192G 139G 53G 73% /home
/dev/sda2 ext4 477M 203M 245M 46% /boot
/dev/sda1 vfat 200M 8.5M 192M 5% /boot/efi
[root@localhost ~]# _

The output above tells us that the xfs filesystem in the 4th partition on /dev/sda is
mounted to the / directory, the xfs filesystem in the 3rd partition on /dev/sda is
mounted to the /home directory, the ext4 filesystem in the 2nd partition on /dev/sda is
mounted to the /boot directory (which stores the Linux kernel), and the vfat filesystem
- 126 -

in the 1st partition on /dev/sda is mounted to the /boot/efi directory (which stores
the UEFI boot loader program that loads the kernel). The other filesystems shown are
special temporary filesystems used internally by Linux and can be ignored.

Alternatively, you can use the lsblk (list block devices) command to see which block
devices are available on the system and where they are mounted:

[root@localhost ~]# lsblk


NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 250G 0 disk
├─sda1 8:1 0 200M 0 part /boot/efi
├─sda2 8:2 0 477M 0 part /boot
├─sda3 8:3 0 192G 0 part /home
└─sda4 8:4 0 50G 0 part /
sr0 11:0 1 2.1G 0 rom
[root@localhost ~]# _

NOTE: You may also see a swap partition in the output of lsblk ([SWAP] under the
MOUNTPOINT column). Swap partitions do not contain a filesystem and serve as virtual
memory when the system has inadequate physical memory for running programs.
Modern Linux kernels allocate memory in a way that avoids the use of swap where
possible, and use an Out Of Memory (OOM) killer to stop low priority processes when
the system is low on available memory. Consequently, most Linux servers do not have a
swap partition. Swap partitions are typically used on Linux workstations only, where
abundant memory is less common.

The DVD drive (/dev/sr0) is displayed by lsblk, but since there is no DVD in the
DVD drive, it is not mounted to a directory. If you insert a DVD into this drive while in a
desktop environment, the automounter daemon will automatically mount the DVD
filesystem to /run/media/username/devicelabel and place a shortcut icon to
it on your desktop or within your graphical file browser app. The automounter daemon
only mounts removeable media (DVDs, USB flash drives). If you are not in a desktop
environment, you could manually mount the filesystem on a DVD or USB flash drive
using the mount command. The following example mounts the filesystem on a USB
flash drive (/dev/sdb1) to the /mnt directory:

[root@localhost ~]# mount /dev/sdb1 /mnt


[root@localhost ~]# df -hT
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 930M 0 930M 0% /dev
tmpfs tmpfs 950M 0 950M 0% /dev/shm
tmpfs tmpfs 380M 596K 380M 1% /run
/dev/sda4 xfs 50G 9.3G 41G 19% /
tmpfs tmpfs 950M 0 950M 0% /tmp
/dev/sda3 xfs 192G 139G 53G 73% /home
/dev/sda2 ext4 477M 203M 245M 46% /boot
/dev/sda1 vfat 200M 8.5M 192M 5% /boot/efi
/dev/sdb1 exfat 64G 0 64G 0% /mnt
[root@localhost ~]# _
- 127 -

The /mnt directory is an empty directory on Linux systems that is intended as a


convenient way to quickly mount removable media. While you can mount a filesystem to
any directory, you should ensure that the directory is empty. If you mount a filesystem to
a directory that contains files, those files will be inaccessible until the filesystem is
unmounted.

You can now copy files to your USB flash drive by copying them to /mnt:

[root@localhost ~]# cp -R reallybigfile letter pro* /mnt


[root@localhost ~]# ls /mnt
letter proposal1 proposal2 reallybigfile
[root@localhost ~]# df -hT
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 930M 0 930M 0% /dev
tmpfs tmpfs 950M 0 950M 0% /dev/shm
tmpfs tmpfs 380M 596K 380M 1% /run
/dev/sda4 xfs 50G 9.3G 41G 19% /
tmpfs tmpfs 950M 0 950M 0% /tmp
/dev/sda3 xfs 192G 139G 53G 73% /home
/dev/sda2 ext4 477M 203M 245M 46% /boot
/dev/sda1 vfat 200M 8.5M 192M 5% /boot/efi
/dev/sdb1 exfat 64G 989M 63G 2% /mnt
[root@localhost ~]# _

To safely unmount your USB flash drive before physically removing it from your system,
you can use either the umount /dev/sdb1 or umount /mnt command. If any user
is using the /mnt directory or has open files on the USB flash drive, Linux will warn
you that the device is busy. You can use the fuser -u /mnt command to see if
anyone is using the /mnt directory.

NOTE: You can also mount ISO images that you download from the Internet on Linux
to access the files within. For example, the mount –o loop –r -t iso9660
project.iso /mnt command mounts the files within project.iso to /mnt.

Creating Partitions and Filesystems


To add an additional hard disk or SSD to a Linux system, you must create at least one
partition on it, and then format that partition with a filesystem. While there are many
different partitioning tools in Linux, the most common one is fdisk. The following
example creates a new 3GB partition on /dev/sdb using fdisk:

[root@localhost ~]# fdisk /dev/sdb


Command (m for help): m
Command action
a toggle a bootable flag
d delete a partition
l list known partition types
m print this menu
n add a new partition
- 128 -

p print the partition table


q quit without saving changes
v verify the partition table
w write table to disk and exit
Command (m for help): p
Disk /dev/sdb: 8075 MB, 8075120640 bytes
255 heads, 63 sectors/track, 981 cylinders, total 15771720 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

Device Boot Start End Blocks Id System

Command (m for help): n


Partition type:
p primary (0 primary, 0 extended, 4 free)
e extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-15771719, default 2048):
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-15771719, default
15771719): +3G

Command (m for help): p


Disk /dev/sdb: 8075 MB, 8075120640 bytes
255 heads, 63 sectors/track, 981 cylinders, total 15771720 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

Device Boot Start End Blocks Id System


/dev/sdb1 2048 6293503 3145728 83 Linux

Command (m for help): w


The partition table has been altered!
Syncing disks.
[root@localhost ~]# _

After using fdisk to edit the disk that holds your / filesystem, you should run the
partprobe command to reload the partition table. Following this, you can run the
fdisk -l /dev/sdb command to list the partition you created on the device.

To format this new partition with the ext4 filesystem, you can run the mkfs command:

[root@localhost ~]# mkfs –t ext4 /dev/sdb1


mke2fs 1.43.8 (1-Jan-2018)
Creating filesystem with 732416 4k blocks and 183264 inodes
Filesystem UUID: c5ce0e70-8eaf-4edc-56d17ca62
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
[root@localhost ~]# _
- 129 -

Note from the previous output that mkfs assigned a UUID (Universally Unique ID) to
the filesystem. This UUID can be used to identify the filesystem at system startup, even if
you move it to a different location in your computer.

To mount the filesystem on /dev/sdb1 to a new directory called /data and verify the
results, you can run the following commands:

[root@localhost ~]# mkdir /data


[root@localhost ~]# mount /dev/sdb1 /data
[root@localhost ~]# df -hT | grep sdb1
/dev/sdb1 ext4 3G 10M 2.9G 1% /data
[root@localhost ~]# _

To ensure that the filesystem is mounted automatically at system startup, you can add the
following line to the /etc/fstab (filesystem table) file:

[root@localhost ~]# tail -1 /etc/fstab


/dev/sdb1 /data ext4 defaults 1 1
[root@localhost ~]#_

device to mount mount point filesystem mount options dump fsck


type

The default mount options mount the filesystem read-write at system startup, and the
1 in the dump and fsck fields allow the filesystem to be automatically backed up with
the dump utility and checked for errors with the fsck utility (specifying 0 disables this
functionality). You can also replace /dev/sdb1 with the filesystem UUID to ensure
that the system can find the filesystem even if you move the physical location of the
storage device in the computer. To automatically activate a swap partition at boot time,
you can specify swap for the mount point and filesystem type.

NOTE: Some other mount options you can use instead of defaults include ro (mount
the filesystem readonly), async (use asynchronous I/O to and from the device),
nosuid (prevent the use of SUID/SGID bits on the device), nouser (prevent non-root
users from mounting the device), owner (allow mounting by non-root users only if they
are the owner of the device file), and noauto (do not automatically mount it at startup).

NOTE: If you do not specify a device file or mount point directory when mounting a
device, the associated line in /etc/fstab is consulted. For example, if you run the
mount /dev/sdb1 command, the mount command will look for the /dev/sdb1
line in /etc/fstab and automatically mount it to /data).
- 130 -

Using the LVM


In the previous section, we created a partition on /dev/sdb, formatted it with ext4 and
mounted it to the /data directory. This is called standard partitioning. Most modern
Linux systems use a different method of working with storage devices called the Logical
Volume Manager (LVM).

With the LVM, you can assign newly-created partitions (called physical volumes, or
PVs) on several different storage devices to the LVM so that it can create pools of
available storage called volume groups (VGs). Next, you can create logical volumes
(LVs) from these pools of available storage that are formatted with a filesystem and
mounted to a directory. The benefit of this method is that you can easily add storage
devices over time that can be used to extend the capacity of your VGs and LVs.

Say, for example, that you add two new 2TB hard disks to your system (/dev/sdb and
/dev/sdc) and use fdisk to create a new partition on each one that uses all of the
available space. To use these partitions with the LVM, you could run the following
command to label them as PVs:

[root@localhost ~]# pvcreate /dev/sdb1 /dev/sdc1


Writing physical volume data to disk "/dev/sdb1"
Physical volume "/dev/sdb1" successfully created
Writing physical volume data to disk "/dev/sdc1"
Physical volume "/dev/sdc1" successfully created
[root@localhost ~]#_

Next, you can create a VG using the available space from both PVs. The following
command creates a VG called vg00 from the /dev/sdb1 and /dev/sdc1 PVs:

[root@localhost ~]# vgcreate vg00 /dev/sdb1 /dev/sdc1


Volume group "vg00" successfully created
[root@localhost ~]#_

To see the total size of your VG, you can run the vgdisplay command. If the output of
the vgdisplay command indicates that you have 4TB of combined space available,
you could create a LV called that uses 4TB of space. The following commands create a
4TB LV called data from the available storage space in vg00, format the LV with the
ext4 filesystem, and then mount it to the /data directory:

[root@localhost ~]# lvcreate -L 4TB -n data vg00


Logical volume "data" created
[root@localhost ~]# mkfs -t ext4 /dev/vg00/data
mke2fs 1.43.8 (1-Jan-2018)
Creating filesystem with 732416 4k blocks and 183264 inodes
Filesystem UUID: fbca-4b1c-8a5f-606d66d56717
Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
- 131 -

Writing superblocks and filesystem accounting information: done


[root@localhost ~]# mkdir /data
[root@localhost ~]# mount /dev/vg00/data /data
[root@localhost ~]# df -hT | grep data
/dev/mapper/vg00-data ext4 4T 10M 4T 0% /data
[root@localhost ~]# _

NOTE: While /dev/vg00/data is used to refer to the data LV in the vg00 VG


within commands, the device mapper component of the Linux kernel displays this as
/dev/mapper/vg00-data in the output of commands.

Now, say that you wanted to add another 2TB to your data LV. To do this, you could
add another 2TB storage device (/dev/sdd), create a single partition on it using
fdisk that uses all available space, and then run the following commands to attribute it
to the LVM as a PV, extend the vg00 VG to include it, and extend the data volume to
use the additional space (resizing the filesystem in the process):

[root@localhost ~]# pvcreate /dev/sdb1 /dev/sdd1


Writing physical volume data to disk "/dev/sdd1"
Physical volume "/dev/sdd1" successfully created
[root@localhost ~]# vgextend vg00 /dev/sdd1
Volume group "vg00" successfully extended
[root@localhost ~]# lvextend –L +2TB –r vg00/data
Extending logical volume data to 6 TB
Logical volume data successfully resized
[root@localhost ~]# df -hT | grep data
/dev/mapper/vg00-data ext4 6T 10M 6T 0% /data
[root@localhost ~]# _

Checking Filesystems
Any filesystem other than zfs (which prevents corruption by design) may encounter
errors over time. If you encounter files on these filesystems that cannot be read or contain
corrupted data, you should run a filesystem check on the device file once the filesystem is
unmounted (running a check on a mounted filesystem can further corrupt it). For
example, to perform a full check and repair of the filesystem on /dev/vg00/data
shown earlier, you could run the fsck -f /dev/vg00/data command. After
running this command you should immediately run the echo $? command to display
the exit status of the previous command (fsck). An exit status of 4 indicates that fsck
couldn’t fix all of the errors. In this case, you should run the same fsck and echo
commands again until you obtain an exit status of 0. If there are files that fsck cannot
repair, it places them in a lost+found directory under the root of the filesystem and
renames them to the file’s inode number.

NOTE: The btrfs and xfs are not supported by fsck. You can use btrfs check
/dev/vg00/data to check and repair a btrfs filesystem, or xfs_repair
/dev/vg00/data to check and repair an xfs filesystem.
- 132 -

Filesystem Quotas
Quotas limit the amount of space that a user can use when storing data on a filesystem, or
the number of inodes (files and directories) that they can create. There are two types of
quota limits: a hard limit cannot be surpassed, whereas a soft limit may be surpassed for
a period of time (7 days by default). Quota configuration is filesystem-specific and
requires several steps. For example, to configure filesystem quotas on an ext4 filesystem
on /dev/vg00/data, you can perform the following tasks:

1. Add the usrquota and grpquota mount options in /etc/fstab:


/dev/vg00/data /data ext4 defaults,usrquota,grpquota 0 0

2. Run the mount /data -o remount,rw command to remount the filesystem


using the new mount options.

3. Run the quotacheck –mavugf –F vfsv0 command to create the quota


database files /data/aquota.user and /data/aquota.group.

4. Run the quotaon /data command to turn quotas on for the /data filesystem.
(You can instead use the quotaoff /home command to turn them off.)

5. Edit quota settings for each user or group you wish to restrict. For example, to set
quotas for the accounting group, you could run edquota -g accounting, and
to set quotas for the woot user, you could run edquota –u woot as shown below.
This will open the vi editor and allow you to specify soft and hard limits for the
number of 1KB blocks and the number of inodes (files and directories created):

Disk quotas for user woot (uid 500):


Filesystem blocks soft hard inodes soft hard
/dev/vg00/data 1208 0 0 411 0 0
~
"/tmp//Pd3.nT4rxxb" 3L, 216C

6. Optionally run edquota –u –t to modify the default time users may surpass their
soft quota limits, or edquota –g –t to modify the default time group members
may surpass their soft quota limits.

7. Monitor your quota configuration by filesystem or user. For example repquota


/data will display quota information for the /data filesystem, quota –u woot
will display quota information for the woot user only on any filesystem that has
quotas configured.

NOTE: You can also use journaled quotas on modern Linux kernels, which protects
quota data during an unexpected shutdown. To do this, use these mount options in Step 1:

defaults,usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0
- 133 -

Storage Summary
The commands and files we introduced in this section demonstrate core Linux storage
management. However, there are many different storage-related commands, files and
directories available. The following tables summarize them for exploration and reference.

Table 5-4 Common Linux Storage Commands


mknod Creates device files manually. For example, mknod
/dev/sda1 b 8 1 will create /dev/sda1 as a block
device file with a major number of 8 and a minor number of 1.
udevadm Used to create rules that control how the udev daemon
automatically creates device files (e.g. for USB flash drives).
These rules are stored in the /etc/udev/rules.d
directory and may be used to restrict the ability for users to
mount removable media, or automatically run a script
following the mounting of removable media.
fdisk Creates and manages partitions on both MBR and GPT disks.
fdisk -l Displays the partitions on an MBR or GPT disk.
partprobe Reloads the partition table into memory.
gdisk Creates and manages partitions on GPT disks.
cfdisk A visual tool that can create and manage partitions on MBR
and GPT disks. It resembles the Windows fdisk command.
parted The GNU parted utility, which can create and manage
partitions on both MBR and GPT disks.
mkfs Creates filesystems on partitions. There are many variants of
this command, including mkfs.ext4 (creates ext4
filesystems), mkfs.vfat (creates FAT filesystems),
mkfs.xfs (creates xfs filesystems), and e2mkfs (creates
ext2, ext3 and ext4 filesystems).
mkisofs Creates an ISO image that contains one or more files or
directories. For example, mkisofs -RJ -o
myimage.iso /stuff creates a Joliet/RockRidge-
compliant ISO image called myimage.iso that contains all
of the files within the /stuff directory.
mkswap Prepares a partition for use as virtual memory (swap).
swapon Activates a swap partition for use.
swapon -a Activates all swap partitions listed in /etc/fstab.
swapoff Deactivates a swap partition.
mount Used to mount filesystems on a device to a directory (e.g.
mount -t xfs /dev/sdc1 /data). If you omit the -t
option, the filesystem type is autodetected. Without
arguments, mount displays mounted filesystems, and mount
-a mounts all filesystems listed in /etc/fstab.
umount Unmount a filesystem from a directory. You can specify either
the device file or directory name as an argument.
- 134 -

eject Unmounts the CD/DVD device on the system and ejects the
CD/DVD from the CD/DVD drive.
df Displays disk free space by mounted filesystem.
df -hT Adds filesystem type and easy-to-read formats to df output.
df -i Displays the inode usage for mounted filesystems (the number
of inodes within the inode table is set during filesystem
creation – if you run out of inodes, you cannot create new files
or directories)
du Displays disk usage by directory (e.g. du -ks /mnt shows
the total size of the /mnt directory in Kilobytes).
lsblk Lists block devices and the directory they are mounted to.
lsblk --fs Lists block devices, their filesystem types and UUIDs.
blkid Lists the UUIDs for block devices on the system.
lsusb Displays USB devices (including USB storage devices).
lsscsi Displays SCSI and NVMe devices.
nvme Displays and configures NVMe device settings.
fuser -u Displays information about filesystem usage. For example, the
fuser -u /mnt command shows which users are using
files within the /mnt directory).
pvcreate Creates LVM physical volumes (PVs).
pvdisplay Displays LVM PVs.
pvscan Displays a short summary of LVM PVs.
pvs Displays LVM PV statistics.
pvremove Removes LVM PVs.
vgcreate Creates LVM volume groups (VGs).
vgextend Extends LVM VGs to include additional PVs.
vgdisplay Displays LVM VGs.
vgscan Displays a short summary of LVM VGs.
vgs Displays LVM VG statistics.
vgremove Removes LVM VGs.
lvcreate Creates LVM logical volumes (LVs).
lvextend Extends LVM LVs to include additional space from VGs.
lvresize Resizes LVM LVs.
lvchange Sets LVM LV attributes (e.g. compression, deduplication).
lvdisplay Displays LVM LVs.
lvscan Displays a short summary of LVM LVs.
lvs Displays LVM LV statistics.
lvremove Removes LVM LVs.
fsck Checks and repairs most filesystems. Use -f for a full check.
btrfs Configures, manages, checks and repairs btrfs filesystems.
btrfsck Checks and repairs btrfs filesystems.
xfs_repair Checks and repairs xfs filesystems.
badblocks Checks hard disks for bad blocks.
fstrim TRIMs SSDs (reclaims unused blocks). It is scheduled weekly
by Systemd (systemctl status fstrim.timer).
- 135 -

quotacheck Updates the quota database files.


quotaon Turns quotas on for a mounted filesystem.
quotaoff Turns quotas off for a mounted filesystem.
edquota Edits quota limits.
repquota Displays a report of quotas for a filesystem.
quota Displays quota information for a user or group.
e2label Adds a label (description) to ext2, ext3 and ext4 filesystems.
fatlabel Adds a label to vfat filesystems.
exfatlabel Adds a label to exfat filesystems.
xfs_admin -L Adds a label to xfs filesystems.
e2image Takes a sector-based image of an ext2, ext3 or ext4 filesystem.
dumpe2fs Shows detailed information for ext2, ext3 or ext4 filesystems.
resize2fs Can resize ext2, ext3 or ext4 filesystems.
tune2fs Modifies ext2/3/4 filesystem parameters (e.g. write caching).
xfs_info Shows detailed information for xfs filesystems.
xfs_growfs Can resize xfs filesystems.
xfs_admin Modifies filesystem parameters for xfs filesystems.
xfs_fsr Optimizes the performance of xfs filesystems.
xfs_quota Configures xfs filesystem quotas.

Table 5-5 Common Linux Storage-Related Files


/etc/mtab Contains a list of mounted filesystems.
/etc/filesystems Contains a list of supported filesystems.
/etc/fstab Contains a list of filesystems that are auto-mounted at
system startup.

Table 5-6 Common Linux Storage-Related Directories


/mnt An empty directory often used to manually mount removable
media and ISO images.
/run/media Contains subdirectories that the automounter daemon creates
to automatically mount removeable media within a desktop
environment.
/sys/block Contains subdirectories for each block device that stores
associated Linux kernel settings.
/sys/class Contains subdirectories with settings for each storage bus on
the system. If you insert a new Serial Attached SCSI (SAS)
disk into a running server, you can run the echo "- - -"
> /sys/class/scsi_host/host0/scan command to
force your SCSI bus to rescan all devices and detect it.
/dev/disk Contains udev-created shortcuts to storage devices by:
-Filesystem UUID (/dev/disk/by-uuid).
-Partition UUID (/dev/disk/by-partuuid).
-Filesystem label (/dev/disk/by-label).
-Kernel ID (/dev/disk/by-id).
-PCI bus ID (/dev/disk/by-path).
- 136 -

Exercise
1. Log into tty3 as the root user and run the following commands:
ls -l /dev/sda* (note the block file type, major number and minor numbers)
ls -l /dev/tty[0-6] (note the character file type, major number and minor numbers)

2. Log into tty1 as the woot user. Next, in the settings for your virtual machine within
your virtualization software, insert your Fedora ISO image into the virtual DVD drive. In
the GNOME desktop, navigate to Activities > Files and note that your DVD is shown as
mounted.

3. Return to tty3 and run the df -hT command. Note which standard partitions are
mounted to /, /home and /boot. Also note that the virtual DVD has been automounted to
/run/media/woot/label (where label is the label on the Fedora DVD). Next, run the lsblk
command to view the same information noting the standard partition used for swap
(which is not mounted). Finally run the ls /sys/block and less /proc/devices commands to
see similar information. These partitions should match the device files you saw in Step 1.

3. Run the following commands:


ls /run/media/woot/label (use the label from Step 2)
umount /run/media/woot/label
df -hT (verify that the virtual DVD is no longer mounted)
mount /dev/cdrom /mnt (/dev/cdrom is a symlink to your CD/DVD device file)
ls /mnt (note the DVD contents)

4. Run the fdisk /dev/sda command. Next, type p to print your partition table, and then n
to create a new partition (choose the next available partition number and follow the
prompts to use the remainder of the space on your storage device). Type p again to verify
and note your new partition number (n) for the next step and then type w to save your
changes and quit fdisk. Finally run partprobe to ensure your partition table is reloaded.

5. Run the following commands:


mkfs -t ext4 /dev/sdan (where n is from the previous step)
mkdir /data
mount /dev/sdan /data
lsblk
cp -R /root/classfiles /data
ls /data
ls /data/classfiles
df -hT
umount /data
pvcreate /dev/sdan (choose y to remove the existing ext4 filesystem)
vgcreate vg00 /dev/sdan
vgdisplay (note the value next to VG Size in GB for creating LVs)
lvcreate -L size -n data vg00 (where size is the VG Size from the last command)
mkfs -t ext4 /dev/vg00/data
mount /dev/vg00/data /data
- 137 -

lvdisplay
lsblk
df -hT

6. Edit the /etc/fstab file using a text editor and add the following line to automatically
mount your data LV at system startup:
/dev/vg00/data /data ext4 defaults 0 0

7. Run the reboot command. When the system has started, log into tty3 as the root user
and run the lsblk command to verify that the data LV is mounted.

8. In your virtualization software, add a second virtual hard disk to your Fedora Linux
virtual machine (this will be /dev/sdb). Next, run the lsblk command to verify that Linux
detects the additional storage device. Following this, run the fdisk /dev/sdb command
and create a single partition on /dev/sdb that uses all available storage. Next, run the
following commands to extend your /data filesystem to use the additional space:
pvcreate /dev/sdb1
vgextend vg00 /dev/sdb1
vgdisplay (note the additional space available in the vg00 pool)
lvextend -L +size –r vg00/data (where size is the additional space)
lsblk
df -hT (note that your /data volume is now much larger!)

9. Run the following commands to check your /data volume for errors:
umount /data
fsck -f /dev/vg00/data
echo $? (note that you receive 0, which indicates that no other errors exist)
mount /data (this works because of the line in /etc/fstab)

10. Run the following commands to create an ISO image of your Poems and mount it:
cd classfiles
mkisofs -RJ -o poems.iso Poems
ls -l poems.iso
mount -o loop -r -t iso9660 poems.iso /mnt
lsblk
df -hT
ls /mnt
ls -R /mnt
umount /mnt

11. As a challenge, follow the steps outlined in this section to configure quotas for your
/data filesystem (assigning the appropriate limits to users of your choice).
- 138 -

5.5 Server Installation


Most systems that run Web apps are Linux servers in the cloud, either installed bare
metal, or within a virtual machine or container. Similarly, most Linux systems that you
install on premises within an organization will be run bare metal, or within a virtual
machine or container. If you are installing Linux bare metal, it will likely be installed on
a rackmount server within a rack. Most racks come with one or more UPSes, the shared
monitor/keyboard/mouse (which usually folds away into the rack), and room for between
8-16 servers. The minimum height of a server is 1.75” (called 1U). Most 1U servers have
up to 4 hard disks or 8 SSDs and 1 or 2 CPUs. Thicker servers take up more than one
spot on the rack; 2U (2x1U) and 4U (4x1U) servers often have between 2 and 4 CPUs
and usually up to 12 hard disks or 48 SSDs. The first two storage devices (typically
SSDs) will be pre-configured as a RAID 1 in your BIOS/UEFI and will where you install
your Linux operating system (the two devices in the RAID 1 are displayed as a single
device to the Linux installation – e.g. /dev/sda). Remaining storage devices will likely
be configured as a software RAID, btrfs or zfs volume (discussed later), and will contain
the data that that is used by the server services configured on the server (e.g. file server
data, Web server data, and so on).

To install a Linux server bare metal, you must burn your Fedora installation ISO file to a
DVD, or image it to a USB flash drive that you can physically boot from on the
rackmount server. For Fedora, you can download and install the Fedora Media Writer
from https://getfedora.org/ on an existing Windows or macOS workstation and run it. The
Fedora Media Writer will download the ISO image for the version of Fedora you want as
well as image it to a USB flash drive. Alternatively, you can download the ISO image
- 139 -

manually for the version of Fedora you want from https://getfedora.org/ and manually
burn it to a DVD (using disc burning software) or image it to a USB flash drive using a
program such as rawrite.exe (Windows) or dd (Linux/macOS). For example, if you
have downloaded the Fedora-WS-Live-34-1-2.iso image file on your Linux
system, you could image it directly to a USB flash drive (e.g. /dev/sdb) using the
command dd if=Fedora-WS-Live-34-1-2.iso of=/dev/sdb. Your flash
drive will no longer have a filesystem, but it will be bootable and contain the contents of
the DVD ISO image.

Of course, if you are installing Linux within a virtual machine, you can simply attach the
downloaded ISO image to the virtual DVD drive within virtual machine settings and boot
the system from it.

You will notice that there is a desktop-focused ISO image and server-focused ISO image
available for download on most Linux distribution websites (including Fedora and
Ubuntu). This is because Linux servers often do not have a desktop environment
installed. Instead, you configure the system entirely from a shell or Web-based console,
such as Cockpit. By not running a graphical desktop, Linux servers require far less
memory (e.g. 1GB of instead of 3GB). Thus, if you download the server-focused ISO
image of Fedora, it will not contain software packages for a desktop environment or
productivity software, but instead contain software packages for network services
commonly used on a Linux server.

Up until this section, you have used the desktop-focused version of Fedora Linux, and the
ISO image you used to install Fedora within a virtual machine in Chapter 1 allowed you
to try a “live” memory-resident version of Fedora or install it to your hard drive when
you booted your virtual machine from it:

When you choose Install to Hard Drive, a graphical installer program guided you through
the remainder of the installation process, including the partition and filesystem
configuration.
- 140 -

When installing a server-focused version of Linux, however, you typically receive a text-
based installation program designed only to install Linux to your system. In this section,
you will install the Ubuntu Server distribution. This will allow us to:
• Review the storage concepts discussed in the previous section.
• Apply concepts to different distributions in future chapters.
• Examine some key differences between Fedora and Ubuntu, such as different
package managers.

NOTE: Any Linux distribution can be used as a server. For example, the Fedora
Desktop installation that you performed at the beginning of the course can be a Linux
server as long as you install software packages that provide network services.

NOTE: You can also install Linux across a network from a Preboot execution
environment (PXE) server in much the same way Windows administrators deploy
Windows workstations using Windows Deployment Services (WDS). DNSMASQ is a
common PXE server package for Linux systems.

Exercise
1. On your Windows or macOS computer, download the latest ISO image for Ubuntu
Server from ubuntu.com/download/server.

2. In your hypervisor software, create a new virtual machine called Ubuntu Server that
has 1GB of RAM, 64GB of virtual hard disk storage, and a connection to the Internet via
the network interface in your computer. Insert the ISO image for Ubuntu into the virtual
DVD of this virtual machine.

3. Start and then connect to your Ubuntu Server virtual machine. At the Welcome screen,
ensure that English is selected, and press Enter.

4. At the Keyboard configuration screen, select English (US) and select Done.

5. At the Ubuntu welcome screen, note the options, ensure that Install Ubuntu is
selected, and press Enter.

6. At the Network connections screen, ensure that your network interface is set to DHCP
and select Done.

7. At the Configure proxy screen, select Done.

8. At the Configure Ubuntu archive mirror screen, select Done.

9. At the Filesystem setup screen, ensure that Use an entire disk and Set up this disk as
an LVM group are selected (the default), and select Done.
- 141 -

10. Note that your installation program creates a volume group called ubuntu-vg that
contains a single logical volume called ubuntu-lv mounted to /. Navigate the menus
using your cursor keys to ensure that this ubuntu-lv uses all remaining space in the
volume group (formatted using ext4) and select Done and Continue when finished.

11. At the Profile setup screen, supply the following and select Done when finished:
Your name = woot
Your server’s name = ubuntu
Username = woot
Password = Secret555

12. At the SSH Setup, select Install OpenSSH server and select Done.

13. At the Featured Server Snaps screen, select Done.

14. At the Install complete screen, select Reboot Now. When prompted to remove your
installation medium, press Enter.

15. After the system has booted, log into tty2 as woot and run sudo passwd root to set
the root user password to Secret555.

16. Switch to the root user (su -) and run the following commands to examine your
filesystem configuration and install Cockpit:
lsblk
fdisk -l
lvdisplay
df -hT
apt install net-tools cockpit
systemctl start cockpit
ifconfig (note your IP address)

17. On your Windows or macOS host computer, open up your Web browser and access
your Cockpit console (https://IPaddress:9090). Log in as the root user, navigate the
interface and note the configuration options.

18. Switch back to tty2 and run poweroff to shut down your Ubuntu Server virtual
machine.
- 142 -

5.6 Server Storage


As discussed in the previous section, Linux servers are often installed in a rackmount
configuration. The first two local storage devices are typically SAS or NVMe SSDs that
are configured in the BIOS/UEFI as a RAID 1 for fault tolerance. Most data that the
server stores is not on these two local storage devices, but instead on a remote Storage
Area Network (SAN) device or local Redundant Array of Independent Disks (RAID)
volume (either software RAID, ZFS or btrfs).

Connecting a Linux Server to a SAN Device


There are two different SAN technologies on the market: iSCSI and Fibre Channel
(FC). iSCSI uses Ethernet to transfer data to a SAN device on the network (typically on
the same rack using the SCSI protocol. The software in the operating system that
connects to the iSCSI SAN is called the iSCSI initiator, and the storage that is shared to
the operating system by the iSCSI SAN is called the iSCSI target. After an iSCSI target
is configured, it will show up as a SCSI device file that Linux can use (e.g. /dev/sdb);
you can then partition and create filesystems as you would any other SCSI device and
add entries to /etc/fstab to mount the filesystems at boot time. Some Linux installers
allow you to specify an iSCSI target configuration. To configure an iSCSI target after
installation, you can run the iscsiadm command. For example, to connect to the iSCSI
SAN device with IP address 10.0.0.3, you run the iscsiadm -m discovery -t
st -p 10.0.0.3 command to create the connection, followed by the iscsiadm -m
node --login command to log into the iSCSI target (you will be prompted for the
password configured on the iSCSI SAN device). Finally, you add or uncomment the
node.startup = automatic line within /etc/iscsi/iscsid.conf to
ensure that the iSCSI initiator is activated at boot time.

If you have a FC SAN instead of an iSCSI SAN, you must first have a proprietary PCIe
card called a FC HBA (Host Bus Adapter) that connects to the FC SAN using fiber optic
cable. Alternatively, you can use a fibre optic Ethernet network card if your SAN
supports FCoE (Fibre Channel over Ethernet).

NOTE: A FC SAN is often called a FC switch because it can be configured to manage


access to a wide array of local and remote storage devices.

All FC storage devices use a World Wide Name (WWN) for identification (WWNs are
not FC-specific, so you may see them on SATA and SAS devices as well).

Most FC HBAs use proprietary (non-GPL) firmware. As a result, the HBA driver is
rarely installed by default during a Linux installation and can either be installed during
the installation or afterwards using the manufacturer-provided driver and instructions.
You can use the lspci or fcstat commands to see your FC HBA devices, as well as
view the entries within the /dev/disk/by-id directory to view the WWN entries,
- 143 -

each of which are symlinks to the appropriate device file (e.g. /dev/sdb) that you can
use to create partitions and filesystems.

Larger datacenters have multiple SANs, and each Linux server can have multiple
connections to each SAN for fault tolerance (in case a single link goes down) or speed
(load balancing). This configuration is called Multipath Input Output (MPIO) and is
performed by the same kernel device mapper used to connect to LVM volumes. If you
have multiple SAN connections configured, you can run the mpathconf --enable
--with_multipathd y command to start the multipath daemon, autodetect your
SAN configuration and create the necessary entries in the /etc/multipath.conf
file. After this, the device mapper will create a /dev/mapper/mpath1 file to refer to
the multiple SAN targets; you should use this file when mounting and accessing your
SAN filesystems in order to use MPIO.

Configuring Software RAID


Software RAID allows you to combine several separate storage devices together into a
single volume that can contain a filesystem. There are currently seven basic RAID
configuration levels, labeled 0 through 6. RAID 0 is called striping. If there are three
storage devices in this RAID configuration, then a file that is saved to the RAID volume
would be divided into three sections, each written simultaneously to one of the storage
devices. This allows the file to be saved in one third the amount of time. This same file
can be read in one third the amount of time for the same reason. Unfortunately, if a
storage device fails in a RAID 0 volume, all data is lost.
RAID 1 is called mirroring and provides fault tolerance in the case of a storage devices
failure. In this RAID configuration, the same data is written to two separate storage
devices at the same time. This results in two storage devices that have identical
information. If one fails, then the second (good) storage device is used automatically. The
only drawback to RAID 1 is the cost involved; you need to purchase twice the storage
space needed for your system.
RAID 2 is no longer used and was a variant of RAID 0 that allowed for error and
integrity checking on storage devices. Modern storage devices do this intrinsically.
RAID 3 is striping with a parity bit, or marker, that indicates what data is where. It
requires a minimum of three storage devices to function, and one of these storage devices
is used to store the parity information. Should one of the hard disks containing data fail,
you can replace the storage devices and regenerate the data using the parity information
stored on the parity storage device. If the parity storage device fails, then the system must
be restored from a backup device.
RAID 4 is only a slight variant on RAID 3. RAID 4 offers greater access speed than
RAID 3, as it is able to store data in blocks and thus does not need to access all storage
devices in the array at once to read data.
RAID 5 replaces RAID 3 and 4 and is the most common RAID configuration used today.
It is commonly referred to as striping with parity. As with RAID 3 and 4, it requires a
minimum of three storage devices for implementation, however the parity information is
- 144 -

not stored on a separate storage device but intermixed with data on the storage devices
comprising the set. This offers better performance and fault-tolerance; if any storage
device in the RAID configuration fails, then the information on the other storage devices
may be used to regenerate the lost information once the failed storage device has been
replaced. If two storage devices fail, then the system must be restored from a backup
device.
RAID 6 is basically the same a RAID 5 but adds a second set of parity bits for added
fault tolerance and allows up to two simultaneous storage devices drive failures while
remaining fault tolerant.
The most common RAID levels used today are RAID 0, 1 and 5. Furthermore, these
RAID levels can be combined. For example, RAID 15 (also called RAID 1+5) refers to a
RAID 5 that is mirrored (RAID 1) to another RAID 5.
In the past, storage calculations impacted server performance; thus, specialized hardware
RAID controllers were typically used to configure RAID. Today, however, RAID level 1
is often provided by the BIOS/UEFI for your operating system volume (i.e. / filesystem
on UNIX/Linux, C:\ for Windows), and all additional RAID is provided by software
within the operating system. Linux provides the md (multiple device) driver for RAID.

Modern Linux systems use the mdadm command to create, view and manage software
RAID volumes. For example, to create a RAID 5 volume from /dev/sdb, /dev/sdc,
and /dev/sdd, you could run the mdadm --create /dev/md0 --level=5 --
raid-devices=3 /dev/sdb /dev/sdc /dev/sdd command.

To see the configuration and status of your RAID 5 volume, you could view the contents
of /proc/mdstat or run the lsblk or mdadm --detail /dev/md0 commands.
RAID volume configurations on a Linux system are autodetected by the md driver at boot
time, but you can instead write the configuration to /etc/mdadm/mdadm.conf to
override detected parameters. Like any other filesystem, you can also mount RAID
volumes at boot time via entries in /etc/fstab but be sure not to use the /dev/md0
file since it sometimes gets remapped to /dev/md127 if issues occur by the md driver;
instead, list the filesystem UUID within /etc/fstab when specifying the device to mount.

Configuring ZFS
While RAID 1 and 5 can provide fault tolerance, RAID is a storage device technology
and doesn’t protect against the different types of file corruption that can occur from
writing to the filesystem, including silent data corruption, bit rot, disk firmware bugs,
phantom/misdirected writes, driver/kernel buffer errors, and accidental driver overwrites.
Zettabyte File System (ZFS) protects against these problems by putting checksums on
everything using a unique hierarchy. It detects and repairs data errors in real time, as well
as supports very large volumes with thousands of storage devices.

ZFS is a 128-bit filesystem (capacity = 256 quadrillion Zettabytes) that was initially
created by Sun Microsystems in 2001 and is often used by the largest Linux and UNIX
- 145 -

systems in the world. It is available for Solaris UNIX, macOS, Linux, and BSD UNIX.
Since ZFS does not currently have a GPL-compatible license, it cannot be bundled within
a Linux distribution, but can be easily added afterwards. On Linux, ZFS support is
maintained by zfsonlinux.org.

Performance enhancement is also a key focus for ZFS. ZFS natively supports new storage
technologies such as PCIe SSDs and storage class memory devices with ultra-low
latency. Moreover, ZFS uses three levels of caching to maximize read and write
performance: ARC (Intelligent memory caching for file access), L2ARC (non-storage
SSD read caching) and ZIL (SSD write caching). ZFS also supports nearly all enterprise
filesystem features, including deduplication (storing one copy of a file that is located in
multiple directories until one of the files change), snapshots, cloning, compression,
encryption, NFSv4, volume management, and more.

Although ZFS configuration can be quite complex when it comes to very large systems
(thousands of CPUs & storage devices), it is very easy to understand and implement on
nearly any server within a typical organization. ZFS pools are groups of physical disks
that ZFS can manage (local disks, SANs, shared devices, large raw files, etc.), and ZFS
filesystems are simply ZFS-managed filesystems that are created from ZFS pools.

To create a ZFS volume, you can use the zpool command. For example, to create a
simple ZFS volume called lala, you could run the command zpool create lala
device(s) where device(s) is a space-delimited list of device files (no partitions or
filesystem need to be on the device for ZFS to use it). This would act as the equivalent of
a RAID 0 volume, but with the file corruption prevention benefits of ZFS. The zpool
command automatically creates a ZFS filesystem on the volume and mounts it to the
/lala directory (it also creates this directory if it doesn’t exist).

To create a mirrored volume called po (the equivalent of RAID 1 but resize-able under
ZFS), you could run the zpool create po mirror devices command
specifying at least 2 devices. This will also mount the po ZFS filesystem to /po.

NOTE: A ZFS mirror needs 2+ disks to protect against single disk failure, and 5+ disks
to protect against multi-disk failure.

To create a raidz volume (the equivalent of a RAID-5 volume with a variable-sized


stripe), you could run the zpool create noonoo raidz devices command
specifying at least 3 devices. This will also mount the noonoo ZFS filesystem to
/noonoo. Because ZFS writes parity info with a variable-sized stripe, performance is
maximized, and there is no chance of the infamous RAID 5 hole (data lost in the event of
a power failure) that is possible with software RAID 5.

NOTE: A raidz needs 3+ disks to protect against single disk failure, and 7+ disks to
protect against multi-disk failure.
- 146 -

NOTE: You can also use raidz2 (double parity like RAID-6) and raidz3 (triple parity) in
place of raidz in the previous command. You need 4+ devices for raidz2 and 5+ devices
for raidz3 at minimum.

Filesystems, subdirectories and subfilesystems are managed with zfs command. This
command allows you to set a wide variety of ZFS-specific functionality including
directory size quotas and file- and directory-specific features and performance options.

NOTE: All ZFS volumes are autodetected and mounted at boot time by the ZFS kernel
modules; as a result, no lines within /etc/fstab are necessary.

Configuring Btrfs
Although ZFS is the industry standard for server storage today, the B-tree File System
(btrfs) is currently being developed as a filesystem that has some ZFS features. At the
time of this writing, btrfs doesn’t have the speed of ZFS, nor the ability to detect and
repair filesystem errors. Moreover, RAID 5 and 6 btrfs volumes are considered
experimental. However, RAID 0, 1 and 10 are supported, as well as compression, quotas,
and snapshots.

To understand btrfs, you must first understand the terms btrfs uses to refer to data blocks
(data), inode tables (metadata), and the superblock (system). Metadata and system are
stored using B-tree structures for speed, and you can specify whether to make the data or
metadata fault tolerant individually. For example, to make the data and metadata fault
tolerant using RAID 1 for a new btrfs filesystem that spans /dev/sdd and /dev/sde,
you could use the mkfs.btrfs -m raid1 -d raid1 /dev/sdd /dev/sde
command. You can then mount either /dev/sdd or /dev/sde to a mount point
directory and add a line to /etc/fstab to mount it at each boot. Automatic
compression is available as a mount option, and you can create btrfs subvolumes that are
mounted to their own directory with different mount options.

You can use the btrfs command to manage btrfs volumes. For example, to check for
and repair errors on a btrfs volume, you can use btrfs check device, where device
is any of the devices within the btrfs volume. Alternatively, you can use the btrfsck
command to do the same.

If a device fails within a fault tolerant btrfs volume, you can replace the device and
reconfigure the btrfs volume while it is online. Similarly, you can add additional storage
to a btrfs volume while it online (btrfs device add device) balancing the data
and metadata across the volume (btrfs filesystem balance mountpoint).
- 147 -

Exercise
1. In the Settings of your Ubuntu Server virtual machine within your virtualization
software, add 4 additional 8GB (dynamically allocated) SATA disks to your SATA
controller (these will be identified as sdb, sdc, sdd and sde within Linux).

2. Boot your Ubuntu Server virtual machine and log into tty2 as the root user. Run the
lsblk command and note that your new block devices are detected.

PART 1: Software RAID


3. Run the mdadm --create /dev/md0 --level=5 --raid-devices=3 /dev/sdb /dev/sdc
/dev/sdd command to create a RAID 5 configuration from sdb, sdc and sdd. Next, run
mkfs –t ext4 /dev/md0 to format your RAID 5 volume, mkdir /raid5, and then mount
/dev/md0 /raid5 to mount the filesystem on your RAID 5 volume to /raid5.

4. Run the lsblk, df –hT, cat /proc/mdstat and mdadm --detail /dev/md0 commands to
view the details of your RAID 5 volume.

5. Run the sync command to ensure that all devices are synchronized in the RAID 5
volume, and then run mdadm --manage /dev/md0 --fail /dev/sdb to simulate a failure
on sdb. Next, run mdadm --detail /dev/md0 and cat /proc/mdstat to view the status of
the RAID 5 volume.

6. Run the mdadm --manage /dev/md0 --remove /dev/sdb command to remove the
failed disk from the RAID 5 volume. At this point, you normally replace the failed disk
(hot swappable) and re-add it to the RAID 5 volume. Since we already have another
device (sde), we can simply add that one instead using the mdadm --manage /dev/md0 -
-add /dev/sde command. Finally, run the mdadm --detail /dev/md0 command to verify
that the data is being rebuilt (after a few minutes, you should see it fully synchronized).

7. Run the umount /raid5 command to unmount your RAID 5 filesystem, and then
remove your RAID configuration using the mdadm --stop /dev/md0 command.

8. Next, run fdisk /dev/sdb, type w and press Enter to save your changes (this will
remove the existing RAID signature from sdb). Repeat this step for /dev/sdc, /dev/sdd,
and /dev/sde.

PART 2: ZFS
9. Run the following commands to install ZFS on your system:
apt-get update
add-apt-repository main
add-apt-repository restricted
add-apt-repository universe
add-apt-repository multiverse
apt-get install zfsutils-linux
- 148 -

10. Use the following commands to create a simple ZFS volume called lala from the
space on /dev/sdb, work with the new volume and remove it afterwards:
zpool create lala /dev/sdb -f
zpool list
lsblk
cp /etc/hosts /lala
ls -l /lala
zpool destroy lala

11. Use the following commands to create a mirrored ZFS volume called po from the
space on /dev/sdb and /dev/sdc:
zpool create po mirror /dev/sdb /dev/sdc -f
zpool list
lsblk
cp /etc/hosts /po
zpool status po

12. Use the following commands to overwrite a portion of /dev/sdb (simulating disk
corruption), update the status of ZFS (called scrubbing), and then detach the bad disk
(/dev/sdb) and mirror the data on /dev/sdc to another disk (/dev/sdd). You will remove
the mirror when finished.
dd if=/dev/zero of=/dev/sdb1 count=100000000 (press [Ctrl]+c after 5-6 seconds)
zpool scrub po
zpool status po
zpool detach po /dev/sdb
zpool status po
zpool attach po /dev/sdc /dev/sdd -f
zpool list
zpool status po
zpool iostat -v po
zpool destroy po

13. Use the following commands to create a raidz volume called noonoo using /dev/sdb,
/dev/sdc, /dev/sdd and /dev/sde:
zpool create noonoo raidz /dev/sdb /dev/sdc /dev/sdd /dev/sde -f
zpool status noonoo
zpool iostat -v noonoo

14. Use the following commands to create three subdirectories under /noonoo that are
managed by ZFS. This will allow you to set ZFS-specific properties on those directories
rather than treat them like regular subdirectories. You will then examine those properties
and set a storage limit for the larry subdirectory.
mkdir /noonoo/larry
mkdir /noonoo/curly
mkdir /noonoo/moe
zfs list
- 149 -

zfs create noonoo/larry


zfs create noonoo/curly
zfs create noonoo/moe
zfs list
ls /noonoo
zfs get all noonoo/larry | less
zfs set quota=1G noonoo/larry
zfs set compression=lz4 noonoo/larry
zfs get all noonoo/larry
zpool destroy noonoo

15. Run fdisk /dev/sdb, type d and press Enter twice to delete the ZFS state partition.
Next, type d and press Enter to delete the ZFS data partition, and then type w and press
Enter to write your changes. Repeat this step for /dev/sdc, /dev/sdd and /dev/sde.

PART 3: Btrfs
16. Run the following commands to create and explore a RAID 0 btrfs volume:
mkfs.btrfs -m raid0 -d raid0 /dev/sdb /dev/sdc /dev/sdd -f
mkdir /btrfs
mount /dev/sdb /btrfs
df –hT
lsblk
btrfs device add -f /dev/sde /btrfs
btrfs filesystem balance /btrfs
df -hT

17. Run the following commands to create and explore a new subvolume called stuff:
btrfs subvolume list /btrfs
btrfs subvolume create /btrfs/stuff
btrfs subvolume list /btrfs
cp /etc/services /btrfs/stuff
mkdir /stuff
mount -o subvol=stuff,compress=lzo /dev/sdb /stuff
df –hT
ls –l /stuff
umount /stuff
umount /btrfs

18. Run the following command to create and explore a RAID 1 btrfs volume:
mkfs.btrfs -m raid1 -d raid1 /dev/sdb /dev/sdc /dev/sdd /dev/sde -f
btrfs filesystem show /dev/sdb
mount /dev/sdb /btrfs
df -hT
lsblk
btrfs filesystem df /btrfs
umount /btrfs
- 150 -

19. Run the following commands to create and explore a RAID 5 btrfs volume:
mkfs.btrfs -m raid5 -d raid5 /dev/sdb /dev/sdc /dev/sdd /dev/sde -f
btrfs filesystem show /dev/sdb
mount /dev/sdb /btrfs
df -hT
lsblk
btrfs filesystem df /btrfs
umount /btrfs
btrfs check /dev/sdb
btrfsck /dev/sdb
poweroff

20. In the Settings of your virtual machine, remove the 4 additional 8GB virtual hard
disks you added earlier.

5.7 System Startup & Daemons


When a Linux system is started, a bootloader program loads the Linux kernel, which
then proceeds to start system services (called daemons) that provide for all other
operating system functionality (storage, network connectivity, Web services, file sharing,
login terminals, and so on). The first daemon started by the Linux kernel is called
Systemd (or init on legacy systems). This daemon is responsible for starting, stopping,
and managing all other daemons on the system.

The GRUB Bootloader


The bootloader program used on modern Linux systems is GRUB (Grand Unified Boot
Loader) version 2, and displays a boot menu once you power on your system that allows
you to choose the correct Linux kernel if more than one is installed:

If your system uses a standard BIOS and an MBR disk for Linux, then the first part of
GRUB (stage 1) is loaded from the MBR and the post MBR gap following it (stage 1.5).
GRUB stage 1.5 then loads stage 2 from the /boot/grub directory and displays the
GRUB boot menu (shown earlier) where you can select your Linux kernel
(/boot/vmlinuz-version).
- 151 -

If your system uses a standard BIOS and a GPT disk for Linux, then GRUB stage 1 is
loaded from the MBR and stage 1.5 is loaded from a small bios_grub partition (typically
/dev/sda1) before loading stage 2 from the /boot/grub directory. This is because
there is no post MBR gap on GPT disks. If your system instead uses a UEFI BIOS, then
GRUB is loaded entirely from the EFI system partition, which is usually the first
partition on the disk (/dev/sda1) and formatted with vfat. This EFI system partition is
mounted to /boot/efi following startup.

You can also reinstall GRUB if it becomes corrupted. For example, if your system uses a
standard BIOS, grub2-install /dev/sda would reinstall GRUB on the MBR or
GPT of /dev/sda. Alternatively, if your system uses a UEFI BIOS, grub2-install
/dev/sda1 would reinstall GRUB on the /dev/sda1 EFI System Partition.

NOTE: If you have a UEFI BIOS, you may have the Microsoft secure boot feature
enabled; in this case, the digital signature of the boot loaders stored within the EFI system
partition are first checked to ensure that they haven’t been modified by malware. Recent
Linux distributions have support for secure boot by adding shim code to the existing
Microsoft secure boot feature to extend the functionality to GRUB.

When your system updates your Linux kernel to a new version, GRUB is automatically
reconfigured to make them available on the GRUB boot menu, with the latest version
becoming the default. GRUB configuration is stored within a file under the /boot
directory that differs based on your Linux distribution. On Fedora systems, GRUB stores
its configuration in /boot/grub2/grub.cfg if the system has a standard BIOS, or
within /boot/efi/EFI/fedora/grub.cfg if the system has a UEFI BIOS.

The first part of the GRUB configuration file loads storage drivers and sets the default
Linux kernel started if one is not selected by the user at the GRUB boot menu within the
timeout period. The second part of the GRUB configuration file contains the actual menu
entries for each Linux kernel that needs to be loaded. For example, the following menu
entry loads the kernel from the first MBR partition on the first disk (hd0,msdos1).

menuentry 'Fedora (4.16.3-301.fc29.x86_64)(Workstation Edition)' --


class fedora --class gnu-linux --class gnu --class os --unrestricted
$menuentry_id_option 'gnulinux-4.16.3-301.fc29.x86_64-advanced-
12a2543d-0251-08d6-552a-aa8d4c3bd0a4fb6' {
load_video
set gfxpayload=keep
insmod gzio
insmod part_gpt
insmod ext2
set root='hd0,msdos1'

linux /vmlinuz-4.16.3-301.fc29.x86_64 root=UUID=12a2543d-0251-08d6-


552a-aa8d4c3bd0a4fb6 ro resume=UUID=4587da8b-d12e-4b4c-124c-
34cf4a22ea01 rhgb quiet LANG=en_US.UTF-8

initrd /initramfs-4.16.3-301.fc29.x86_64.img
}
- 152 -

The partition that holds the /boot directory is referred to in a special format:
(hd#,partition#) where drive numbers start at 0 and MBR/GPT partition numbers
start at 1. Thus, if the second GPT partition on the first disk contains the /boot directory,
then we would specify the location of this partition as hd0,gpt2. Alternatively, the
second MBR partition on the first disk would be referred to as hd0,msdos2.

There are also a wide variety of different options you can specify on the line that loads
the kernel (linux /vmlinuz-4.16.3-301.fc29.x86_64), including:
• rhgb – Red Hat Graphical Boot (shows splash screen while the system starts).
• quiet – Suppresses error messages while the system starts.
• root=device – Specifies the device file (or UUID) for the / filesystem.
• single – Loads the kernel in Single User Mode (discussed later)

An initial ramdisk (initrd) is loaded alongside the Linux kernel that contains key
device drivers needed by the Linux kernel to boot the system (e.g. proprietary storage
drivers). The linux and initrd lines may be replaced by linuxefi and
initrdefi if your system has a UEFI BIOS. The remainder of the GRUB
configuration file contains sections for booting specialized devices and other non-Linux
operating systems. On a server, this section contains little, if any, configuration data.

If new Linux kernels are installed on the system, entries to boot them are added to files in
the /etc/grub.d directory, and the grub2-mkconfig program automatically
creates a new GRUB configuration file. This program looks for configuration in both the
/etc/grub.d directory as well as the /etc/default/grub file, which contains
common entries that administrators can set to alter startup behaviour and kernel options:

[root@localhost ~]# cat /etc/default/grub


GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=UUID=4587da8b-d12e-4b4c-124c rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
[root@localhost ~]# _

To manually modify GRUB, you can edit /etc/default/grub and then run
grub2-mkconfig (or grub2-update) to generate a new GRUB configuration file:

[root@localhost ~]# grub2-mkconfig -o /boot/grub2/grub.cfg


Generating grub.cfg ...
Found linux image: /boot/vmlinuz-4.16.3-301.fc29.x86_64
Found initrd image: /boot/initramfs-4.16.3-301.fc29.x86_64.img
done
[root@localhost ~]# _

NOTE: Some distributions omit 2 in the names of GRUB commands for simplicity
- 153 -

To configure other aspects of GRUB not available in /etc/default/grub, you can


use helper programs, such as grubby. Also, if you update a proprietary storage
controller driver (e.g. for a FC HBA), you may need to generate a new initrd image file
that includes the updated driver using either the dracut or mkinitrd command.

Daemons, Runlevels and Targets


Legacy UNIX and Linux systems that use the init daemon categorized the number and
type of daemons started on a Linux system by runlevel as described in Table 5-1.

Table 5-1 Linux Runlevels


0 Halt – no daemons are running
1 s or S Single User Mode – only enough daemons running to allow the root
user to log in and perform system maintenance
2 Multi-user Mode – most daemons are started including services that
allow multiple users to log into the system
3 Extended Multi-user Mode – same as 2, but with network services
started (the default runlevel for servers with no desktop environment)
4 Not used by default
5 Display Manager – same as 3, yet with a display manager started on
tty1, such as gdm (the default runlevel for graphical workstations)
6 Reboot – reboots the system

To see which runlevel you are in, you can use the runlevel command:

[root@localhost ~]# runlevel


N 5
[root@localhost ~]# _

The output above tells us that the current runlevel is 5 and the previous runlevel is non-
existent (N) since the system proceeded directly to runlevel 5 after system startup.

NOTE: When in runlevel 1, the runlevel command will not indicate the most recent
runlevel since there are no system processes loaded that can monitor it.

You can change runlevels after the system has started by using the init (or telinit)
command. The init daemon will then start or stop the appropriate daemons to enter the
desired runlevel. For example, to change to Single User Mode you could run the init 1
command, and to change to Extended Multi-user Mode you could use the init 3
command. You can also use the service command to manage daemons. For example:
service crond start starts the cron daemon
service crond stop stops the cron daemon
service crond restart restarts the cron daemon
service crond reload forces the cron daemon to re-read its config files
service crond status displays status information for the cron daemon
- 154 -

At system startup, legacy Linux systems identify the default runlevel from the
/etc/inittab file. Next, they run files from the /etc/rc.d/rcrunlevel.d
directory (which are symlinks to daemon scripts in the /etc/rc.d/init.d directory)
to start and stop daemons when entering the runlevel. The chkconfig command can be
used to view and configure the runlevels each daemon starts in.

Modern Linux systems that use Systemd are backwards-compatible to the legacy init
system. Thus, you can use the runlevel, init and telinit commands to view and
change your runlevel, as well as the service command to start and stop daemons.
However, the Systemd daemon can manage many other operating system components,
including network settings, mounts, logs and drivers. Consequently, Systemd calls each
operating system component a unit.
• Runlevels are called target units (or just targets)
• Daemons are called service units because they provide a system service

NOTE: There are additional units that provide functionality beyond what was available
with the legacy init system. For example, socket units can start a network daemon on
demand, timer units can schedule a task repetitively, mount units can mount filesystems,
and automount units can mount filesystems on demand.

By default, there are target units that map to each legacy init runlevel:
• poweroff.target is the same as runlevel 0
• rescue.target is the same as runlevel 1 (Single User Mode)
• multiuser.target is the same as runlevel 2, 3 and 4 combined
• graphical.target is the same as runlevel 5
• reboot.target is the same as runlevel 6

NOTE: Systemd maintains backwards-compatible names for runlevels. For example, you
can use runlevel5.target in place of graphical.target.

Systemd also has targets that specify special states that are often requested by the
runlevel targets shown above. For example, printer.target represents CUPS print
services whereas network-online.target denotes when the network is fully
online and functional.

To manage daemons using Systemd, you can specify the appropriate service unit
alongside the systemctl command:
systemctl start crond.service starts the cron daemon
systemctl stop crond.service stops the cron daemon
systemctl restart crond.service restarts the cron daemon
systemctl reload crond.service forces cron to re-read its config files
systemctl status crond.service displays cron status information
systemctl daemon-reload forces all daemons to re-read their config files
- 155 -

The final command above is useful after modifying config files for a daemon, since it
ensures that the changes will take effect immediately. Without arguments, systemctl
displays a list of all units that are currently executing and their status. To view daemons
(service units), you could run systemctl -a | grep service | less.

The systemctl command can also configure daemons to start or stop automatically in
the default target, change targets, or set the default target. For example, to ensure start the
cron daemon is always started in the default target, you can use the systemctl
enable crond.service command. Alternatively, to ensure that the cron daemon
does not start in the default target, you could run the systemctl disable
crond.service command. To set the default target to multi-user, you can use the
systemctl set-default multi-user.target command, and to change your
system to the multi-user target, you can use the systemctl isolate multi-
user.target command. You can also use systemd-analyze blame to list all
daemons sorted by the time it took them to load during the previous boot to identify
daemons that slow down system startup.

The configuration of Systemd is provided by a series of unit files under the the
/lib/systemd/system directory. At system startup, Systemd searches for
/etc/systemd/system/default.target, which is a symlink to the appropriate
target unit file (e.g. /lib/systemd/system/graphical.target). For example,
the graphical.target file below tells Systemd that the graphical target:
• Requires all services started by the multi-user target (otherwise it will fail)
• Will start (Wants) the display-manager service (if it doesn’t start, it won’t fail)
• Cannot be run at the same time (Conflicts) as the rescue target
• Instructs Systemd to start the multi-user target and display-manager service before
entering (After) the graphical target (Before would start these services after
entering the graphical target)
• Allow users to switch to the target (AllowIsolate)

[root@localhost ~]# cat /lib/systemd/system/graphical.target


[Unit]
Requires=multi-user.target
Wants=display-manager.service
Conflicts=rescue.target
After=multi-user.target display-manager.service
AllowIsolate=yes
[root@localhost ~]# _

In addition to these requirements, Systemd will also process any unit files in the
/lib/systemd/system/runlevel.target.wants directory (these unit files
are merely shortcuts to the unit files in /lib/systemd/system). Since the graphical
target shown earlier will first start the multi-user target, this will include everything in
both the /lib/systemd/system/graphical.target.wants directory and the
/lib/systemd/system/multi-user.target.wants directory (as well as any
additional targets that the multi-user target requires).
- 156 -

Each service unit also has a service unit file under /lib/systemd/system that
specifies the daemon it needs to start, the targets it should start and stop in, as well as any
other necessary options. For example, the crond.service file below tells Systemd:
• To make sure that the network is fully started before starting the crond service
(Wants=network-online.target)
• To start the network, as well as the auditd and systemd-user-sessions services
before starting the crond service (After)
• That crond is a regular daemon process (simple) that is started as the root user
• The file that stores crond settings (EnvironmentFile)
• The command to start, stop and restart the crond service (ExecStart,
ExecStop and ExecReload)
• That crond should be restarted automatically 30 seconds after it fails by Systemd
• That crond should be started automatically when entering the multi-user target
(WantedBy)

[root@localhost ~]# cat /lib/systemd/system/crond.service


[Unit]
Wants=network-online.target
After=network-online.target auditd.service systemd-user-
sessions.service

[Service]
Type=simple
User=root
EnvironmentFile=/etc/sysconfig/crond
ExecStart=/usr/sbin/crond
ExecStop=/bin/kill -INT $MAINPID
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=multi-user.target
[root@localhost ~]# _

You shouldn’t edit the /lib/systemd/system/crond.service file directly.


Instead, you should use the systemctl edit crond.service command. This
opens the nano editor and displays the original crond unit file contents – any changes you
make will be saved to /etc/systemd/system/crond.service.d/override.
This file contains settings that override the original crond unit file, and be easily deleted
should you wish to return to the original configuration.

Systemd can start any daemon, including network daemons that respond to requests from
across the network. However network daemons will be run when Systemd enters the
target those daemons list in their unit file. To run a network daemon only when the
system receives associated network traffic, you can use a socket unit instead of a service
- 157 -

unit. For example, telnet.socket would start a telnet daemon on demand only when
the system receives telnet traffic.

You can also use Systemd timer units as an alternative to the cron daemon. For example,
most Linux systems use a Systemd timer to run TRIM on the physical SSDs periodically
to reclaim unused blocks. For example, the following fstrim.timer unit file tells
Systemd to automatically run fstrim.service (which runs the fstrim command)
Monday through Friday at 10:30pm and 3600 seconds after system boot, but only if the
Linux system is not run within a virtual machine or container:

[root@localhost ~]# cat /lib/systemd/system/fstrim.timer


[Unit]
ConditionVirtualization=!container

[Timer]
Unit=fstrim.service
OnCalendar=Mon..Fri 22:30
OnBootSec=3600

[Install]
WantedBy=timers.target
[root@localhost ~]# _

Similarly, you can use mount units as an alternative to /etc/fstab for mounting
filesystems. The following lala.mount file tells Systemd to mount an ext4 filesystem
on a partition (that must be identified by UUID) to the /lala directory when the system
enters the graphical.target. After creating a new mount unit, you must also enable
them to allow Systemd to process them when entering the appropriate target.

[root@localhost ~]# cat /lib/systemd/system/lala.mount


[Unit]
Description=Mount lala filesystem in graphical target

[Mount]
What=/dev/disk/by-uuid/4f07cfa5-fb5b-4511-8688-3305759d9006
Where=/lala
Type=ext4
Options=defaults

[Install]
WantedBy=graphical.target
[root@localhost ~]# systemctl enable lala.mount
[root@localhost ~]# _

If you instead call the same unit file lala.automount, then the filesystem would only
be mounted once the /lala directory is accessed for the first time. You can also
manually mount filesystems using Systemd. For example, the systemd-mount
/dev/sda5 /stuff command can be used to mount /dev/sda5 to the /stuff
directory, and you can add the –A option if you want it to be mounted only when it is
accessed for the first time.
- 158 -

NOTE: Normally, to run a desktop environment, you must be in runlevel 5


(graphical.target) and log into the display manager (e.g. gdm) on tty1. However, you can
start the default desktop environment from any runlevel using the startx command.

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user, edit
/etc/default/grub using the vi editor and change the GRUB_TIMEOUT to 30 seconds.
Next, run the following commands:
grub2-mkconfig –o /boot/grub2/grub.cfg (if your hypervisor emulates standard BIOS)
grub2-mkconfig –o /boot/efi/EFI/fedora/grub.cfg (if your hypervisor emulates UEFI)
less /boot/grub2/grub.cfg (if standard BIOS, noting the timeout value)
less /boot/efi/EFI/fedora/grub.cfg (if UEFI, noting the timeout value)
reboot

2. Note the countdown on the GRUB menu, and press c to obtain a GRUB prompt. Next,
press Tab to see available GRUB commands, and then run ls to show the devices
recognized on your system. Press Esc to return to the GRUB menu and press e to edit the
default boot kernel line (loaded from the GRUB configuration file). Scroll down to the
linux ($root)/vmlinuz-* line, append the word single and press F10 to continue booting
the system to Single User Mode, where you must supply the root user password to obtain
a shell. Next, run runlevel to verify your runlevel and then reboot to restart the system.
Press Enter at the GRUB menu to boot the default kernel normally.

3. Log into tty3 as the root user and run the following commands:
runlevel (note that your current runlevel is 5 and previous is N)
init 3
runlevel (note that your current runlevel is 3 and previous is 5)

4. Switch to tty1 and note that the gdm is not available in runlevel 3. Next, switch back to
tty3 and run the startx command to start GNOME as the current (root) user. Finally, log
out of GNOME and run the init 6 command to reboot your system.

5. Log into tty3 as the root user and run the following commands:
chkconfig --list (note the livesys and livesys-late legacy daemons)
systemctl | grep crond (note that crond is a modern Systemd-managed daemon)
service crond restart (this works because Systemd is init-compatible)
systemctl restart crond.service
systemctl status crond.service (crond is started, and set to start in the default target)
systemctl disable crond.service
systemctl status crond.service (note that crond not set to start in the default target)
systemctl enable crond.service
systemctl get-default (note that graphical.target is the default target)

6. Explore various unit files under the /lib/systemd/system folder, interpreting their
contents. Optionally create a mount unit that mounts a new filesystem of your choice.
- 159 -

5.8 Localization
There are many settings on a Linux system that are specific to the region of the world that
you live in, including language, time/date, and formatting. These settings are collectively
referred to as localization options, and are normally set during Linux installation, but can
be modified afterwards.

Time and Timezone Localization


The default time on your Linux system is taken from the clock in the system BIOS/UEFI.
You can modify the clock in your BIOS/UEFI using the hwclock command (e.g.
hwclock --set --date='2025-11-02 00:00:00' for midnight on Nov 2nd
2025), or you can configure the appropriate time/date within the Linux operating system
using the date command (e.g. date –s '2025-11-02 00:00:00') and copy the
configuration to the BIOS/UEFI clock using the hwclock –w command.

Time is a bit more complicated on Linux systems compared to other systems. The Linux
kernel stores time internally as the number of seconds since January 1, 1970 (the birth of
UNIX, also called the UNIX epoch). You can see this time using the date +%s
command. Thus, the hardware time must be translated to this time, and in the process is
also modified according to the time zone configured on your system. Without a time
zone, all time information defaults to Greenwich Mean Time (GMT); thus you must also
ensure that your system is configured with the appropriate time zone to ensure that the
time is displayed properly by the system.

The binary /etc/localtime file stores the information needed for the kernel to
calculate your time based on your local time zone and is usually a symlink to a specific
time zone file (e.g. /usr/share/zoneinfo/America/Toronto for most of
Ontario, Canada). You may also have an /etc/timezone file on your Linux
distribution; this is a text file that contains a reference to this same time zone file
(/usr/share/zoneinfo/America/Toronto) for use by applications. If you
don’t know which time zone you should use, you can run the tzselect command,
which will ask you a series of questions and display the correct time zone file.

You can set your time zone (as well as your time) using the timedatectl command
(e.g. timedatectl set-timezone 'America/Toronto'), or even configure
your system to obtain time and time zone information from a Network Time Protocol
(NTP) server on the Internet (discussed in the next chapter). Within an environment file
or shell script, you can also use the TZ variable to manually specify a specific time zone
(e.g. export TZ='America/Toronto').
- 160 -

Format Localization
Perhaps the most common localization option changed following a Linux installation
involves the formats used to display different types of information. Different areas of the
world often use different formatting conventions for money (e.g. Europe uses 9,25$ to
represent $9.25), dates (e.g. China uses YYYYMMDD instead of DDMMYYYY),
keyboard layouts (e.g. French Canadian vs. US English), and more.

The character set used on your system will also affect your format localization. The
original ASCII character set was mostly English-specific but was extended to include
some other languages with ISO-8859. The Unicode character set is the standard today as
it provides for all languages worldwide, and the UTF-8 standard allows these characters
to be represented by one to four bytes. Within a shell script, you could use the iconv
command to convert data between different character sets.

We set a locale on a Linux system to identify a unique combination of language,


character set, and regional formatting options. In Fedora, the correct locale is passed to
the Linux kernel as it is loaded by GRUB using the LANG option on the kernel line within
the GRUB configuration file. This instructs the Linux kernel to set the LANG variable to
the correct locale. For example, LANG=en_US.UTF-8 locale represents US regional
English, with a UTF-8 character set, and LANG=en_US represents US regional English,
with an ASCII character set (the default if no character set is specified). If this option is
not set by GRUB, it is loaded from the /etc/locale.conf file after boot time on
Fedora systems, or from /etc/default/locale on Ubuntu systems. You can list
the locales on your system using the locale –a command.

Without arguments, the locale command displays the LANG variable, as well as any
other variables that can override the LANG variable for specific unit formatting (the
LC_ALL variable overrides all others, if set):

[root@localhost ~]# locale


LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
[root@localhost ~]# _
- 161 -

To change your locale variables, you should use the localectl command. For
example, localectl set-locale LC_TIME="en_CA.UTF-8" will ensure that
dates are represented as the Canadian DDMMYYYY instead of the US default of
MMDDYYYY. Alternatively, you could do the same within an environment file or shell
script using the export LC_TIME="en_CA.UTF-8" line.

Without arguments, localectl displays your locale information, and the localectl
list-locales command lists available locales. You can also change your keyboard
layout using localectl. The localectl list-keymaps command will list available
keyboard layouts, and the localectl set-keymap layout command will set
your keyboard to a specific layout.

NOTE: The line export LANG=C is often added to the beginning of shell scripts
because the special C locale is standard on all UNIX systems. This will ensure that the
shell script will give standard output when run on any system, regardless of locale.

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
date +%s
hwclock
date
timedatectl
ls -l /etc/localtime
cat /proc/cmdline (this file lists the kernel line that was loaded during the previous
boot – note that no locale was passed by GRUB to your kernel)
locale
localectl
locale –a | less
localectl list-locales | less
localectl list-keymaps | less (note the us-mac keymap for U.S. Apple keyboards)

5.9 Compression
There are five main compression utilities that are available on most Linux systems:
compress, zip, gzip, xz, and bzip2. The compress, zip, gzip and xz utilities
use the Lempel-Ziv compression algorithm, whereas the bzip2 utility uses Burrows-
Wheeler Block Sorting Huffman Coding. Different data responds differently to different
compression algorithms. Thus, if you do not receive a good compression ratio from
compress, zip, gzip or xz, try using bzip2 and vice versa.

After compressing a file, each utility renames it to include a file extension: compress
uses the .Z extension, zip uses the .zip extension, gzip uses the .gz extension, xz
uses the .xz extension and bzip2 uses the .bz2 extension.
- 162 -

The following example compresses testfile with compress (-v prints verbose
information during the process):

[root@localhost ~]# ls -l
total 4
-rw-r--r--. 1 root root 13649 Sep 8 09:02 testfile
[root@localhost ~]# compress -v testfile
testfile: -- replaced with testfile.Z Compression: 54.49%
[root@localhost ~]# ls -l
total 4
-rw-r--r--. 1 root root 6211 Sep 8 09:02 testfile.Z
[root@localhost ~]# _

To uncompress testfile.Z, you can use the uncompress command:

root@localhost ~]# uncompress -v testfile.Z


testfile.Z: -- replaced with testfile
[root@localhost ~]# ls -l
total 4
-rw-r--r--. 1 root root 13649 Sep 8 09:02 testfile
[root@localhost ~]# _

The zip command requires that you specify the compressed filename, but often results
in a slightly better compression ratio as shown in the following example that compresses
and uncompresses testfile:

[root@localhost ~]# zip -v testfile.zip testfile


adding: testfile (in=13649) (out=4687) (deflated 66%)
total bytes=13649, compressed=4687 -> 66% savings
[root@localhost ~]# ls -l
total 5
-rw-r--r--. 1 root root 13649 Sep 8 09:02 testfile
-rw-r--r--. 1 root root 4851 Sep 23 13:13 testfile.zip
[root@localhost ~]# unzip testfile.zip

Archive: testfile.zip
replace testfile? [y]es, [n]o, [A]ll, [N]one, [r]ename: y
inflating: testfile
[root@localhost ~]# _

The standard compression utility today on Linux systems is GNU zip (gzip). To
compress testfile with gzip, you can use the following command:

[root@localhost ~]# gzip -v testfile


testfile: 65.7% -- replaced with testfile.gz
[root@localhost ~]# ls -l testfile*
-rw-r--r--. 1 root root 4714 Sep 8 09:02 testfile.gz
[root@localhost ~]# _

You can also control the compression ratio using options. The -1 (or --fast) option
compresses a file quickly but with a lower compression ratio. The -9 (or --best)
- 163 -

option compresses a file slowly but with the best compression ratio. If no option is
specified, -6 (medium compression) is assumed. To uncompress testfile.gz, you
can use the gunzip command (or gzip –d):

[root@localhost ~]# gunzip -v testfile.gz


testfile.gz: 65.7% -- replaced with testfile
[root@localhost ~]# ls -l testfile*
-rw-r--r--. 1 root root 13649 Sep 8 09:02 testfile
[root@localhost ~]# _

The xz utility is a modern replacement for gzip (although gzip is still the most
common today). It provides similar functionality to gzip (including the -1 to -9
options to control the compression ratio), but implements the compression in a more
efficient way, as shown below. To decompress files, you can use unxz (or xz –d).

[root@localhost ~]# xz -9 -v testfile


testfile (1/1)
100 % 4,584 B / 13.3 KiB = 0.336
[root@localhost ~]# ls -l testfile*
-rw-r--r--. 1 root root 4584 Sep 8 09:02 testfile.xz
[root@localhost ~]# unxz -v testfile.xz
testfile.xz (1/1)
100 % 4,584 B / 13.3 KiB = 0.336
[root@localhost ~]# _

Once compressed, you may use the zcat or zmore or zless commands to view the
contents of a gzip- or xz-compressed file (if it contains text). Additionally, there are
xz-specific commands available, such as xzcat.

Since bzip2 utility uses a different compression algorithm, you cannot use zcat,
zmore, or zless on text files compressed with bzip2. Instead you must use bzcat,
bzmore or bzless. To compress testfile with bzip2, and then uncompress it
using bunzip2 (same as bzip2 –d), you can use the following commands:

[root@localhost ~]# bzip2 -v testfile


testfile: 3.100:1, 2.581 bits/byte, 67.74% saved, 13649 in, 4403 out
[root@localhost ~]# ls -l testfile*
-rw-r--r--. 1 root root 4403 Sep 8 09:02 testfile.bz2
[root@localhost ~]# bunzip2 -v testfile.bz2
testfile.bz2: done
[root@localhost ~]# _

NOTE: Hard linked files cannot be compressed with compression utilities, and symlinks
are not compressed unless you specify the –f option to the command.
- 164 -

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
dnf install ncompress (the compress tool is not installed by default)
cd classfiles
compress -v bigfile (note the compression ratio)
uncompress bigfile
zip -v bigfile.zip bigfile (note the compression ratio)
unzip bigfile.zip (choose to overwrite bigfile)
gzip -v -9 bigfile (note the compression ratio)
gunzip bigfile.gz
xz -v -9 bigfile (note the compression ratio)
unxz bigfile.xz
bzip2 -v bigfile (note the compression ratio is better than other commands)
bunzip2 bigfile.bz2

5.10 Backup
To prepare for data loss, you should regularly back up data files to another device; this
process is known as system backup, or archiving. Today, we typically use a backup
utility to backup files to one large file (called an archive file) on the filesystem, but in the
past, backups were sent to tape drives (e.g. /dev/st0 for the first SCSI tape drive).

Archive files may then be compressed and sent to a file server across a network (file
servers are often referred to as Network Attached Storage or NAS). The most common
backup utility available on Linux systems is tar. In addition to performing file backups,
it is often used to create archive files of source code and software. Many software
developers today distribute their source code using tar archive files. However, there are
other backup utilities available, including cpio (copy in and out), dump, and dd (clone
copy – dd was used because cc was already taken by the C compiler command).

NOTE: You can also consider fault tolerant disk storage technologies such as RAID 1,
ZFS mirrors and btrfs RAID 1 a form of automatic backup. Even the LVM can be
configured to create mirrored logical volumes that are functionally equivalent to RAID 1
by ensuring that a copy of the data is always stored on two different physical devices
(refer to the lvcreate manual page for more information).

For many of the following examples, we’ll back up the sample files shown below
(including the projects subdirectory):

[root@localhost ~]# ls -F
hrlists projects/ revol testfile
[root@localhost ~]# ls projects
april feb jan march may
[root@localhost ~]#
- 165 -

Using tar
To take all of the files in your current directory and place them in an archive file called
/mystuff.tar on the filesystem, you could run the following command:

[root@localhost ~]# tar -cvf /mystuff.tar *


hrlists
projects/
projects/jan
projects/feb
projects/april
projects/may
projects/march
revol
testfile
[root@localhost ~]#

The –c (create) option creates a new archive, the –v (verbose) option shows you what it
is doing, and the –f (file) option indicates the archive file name (/mystuff.tar).
The * at the end of the command indicates what to place inside the archive file; in this
case, all files in the current directory. Since tar is recursive by default, all subdirectories
will be backed up as well. To view the contents of the archive file, you can use the –t
(table of contents) option to the tar command:

[root@localhost ~]# tar -tvf /mystuff.tar


-rw-r--r-- root/root 55 2019-10-12 02:49:42 hrlists
drwxr-xr-x root/root 0 2019-10-12 02:50:43 projects/
-rw-r--r-- root/root 147 2019-10-12 02:50:08 projects/jan
-rw-r--r-- root/root 147 2019-10-12 02:50:12 projects/feb
-rw-r--r-- root/root 147 2019-10-12 02:50:18 projects/april
-rw-r--r-- root/root 147 2019-10-12 02:50:28 projects/may
-rw-r--r-- root/root 147 2019-10-12 02:50:15 projects/march
-rw-r--r-- root/root 147 2019-10-12 02:49:31 revol
-rw-r--r-- root/root 1756 2019-10-12 01:30:38 testfile
[root@localhost ~]# _

To extract the contents of the archive file to a new location, you can use the –x (extract)
option to tar:

[root@localhost ~]# mkdir /newlocation


[root@localhost ~]# cd /newlocation
[root@localhost newlocation]# tar -xvf /mystuff.tar
hrlists
projects/
projects/jan
projects/feb
projects/april
projects/may
projects/march
revol
testfile
- 166 -

[root@localhost newlocation]# ls -F
hrlists projects/ revol testfile
[root@localhost newlocation]# ls projects/
april feb jan march may
[root@localhost newlocation]# pwd
/newlocation
[root@localhost newlocation]# _

While this archive file can be sent across the network, it is good form to compress it first.
However, support for compression tools is built into the tar command. For example, to
create a gzip-compressed archive file called /mystuff.tar.gz from all of the files
in the current directory, you can add the –z (gzip) option to the tar -cvf command:

[root@localhost ~]# tar -zcvf /mystuff.tar.gz *


hrlists
projects/
projects/jan
projects/feb
projects/april
projects/may
projects/march
revol
testfile
[root@localhost ~]# _

This compressed archive file (/mystuff.tar.gz) is called a tarball; tarballs are the
most common form used to transfer Linux files across the Internet. Tarballs created using
gzip typically use the extension .tar.gz or .tgz. To view the contents of this tarball,
add the –z (gzip) option to the tar -tvf command:

[root@localhost ~]# tar -ztvf /mystuff.tar.gz


-rw-r--r-- root/root 55 2019-10-12 02:49:42 hrlists
drwxr-xr-x root/root 0 2019-10-12 02:50:43 projects/
-rw-r--r-- root/root 147 2019-10-12 02:50:08 projects/jan
-rw-r--r-- root/root 147 2019-10-12 02:50:12 projects/feb
-rw-r--r-- root/root 147 2019-10-12 02:50:18 projects/april
-rw-r--r-- root/root 147 2019-10-12 02:50:28 projects/may
-rw-r--r-- root/root 147 2019-10-12 02:50:15 projects/march
-rw-r--r-- root/root 147 2019-10-12 02:49:31 revol
-rw-r--r-- root/root 1756 2019-10-12 01:30:38 testfile
[root@localhost ~]# _

To extract the contents of this tarball, you can add the –z (gzip) option to the tar -xvf
command:
[root@localhost ~]# mkdir /newlocation2
[root@localhost ~]# cd /newlocation2
[root@localhost newlocation2]# tar -zxvf /mystuff.tar.gz
hrlists
projects/
projects/jan
projects/feb
- 167 -

projects/april
projects/may
projects/march
revol
testfile
[root@localhost newlocation2]# ls -F
hrlists projects/ revol testfile
[root@localhost newlocation2]# ls projects
april feb jan march may
[root@localhost newlocation2]# pwd
/newlocation2
[root@localhost newlocation2]#

NOTE: Instead of using –z to create a tarball using gzip, you can use the –J option to
use xz, or the –j option to use bzip2.

Using cpio
The cpio utility is often used instead of tar for full system backups, as it can back up
any file type, including special files (e.g. device files) and files that have filenames longer
255 characters. The cpio command must take a list of files to back up from stdin; thus,
to back up all the files in the current directory to /backup.cpio, you could use the
following command:

[root@localhost ~]# find . | cpio -vocBL -O /backup.cpio


.
./.bash_logout
./.Xresources
./testfile
./revol
./.bash_profile
./.bashrc
./.cshrc
./.tcshrc
./.gtkrc
./.bash_history
./hrlists
./projects
./projects/jan
./projects/feb
./projects/april
./projects/may
./projects/march
2 blocks
[root@localhost ~]# _

There are many options to the cpio utility; the ones used in the above example write the
files out (-o) as well as any data from symbolic links (-L) to an output device (-O) with
an ASCII header (-c) and block size of 5KB (-B) while showing all information (-v).
- 168 -

To view the contents of the archive in the above example, simply use the following
command to read the table of contents (-t) in (-i) from the input file (-I) using a block
size of 5KB (-B) and showing all information (-v):

[root@localhost ~]# cpio -vitB -I /backup.cpio


drwxr-x--- 3 root root 0 Oct 12 02:49 .
-rw-r--r-- 1 root root 24 Jun 10 17:00 .bash_logout
-rw-r--r-- 1 root root 1126 Aug 23 1995 .Xresources
-rw-r--r-- 1 root root 1756 Oct 12 01:30 testfile
-rw-r--r-- 1 root root 147 Oct 12 02:49 revol
-rw-r--r-- 1 root root 234 Jul 5 2001 .bash_profile
-rw-r--r-- 1 root root 176 Aug 23 1995 .bashrc
-rw-r--r-- 1 root root 210 Jun 10 17:09 .cshrc
-rw-r--r-- 1 root root 196 Jul 11 11:53 .tcshrc
-rw-r--r-- 1 root root 118 Aug 9 2001 .gtkrc
-rw------- 1 root root 397 Oct 11 04:59 .bash_history
-rw-r--r-- 1 root root 55 Oct 12 02:49 hrlists
drwxr-xr-x 2 root root 0 Oct 12 02:50 projects
-rw-r--r-- 1 root root 147 Oct 12 02:50 projects/jan
-rw-r--r-- 1 root root 147 Oct 12 02:50 projects/feb
-rw-r--r-- 1 root root 147 Oct 12 02:50 projects/april
-rw-r--r-- 1 root root 147 Oct 12 02:50 projects/may
-rw-r--r-- 1 root root 147 Oct 12 02:50 projects/march
2 blocks
[root@localhost ~]#

To extract the contents of /backup.cpio using cpio, you could specify to read files
in (-i) from the input file (-I) with an ASCII header (-c), block size of 5KB (-B),
creating directories as needed (-d) keeping the modification date (-m) and overwriting
files unconditionally (-u) while displaying the process (-v):

[root@localhost ~]# cpio -vicdumB -I /backup.cpio


.
.bash_logout
.Xresources
testfile
revol
.bash_profile
.bashrc
.cshrc
.tcshrc
.gtkrc
.bash_history
hrlists
projects
projects/jan
projects/feb
projects/april
projects/may
projects/march
2 blocks
[root@localhost ~]# _
- 169 -

Using dump
Like cpio, the dump utility was also designed to perform system backups, however it
only works with ext2, ext3, or ext4 filesystems and creates a full backup followed by up
to 9 incremental backups. These backup types are called dump levels (0 is a full backup,
whereas 1-9 are the first to ninth incremental backup). Dump levels for each backup are
recorded in the /etc/dumpdates file.

For example, to perform a full backup (-0) of the filesystem on the partition
/dev/sda6 to the file (-f) called /sda6.dump and update the /etc/dumpdates
file (-u), you can use the following command:

[root@localhost ~]# dump -0uf /sda6.dump /dev/sda6


DUMP: Date of this level 0 dump: Thu Oct 12 04:06:32 2019
DUMP: Dumping /dev/sda6 (/data) to /sda6.dump
DUMP: Exclude ext4 journal inode 8
DUMP: Label: /data
DUMP: mapping (Pass I) [regular files]
DUMP: mapping (Pass II) [directories]
DUMP: estimated 6286 tape blocks.
DUMP: Volume 1 started with block 1 at: Thu Oct 12 04:06:32 2019
DUMP: dumping (Pass III) [directories]
DUMP: dumping (Pass IV) [regular files]
DUMP: Closing /sda6.dump
DUMP: Volume 1 completed at: Thu Oct 12 04:06:33 2019
DUMP: Volume 1 6280 tape blocks (6.13MB)
DUMP: Volume 1 took 0:00:01
DUMP: Volume 1 transfer rate: 6280 kB/s
DUMP: 6280 tape blocks (6.13MB) on 1 volume(s)
DUMP: finished in 1 seconds, throughput 6280 kBytes/sec
DUMP: Date of this level 0 dump: Thu Oct 12 04:06:32 2019
DUMP: Date this dump completed: Thu Oct 12 04:06:33 2019
DUMP: Average transfer rate: 6280 kB/s
DUMP: DUMP IS DONE
[root@localhost ~]# _

NOTE: If you are backing up a directory only (not a whole filesystem), do not update the
/etc/dumpdates file.

To view the contents of the archive file, you can use the –t option (table of contents) to
the restore command as shown:

[root@localhost ~]# restore -tf /sda6.dump


Dump date: Thu Oct 12 04:06:32 2019
Dumped from: the epoch
Level 0 dump of /data on localhost.localdomain:/dev/sda6
Label: /data
2 .
11 ./lost+found
4017 ./oracle
4019 ./oracle/daemon.conf
- 170 -

4020 ./oracle/0000393z
4021 ./oracle/0000394z
12 ./ot.b
13 ./ch.b
14 ./message
15 ./os2_d.b
27 ./4.7-10debug
28 ./4.7-10debug
31 ./boot.0300
[root@localhost ~]# _

And to restore the contents of the full archive (level 0) on the tape device, you must first
remake the filesystem (to clean the inode table of corruption), mount it, enter the mount
point directory and use the –r (restore) option to the restore command as shown:

[root@localhost ~]# umount /dev/sda6


[root@localhost ~]# mkfs –t ext4 /dev/sda6
mke2fs 1.43.8 (1-Jan-2019)
Creating filesystem with 732416 4k blocks and 183264 inodes
Filesystem UUID: c5ce0e70-8eaf-4edc-56d17ca62
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912

Allocating group tables: done


Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done
[root@localhost ~]# mount /dev/sda6 /data
[root@localhost ~]# cd /data
[root@localhost data]# restore -rf /sda6.dump
[root@localhost data]# _

Afterwards, you can proceed to run additional restore commands if you made
incremental backups (levels 1-9) following your full backup.

Using dd
The dd command is fundamentally different than the other backup utilities we’ve
discussed as it doesn’t copy files to an archive file. Instead, dd copies information sector-
by-sector from a filesystem to an archive file (called an image-based backup). For
example, to back up the filesystem on /dev/sda1 partition (the input file) using to the
archive file /sda1.dd (the output file), you could run the following:

[root@localhost ~]# dd if=/dev/sda1 of=/sda1.dd


2097152+0 records in
2097152+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 7.81663 s, 137 MB/s
[root@localhost ~]# _

To restore the image of the /dev/sda1 partition, simply reverse the input file (if) and
output file (of) arguments in the previous command.
- 171 -

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
cd classfiles
tar -zcvf /sample.tar.gz Poems
tar -ztvf /sample.tar.gz
mkdir test1 && cd test1
tar -zxvf /sample.tar.gz
ls -R
cd ~/classfiles
find Poems | cpio -vocBL -O /sample.cpio
cpio -Bitv -I /sample.cpio
mkdir test2 && cd test2
cpio -vicdumB -I /sample.cpio
dnf install dump
#The following two dump commands back up the /boot filesystem (ext4):
dump -0uf /sda1.dump0 /dev/sda1 (if UEFI, use sda2 instead of sda1)
dump -1uf /sda1.dump1 /dev/sda1 (if UEFI, use sda2 instead of sda1)
cat /etc/dumpdates (note the two backups and their dump levels)
dd if=/dev/sda1 of=/sda1.dd
df -hT
ls -l /sda1.dd (note that the size of the backup is exactly the same size as the
filesystem, even though only a fraction of the filesystem is actually
used to store files)

5.11 Software
Most open source software is available via the Internet. Furthermore, there are three
methods for installing software on a Linux system: compiling software from source code,
installing software using a package manager, or installing a sandboxed software package.

Installing Software from Source Code


One of the benefits of open source software is that you can always obtain a copy of the
source code for the program. Most source code is available on the developer’s website as
a tarball, or within a git repository such as GitHub. Moreover, source code is platform-
independent; you may compile the same source code on any platform (x86, x64, ARM,
RISC-V, etc.) as long as you have the compiler tools installed. To install a program from
source code on Linux, perform the following steps:

1. Clone a git repository of source code or download a tarball of source code from
an Internet site. You can use a Web browser, or the wget or curl commands to
download a tarball on a system that does not have a Web browser (e.g. wget
https://samplewebsite.com/files/sourcecode.tgz).
- 172 -

2. If you downloaded a tarball, extract it (e.g. tar –zxvf sourcecode.tgz).


This will create a subdirectory underneath your current directory with the
necessary source code files.

3. Change to the source code directory and view the README and INSTALL files (if
present) for detailed information regarding the installation.

4. Execute the configuration script that comes with the source code by typing
./configure while in the source code directory. This performs a check on
your system to learn about system settings and verify that any necessary
prerequisites for the program have been met. When finished, the configuration
program creates a file called Makefile in the source code directory with all of
the system information needed to compile the program.

5. Compile the program by typing make while in the source code directory (or use
the –f option to specify the location of the directory). The compiler uses the
information in the Makefile to compile the actual program and could take a
long time depending on the size of the program.

6. Once the program has been compiled, copy the program to the correct location on
the hard disk (usually /usr/local/bin) by typing the command make
install while in the source code directory. Following this, the source code
directory may be safely removed from the system.

Installing Software using a Package Manager


Most Linux software today is pre-compiled for a particular architecture and distributed in
a format that must be used with a package manager. Package managers define a common
package format and can be used to install, maintain, query and remove software using
package information that is stored in a central database on the system.

NOTE: Microsoft Windows also has a standard package manager format called MSI
(Microsoft Installer), which packages software in files that have a .msi file extension.

While there are several package managers available for Linux, most distributions use
either the Debian package manager or the Red Hat package manager. Ubuntu uses the
Debian package manager, while Fedora uses the Red Hat package manager.

The Red Hat package manager uses the rpm command to install package files that end
with the .rpm file extension, as well as manage them afterwards:

rpm –i filename.rpm Installs software a package file

rpm –U filename.rpm Upgrades a package to a newer version


- 173 -

rpm –qa | less Queries all package names on the system from the
Red Hat package manager database

rpm –qi packagename Lists information about an installed package

rpm –qc packagename Lists configuration files for an installed package

rpm –ql packagename Lists all files in an installed package

rpm –qf /path/to/a/file Lists the package a certain file belongs to

rpm –V packagename Verifies that no files have been deleted from an


installed package

rpm –e packagename Uninstalls (erases) an installed package

NOTE: By default, rpm checks for dependencies (other packages required by the
package) when installing software. The --nodeps option may be used to override this,
but it is highly discouraged to ensure system stability.

Most .rpm packages are freely available on Internet servers (called software
repositories). To automatically download and install these packages (including any
dependency packages), you can use the yum utility (Yellowdog Updater Modified), or
the dnf utility (Dandified YUM), which replaces the yum utility on modern Linux
distributions. OpenSUSE Linux distributions use zypper in place of yum.

Fedora uses dnf by default and lists available settings and software repositories in the
/etc/dnf/dnf.conf and /etc/yum.repos.d/* files (some other distributions
use /etc/yum.conf instead of /etc/dnf/dnf.conf). In previous chapters, you
used the dnf command several times to install and update software from a software
repository. Following are some common dnf commands:

dnf check-update Checks for installed software updates

dnf update packagename Updates a package to the latest version

dnf update Updates all packages (including the kernel)

dnf install packagename Installs a package from a software repository (you


can also specify multiple packagename arguments)

dnf groupinstall packagegroupname


Installs a common set of packages (package group)
- 174 -

dnf search keywords Search the repository by keyword

dnf list installed Lists installed packages

dnf list available Lists available packages

dnf grouplist Lists package groups

dnf repolist Lists repository information

dnf info packagename Lists detailed information for a particular package

dnf remove packagename Removes a package (you can also specify multiple
packagename arguments)

dnf groupremove packagegroupname


Removes all packages in a package group

If an RPM package update includes a new version of a configuration file, the update will
either leave the original file untouched and create another one called file.rpmnew
with the new settings, or overwrite it with the new settings and save the original settings
to file.rpmsave. To ensure that packages contain the settings you want following an
upgrade, run find /|egrep "(rpmnew$|rpmsave$)" to locate these files, view
their contents, and make any necessary changes to the configuration files.

NOTE: If your repository cache becomes corrupted, you will see errors when running
dnf. In this case, you can run the dnf clean all command to clear the cache.

The Debian package manager is similar to the Red Hat package manager, but instead uses
the dpkg command to install packages that have a .deb file extension. Some common
Debian package manager commands include:

dpkg –i filename.deb Installs software contained in the filename.deb file

dpkg -l Lists installed packages

dpkg –l '*ubuntu*' Lists installed packages with the word ubuntu in


their name or description

dpkg –L packagename Lists file contents of an installed package

dpkg –S /path/to/a/file Lists the installed package a file belongs to

dpkg –V packagename Verifies that no files have been deleted from an


installed package
- 175 -

dpkg –r packagename Uninstalls (removes) an installed package, except


for configuration files

dpkg –P packagename Uninstalls (purges) an installed package, including


any configuration files

NOTE: You can also use the dpkg-query command to query packages based on
several criteria. Consult the manual page for more information.

The apt-get (Advanced Package Tool) utility can download and install software
automatically from Debian software repositories on the Internet. You can search online
Debian repositories for packages using apt-cache, and add additional repositories
using add-apt-repository. A newer version of apt-get called apt is often used
on newer Linux distributions; it provides a subset of the most commonly-used features of
apt-get and apt-cache, but with more useful output during the installation process.
Settings and repository information are stored in the /etc/apt/apt.conf.d/*,
/etc/apt/sources.list and /etc/apt/sources.list.d/* files.
Following are some common Advanced Package Tool commands:

apt-get update (or apt update) Updates the package list from repositories

apt-get upgrade packagename (or apt upgrade packagename)


Upgrades a package to the latest version

apt-get upgrade (or apt upgrade)


Upgrades all packages (including the kernel)

apt-get install packagename (or apt install packagename)


Installs a package (you can specify multiple
package names as arguments)

apt-get remove packagename (or apt remove packagename)


Removes a package (without removing any
package configuration files on the system)

apt-get purge packagename (or apt purge packagename)


Removes a package (also removing any package
configuration files on the system)

apt list criteria Lists packages by criteria (installed, upgradable)

apt-cache show packagename (or apt show packagename)


Shows detailed package information.
- 176 -

apt-cache search keyword (or apt search keyword)


Searches repository for packages by keyword
(to search all packages, use a keyword of . )

NOTE: Whenever you install software packages on your system (regardless of the
method you use), the associated manual are copied to /usr/share/man (info pages
are copied to /usr/share/info if available). However, many packages come with
additional useful documentation such as tutorials, FAQs, and HOWTOs that are copied to
a subdirectory under /usr/share/doc named for the package.

Installing Sandboxed Software


While package managers are the most common way to install software on Linux systems
today, their packages often have several dependencies. If one of these dependent
packages were removed later, other packages would become unstable or fail to execute.

For example, to see the dependencies needed by your shell (/bin/bash), you could run
the ldd /bin/bash command. If a required dependency is missing, it will be labelled
in red and be identified as a failed dependency. In this case, you can install the
dependency from an online software repository and run the ldconfig command to
update the list of shared library directories (/etc/ld.so.conf) and dependency
cache (/etc/ld.so.cache) to solve the problem.

To avoid dependency problems altogether, you can use a sandboxed package manager.
When you install software using a sandboxed package manager, the software is installed
in a special container, and all required dependencies needed by the software are also
installed in that container. If two different pieces of software need the same dependency
package, they will each have a copy in their own container. While this solves the problem
of package dependencies, it requires additional storage. Flatpak is a common sandboxed
package manager that is installed by default on Fedora Workstation. Some common
Flatpak commands include:

flatpak install packagename


Installs a Flatpak package (add --user to make the
package available only to the current user)

flatpak list Lists installed Flatpak package

flatpak info Lists detailed information for a Flatpak package

flatpak search Searches for available Flatpak packages in repository

flatpak uninstall packagename Removes a Flatpak package


- 177 -

Snap is a common sandboxed package manager that is installed by default on Ubuntu


systems. Some common Snap commands include:

snap install packagename


Installs a Snap package (for packages that cannot be
entirely sandboxed, add the --classic option)

snap list Lists installed Snap package

snap info Lists detailed information for a Snap package

snap find Searches for available Snap packages in repository

snap remove packagename Removes a Snap package

Flatpak and Snap are package managers that must be installed on your system before you
install and run sandboxed software packages. However, you don’t need a sandboxed
package managers to run sandboxed software on Linux. Like Flatpak and Snap packages,
AppImage packages contain all dependencies, but do not depend on a package manager
whatsoever. Instead, they can be downloaded and run directly on the system. To do this,
download the packagename.appimage file from the appropriate website, assign it
execute permission (chmod +x packagename.appimage) and then execute it
(./packagename.appimage). To remove the package, you can simply delete the
packagename.appimage file. You can find AppImage packages on various open
source software project websites, as well as on https://www.appimagehub.com.

NOTE: When executed, AppImage packages create their own sandboxed filesystem to
execute the app. This feature is called Filesystem in Userspace (FUSE), and is used by
software and operating system components that need to create filesystems without relying
on support from the Linux kernel.

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
dnf groupinstall “Development Tools”
git clone https://github.com/bartobri/no-more-secrets.git
cd no-more-secrets
less README.md
make nms
make sneakers
make install
which sneakers
which nms
sneakers
ls -l | nms
- 178 -

dnf install nmap


rpm -qi nmap
rpm -V nmap
rpm -ql nmap
dnf info nmap

dnf grouplist
dnf list installed
rpm -qf /bin/bash

2. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
apt install nmap
dpkg -l
dpkg -l '*nmap*'
dpkg -L nmap | less
snap install powershell --classic
snap info powershell
snap list
powershell (run the Get-Host command in PowerShell and then exit)

3. In tty3 on your Fedora Workstation, run the flatpak --system remote-add --if-not-
exists flathub https://flathub.org/repo/flathub.flatpakrepo command. Next, run the
following commands:
flatpak search vlc
flatpak install flathub org.videolan.VLC

4. Switch to tty1 on your Fedora system and log into the GNOME desktop environment
as the woot user. Navigate to Activities > All Applications, type vlc to locate and run the
VLC media player.

5. In Firefox on your Fedora system, download the sample Subsurface .appimage


package from https://appimage.org/ for your architecture. Next, open a Terminal app and
run the following commands (as woot) to run your AppImage package:
cd Downloads
chmod +x Subsurface*.appimage
./Subsurface*.appimage
- 179 -

5.12 Logs
When troubleshooting system-related issues, checking event logs is important. Most
events are saved to log files within the /var/log directory. For example, the Apache
Web server logs information to the /var/log/httpd directory (Fedora) or
/var/log/apache2 directory (Ubuntu), and you can often find system startup- and
daemon-related events in /var/log/boot.log, /var/log/messages or
/var/log/syslog depending on your distribution.

Not all files in /var/log contain text that can be viewed using a text tool. For example,
previous user logins are stored in binary format within the /var/log/wtmp log file
and can be viewed using the who /var/log/wtmp command. Other logs are only
stored in memory. For example, to see the hardware detected by the Linux kernel during
the last system startup, you can the dmesg command to retrieve the list from memory.

Working with the Systemd Journal Daemon


On modern Linux systems that use Systemd, operating system-related events are logged
by the Systemd Journal Daemon (journald) to a database in the /var/log/journal
directory. To view the events in this database, you can use the journalctl command.
For example, you can use journalctl –k to view the hardware detected by the Linux
kernel at system startup, or the journalctl –b command to view messages from
daemons that were run at system startup. You can also search for events by Systemd
service units. For example, to locate any information related to the cron daemon
(crond.service), you can run the journalctl --unit crond.service
command. To narrow down the time period to only show cron daemon log entries from
1pm until 2pm (using 24-hour time), you can append the following options:

[root@localhost ~]# journalctl --unit crond.service --since "13:00"


--until "14:00"

Logs begin at 2021-06-18 13:00:00, end at 2021-06-18 14:00:00


Jun 18 13:01:01 localhost CROND[3361566]: (root) CMD (run-parts /e>
Jun 18 13:01:01 localhost CROND[3361953]: (root) CMD (run-parts /e>
Jun 19 13:01:01 localhost CROND[3362348]: (root) CMD (run-parts /e>
Jun 19 13:01:01 localhost CROND[3362644]: (root) CMD (run-parts /e>
Jun 19 13:01:02 localhost anacron[3362653]: Anacron started on 202>
Jun 19 13:01:02 localhost anacron[3362653]: Normal exit (0 jobs ru>
[root@localhost ~]# _

The > symbol at the end of each line in the output above indicates that there is more log
information to be displayed; you can press the right cursor key to see this information.
To instead view entries by executable program (or script), you can use the journalctl
_COMM=programname syntax. If you type journalctl --unit or
journalctl _COMM= and press the [Tab] key, a list of available Systemd service
units or programs will be shown.
- 180 -

NOTE: To modify Journal Daemon settings (such as setting a maximum database size,
specifying a database location, or forwarding events to the Remote System Log Daemon),
you can edit the appropriate lines within /etc/systemd/journald.conf.

Working with the Remote System Log Daemon


On legacy Linux systems that do not use Systemd, operating system-related events are
logged by the Remote System Log Daemon (rsyslogd). Rsyslogd opens a socket
(/dev/log) and listens for events that are sent by different areas of the system (called
facilities) of a certain priority level. When it receives an event, it appends it to the
appropriate log file under /var/log according to the rules within
/etc/rsyslog.conf and files in the /etc/rsyslog.d directory. Some sample
lines from the /etc/rsyslog.d/50-default.conf file on an Ubuntu system are
listed below. As with shell scripts, commented lines begin with a # symbol.

[root@localhost ~]# grep /var/log/mail /etc/rsyslog.d/50-default.conf


mail.* -/var/log/mail.log
#mail.info -/var/log/mail.info
#mail.warn -/var/log/mail.warn
mail.err /var/log/mail.err
[root@localhost ~]# _

The first line in the output logs all messages from the mail daemon to
/var/log/mail.log (the – symbol before the path indicates that the system should
sync changes to the disk immediately after writing). If the second line shown above was
uncommented, it would log any messages of info priority and above to the
/var/log/mail.info file and sync changes afterwards. To instead log info
priority messages only, you could use the format mail.=info in place of
mail.info. If you modify lines within the /etc/rsyslog.d/50-default file,
you must restart the rsyslogd daemon for them to take effect.

NOTE: Available priority levels (from least priority to highest priority) are: debug,
info, notice, warning (or warn), error (or err), critical (or crit),
alert and panic (or emerg).

Available facilities include auth/security (login events), authpriv (network


authentication events), cron (cron daemon events), daemon (system daemon events),
kern (kernel events), lpr (LPD events), mail (mail daemon events), news (news
daemon events), syslog (rsyslogd events), user (user process events), uucp (UUCP
daemon events), and local0-7 (8 custom event facilities that you can define).

To send events to rsyslogd on another computer, you can use @hostname:514 in


place of the log file path in /etc/rsyslog.d/50-default. However, the remote
computer must have the following lines uncommented in /etc/rsyslog.conf:
- 181 -

# Provides UDP syslog reception


$ModLoad imudp
$UDPServerRun 514
# Provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514

NOTE: Many modern Linux distributions configure journald to forward events to


rsyslogd for flexibility and compatibility. For example, many network devices (e.g. Cisco
routers) are configured to centrally forward their logging events to rsyslogd on a Linux
server.

NOTE: If you want to generate events that are logged by either journald or rsyslogd (e.g.
within a shell script), you can use either the systemd-cat command (for journald) or
the logger command (for either journald or rsyslogd). When using the logger
command with rsyslogd, you must use the -p facility.priority option to
specify the appropriate facility and priority.

Clearing and Rotating Log Files


Although log files contain important information, this information is often only useful for
a limited period of time. As a result, the contents of log files should be cleared on a
periodic basis to save disk space. To do this, you should use output redirection
(>logfile) to clear file contents instead of removing the log file with the rm command
(which also removes the file permissions and ownership).

If you are using journald, you can limit the size of the event database to ensure that old
events are automatically removed. For example, setting the line SystemMaxUse=75M
in /etc/systemd/journald.conf would limit the database size to 75MB. If you
add the line Storage=volatile, all events will be stored in 75MB of RAM only,
with the oldest events overwritten as necessary and cleared during a system reboot.

Even on systems that use journald, there will still be many log files under /var that are
created by daemons on the system, such as Apache. To simplify clearing these log files,
you can instead rotate them, which renames them with an extension and creates new ones
for recent events. You can then remove the oldest rotated log files as you see fit.

To automate the rotation of log files, you can use the logrotate command, which is
scheduled on a daily basis via /etc/cron.daily/logrotate. This command
rotates log files according to log rotation rules within the /etc/logrotate.conf
file as well as files in the /etc/logrotate.d directory. The log rotation rules shown
below rotate log files listed in this file (and files in the /etc/logrotate.d directory)
weekly by default. Rotated log files will have the date of rotation appended to their
filename, and up to 4 rotated log files will be kept (with older ones removed during the
next rotation).
- 182 -

[root@localhost ~]# cat /etc/logrotate.conf


weekly
rotate 4
create
dateext
# uncomment this next line if you want your log files compressed
#compress
include /etc/logrotate.d
/var/log/wtmp {
monthly
create 0664 root utmp
minsize 1M
rotate 1
}
/var/log/btmp {
missingok
monthly
create 0600 root utmp
rotate 1
}
[root@localhost ~]# _

From the above output, /var/log/wtmp is rotated with non-default values. It is


rotated monthly instead of weekly if larger than 1MB, and only one old log file is kept.
New files will have the permissions 0644 and be owned by the root and the utmp group.
Likewise, /var/log/btmp is rotated monthly instead of weekly and only one old log
file is kept. If the log file is missing, no errors will be generated, and new files will have
the permissions 0600, root owner and utmp group owner.

As another example, the following file indicates that the /var/account/pacct file
is rotated daily (only if it is not empty) with new files gaining root ownership and 0600
permissions. Up to 31 old log files will be kept and they will be compressed, but this
compression is delayed until the next rotation. Furthermore, the /usr/sbin/accton
program will be run after each rotation if the psacct daemon is active.

[root@localhost ~]# cat /etc/logrotate.d/psacct


/var/account/pacct {
compress
delaycompress
notifempty
daily
rotate 31
create 0600 root root
postrotate
if /etc/init.d/psacct status >/dev/null 2>&1; then
/usr/sbin/accton /var/account/pacct
fi
endscript
}
[root@localhost ~]# _
- 183 -

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
ls /var/log
less /etc/logrotate.conf
ls /etc/logrotate.d
less /etc/logrotate.d/*
who /var/log/wtmp
journalctl _COMM=su
journalctl _COMM=gdm --since “08:00”
journalctl --unit=crond.service --since “08:00”
journalctl --unit=cups.service --since “08:00”
journalctl -b
journalctl -k
dmesg | less

2. On your Ubuntu Server virtual machine, log into tty3 as the root user and run the
following commands:
ls /var/log
less /etc/logrotate.conf
ls /etc/logrotate.d
less /etc/logrotate.d/*
who -f /var/log/wtmp
less /etc/rsyslog.conf
less /etc/rsyslog.d/50-default.conf
less /var/log/kern.log
less /var/log/syslog
less /var/log/boot.log

5.13 Performance
Monitoring performance allows you to identify whether a system issue is caused by
inadequate hardware, as well as ensure that you understand when it is time to add
additional hardware resources to a system. Most performance monitoring utilities are
part of the sysstat (System Statistics) package and must be run as root. Some common
sysstat utilities include:
mpstat Displays processor statistics.
mpstat -P 0 Displays processor statistics for the first processor core.
mpstat 1 7 Displays processor statistics every one second for 7 seconds.

pidstat Displays processor statistics for each process on the system.

iostat Displays processor and Input/Output (I/O) statistics.


- 184 -

Some of the key statistics shown in these utilities include:


%user or %sys (the % of time the processor is executing programs)
%nice (the % of time the processor is executing programs with non-default nice values)
%system or %sys (the % of time the processor is performing system maintenance tasks)
%iowait (the % of time the processor was idle with outstanding disk I/O requests)
%irq and %soft (the % of time the processor is using to respond to interrupts)
%steal and %guest (the % of time the processor is servicing virtual machines)
%gnice (the same as %nice but for virtual machines running on the system)
%idle (the % of time the processor is idle)
%intr/s (the number of interrupts the received from peripheral devices per second)
kB_read/s (the number of KB read from I/O per second)
kB_wrtn/s (the number of KB written to I/O per second)
tps (I/O transfers per second)

In general, %usr should be much greater than %system during normal operation. High
%iowait, tps, kB_read/s and kB_wrtn/s values may indicate the need for a faster
storage device. Rapid increases in %irq, %soft and %intr/s over time typically indicate
malfunctioning hardware, and a %idle of less than 25% consistently indicates that the
processor is not adequate to handle the application load.

The most comprehensive utility in the sysstat package is sar (System Activity
Reporter). The cron daemon schedules sar commands every 10 minutes (via
/etc/cron.d/sysstat) and logs the results to files called sa# (where # is the day
of the month) in the /var/log/sa (Fedora) or /var/log/sysstat (Ubuntu)
directory, provided that ENABLED=true is present in the /etc/default/sysstat
file. To see the statistics for the current day (every 10 minutes), run sar without any
options or arguments:

[root@localhost ~]# sar


07:30:02 AM CPU %user %nice %system %iowait %idle
07:40:02 AM all 1.06 0.00 0.15 0.01 98.79
07:50:02 AM all 1.41 0.00 0.24 0.02 98.33
08:00:01 AM all 1.32 0.00 0.34 0.22 98.12
08:10:02 AM all 1.20 0.00 0.16 0.03 98.60
08:20:02 AM all 1.22 0.00 0.17 0.00 98.61
Average: all 2.44 31.65 2.87 1.69 61.36
[root@localhost ~]# _

Alternatively, you could type sar -f /var/log/sa/sa24 to see the statistics from
the 24th of the month. Or, to take sar measurements every second for 7 seconds, you
could type sar 1 7 at the command prompt. You can also run sar for long periods of
time using the format sar –s hh:mm:ss –e hh:mm:ss (where –s specifies the
hour:minute:second to start monitoring and –e specifies the hour:minute:second to stop
monitoring).

The sar command displays processor statistics by default, but you can add the -b option
to display I/O statistics, -d to display statistics for each block storage device, -n to
- 185 -

display network statistics, -q to display processor queue statistics, -R to display memory


statistics, -u to display processor statistics (the default), or -W to display swap statistics.
The –b and –d options give you the same statistics as iostat and the –u option gives
you the same statistics as mpstat; interpreting the output of these options requires that
you have knowledge of what their values are during normal operating on the system.
However, if the runq-sz (run queue size) displayed by the sar –q command is greater
than 2, the system cannot hand the number of processor requests. Alternatively, if the
values of pswpin/s and pswpout/s from a sar –W command are high, the system needs
more memory.

Linux also contains many performance tools outside of the sysstat package, including
the top utility discussed earlier, which displays many of the same performance statistics
discussed earlier at the top of the utility. There is also an iotop command that can
monitor storage I/O for each process on the system, as well as an ioping command that
can monitor the latency for a storage device (e.g. ioping /dev/sdb). If you identify
storage I/O issues with either of these commands, try changing the schedule set in the
/sys/block/device/queue/scheduler file. The scheduler is used by the kernel
to perform storage I/O for the device. By default, cfq (common fair queueing) is used,
which is best for slower hard disks, but this can be changed to deadline or noop (no
operation) to improve write performance (especially for SSDs). For example, to change
the scheduler for /dev/sda to noop, you can run echo noop >
/sys/block/sda/queue/scheduler. If this solves the I/O issues with
/dev/sda, then you can append elevator=noop to the GRUB_CMDLINE_LINUX
line in /etc/default/grub and run the grub2-mkconfig command to rebuild
the GRUB configuration file with the new kernel option to ensure it is automatically
configured at system startup.

The iftop command can be used to troubleshoot network connectivity, the free
command shows free physical and swap memory, and the vmstat command displays
memory, I/O and processor statistics:

[root@localhost ~]# vmstat


procs -----------memory---------- --swap-- ---io--- --system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 10152 27412 152780 0 0 1692 111 1065 298 36 17 28 19 0
[root@localhost ~]# _

The columns displayed by vmstat are described below:


r the # of processes waiting to be run
b the number of sleeping processes
swpd the amount of swap memory used in Kilobytes
free the amount of free physical memory
buff the amount of memory used by buffers in Kilobytes
cache the amount of memory used as cache
si the amount of memory in Kilobytes per second swapped in to the disk
so the amount of memory in Kilobytes per second swapped out to the disk
- 186 -

bi the number of blocks per second sent to block devices


bo the number of blocks per second received from block devices
in the number of interrupts sent to the processor per second
cs the number of context changes sent to the processor per second
us the processor user time
sy the processor system time
id the processor idle time
wa the time spent waiting for I/O
st the time stolen from a virtual machine

You can also quickly measure overall system load using the top, tload, uptime, and
w commands. For example, the uptime output below shows that the processor was:
• overloaded by 24% in the last minute
• underloaded 22% in the last 5 minutes (no processes had to wait for a turn)
• overloaded 21% in the last 15 minutes

[root@localhost ~]# uptime


17:04:56 up 10 min, 3 users, load average: 1.24, 0.78, 1.21
[root@localhost ~]# _

On a system with multiple processors, you must divide these numbers by the number of
total processors. For example, a load average of 2.48, 1.56, 2.42 on a system with two
processors would be equivalent to the output above.

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
dnf install sysstat iotop ioping iftop
mpstat 2 5
pidstat
iostat
vmstat
free
sar -u 2 5
sar -q 2 5
sar -d 2 5
sar -f /var/log/sa/saX (where X is the day of the month – this will work tomorrow!)
top
iotop
ioping /dev/sda
iftop
uptime
tload (use [Ctrl]+c to quit)
w
- 187 -

5.14 Devices
Standard devices, such as terminals and disks (e.g. /dev/sda) have a driver within the
Linux kernel and are referenced by a device file under the /dev directory. Linux drivers
for most hardware other than standard devices are inserted into the kernel as a kernel
module (a .ko file under the /lib/modules/kernelversion directory, often
compressed with xz), and normally installed via a package from a software repository or
from the manufacturer’s website.

There are many Linux commands that can identify hardware devices on your system,
including lscpu (CPU info), lsmem (RAM info), lsdev (IO ports, DMA and
interrupts for hardware devices), lspci (devices connected to the PCI bus), lsusb
(connected USB devices), lsscsi (connected SCSI and NVMe disks), lsblk
(connected block devices), nvme list (connected NVMe disks), dmesg (hardware
detected by the Linux kernel at boot time), dmidecode (hardware detected by the
system BIOS/UEFI), and lshw (hardware information for all devices). Many of these
commands will tell you if the associated driver for a hardware device is missing and must
be installed. For example, to display network adapter and driver information you can run
the lshw –class network command. If the driver is missing for one or more of
your network adapters, you can Google the hardware information shown to obtain
instructions on how to install the right driver for your Linux distribution. Once the driver
is installed, the Linux kernel will likely detect the new hardware on the next system
startup and automatically insert the correct module.

However, some hardware is not detectable by the Linux kernel. In this case, you can list
the module name in the /etc/modprobe.conf file, or within a file under the
/etc/modprobe.d, /etc/modules-load.d or /usr/lib/modules-
load.d directories, where they are automatically loaded by the kernel at boot time using
the modprobe command. When you install a module from a package, it usually adds a
file to one of these directories to ensure that it is loaded by the kernel. If you install a
package that adds and updated module that should replace an existing module, the old
module name is added to /etc/modprobe.d/blacklist.conf to ensure that it is
not loaded.

NOTE: On legacy systems, modules are typically loaded from the /etc/modules file.

You can also manually load modules into the Linux kernel using the modprobe or
insmod command (modprobe checks for module dependencies first and loads any
prerequisite modules as necessary). This allow you to test a driver that you’ve installed to
ensure that it functions properly (if it doesn’t, you can uninstall the driver package). You
can also run lsmod to list modules currently inserted into the kernel, modinfo to list
information about a module, depmod to list module dependencies, or rmmod to remove
a module from the kernel.
- 188 -

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
dnf install lshw
lshw -class network (note the driver used)
lsmod | less (note that your driver is listed)
modprobe dummy (dummy is a fake network card driver used for testing only)
lsmod | grep dummy (note that your driver is listed)
modinfo dummy
rmmod dummy
less /etc/modprobe.d/* (note any manually-loaded modules)
poweroff

5.15 System Rescue


Certain problems can prevent a Linux system from starting, including corrupted
bootloaders or root filesystems, as well as incorrectly configured drivers or errors within
configuration files (e.g. /etc/fstab). To fix these issues, you can boot your system from
Linux installation media and choose the option to load a small memory-resident Linux
(called a live distribution) from which you can repair the issue. This process is called
system rescue.

When you boot your system from the installation media for a workstation-focused Linux
distribution, such as Fedora Workstation, a graphical live distribution is automatically
started. However, if you boot your system from the installation media for a server-
focused Linux distribution, you must manually select the appropriate option to start a
non-graphical live distribution.

You can use the live distribution to check and repair a corrupted root filesystem on your
hard disk or SSD or reinstall GRUB to fix a corrupted bootloader issue. You can even
mount the root filesystem on your hard disk or SSD to a directory under the live
distribution to access the files on it in order to fix a driver or configuration file issue. To
use the live distribution to perform commands from the Linux distribution installed on
your hard disk or SSD, you must first change the root of the live distribution to the one
installed on your hard disk or SSD using the chroot command.

Exercise
1. In the Settings of your hypervisor, insert the Fedora Workstation installation media
into the virtual DVD of your Fedora Workstation virtual machine and ensure that it is at
the top of the boot order.

2. Start your Fedora Workstation virtual machine.


- 189 -

3. Once the live distribution has loaded, open a Terminal and run the following
commands:
su -
df -hT (note the partitions that are mounted)
ls -l / (note that there are fewer directories under the root of the
live distribution)
fsck -f device1 (where device1 is the device file for your root filesystem –
e.g. /dev/sda3)
mount device1 /mnt
mount device2 /mnt/boot (where device2 is the device file for your /boot filesystem –
e.g. /dev/sda1)
mount -t proc proc /proc (this activates the /proc filesystem)
chroot /mnt (changes the system root from the live distribution to the
root of the system installed on /dev/sda as the root user)
ls -l / (note that you are on the root filesystem on /dev/sda)
cat /etc/fstab (note the entry you created earlier in this chapter)
passwd root (specify a new root password of your choice)
exit
exit

4. Shut down your live distribution from the menus in the GNOME desktop environment
and remove the Fedora Workstation installation media from the virtual DVD drive in
your virtual machine.

5. Start your Fedora Workstation virtual machine again and log into tty3 as the root user
with your new password. Optionally run the passwd command to change your password
back to the previous one.
- 190 -

CHAPTER

Network Administration
Now that we have explored Linux system administration, we’ll examine the
basics of Linux network and network service configuration, including SSH
(for remote access), FTP (for file transfer), and Apache (for hosting websites
and WordPress sites). Additionally, we’ll examine secure practices you
should implement for Linux systems, as well as the configuration of
firewalls and security services.

6.1 Network Interfaces & Services


6.2 Secure Shell (SSH)
6.3 Virtual Network Computing (VNC)
6.4 File Transfer Protocol (FTP)
6.5 Network File System (NFS)
6.6 Samba
6.7 Apache
6.8 WordPress
6.9 PostgreSQL
6.10 Infrastructure Services (DNS, DHCP, NTP)
6.11 Securing a Linux Server
6.12 Firewalls
6.13 Security Services
- 191 -

6.1 Network Interfaces & Services


Network Interfaces
Unlike storage devices, which have a driver in the Linux kernel that is referenced by a
device file under the /dev directory, the driver for the network interface in your system
is loaded into the Linux kernel as a module and given an alias. If you install a modern
Linux distribution within a virtual machine or container, eth0 typically represents the
first Ethernet network interface, eth1 represents the second, and so on. Similarly,
wlan0 represents the first wireless network interface, wlan1 represents the second, and
so on. However, if you install a modern Linux system bare metal, you may instead find
names that better reflect the location of the actual network interface hardware. For
example, enp0s2 is the wired Ethernet (en) network interface, in slot 2 (s2) on the first
PCI bus (p0) in the computer. If this network interface was a wireless network interface,
the device name would instead by wlp0s2. The PCI information for these network
interfaces will match the output of the lspci command.

To view the Internet Protocol (IP) configuration on your network interface you can use
either the ifconfig (interface configuration) or ip addr show command:

[root@localhost ~]# ifconfig


lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
RX packets 491973 bytes 146780102 (139.9 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 491973 bytes 146780102 (139.9 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500


inet 10.0.3.33 netmask 255.255.248.0 broadcast 10.0.7.255
inet6 fe80::59c4:3e74:e52c:142a prefixlen 64 scopeid 0x20
ether 00:15:5d:03:14:05 txqueuelen 1000 (Ethernet)
RX packets 451884501 bytes 46874425724 (43.6 GiB)
RX errors 0 dropped 24633266 overruns 0 frame 0
TX packets 214396 bytes 467527351 (445.8 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@localhost ~]# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP
link/ether 00:15:5d:03:14:05 brd ff:ff:ff:ff:ff:ff
inet 10.0.3.33/21 brd 10.0.7.255 scope global dynamic noprefixroute
inet6 fe80::59c4:3e74:e52c:142a/64 scope link noprefixroute
[root@localhost ~]# _
- 192 -

In the previous output, the first network interface (eth0) has both an IPv4 address
(10.0.3.33) and IPv6 address (fe80::59c4:3e74:e52c:142a). The loopback
adapter (lo) represents the local computer and always has an IPv4 address of
127.0.0.1, and IPv6 address of ::1. Normally, these IP addresses are automatically
provided by a router or Dynamic Host Configuration Protocol (DHCP) server on the
network, but you can configure them manually.

To manually configure the IP protocol on a network interface to obtain network and


Internet access, you must set the following parameters at minimum:
• IP address – The number (either IPv4 or IPv6) that uniquely identifies your
system on the network.
• Subnet mask (or netmask) – Identifies which portion of your IP address refers to
the network that you are part of, and which portion identifies your system. It is
often written in the number of bits (e.g. /24 = 24 bits, /64 = 64 bits), but IPv4 can
also specify it in binary value form (e.g. 255.255.255.0 = 24 bits).
• Default gateway (or gateway) – The IP address of a router on the network that
allows you to access other networks, and the Internet.
• Domain Name Space (DNS) servers (or nameservers) – The IP address of one or
more servers that can resolve a server host name (e.g. www.trios.com) to an
associated IP address such that your system can connect to it across the network.

You can also use the ifconfig or ip commands to directly configure these parameters
on a network interface if you supply the appropriate arguments or use the dhclient
command to obtain them from a DHCP server. For example, dhclient eth0 would
immediately obtain DHCP configuration for the eth0 network interface.

However, you should instead set manual or DHCP settings for each network interface in
a configuration file to ensure that they are configured at system startup. This
configuration file varies from distribution to distribution and may be processed by a
helper daemon (called a renderer), such as NetworkManager or Systemd-networkd.

On modern Ubuntu Server distributions, YAML files that are part of the NetPlan/cloud-
init subsystem are used to specify network interface settings configured by the Systemd-
networkd renderer. YAML is a superset of the JSON text file format; it uses key-value
pairs to store data but has simplified syntax and added support for comments. A sample
manual IPv4 configuration for eth0 is shown below (IPv6 configuration is obtained
automatically using DHCP). After changing this file, you must run the netplan
apply command to apply the configuration.

[root@localhost ~]# cat /etc/netplan/00-installer-config.yaml


network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: no
dhcp6: yes
- 193 -

addresses: [10.3.101.112/24]
gateway4: 10.3.101.254
nameservers:
addresses: [8.8.8.8,8.8.4.4]
[root@localhost ~]# _

Up until recently, Fedora systems specified network configuration in files within


/etc/sysconfig/network-scripts. For example, the IP configuration for eth0
would be listed in the /etc/sysconfig/network-scripts/ifcfg-eth0 file.
After editing this file, you could run ifdown eth0;ifup eth0 to apply the
changes. However, on recent Fedora Workstation systems, the NetworkManager
renderer is used to configure network interfaces via entries in configuration files under
the /etc/NetworkManager/system-connections directory (if no files exist
within this directory, NetworkManager obtains IP configuration for each network
interfaces from DHCP). The following file performs the same configuration shown in the
previous two examples for eth0 on a Fedora Workstation. After changing this file, you
must run the nmcli connection down Wired1; nmcli connection up
Wired1 command to apply the configuration.

[root@localhost ~]# cd /etc/NetworkManager/system-connections


[root@localhost system-connections]# cat Wired1.nmconnection
[connection]
id=Wired1
uuid=e4006c02-9479-340a-b065-01a328727a75
type=ethernet
interface-name=eth0

[ipv4]
address1=10.3.101.112/24,10.3.101.254
dns=8.8.8.8;8.8.4.4;
method=manual

[ipv6]
addr-gen-mode=stable-privacy
method=auto
[root@localhost system-connections]# _

The NetworkManager renderer is commonly used on workstation-focused distributions,


as it maintains profiles for each individual network a workstation connects to (e.g. home,
work, Tim Hortons Wi-Fi, and so on.).

You can also use the graphical Network tool within a desktop environment to configure
settings for the network interfaces on your system. To access this tool in GNOME, you
can navigate to Activities > Show Applications > Settings > Network.

After configuring a network interface, you should test connectivity. The ping (Packet
INternet Groper) command can be used to test connectivity to a remote system by host
name or IPv4 address. It sends requests until you press [Ctrl]+c as shown below. To
- 194 -

just perform 5 ping requests, you can add the –c 5 option to the ping command. To
test IPv6 connectivity, you can instead use the ping6 command.

[root@localhost ~]# ping www.trios.com


PING www.trios.com (68.183.194.157) 56(84) bytes of data.
64 bytes from 68.183.194.157 : icmp_seq=1 ttl=57 time=4.47 ms
64 bytes from 68.183.194.157 : icmp_seq=2 ttl=57 time=3.08 ms
64 bytes from 68.183.194.157 : icmp_seq=3 ttl=57 time=3.58 ms
[Ctrl]+c
--- www.trios.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 4006ms
rtt min/avg/max/mdev = 3.077/3.658/4.465/0.493 ms
[root@localhost ~]# _

There are a wide variety of different commands available on Linux that can be used to
configure network interfaces, or display network-related information, including:

networkctl (views and configures network interfaces via Systemd-networkd)


nmcli (views and configures network interfaces via NetworkManager)
nmtui (a command line, menu-based NetworkManager configuration tool)
nm-connection-editor (a graphical NetworkManager configuration tool)

iwconfig (displays hardware configuration for wireless interfaces)


iwconfig wlan0 channel 6 (configures wlan0 to use wireless channel 6)
iw dev wlan0 connect HomeWiFiLAN (connects wlan0 to the wireless
network called HomeWiFiLAN)
ethtool (displays hardware configuration for wired interfaces)
ethtool -i eth0 (displays driver information for eth0)
ethtool –S eth0 (configures eth0 to use duplex transmission mode)

ifcfg eth0 del 192.168.0.1 (removes existing IP from eth0)


ifcfg eth0 add 192.168.0.2 (adds new IP to eth0)
ifup eth0 (activates eth0)
ifdown eth0 (deactivates eth0)

whois (displays information about registered domains on the Internet)


arp (displays entries in the ARP cache)
netstat –i (displays network interface statistics)
netstat (displays active network connections, or sockets)
ss -t -a (displays active TCP sockets)
ss -u -a (displays active UCP sockets)

iftop (displays network connections and statistics by destination, interactively)


iperf -s (starts an iperf server on the local system that listens for requests)
iperf -c remotesystemIP (connects to an iperf server running on a remote
system and displays the bandwidth between your system and the target system)
- 195 -

If your system uses NetworkManager, you can use nmcli, nmtui, or nm-
connection-editor to set up a wide variety of other connections, including DSL,
VPNs, network bridges, network bonding, and InfiniBand. Virtual Private Networks
(VPNs) add an overlay network to your physical network to provide encryption. Network
bridges are often used to bridge a network interface in a virtual machine to the physical
network interface on a computer (you can instead use the brctl command to do this).
Network bonding can configure two network interfaces into a single network interface
for load balancing (also called aggregation) or active/passive fault tolerance (if the active
network interface fails, the passive one will be used). InfiniBand is often used in large
compute clusters to transfer information between nodes using Remote Direct Memory
Access (RDMA).

Host Name Resolution


Each Linux system must be configured with a host name. If a host name includes a
domain name and suffix, it is also called a Fully Qualified Domain Name (FQDN).
Server-focused distributions allow you to supply a host name during installation, while
workstation-focused distributions usually obtain a host name from a DHCP server (if the
DHCP server is configured to provide one) or provide a default generic host name. You
can view your host name using the hostname command, as well as change it using the
hostnamectl command (or by editing the /etc/hostname file):

[root@localhost ~]# hostname


localhost.localdomain
[root@localhost ~]# hostnamectl set-hostname srv1.trios.com
[root@srv1 ~]# hostname
srv1.trios.com
[root@srv1 ~]# cat /etc/hostname
srv1.trios.com
[root@www ~]# _

Host names for servers should have a corresponding record on a DNS server that allows
other systems on the network to resolve it to an IP address in order to connect to it on the
network (DNS server configuration is discussed later in this chapter).

Moreover, UNIX and Linux systems have three methods they can use to resolve host
names of other computers to the associated IP address: /etc/hosts, DNS servers, and
Network Information Service (NIS) servers (legacy). By default, nearly all Linux
systems attempt to resolve host names from entries in the /etc/hosts file, and then
consult the DNS servers configured within their network interface settings. However, you
can change this order using the /etc/nsswitch.conf file:

[root@localhost ~]# grep ^hosts /etc/nsswitch.conf


hosts: files dns
[root@localhost ~]# _
- 196 -

If you wish to add local entries for name resolution using /etc/hosts, you must use
the following format (each line can have multiple host names):

[root@localhost ~]# cat /etc/hosts


127.0.0.1 srv1.trios.com localhost
::1 srv1.trios.com localhost
10.3.101.222 brain.world.com brain
10.3.101.223 pinky.world.com pinky
10.3.101.254 gateway
[root@localhost ~]# _

The most common method for host name resolution today is DNS, and the IP addresses
of DNS servers to use are stored in the /etc/resolv.conf file. You should never
edit the /etc/resolv.conf file as it is overwritten at system startup by runtime
scripts, NetworkManager or Systemd-networkd to reflect the DNS servers you specified
within network interface configuration files or obtained via DHCP.

[root@localhost ~]# cat /etc/resolv.conf


# Generated by NetworkManager
nameserver 8.8.8.8
nameserver 8.8.4.4
[root@localhost ~]# _

On systems that use Systemd, DNS host name resolution is performed by the Systemd-
resolved daemon. In this case, /etc/resolv.conf will contain the line
nameserver 127.0.0.53 to redirect all DNS host name resolution requests to
Systemd-resolved.

You can manage Systemd-resolved using the resolvectl command. For example,
resolvectl dns eth0 8.8.8.8 will configure the eth0 interface to use a DNS
server of 8.8.8.8, resolvectl flush-caches will clear the DNS cache, and
resolvectl status will display DNS status information.

To test host name resolution, you can use the host, resolvectl query,
nslookup or dig commands, each of which provide different levels of detail:

[root@localhost ~]# host www.trios.com


www.trios.com has address 192.139.188.172
trios.com mail is handled by 10 mx.trios.com.
[root@localhost ~]# resolvectl query www.trios.com
www.trios.com: 192.139.188.172
-- link: eth0
-- Information acquired via protocol DNS in 91.3ms.
-- Data is authenticated: no
-- Data was acquired via local or encrypted transport: no
-- Data from: network.
[root@localhost ~]# nslookup www.trios.com
Server: 10.0.1.1
Address: 10.1.1#53
- 197 -

Non-authoritative answer:
www.trios.com canonical name = trios.com.
Name: trios.com
Address: 192.139.188.172
[root@localhost ~]# dig www.trios.com
; <<>> DiG 9.8.1-P1-RedHat-9.8.1-4.P1.fc16 <<>> www.trios.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55425
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.trios.com. IN A

;; ANSWER SECTION:
www.trios.com. 3531 IN CNAME trios.com.
trios.com. 47 IN A 192.139.188.172

;; Query time: 3 msec


;; SERVER: 10.0.1.1#53(10.0.1.1)
;; WHEN: Thu Dec 1 13:23:00 2011
;; MSG SIZE rcvd: 61
[root@localhost ~]# _

Routing
When a network interface is configured with an IP address and default gateway (either
manually or via DHCP), two routes are automatically added to the route table: a route to
the local network via the IP address, and a route to all other networks via the default
gateway IP. These two routes ensure that the system can communicate on the local
network, as well as send traffic destined for other networks to the router on the network
for forwarding.

For a Linux client or server, these two automatically-configured routes are all that are
necessary for normal operation. However, if your Linux system is configured as a router,
then you may need to add additional routes to the other networks within your
organization, as ensure that the default gateway route forwards traffic to the router in
your organization that forwards traffic to your Internet Service Provider (ISP).

NOTE: To turn a Linux server into a network router, you must enable IP forwarding in
the kernel. You can do this using the sysctl net.ipv4.ip_forward = 1
command for IPv4, and sysctl net.ipv6.conf.all.forwarding = 1 for
IPv6. To enable IP forwarding at every boot, add the line net.ipv4.ip_forward =
1 (for IPv4) and net.ipv6.conf.all.forwarding = 1 (for IPv6) to the
/etc/sysctl.conf file.

To view your route table, you can use either the ip route, route -n, or
netstat –rn command. To manually add a route to a target network, you can either
use the ip route add, or route add command. For example, you can use either of
- 198 -

the following two commands to create a route in the route table that forwards traffic
destined for the 5.0.0.0/8 network to the router that has the IP address 3.0.82.2:
ip route add 5.0.0.0/8 via 3.0.82.2
route add -net 5.0.0.0 netmask 255.0.0.0 gw 3.0.82.2

To manually set a default gateway route, you specify a target network of 0.0.0.0, and to
manually delete a route, you can specify the target network alongside either the ip
route del or route del command.

Alternatively, if the routers on your network use a routing protocol to automatically


configure routes, you can install the Quagga package on your Linux router and configure
it using the zebra command (view the manual page for usage information). The Quagga
package contains a Routing Information Protocol (RIP) protocol daemon, Open
Shortest Path First (OSPF) protocol daemon, as well as a set of daemons that can
implement WAN routing protocols such as Border Gateway Protocol (BGP).

To test routing to a target host name or IP address, you can use the traceroute,
traceroute6 (IPv6), tracepath, tracepath6 (IPv6) commands, or the
interactive mtr (my traceroute) utility.

Network Services
The primary purpose of most Linux servers today is to provide network services. Each
network service runs as a daemon that listens for IP packets on a certain port number.
For example, a Web server will listen on port 80 (HTTP) and 443 (HTTPS), whereas an
FTP file server will listen on port 21 (or port 22 if the FTP protocol is encrypted using
SSH). A list of common ports and the network services that listen on them is contained
within the /etc/services file.

When you start a daemon that provides a network service, your Linux server starts
listening to the associated port number. To see which port numbers your Linux server is
listening to and providing services for, you can run the following nmap command (which
you installed in the previous chapter):

[root@localhost ~]# nmap -sT localhost


Starting Nmap 7.80 ( https://nmap.org ) at 2021-06-22 21:25 EDT
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00052s latency).
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open http
443/tcp open https
3306/tcp open mysql

Nmap done: 1 IP address (1 host up) scanned in 0.37 seconds


[root@localhost ~]# _
- 199 -

Nearly all daemons that provide a network service today are run and managed like any
other daemon on the system. In the past, however, some network services were started on
demand by an Internet Super Daemon. The Internet Super Daemon would listen to
several different port numbers on behalf of other network service daemons that it would
start when it received traffic on the port number for the service. This dramatically
reduced the amount of memory required on the server. While this is no longer an issue on
modern servers, you can still start daemons on demand using Systemd sockets. This is
common for legacy daemons that were originally written to work with the Internet Super
Daemon. One example of this is the telnet daemon (telnetd), which provides unencrypted
remote shell access on a legacy Linux system. After installing this daemon, you can run
the following commands to ensure that Systemd starts it on demand, and ensures that it is
started on demand at system startup:
systemctl enable telnet.socket
systemctl start telnet.socket

When troubleshooting access to a network service on your server, you should first view
the output of one of the following commands to ensure that the associated network
daemon is listening for requests on the network. If it is not, then the daemon is not started
or misconfigured:
nmap -sT localhost
netstat
ss -t (for TCP sockets), or ss -u (for UDP sockets)

You can also interact with the daemon to ensure that it is responding to client traffic
using either the telnet or ncat (nc on some distributions) command. For example,
ncat 127.0.0.1 80 or telnet 127.0.0.1 80 will attempt to interact with the
Web server daemon listening on TCP port 80.

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
ifconfig (note the IP address you received from DHCP)
ip addr show
nmcli conn show
cat /etc/NetworkManager/system-connections (note that the folder is empty)

2. Log into GNOME desktop as the woot user and navigate to Activities > Show
Applications > Settings > Network and modify your network interface to use a static
configuration for your network and Apply your changes. Next, return to tty3 and run the
following commands:
ls /etc/NetworkManager/system-connections (note the name.nmconnection file)
cat /etc/NetworkManager/system-connections/name.nmconnection
nmcli connection down "name" ; nmcli connection up "name"
nmcli conn show
ifconfig (note the new IP address for future exercises)
ip addr show
- 200 -

ping -c 5 www.yahoo.ca
hostnamectl set-hostname fedoraworkstation
hostname
cat /etc/hostname
bash (new shells will indicate the new hostname)
cat /etc/resolv.conf (note the redirection to Systemd-resolved)
resolvectl status
vi /etc/hosts (add the line 1.2.3.4 fakehost.fakedomain.com fakehost)
host fakehost.fakedomain.com
resolvectl query fakehost.fakedomain.com
nslookup fakehost.fakedomain.com
dig fakehost.fakedomain.com
host www.yahoo.ca
resolvectl query www.yahoo.ca
nslookup www.yahoo.ca
dig www.yahoo.ca
ip route (note the two routes for your network and gateway)
traceroute www.yahoo.ca
tracepath www.yahoo.ca
mtr www.yahoo.ca (press q to quit)
grep -i HTTP /etc/services | less
nmap -sT localhost (note the default services started, and the ports they listen on)
dnf install telnet-server telnet
systemctl start telnet.socket ; systemctl enable telnet.socket
nmap -sT localhost (note the telnet service listening to port 23/tcp, on demand)
telnet localhost (log in as the root user)
who (note that you are logged in remotely via a pseudo terminal, pts/0)
ss -t (note the established telnet socket on your system – after initially
connecting on port 23/tcp, communication is redirected to a
random port above 32768 for the session duration – this port is
called an ephemeral port)
exit (quits your telnet session to localhost)
who (note that your pseudo terminal is no longer present)
ss -t (note that your telnet socket is no longer present)

3. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
ifconfig (note the DHCP address assigned for future exercises)
ip addr show
networkctl
cat /etc/netplan/00-installer-config.yaml
hostname
cat /etc/hostname
ping -c 5 www.yahoo.ca
nmap -sT localhost (we’ll be adding several additional services to this virtual machine
throughout this chapter)
- 201 -

6.2 Secure Shell (SSH)


The Secure Shell (SSH) daemon (sshd) is a network service that allows you to use the
ssh command to remotely obtain a shell on a Linux server via an encrypted connection.
SSH is a modern replacement for telnet that is primarily used to perform all remote Linux
server administration but can also be used to encrypt the traffic used by other network
services, such as FTP.

SSH uses symmetric encryption to protect communication across the network, using one
of the supported symmetric ciphers (algorithms) listed in /etc/ssh/sshd_config
alongside a symmetric key that is randomly generated by the SSH client for each session.
However, to transfer this symmetric key to the SSH server running sshd, asymmetric
encryption is used, and the SSH server contains an asymmetric public/private key pair.
At the beginning of an SSH connection request, the SSH client downloads the public key
from the SSH server (called the SSH host key), uses it to encrypt the symmetric key it
would like to use for the session, and sends it back to the SSH server. The SSH server
then uses the matching private key to decrypt the symmetric key the client would like to
use for the remainder of the session.

The asymmetric ciphers supported include RSA, DSA, ECDSA and ED25519, and the
associated public/private keys are stored within the /etc/ssh directory on the SSH
server (e.g. /etc/ssh/ssh_host_rsa_key is the RSA private key, and
/etc/ssh/ssh_host_rsa_key.pub is the RSA public key). You can regenerate
these keys using the ssh-keygen command. It is good security practice to generate
new keys on each SSH server every year at minimum.

Using SSH for Remote Administration


The SSH daemon is installed and started by default on Linux server distributions. By
default, SSH does not allow you to connect as the root user. Instead, you must connect as
a regular user and use sudo to run root commands or use su – to obtain a root shell.
However, you can instead configure the line PermitRootLogin yes in
/etc/ssh/sshd_config and restart the SSH daemon to allow connections as root.

To connect to a remote Linux server running the SSH daemon, you can run the command
ssh hostname_or_IPaddress command. Your local user name will be passed to
the server automatically. If you would like to connect as a different user, you should
instead run the command ssh username@servername_or_IPaddress. During
the first connection, you will be prompted to accept (trust) the SSH host key for the
server, which is cached on your local computer in ~/.ssh/known_hosts for
subsequent connections. Once connected, you’ll receive a shell on the target system that
you can use to run commands. Any data passed between your local computer and the
Linux server will automatically be encrypted. If both the server and client run X.org and
the X11Forwarding yes line exists within /etc/ssh/sshd_config, you can
- 202 -

also run graphical commands in an SSH session (graphics and mouse movements are sent
through the encrypted SSH session). To do this, you can add the -X option to the ssh
command when connecting to the server.

Each time you connect to a server with ssh, you will be prompted for the password
associated with the account you specified. To prevent this, you can optionally create an
asymmetric public/private key pair for your local user account and add the public key
(called the SSH user key) to each server you manage to avoid having to enter your
password each time. Without arguments, the ssh-keygen command generates an SSH
user key for your current user account and stores it in the ~/.ssh directory on your
local computer. Next, you can copy your SSH user key to each server you wish to
administer. To copy it to server1, you can use the ssh-copy-id -i
root@server1 and supply the root password to complete the copy (your SSH user key
will be stored in ~/.ssh/authorized_keys on the target server). Following this,
you can execute ssh root@server1 to connect to server1 and you won’t be
prompted to supply the password for the root user.

NOTE: The ssh-keygen command and will ask you if you want to protect your
private key (used to decrypt data encrypted by your SSH user key) with a passphrase. If
you choose yes to this option, you will need this passphrase each time you connect to
another computer with SSH, unless you have a password manager program. One common
password manager program for SSH is ssh-agent, which can cache private keys and
passphrases that you add to it using the ssh-add command.

You can also use the ~/.ssh/config file to store options used when connecting to
other systems with SSH. For example, the following options automatically connect to
192.168.1.100 as the user jason.eckert if you run the ssh webserver command (you
can have multiple Host paragraphs defined in this file):

[root@localhost ~]# cat .ssh/config


Host webserver
HostName 192.168.1.100
User jason.eckert
[root@localhost ~]# _

The ~/.ssh/config file can also be used to store a wide variety of other SSH options
for the specific user, including SSH traffic tunneling (discussed later in this section).
System-wide SSH options can also be defined in the /etc/ssh/ssh_config file.

Copying Files using SSH


You can also copy files using SSH across the network. For example, to copy the remote
/root/lala file (on server1) to the local /var directory, you could run the ssh
root@server1 cat /root/lala > /var/sample command. Alternatively,
you could use the scp root@server1:/root/lala /var command. Similarly,
- 203 -

to copy the local /root/lala file to the /var directory on a remote computer
(server1), you could use the ssh root@server1 cat </root/lala ">"
/var/lala command, or the scp /root/lala root@server1:/var
command.

Tunneling from one command to another using a pipe symbol also works within an SSH
session. For example, to copy the /root/lala file to server1 and save it as
/var/lala, you could use the dd if=/root/lala | ssh -C root@server1
dd of=/var/lala command.

NOTE: Many file copy commands also have support for SSH encryption. For example,
the rsync command (used to copy and synchronize files between multiple computers)
can use SSH encryption if you specify the –e ssh option.

Tunneling Traffic using SSH


SSH can be used to add encryption to any type of traffic, creating an SSH tunnel (a type
of VPN tunnel). For example, you can use local SSH tunneling to evade firewall
restrictions that prevent you from accessing the https://lala.com website by
redirecting all HTTPS (port 443) traffic sent to your local computer on port 443 to a
server at your home (homeserver) through SSH. To do this, run the ssh homeserver
–L 443:lala.com:443 command. Next, edit the /etc/hosts file and append
lala.com to the 127.0.0.1 line. Finally, you can access https://lala.com in
your Web browser to obtain your webpage. The general format for this type of SSH
tunneling is ssh computerB –L 443:computerC:443 and is illustrated below:

To instead forward all Web traffic to homeserver, you can use the SSH SOCKS5 proxy
forwarding functionality by running the ssh -D 8080 homeserver command.
Next, you can configure the proxy settings in your Web browser (e.g. Firefox) to forward
all Web requests to localhost:8080. It is important to note that DNS name
resolution will still be performed locally unless you tunnel DNS requests through a
SOCKS5 proxy with software such as DNSCrypt or a Firefox plugin.
- 204 -

Exercise
1. On your Fedora system, log into tty3 as the root user and run the ssh
woot@UbuntuIPaddress command. Accept the SSH host key and supply the woot user’s
password when prompted. Next, run the following commands:
su - (supply the root user’s password when prompted)
who (note that you are connected via a remote pseudo terminal session)
exit
ssh-keygen (press Enter at each prompt to use default settings)
ssh-copy-id -i woot@UbuntuIPaddress
ssh woot@UbuntuIPaddress (note that you are not prompted to supply a password!)
exit

ssh woot@UbuntuIPaddress cat /etc/hosts > downloadedhosts


cat downloadedhosts
exit
2. If the operating system on your PC is Windows, download and install the Putty
Windows SSH client (putty.exe) from the following website:
http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html.

Next, run the putty.exe executable and connect to the IP address of your Ubuntu Server
using SSH on port 22. Accept the SSH host key and log in as the woot user. Run the who
command to view the pseudo terminal session connection from your Windows host and
type exit when finished to close the connection.

3. If the operating system on your PC is macOS, navigate to Applications > Utilities >
Terminal to open a shell on your local system. Next, run the ssh
woot@UbuntuIPaddress command, accept the SSH host key and supply woot’s
password when prompted. Run the who command to view the pseudo terminal session
connection from your macOS host and type exit when finished to close the connection.

6.3 Virtual Network Computing (VNC)


Since nearly all Linux servers in your corporate or cloud environment will not contain a
desktop environment, SSH is the primary means of managing Linux systems. However,
there exist many different tools that you can use to access a graphical desktop running on
a Linux server for remote administration. Virtual Network Computing (VNC) is a
popular remote desktop tool, similar to Remote Desktop Protocol (RDP) on Windows
systems. By installing a VNC server on a computer, other computers that run a VNC
client can connect to the VNC server to obtain a desktop environment. However, VNC
uses a special protocol called Remote FrameBuffer (RFB) instead of RDP.

VNC server software and client software exist for Linux, UNIX, macOS and Windows
systems. This means that you can use a single system to obtain the desktop of any Linux,
UNIX, macOS and Windows systems on your network that have a desktop. There are
- 205 -

also many other alternatives to VNC for Linux desktop administration, including
NoMachine (NX) and Xrdp (a Microsoft RDP server for Linux).

NOTE: Since X is not installed on Ubuntu Server, we’ll focus on the configuration of
VNC on Fedora Workstation in this section. However, the steps are very similar on the
Ubuntu and all other Linux distributions.

After installing a VNC server (dnf install tigervnc-server), you can


configure the VNC server by associating user connections on a certain display number:

[root@localhost ~]# /etc/tigervnc/vncserver.users


:1=woot
:2=larry
:3=curly
:4=moe
[root@localhost ~]# _

The port number that the VNC server listens on is 5900 plus the display number. Thus,
connections as curly would use port 5903.
Next, each user must run the vncpasswd command and supply a password that will be
used to authenticate them to the VNC server (and optionally a view-only password).
Users can optionally edit their ~/.vnc/config file to specify additional parameters
for their connection, such as desktop environment and resolution:

[woot@localhost ~]$ vncpasswd


Password: ******
Verify: ******
Would you like to enter a view-only password (y/n)? n
[woot@localhost ~]$ cat .vnc/config
session=gnome
securitytypes=vncauth,tlsvnc
geometry=1200x900
[woot@localhost ~]$ _

Following this, you will need to start a copy of the vncserver for each user (and
associated display number), as well as configure these copies to start automatically at
system startup:

[root@localhost ~]# systemctl start vncserver@:1 vncserver@:2


vncserver@:3 vncserver@:4
[root@localhost ~]# systemctl enable vncserver@:1 vncserver@:2
vncserver@:3 vncserver@:4
[root@localhost ~]# _

Now, you can connect to the VNC server from a remote computer using a VNC viewer.
Depending on the VNC viewer used, you will need to specify the VNC server name or IP
as well as the port number or display number. You will then be prompted for the
associated user’s VNC password before you obtain a desktop session on the remote
computer as that user.
- 206 -

NOTE: Recall that you can also use SSH to add encryption to other protocols, such as
VNC sessions. This is normally performed using local SSH tunneling. For example, to
forward local traffic on port 5903 to server1 on port 5903 as curly, you could run the ssh
-L 5903:localhost:5903 curly@server1 command. Following this, you can
start a VNC viewer and connect to localhost:5903 in order to access the VNC
server running on server1 as curly via an SSH tunnel.

Alternatively, you can use remote SSH tunneling from the computer running the VNC
server. For example, running the ssh -R 5999:server1:5900
curly@client1 on the VNC server (server1) will allow curly on client1 to start a
VNC viewer and connect to server1:5999 in order to access the VNC server running
on port 5900 on your server (server1) via SSH.

Exercise
1. On your Fedora Workstation virtual machine, log into the GNOME desktop
environment as the woot user, open a Terminal app and switch to the root user (su -).
Next, run the following commands:
dnf install tigervnc-server
firewall-cmd --add-service vnc-server (allows VNC in the firewall, discussed later)
firewall-cmd --add-service vnc-server --permanent
su - woot
vncpasswd (supply Secret555 when prompted, do not set a view-only password)
vncserver (this starts the VNC server directly instead of via Systemd for testing only)
In the output shown, note the display number that was assigned to woot:____

2. On your Windows or macOS PC, download and install the RealVNC Viewer (64-bit)
from https://realvnc.com/download/viewer. Connect to IPaddress:x (where IPaddress is
the IP address of your Fedora Workstation virtual machine and x is the display number
you recorded in the previous step. Supply woot’s VNC password when prompted.
Explore your desktop environment and close your VNC viewer when finished.

6.4 File Transfer Protocol (FTP)


FTP is the most common method to transfer files across the Internet, and often used by
developers to upload Web content to servers. FTP also allows you to host files publicly to
anonymous users on the Internet. Most modern Linux distributions use the Very Secure
FTP daemon (vsftpd), which supports both unencrypted FTP and SSH-encrypted FTP
(SFTP). To set whether uploading and anonymous connections are allowed, as well as
whether users should be confined to their home directory only (called a chroot jail), you
can edit the /etc/vsftpd.conf file and then restart the Very Secure FTP daemon.
- 207 -

When you connect to the FTP server and supply a username and password, you will be
placed in your home directory. If you specify the anonymous user, you will be placed
in the /srv/ftp or /var/ftp directory depending on your distribution. For security
reasons, you cannot log in as the root user using FTP, unless you remove or comment the
root line in the /etc/ftpusers file (which lists users denied FTP access). To
download a file from the server, you must have r permission to it, and to upload a file to
the server, you must have w to the directory on the server.

To access public files using SFTP, you can open a Web browser and navigate to
sftp://hostname_or_IPaddress (or ftp:// for unencrypted FTP).
Alternatively, you can install a graphical FTP client (e.g. FileZilla) or use the sftp
hostname_or_IPaddress command (or ftp command for unencrypted FTP) to
connect to an FTP server using a username and password. To access public files, you
would use a username and password of anonymous. If you use the sftp or ftp
commands, you receive an interactive ftp> prompt that allows you to enter FTP
commands to transfer files. Table 6-1 lists some common FTP commands.

Table 6-1 Common FTP commands


ascii Specifies text file downloads, which may cause file transfer
failure if the content is not text
bin Specifies binary file downloads (default) – all files are
downloaded in binary format (including text)
bye or quit Exits the FTP client
cd directory Changes to a subdirectory on the FTP server
close Closes an FTP connection
dir or ls Displays a directory listing from the FTP server
get filename Downloads a file to the current directory on the local
computer
help Displays a list of commands
lcd directory Changes to a subdirectory on the local computer
mget filename Downloads a file to the current directory (allows the use of
wildcards to specify multiple files)
mput filename Uploads a file from the current directory on the local
computer to the FTP server (allows the use of wildcards to
specify multiple files)
open hostname (or Opens an FTP connection to a server
IPaddress)
put filename Uploads a file from the current directory on the local
computer to the FTP server
pwd Displays the current directory on the FTP server
- 208 -

Exercise
1. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
apt install vsftpd
cp -f /etc/hosts ~woot/file1
cp -f /etc/hosts ~woot/file2
cp -f /etc/hosts /srv/ftp/file3

2. Edit the /etc/vsftpd.conf file using the vi editor. Take a few moments to read the
options in this file and the comments that describe each option. Uncomment/modify the
appropriate lines to ensure that anonymous_enable=YES and write_enable=YES, save
your changes and quit the vi editor.

3. Run the command systemctl restart vsftpd.service to restart the Very Secure FTP
daemon.

4. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands (commands run within the ftp utility are indented):
ftp UbuntuIPaddress (log in as the woot user when prompted)
dir (note that file1 and file2 are present)
lcd /etc (changes the directory on Fedora to /etc)
put hosts (uploads the hosts file to woot’s home directory on the Ubuntu server)
dir
lcd / (changes the directory on Fedora to /)
get hosts (downloads the hosts file from the Ubuntu server to / on Fedora)
mget file* (press y when prompted to download the file1 and file2)
help
bye

ls / (note that hosts, file1, and file2 were downloaded successfully)


ftp UbuntuIPaddress (log in as the anonymous user when prompted)
dir (note that file3 is present)
lcd /
get file3
put file2 (note that you receive a permission error!)
bye

5. On your Windows or macOS PC, open a Web browser and navigate to


ftp://UbuntuIPaddress and note that you can see and download file3.

6. On your Windows or macOS PC, download and install the FileZilla FTP client
program from https://filezilla-project.org/. Next, open FileZilla and connect to
sftp://UbuntuIPaddress as the woot user and practice some graphical file transfers.
Programs like FileZilla are likely what you’ll use to transfer files to/from FTP servers.
- 209 -

6.5 Network File System (NFS)


UNIX and Linux systems have traditionally used the Network File System (NFS) to
access shared resources on other UNIX or Linux systems. Windows Server also supports
NFS file sharing if you install the Server for NFS role, and Windows 10 and later systems
can access NFS shared directories if they have the Client for NFS feature installed.

To access files shared with NFS, you mount a shared (or exported) directory on another
system to a mount point directory on your system. For example, to see the NFS-shared
directories on fileserver, you could run the showmount -e fileserver command,
and to mount the NFS-shared /data directory on fileserver to your local /mnt
directory, you could run the mount –t nfs fileserver:/data /mnt
command. You can also add lines to /etc/fstab to automatically mount NFS-shared
directories at system startup.

To configure your Linux system as a NFS server, you can install the NFS server package
and start the NFS daemons. The name of the NFS package and associated daemons varies
from Linux distribution to distribution. Next, you must add lines to the /etc/exports
file that allows directories to be shared. The following example shares the /data
directory to the computer client1 (allowing users to read and write data, while forcing the
root user to be treated as an anonymous user on the NFS server), as well as shares the
/data2 directory to all computers (allowing users to read and write data):

[root@localhost ~]# cat /etc/exports


/data client1(rw,root_squash)
/data2 *(rw)
[root@localhost ~]# _

Next, you must run the exportfs –a command to copy the /etc/exports file to
the NFS cache in memory. To view the options available within /etc/exports, view
the associated manual page (man exports).

NOTE: You can also use SSH Filesystem (sshfs) to provide NFS-like file sharing
functionality using SSH. Like AppImage packages, sshfs uses FUSE to create a
filesystem without relying on support from the Linux kernel.

Exercise
1. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
apt install nfs-kernel-server (this installs the NFS server)
vi /etc/exports (add the following line, save your changes and quit the vi editor)
/etc *(rw)
exportfs -a
systemctl restart nfs-kernel-server
- 210 -

showmount –e localhost
mount –t nfs localhost:/etc /mnt
df -hT
cat /mnt/hosts (note that you are viewing /etc/hosts on the remote system via NFS)
umount /mnt to unmount your NFS filesystem.

2. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
dnf install nfs-utils (this installs the NFS client utilities if not already installed)
mount –t nfs IPaddress:/etc /mnt (where IPaddress is your Ubuntu Server IP address)
df -hT
ls /mnt
cat /mnt/hosts
umount /mnt

dnf install sshfs


sshfs woot@IPaddress:/etc /mnt (where IPaddress is your Ubuntu Server IP
address)
df -hT
ls /mnt
cat /mnt/hosts
umount /mnt

6.6 Samba
Linux servers can provide native Windows file and printer sharing using the Server
Message Blocks (SMB) protocol via Samba (which is “SMB” with an “a” in the middle
and end). Moreover, Linux, UNIX and macOS systems can also connect to SMB-shared
files and printers on a Windows system using a wide variety of different tools.
NOTE: SMB is sometimes called CIFS (Common Internet File System).

NOTE: Each Windows computer has a unique NetBIOS name (up to 15 characters long)
that is used when specifying shared resources, as well as a workgroup/domain NetBIOS
name that is used when browsing resources.

After installing Samba and configuring the Samba daemon (smbd) and NetBIOS name
daemon (nmbd) to start automatically at system startup, you can configure Samba
settings and shares by editing the /etc/samba/smb.conf file. This file is well
documented using comments within the file itself to explain the different configurations
available (including examples). After editing /etc/samba/smb.conf, you should
run the testparm command to check for syntax errors and display a summary of your
configuration. Following this, you must restart the Samba and NetBIOS name daemons
for the changes to take effect.
- 211 -

When connecting to an SMB file or printer share, Windows clients automatically pass a
hash of the user’s password alongside SMB communications to the server. Unfortunately,
the hash format that Windows uses is not the same as on Linux systems. Thus, you must
create a Samba password for each local user account on the system using the
smbpasswd -a username command. By default, Samba passwords are stored in a
Trivial Database (TDB) and can be viewed or managed using the pdbedit command.

Alternatively, you can join your Linux server to an Active Directory domain, which is a
centralized Lightweight Directory Access Protocol (LDAP) database that uses the secure
UNIX Kerberos protocol for issuing authentication tickets for single sign on (SSO) in a
network environment (an Active Directory domain is a type of Kerberos realm). This will
allow your Linux server to authenticate remote Windows users based on the Kerberos
ticket they were issued by Active Directory and will automatically create a matching
local home directory on the system for them. The Linux components that allow your
system to do this include the System Security Services Daemon (sssd), which provides
LDAP/Kerberos connectivity and integration with Linux security subsystems, as well as
the Realm Daemon (realmd) that can discover and join an Active Directory domain.

Joining Linux to an Active Directory Domain


On Fedora Workstation, sssd and realmd are installed automatically and you can join an
Active Directory domain by clicking the Enterprise Login button during the graphical
Welcome wizard following installation (supply the name of your domain and domain
administrator credentials). Alternatively, you can use the realm join domainname
-v command and supply domain administrator credentials when prompted to join a
domain (domain configuration is stored in /etc/sssd/sssd.conf). You can also
use the to realm leave domainname unjoin a domain

Before joining a domain on Ubuntu Server, you must first run apt install sssd-
ad sssd-tools realmd adcli to install the packages needed for sssd and
realmd, as well as run pam-auth-update --enable mkhomedir to ensure that
local home directories are automatically created for Active Directory users that
authenticate to your system. Following this, you can join a domain using the realm
join domainname -v command.

NOTE: After joining your Linux system to an Active Directory domain, you can also log
in to your display manager or shell as a domain user (a local home directory for your
domain user account will be created, and cached logins are also supported). You can also
use the klist command to view your Kerberos ticket information.
- 212 -

Accessing SMB Shares from Linux


To resolve a remote system’s NetBIOS name to the associated IP address, you can use
the nmblookup NetBIOSname command, and to see the SMB shares on a remote
Samba or Windows server with the NetBIOS name fileserver, you can use the
smbclient -L fileserver command (you can use the IP address of fileserver
instead of the NetBIOS name).

To connect to the SMB share called data on fileserver, you could then use the
smbclient //fileserver/data command to obtain an interactive prompt where
you can use FTP commands to navigate folders, download or upload files. The
//fileserver/data format is Linux’s equivalent of the \\fileserver\data
Universal Naming Convention (UNC) format on Windows systems. Alternatively, you
could mount the data folder on fileserver to the /mnt directory to access the files within
using the mount -t cifs //fileserver/data /mnt command.

Exercise
1. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
apt install samba smbclient cifs-utils
ps –ef | grep mbd (note that smbd and nmbd are running)

2. Edit the /etc/samba/smb.conf file using the vi editor. Spend a few minutes examining
the comments within this file to understand what you can configure. Under the Share
Definitions section, notice that the only two shares configured by default are the
[printers] share (which shares all CUPS printers to Windows hosts) and the hidden
[print$] share (which shares CUPS print drivers to Windows hosts). Add the following
line under the workgroup = WORKGROUP line:
netbios name = ubuntu

Next, uncomment/modify the section that shares out all home directories to users who
authenticate successfully:
[homes]
comment = Home Directories
browseable = yes
read only = no

Next, add the following share definition to the bottom of the file to share out the /etc
directory read-only to all users:
[etc]
comment = The etc directory
path = /etc
guest ok = yes
read only = yes
browseable = yes
- 213 -

3. Save your changes and quit the vi editor. Next, run the following commands:
testparm (if you made an error, re-edit the /etc/samba/smb.conf file to fix it)
systemctl restart smbd.service ; systemctl restart nmbd.service
smbpasswd –a root (supply Secret555 when prompted)
smbpasswd –a woot (supply Secret555 when prompted)
pdbedit –L (note that both root and woot have Samba passwords)
nmblookup ubuntu (note that your NetBIOS name resolves to your IP address)
smbclient –L ubuntu (note your shares shown, including the root home directory)
mount –t cifs //ubuntu/root /mnt
df -hT
ls -a /mnt (note your home directory contents)

4. If the operating system on your PC is Windows, open File Explorer and enter \\ubuntu
in the search bar. When you access your Ubuntu Server system, Windows passes your
username and password hash to the server in the background to establish the SMB tree
connect process. Thus, you should see any printer shares (if you configured CUPS) as
well as the etc share, but home directory shares will not be accessible, since you are not
logged into your Windows system as either root or woot.

Open PowerShell as Administrator and run the net use z: \\ubuntu\woot /user:woot
Secret555 command to map Z:\ to woot’s home directory while manually passing the
correct credentials for the woot user. Transfer some files to your Z:\ in File Explorer or
PowerShell (e.g. copy C:\path\to\file Z:\), and then verify they are in woot’s home
directory on your Ubuntu Server.

5. If the operating system on your PC is macOS, open the Finder app and navigate to Go
> Connect to Server and enter smb://ubuntu and click Connect. When prompted for
credentials, log in as woot and Secret555. Note the shares available to connect to and
connect to the woot share. You will now have a shortcut to the woot share on your
Ubuntu server in the Finder. Transfer some files to it in the Finder and then verify they
are in woot’s home directory on your Ubuntu Server.

6.7 Apache
Apache is the most powerful and versatile Web server daemon for Linux systems. It
responds to each webpage request (or "hit") with a separate small Apache daemon. Thus,
20 concurrent hits results in at least 20 Apache daemons running on the system. Web
servers hand out HTML webpages, as well as interpret CGI (Common Gateway
Interface)-compliant scripts such as PHP, PERL, JavaScript, and so on. Apache loads the
correct module into each Apache daemon automatically to interpret these scripts.

NOTE: Another common Web server used on Linux systems is Nginx (pronounced
“engine X”). It is very fast for static Web content, but has far less functionality compared
to Apache.
- 214 -

Starting and Managing Apache


You should use the systemctl enable apache2.service command to
configure Apache to run at system startup. However, to manage Apache afterwards, you
should use the apachectl program since it performs additional safety checks:
apachectl start (starts the Apache daemons)
apachectl stop (stops the Apache daemons)
apachectl restart (restarts the Apache daemons)
apachectl graceful (restarts the Apache daemons without dropping connections)
apachectl configtest (identifies syntax errors in the Apache config files)

Configuration Files
The /etc/httpd or /etc/apache2 directory (depending on Linux distribution)
contains the configuration for Apache. On Ubuntu, the main Apache directories and
configuration files (often referred to as config files) include:

/etc/apache2/apache2.conf (the main Apache config file)


/etc/apache2/ports.conf (lists the ports that Apache listens on – e.g. 80, 443)
/etc/apache2/envvars (lists Apache-specific environment variables)

/etc/apache2/sites-available/ (contains virtual host config files)


/etc/apache2/sites-enabled/ (contains symlinks to virtual host config files
that can be made using the a2ensite command)

/etc/apache2/conf-available/ (contains additional config files)


/etc/apache2/conf-enabled/ (contains symlinks to additional config files that
can be made using the a2enconf command)

/etc/apache2/mods-available/ (contains module config files)


/etc/apache2/mods-enabled/ (contains symlinks to module config files that
can be made using the a2enmod command)

/var/www/html/ (stores Web content – called the document root directory)


/usr/lib/cgi-bin/ (stores CGI scripts)

All Apache config files contain statements called directives which are used to specify the
values used to configure Apache. There are two main types of directives:
• Variable assignment directives: variablename value
• Block directives: <Directory /etc>
variablename value
variablename value
variablename value
</Directory>
- 215 -

Block directives are used when several variable assignment directives pertain only to a
certain directory, URL Location, module or file. Following are some basic directives that
you’ll see in Apache config files:

ServerRoot "/etc/apache2"
Specifies the location of Apache (all other file names are relative to this path)

Listen 80
The default port number that Apache listens for Web requests on.

User www-data
Group www-data
The user and group account that Apache daemons run as (the first Apache daemon is
run as the root user and serves only to start these other Apache daemons).

DocumentRoot "/var/www/html"
The location of the document root directory (location of Web content).

DirectoryIndex index.html home.html


The name of the file that is automatically served to clients from the document root
directory (e.g. search for index.html first, and then home.html if
index.html does not exist).

NOTE: If you make changes to an Apache configuration file, you must restart the
Apache daemons for the changes to take effect. Moreover, If you don’t want to use a
Web browser to test access to your Apache Web server, you can instead use the curl
command following by the URL of the website (e.g. curl http://hostname/).

Logging
Apache uses two log files by default. On Ubuntu, these two log files are:
/var/log/apache2/error.log (stores Apache daemon errors)
/var/log/apache2/access.log (stores a history of Web server hits)

The location of the error.log can be set using the ErrorLog directive, and you can
modify the level of logging detail sent to it using the LogLevel directive. For example,
Loglevel warn indicates that the Apache daemon will log any errors of level warn
and more serious to error.log. The different levels in increasing severity are debug,
info, notice, warn, error, crit, alert, and emerg.

The location and format of access.log is determined by the following directives:


CustomLog /var/log/apache2/access.log combined
LogFormat "%h %l %u %t \"%r\" %>s %b" combined
- 216 -

The CustomLog directive creates access.log using a combined log format


defined by the associated LogFormat line. The LogFormat directive defines the
information that is logged using variables:
%h remote host
%l remote login name (if the user had to log in)
%u remote user (if the user had to log in)
%t time
%r request (usually a GET statement for a web page or graphic file)
%>s status (200=Page Delivered Successfully, 404=Object not found, etc.)
%b bytes transmitted during the request (dependent on network traffic/load)

Web Content Access


Every user and process must log into a Linux system in order to use it, including Apache.
On Ubuntu, Apache daemons that serve webpages run as the www-data user and group.
If the www-data user or group does not have at least read permission to the files in the
document root directory, then the Apache daemon will not be able to serve them to
clients that request them, and instead display a Forbidden (HTTP 403) error message.

In addition to underlying Linux permissions, Apache must also specifically allow access
to any directories that host Web content using a block directive. By default, Ubuntu uses
the following directives to deny all hosts from accessing all directories under the Linux
root directory (/) except for /var/www/ (and subdirectories of /var/www/):
<Directory />
AllowOverride None
Require all denied
</Directory>
<Directory /var/www/>
AllowOverride None
Require all allowed
</Directory>

Instead of using a block directive to restrict access to directories, you may create a
.htaccess file in each directory that contains the appropriate statements. This method
is seldom used today as it slows down the Apache web server. As a result, Apache does
not search for .htaccess files due to the AllowOverride none directive present
by default in these block directives.

Performance Benchmarking
You can use ab (Apache benchmark) to monitor the performance of your Apache Web
server. To send 1000 null requests to the local Apache server (100 at a time), you could
run the ab –n 1000 –c 100 http://127.0.0.1/ command.
- 217 -

Hosting Content Outside the Document Root


Each Linux user can host Web content in their own home directory via the userdir
module. This module searches for an index.html file within the public_html
subdirectory when their username is specified in the URL. For example, if you use the
URL http://hostname/~bozo/ then the userdir module will search for the
index.html file in the /home/bozo/public_html directory. To do this you need
to have at least two block directives. The first defines the directory name, and the second
allows Apache to access content within user home directories:
<IfModule mod_userdir.c>
UserDir public_html
</IfModule>
<Directory /home/*/public_html>
Require all granted
</Directory>

Aliases are another method for accessing content outside the document root. For
example, the following alias obtains Web content in the /var/mywebsite/ directory
if a user enters http://hostname/sample/ in a Web browser, and the block
directive following it allows access to the /var/mywebsite/ directory:
alias /sample/ /var/mywebsite/
<Directory /var/mywebsite>
Require all granted
</Directory>

Virtual Hosts
Today, most Web servers host multiple different websites, and the Apache Web server
understands which website to return to a client Web browser by looking at the original
host name in the HTTP request. This process is known as virtual hosting. To configure
virtual hosts, you use the VirtualHost block directive for each unique website. At
minimum, each virtual host must associate a hostname (ServerName) with a different
document root directory (DocumentRoot) as shown below:
<VirtualHost *:80>
ServerName www.site1.com
DocumentRoot /var/www/site1
</VirtualHost>
<VirtualHost *:80>
ServerName www.site2.com
DocumentRoot /var/www/site2
</VirtualHost>

On Ubuntu, a single virtual host is set up by default to host the original document root
(/var/www/html/). Virtual hosts are often stored under /var/www/ since Apache is
granted access to that directory and all of its subdirectories by default.
- 218 -

Secure HTTP (HTTPS)


Nearly all public websites encrypt traffic using SSL/TLS – this is called Secure HTTP
(HTTPS). HTTPS uses symmetric cryptography to encrypt HTTP data, but uses
asymmetric public/private keys to protect the symmetric encryption key used by the
session. At the beginning of an HTTPS session, the client Web browser downloads the
public key from the Web server, generates a symmetric key it would like to use with the
Web server, encrypts that symmetric key using the Web server’s public key, and then
sends the encrypted symmetric key to the Web server. The Web server then uses its
private key to decrypt the symmetric key. At this point, both the client Web browser and
Web server have the same symmetric key and use it for HTTPS (this whole process takes
a fraction of a second).
To ensure that the public key downloaded from the Web server can be trusted by the
client Web browser, it is digitally signed by a trusted third party organization called a
Certification Authority (CA). The CA digitally signs the Web server’s public key by
encrypting a hash (checksum) of it with the CA private key. Web browsers then use the
CA’s public key to decrypt this digital signature to verify the hash of the Web server
public key.

Once a CA digitally signs a Web server’s public key, it combines the public key and
digital signature into a certificate that Web browsers can download. To use HTTPS, your
Apache Web server should have a public/private key pair, and the public key must be part
of a certificate. There are many public CAs available on the Internet that will accept your
Web server’s public key, digitally sign it and return it as a certificate that can be used by
Apache for HTTPS. The most popular free public CA is called Let’s Encrypt. To
configure Apache with a free Let’s Encrypt HTTPS certificate:
1. Ensure that you have a public IP address and public DNS record registered
for your Apache Web server (e.g. www.lala.com).
2. Ensure that the ServerName directive in your Apache configuration
matches the publicly-registered DNS record (e.g. www.lala.com).
3. Install the certbot tool, which automatically configures HTTPS
certificates for use with Web servers (on Ubuntu Server, you can run apt
install certbot python3-certbot-apache).
4. Run certbot --apache to generate a new public/private key, send the
public key to Let’s Encrypt for digital signing, download the associated
certificate, and configure Apache to use it.
5. Run apachectl restart to restart Apache.

Users will now be able to use HTTPS (e.g. https://www.lala.com) when


navigating to your Web server. Since Let’s Encrypt uses a 3-month expiry for certificates
you will need to run the certbot --renew command periodically (Let’s Encrypt
sends a renew reminder to the email address you specify when you run Step 4).
- 219 -

NOTE: If your Apache Web server hosts websites for several domains/subdomains (e.g.
www.lala.com, www.west.lala.com, www.east.lala.com, and so on.), you
can create a public DNS record for *.lala.com that resolves to the public IP address
of your Web server. When you run certbot, it will ask you whether you want to obtain
a wildcard certificate that can be used to provide HTTPS for all of the
domains/subdomains.

NOTE: You can also generate a self-signed certificate (not issued by a CA) for an
Apache Web server using the openssl command. However, each client computer must
be configured to trust this self-signed certificate (otherwise they will get an error in their
Web browser). You can also use the openssl command to test HTTPS connectivity.
For example, openssl s_client -connect www.lala.com:443 will attempt
to connect to the host www.lala.com using the HTTPS port (443).

Exercise
1. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
apt install apache2
systemctl enable apache2.service
systemctl status apache2.service
ps -ef | grep apache (note that additional Apache daemons run as www-data)
grep RUN /etc/apache2/envvars (note that the default user and group are www-data)
grep ^User /etc/apache2/apache2.conf
grep ^Group /etc/apache2/apache2.conf

grep LOG /etc/apache2/envvars (note that logs are stored in /var/log/apache2)


grep ErrorLog /etc/apache2/apache2.conf
grep LogLevel /etc/apache2/apache2.conf
cat /var/log/apache/error.log

grep LogFormat /etc/apache2/apache2.conf (note the log formats defined)


ls -l /etc/apache2/sites-enabled (note the symlink to ../sites-available/000-default.conf)
less /etc/apache2/sites-available/000-default.conf (note the default virtual host that
listens for HTTP requests on port 80 and serves Web content from the
/var/www/html document root directory, logging each hit to access.log using a
combined log format)
less /etc/apache2/apache2.conf (note the <Directory /> and <Directory /var/www/>
block directives that deny access to / and allow access to /var/www/ and its
subdirectories)
less /var/www/html/index.html
curl http://localhost | less (note the same HTML webpage)
- 220 -

2. Open a Web browser on your Windows or macOS PC and navigate to


http://UbuntuIPaddress to view your webpage. Keep the Web browser open. Next, run
the following commands:
ls -l /var/www/html/index.html (note the owner/group = root, and the mode = 644)
chmod 640 /var/www/html/index.html

3. Refresh the webpage in your Web browser and note the Forbidden (HTTP 403) error.
Next, run the following command to change the group ownership of index.html:
chgrp www-data /var/www/html/index.html

4. Refresh the webpage in your Web browser and note the webpage displays properly.
Next, run the cat /var/log/apache2/access.log command, noting the log entries for the
successful requests (HTTP 200) and the forbidden request (HTTP 403) from Step 3.

5. Edit the /etc/apache2/sites-available/000-default.conf file using the vi editor. Change


combined to referer at the end of the CustomLog line, save your changes and quit the vi
editor. Next, run the apachectl configtest command to check for syntax errors, and then
run the apachectl graceful command to restart the Apache daemons.
6. Refresh the webpage in your Web browser five times. Next, run the cat
/var/log/apache2/access.log command, noting that the last 5 lines only log the requested
resource (index.html).

7. Run the ab -n 1000 -c 100 http://localhost/ command to test the responsiveness of


your Apache server.

8. Run the useradd –m shrek command to create the shrek user. Next, run the mkdir
/home/shrek/public_html command to create a Web content folder for shrek. Next,
create a new /home/shrek/public_html/index.html file using the vi editor with the
following contents (save and quit the vi editor when finished):
<html>
<body>
<h1> Welcome to Shreks Homepage! </h1>
</body>
</html>

9. Run the cat /etc/apache2/mods-available/userdir.conf command, noting the lines that


allow Apache to enter the public_html folder under everyone’s home directory, except
for the root user). Next, run the following commands:
ls -l /etc/apache2/mods-enabled (note the absence of a shortcut to userdir.conf)
a2enmod userdir
systemctl restart apache2.service
ls -l /etc/apache2/mods-enabled (note the shortcut to userdir.conf)

10. Open a Web browser on your Windows or macOS PC and navigate to


http://UbuntuIPaddress/~shrek/ to view shrek’s webpage.
- 221 -

11. Edit the /etc/apache2/apache2.conf file using the vi editor and add the following line
to the end of the file (save and quit the vi editor when finished):
alias /donkey/ /home/shrek/public_html/

12. Run the apachectl graceful command. Next, open a Web browser on your Windows
or macOS PC and navigate to http://UbuntuIPaddress/donkey/ to view shrek’s
webpage.

13. Run the mkdir /var/www/html2 command to create another document root directory
under /var/www/. Next, create a new /var/www/html2/index.html file using the vi editor
with the following contents (save and quit the vi editor when finished):
<html>
<body>
<h1> It works! </h1>
</body>
</html>

14. Edit the /etc/apache2/sites-available/000-default.conf file using the vi editor.


Uncomment the line that says ServerName www.example.com to assign a sample host
name to your first virtual host. Next, add the following lines to the end of the file to add
another virtual host with a different document root directory (save and quit the vi editor
when finished):
<VirtualHost *:80>
ServerName www.example2.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html2
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log referer
</VirtualHost>

15. Run the systemctl restart apache2.service command to restart Apache.

16. If you have a Windows PC, add the following line to the bottom of the
C:\Windows\System32\drivers\etc\hosts file to perform local name resolution for the
two sample host names used in Step 14. If you have a macOS PC, add this same line to
the bottom of your /etc/hosts file (sudo /etc/hosts):
UbuntuIPaddress www.example.com www.example2.com

17. Open a Web browser on your Windows or macOS PC and navigate to


http://www.example.com to view the webpage from /var/www/html/. Next, navigate to
http://www.example2.com to view the webpage from /var/www/html2/.
- 222 -

18. Create a new /usr/lib/cgi-bin/hello.pl Perl script using the vi editor with the
following contents (save and quit the vi editor when finished):
#!/usr/bin/perl
print "Content-type: text/html \n\n";
print <<"EOF";
<html>
<body>
<h1> Hello World!</h1>
</body>
</html>
EOF

19. Run the chmod 755 /usr/lib/cgi-bin/hello.pl command. Next, run a2enmod cgid (to
enable the CGI module) followed by systemctl restart apache2.service.

20. Open a Web browser on your Windows or macOS PC and navigate to


http://www.example.com/cgi-bin/hello.pl to execute your Perl script.

21. Run the openssl s_client -connect localhost:443 and note the error. Next, run
openssl s_client -connect localhost:9090 to connect to Cockpit using HTTPS and note
that you are successful via a self-signed certificate.

6.8 WordPress
Many websites today are built using WordPress, which is an open source content
management system (CMS) written in PHP. Any Web server can host WordPress as long
as it has PHP support as well as either a MySQL or MariaDB database for storing the
WordPress configuration.

The following exercise will guide you through setting up WordPress on your existing
Apache Web server within a wordpress subdirectory of your default document root.
You can then explore and customize your WordPress site.

Exercise
1. On your Ubuntu system, log into tty1 as the root user and run the apt update &&
apt install mysql-server command to install MySQL.

2. Next, run the mysql_secure_installation command to configure MySQL. Supply


Secret555 as the mysql root password, but press Enter for all other options. Next, run the
mysql command to open the interactive MySQL tool and run the following five (5) SQL
commands to create a WordPress database and user:
CREATE DATABASE wordpress;
CREATE USER 'wordpressuser'@'%' IDENTIFIED WITH
mysql_native_password BY 'Secret555';
GRANT ALL ON wordpress.* TO 'wordpressuser'@'%';
- 223 -

FLUSH PRIVILEGES;
EXIT;

3. To install PHP support for WordPress, run the apt install php libapache2-mod-php
php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl
php-zip command.

4. Edit the /etc/apache2/apache2.conf file using the vi editor. Change the


AllowOverride none line to read AllowOverride all within the following block
directive for /var/www/ (save and quit the vi editor when finished):
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride all
Require all granted
</Directory>

5. Run the systemctl restart apache2.service command to restart Apache, and then run
the following commands to download and install WordPress to your document root:
curl -O https://wordpress.org/latest.tar.gz
tar -zxvf latest.tar.gz
mv wordpress /var/www/html
chgrp -R www-data /var/www/html/wordpress

6. Open a Web browser on your


Windows or macOS PC and navigate to
http://UbuntuIPaddress/wordpress/wp-
admin to run the WordPress installation
wizard. Enter the following information
and click Submit:

When you click Submit, copy the text in


the window (shown below). Next, create
a new /var/www/html/wordpress/wp-
config.php file using the vi editor in tty1
on your Ubuntu system and press
[Ctrl]+v to paste the contents into this
new file. Save your changes and quit vi
when finished. Following this, click Run
the installation.
- 224 -

Next, supply some sample site user


information (shown below) and click
Install WordPress to finalize the
installation (remember your username and
password!):

7. Open a Web browser on your Windows or macOS PC and navigate to


http://UbuntuIPaddress/wordpress/ to view your default WordPress site. Next, navigate
to http://UbuntuIPaddress/wordpress/wp-login.php and log in using the username and
password you set in the previous step to configure your WordPress site and content.

6.9 PostgreSQL
Databases are store information in tables. Each table contains a list of records (rows)
that describe the objects in the database (e.g. people, inventory items, transactions) using
attributes called fields (columns). Tables are often linked to other tables in a database
using a common field. As a result, we often refer to these databases as relational
databases. The following shows a simple relational database that consists of three tables
connected via a related PubID and AuthorID field.

To store and retrieve the data stored within database tables, applications use Structured
Query Language (SQL) statements.
- 225 -

Some common SQL statements include:


CREATE DATABASE lala (creates the lala database)
DROP DATABASE lala (deletes the lala database)
CREATE TABLE table1 field_definitions (creates table1)
DROP TABLE table_name (deletes table1)
INSERT INTO table1 VALUES record (inserts record into table1)
UPDATE table1 SET record_modifications (modifies existing record)
DELETE FROM table1 record (deletes record from table1)
CREATE USER user1 WITH PASSWORD secret (creates SQL user1 account)
GRANT permissions ON table1 TO user1 (grants permissions to user1)
SELECT * FROM table1 (displays all fields for records from table1)

You can also display only certain fields by criteria, as well as group and order results:
SELECT fields FROM table_name WHERE criteria
SELECT fields FROM table_name GROUP BY field
SELECT fields FROM table_name ORDER BY field [ASC/DESC]

The database server (or SQL server) usually allows several programs on the network to
access the database using SQL statements. For example, several accounting client
computers can use an accounting program that accesses a central database to store
financial transactions). Moreover, a database server can contain several different
databases. It is not uncommon to find more than 100 databases on a single SQL server,
with each database used by a different program (e.g. ACCPAC, ERP system, eCommerce
website, WSUS, and so on.). SQL servers also ship with database backup and repair
software as well as advanced features such as replication and data recovery.

While MySQL, MariaDB are common SQL services used on Linux systems, the most
powerful and fully-featured one is PostgreSQL, which stores its configuration under the
/var/lib/postgresql directory.

During Postgres installation, a postgres user is created on the system for administration.
You must assign this user a password using the passwd postgres command. Next,
you can run the su - postgres command to switch to this user and then run one of
several commands that can create and manage databases and users:
createdb databasename Creates a new PostgreSQL database
dropdb databasename Deletes a PostgreSQL database
createuser username Creates a new PostgreSQL user (for access to data)
dropuser username Deletes a PostgreSQL user
psql databasename Connects interactively to a PostgreSQL database to
perform database configuration or manipulate the
data within the database.
- 226 -

If you created a database called database1, you could connect to it using the psql
database1 to obtain the following interactive prompt:

psql (9.1.1)
Type "help" for help.
database1=#_

If you type help at the prompt, you will see a list of common commands that you can
use. You can also view databases and tables using the appropriate \option:
\l Lists available databases on your SQL server
\c database Connects to a different database
\d Lists the tables in the current database
\d table Lists the fields in a table
\q Exits the psql utility

You can also enter SQL statements within psql to manipulate the data within your
database. Each SQL statement must end with a ; metacharacter.

Exercise
1. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
apt install postgresql
passwd postgres (specify Secret555 when prompted).
su - postgres
createdb quarry
psql quarry
CREATE TABLE employee (Name char(20),Dept char(20),jobTitle char(20));
INSERT INTO employee VALUES ('Fred Flintstone','Quarry','Digger');
INSERT INTO employee VALUES ('Wilma Flintstone','Finance','Analyst');
INSERT into employee VALUES ('Barney Rubble','Sales','Neighbor');
INSERT INTO employee VALUES ('Betty Rubble','IT','Neighbor');
SELECT * from employee;
\d
\d employee
\l
\q
- 227 -

6.10 Infrastructure Services


There are three network services that provide key functionality for an organization’s
network infrastructure: DNS, DHCP and NTP.

DNS Services
The Domain Name Space (DNS) is a hierarchical namespace used to identify computers
on large IP networks such as the Internet. It has an imaginary root zone denoted by “.”
Followed by several Top Level Domains (TLDs) to identify the type of organization
(.com is a commercial organization, .org is a non-profit organization, .net is an
organization that maintains the structure of the Internet, and so on.). Under each TLD is
the name of the organization (called a domain). There can be several levels of domains
within the domain name space. Under each domain are host computers that are identified
by a host name. The entire DNS name is called the FQDN (Fully Qualified Domain
Name) and has the format hostname.domain.TLD if there is only a single domain level.

Each domain within DNS is represented by a zone file on a DNS server. For example, the
trios.com DNS server would contain a trios.com zone that stores the records that identify
the IP addresses for the hostnames under the trios.com domain. DNS servers resolve
FQDNs to IP addresses (called a forward lookup) or IP addresses to FQDNs (called a
reverse lookup). The primary purpose for DNS is for forward name resolution on the
Internet. When you contact a Web server on the Internet such as www.trios.com, the
FQDN of www.trios.com must be resolved by a DNS server, or series of DNS servers.
The whole process is illustrated below:

1. Your client Web browser (e.g. Firefox) requests to resolve the FQDN
www.trios.com on your local DNS server configured in network interface properties.
2. If the local DNS server has recently resolved the FQDN and placed the result in its
local DNS cache, you will get the response immediately (called an iterative query),
otherwise the local DNS Server must contact the DNS Server for the .com zone and
repeat the query (called a recursive query). The .com DNS server will reply with the
IP address of the DNS server for the trios.com zone.
- 228 -

NOTE: All DNS servers contain a DNS root hints file that contains the IP addresses
of DNS servers that hold top-level DNS zones (.com, .ca, .org, etc.).

3. Your local DNS server will then contact the DNS server that holds the trios.com
zone and request to resolve the FQDN www.trios.com (another recursive query).
This server resolves the name and return the IP address to the local DNS server.
4. The local DNS server caches the result for future access and returns it to the client
Web browser. Results are cached only for the length of time specified by the Time
To Live (TTL) value in the result.
5. The client Web browser connect to the remote Web server by IP address.

Each DNS server may contain several zones provided that they are for different domains
within DNS (e.g. trios.com and linux.org). Every domain on the Internet must have more
than one DNS server to ensure that names may be resolved if one server is unavailable.
The first DNS server in a zone is called the primary (master) DNS server and all
additional DNS servers are called secondary (slave) DNS servers. When new information
is added to a zone, it is added to the primary DNS server; secondary DNS servers
periodically copy the information from the primary DNS server using a zone transfer.
To configure your Linux computer as a DNS server, you must first configure the DNS
name daemon and modify the DNS configuration file and one or more zone files that
follow a standard format called BIND (Berkeley Internet Name Domain):
/etc/named.conf lists DNS zones and their type (primary/secondary). The following
example configures a primary (master) forward lookup zone for trios.com in the file
/var/named/trios.com.db, a primary reverse lookup zone for the 192.168.1.0 subnet in the
file /var/named/192.168.1.db. In addition, all queries that cannot be resolved by
this DNS server will be forwarded to 192.168.1.254, or TLD DNS servers listed in the
root hints file (/var/named/named.ca) if the forwarder is unavailable.

options {
directory "/var/named";
forwarders { 192.168.1.254; };
};

zone "trios.com" in {
type master;
file "trios.com.db";
};

zone "1.168.192.in-addr.arpa" in {
type master;
file "192.168.1.db";
};

zone "." in {
type hint;
file "named.ca";
};
- 229 -

/var/named/zonename.db contains the resource records used to perform forward


lookups for a particular zonename. Each resource record in this file has a type that
determines how it will be used:
o SOA (Start of Authority) stores the parameters used for zone transfers as well as the
minimum TTL for resolved resource records. The serial number is updated each
time a record is changed. Secondary DNS servers probe for this serial number to see
if changes have occurred every refresh interval and will retry zone transfers every
expiry interval.
o A (add host) resource records associate FQDNs to IP addresses (the IPv6 equivalent
of an A record is AAAA since the IPv6 address space is four times longer in bits).
o CNAME (canonical name) resource records associate another name with an already
existing A or AAAA resource record.
o NS (name server) resource records list the DNS servers for the zone.
o MX (mail exchange) resource records provide the email server name(s) for a zone as
well as their priority (e.g. 10 is higher than 20).

$TTL 2D
trios.com. IN SOA admin root.trios.com. (
2005032241 ; serial
1D ; refresh
2H ; retry
1W ; expiry
2D ) ; minimum TTL

IN NS arfa
IN MX 10 berta
lala IN A 192.168.1.1
po IN A 192.168.1.2
noonoo IN A 192.168.1.3
tinky IN A 192.168.1.4
winky IN A 192.168.1.5
www IN CNAME lala

/var/named/network_ID.db (or reverse_network_ID.in-addr.arpa)


contains resource records used for reverse lookups. The records in this file are of type
PTR (pointer).

$TTL 2D
1.168.192.in-addr.arpa. IN SOA trios.com. root.trios.com. (
2005032241 ; serial
1D ; refresh
2H ; retry
1W ; expiry
2D ) ; minimum

IN NS arfa.trios.com.
1 IN PTR lala.trios.com.
2 IN PTR po.trios.com.
3 IN PTR noonoo.trios.com.
4 IN PTR tinky.trios.com.
5 IN PTR winky.trios.com.
- 230 -

/var/named/named.localhost (or named.local) contains a PTR records used


to identify the local host (127.0.0.1, ::1).

/var/named/named.ca is the root hints file that contains the IP addresses of top-
level DNS servers.

If you change any of the zone files listed earlier, you must increment the serial number in
the SOA record and restart the DNS name daemon. This daemon is called named or
bind9, depending on your distribution. To test name resolution using a specific DNS
server, you can use the dig @servername record type command, where
servername is the name of the DNS server, record is the name of the resource record or
domain, and type is the type of record (A, CNAME, PTR, MX, NS, SOA, ANY, etc.)
This is especially useful if you want to see whether a zone transfer occurred successfully
from a primary DNS server to a secondary DNS server; simply query the secondary DNS
server to find out whether the new records have been added.

DHCP Services
Dynamic Host Configuration Protocol (DHCP) leases IP configuration to systems on
the network for a period of time, usually a few hours for wireless networks or several
days for wired networks. When a DHCP client requests IP configuration from a DHCP
server, there are several stages to the request. First, the client sends a broadcast request
(DHCPDISCOVER packet) on the network. DHCP servers respond to this request with
an offer (DHCPOFFER packet) that contains a potential IP configuration and lease
duration. The DHCP client then accepts the first offer it receives (DHCPREQUEST
packet) and the DHCP server acknowledges the acceptance (DHCPACK packet). At 50%
of the lease duration, the DHCP client will send another DHCPREQUEST packet to its
DHCP server to renew its IP configuration for another lease interval. If the DHCP server
is unreachable, it will try again at 87.5% of its lease by sending a DHCPDISCOVER
packet in hopes that any DHCP server on the network will provide an offer.

To configure Linux computer as a DHCP server, you can install the DHCP daemon
(dhcpd) and specify IPv4 lease configuration within the /etc/dhcp/dhcpd.conf
file. For example to lease clients an IP address between 192.168.1.50 and 192.168.1.175
for 72000 seconds and configure clients with a default gateway of 192.168.1.254 and a
DNS server of 192.168.1.254, you could use the following configuration:

[root@localhost ~]# cat /etc/dhcp/dhcpd.conf


default-lease-time 72000;
option routers 192.168.1.254;
option domain-name-servers 192.168.1.254;
subnet 192.168.1.0 netmask 255.255.255.0 {
range 192.168.1.50 192.168.1.175;
}
[root@localhost ~]# _
- 231 -

After modifying the /etc/dhcp/dhcpd.conf configuration file, you must restart the
DHCP daemon. To view current DHCP leases, you can view the contents of the
/var/lib/dhcpd/dhcpd.leases file.

NOTE: The DHCP daemon also supports IPv6 address assignment. In this case, you
place the IPv6 configuration within the /etc/dhcp/dhcpd6.conf file, and IPv6
address assignments are stored in the /var/lib/dhcpd/dhcpd6.leases file.
View the dhcpd6.conf manual page for more information.

NTP Services
Network Time Protocol (NTP) is one of the oldest Internet protocols and is used simplify
the configuration of time and date information. NTP uses a hierarchical series of time
sources called strata. Stratum 0 is at the top of this hierarchy and consists of atomic
devices or GPS clocks. Stratum 1 systems obtain their time directly from Stratum 0
devices (most Internet time servers are stratum 1). Stratum 2 systems obtain their time
from Stratum 1 servers, and so on. The stratum is not an indication of quality or
reliability since NTP servers use an algorithm to determine the most reliable time
information from multiple time sources (NTP servers) at every level.

The original NTP daemon (ntpd) can act as both an NTP client to obtain time from an
Internet time server, or an NTP server that other computers can query for time
information. Most modern Linux distributions instead use the Chrony NTP daemon as it
provides a faster NTP response time and is backwards compatible with the original NTP
daemon.

For example, to configure the original NTP daemon to query the ntp.research.gov and
0.fedora.pool.ntp.org time servers, you can add the following lines to /etc/ntp.conf:
pool ntp.research.gov iburst
pool 0.fedora.pool.ntp.org iburst

Each of these servers may actually be several different servers since NTP servers often
have several host records in DNS for the same name pointing to different IP addresses,
and the iburst option shortens the time needed for time synchronization. You can
manually synchronize your time with these servers if you stop the NTP daemon and run
the ntpdate command (you must start the NTP daemon afterwards). To view the NTP
servers you are obtaining time from, you can use the ntpq –p command (NTP resists
the effects of network latency by using a jitter buffer):

[root@localhost ~]# ntpq -p


remote refid st t when poll reach delay offset jitter
======================================================================
rap.t5.com 132.163.4.102 2 u 23 64 3 61.213 25245 11.591
ns1.ptp.net 209.172.32.214 4 u 23 64 3 16.828 25485 11.466
mircx.com 132.163.4.103 2 u 20 64 3 60.077 24485 11.058
[root@localhost ~]# _
- 232 -

By default, the /etc/ntp.conf file contains a restrict 127.0.0.1 line that


allows your local system to query the NTP daemon for time information. To allow other
computers to query your NTP daemon for time information, you can modify this line. For
example, restrict 10.3.108.0 mask 255.255.255.0 nomodify
notrap would allow all computers on the 10.3.108.0 network to query your NTP
daemon for time information.

The Chrony NTP daemon is installed by default on most modern Linux distributions and
uses the /etc/chrony.conf configuration file. As with /etc/ntp.conf, you can
specify NTP servers to query using pool name iburst lines. However, to allow
other systems to query the Chrony NTP daemon for time information, you must use
allow subnet lines. For example, allow 192.168.1/24 would allow clients to
connect from the 192.168.1.0 network (with a 24-bit subnet mask of 255.255.255.0) to
obtain time from the Chrony NTP daemon. To manually synchronize or view NTP server
configuration, you must use the chronyc command. For example, to view the servers
that you are currently polling for time, you can run the following command:

[root@localhost ~]# chronyc sources


210 Number of sources = 4
MS Name/IP address Stratum Poll LastRx Last sample
=====================================================================
^+ 209.167.68.100 2 6 22 +11ms[ +11ms] +/- 87ms
^+ 66.102.79.92 2 6 32 +3556us[+3669us] +/- 62ms
^+ ox.eicat.ca 2 6 42 -4311us[-4198us] +/- 75ms
^* 216.234.161.11 2 6 50 -41us[ +72us] +/- 81ms

[root@localhost ~]# _

NOTE: To configure Fedora Workstation as an NTP client within the GNOME desktop
environment, you can navigate to Activities > Show Applications > Settings > Date &
Time and set Automatic Date & Time to ON (the default value following installation).

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
dnf install bind
curl http://triosdevelopers.com/jason.eckert/trios/example.com.dns --output
/var/named/example.com.dns (this is one long command)
cat /var/named/example.com.dns (view the sample records)
chmod 644 /var/named/example.com.dns
curl http://triosdevelopers.com/jason.eckert/trios/named.conf.additions --output
named.conf.additions (this is one long command)

2. Edit /etc/named.conf with the vi editor, navigate to the bottom of the file and enter the
: prompt. At the : prompt, type r named.conf.additions to read the contents of the file
you downloaded in the previous step. Ensure that the last line (the forwarders option for
- 233 -

8.8.8.8) is added to the options block near the top of the file (remove the word “options”
when adding it to this block), and then comment this last line. Save your changes, quit vi
and run the systemctl start named.service ; systemctl enable named.service command.

3. Log into the GNOME desktop environment and set a static DNS server of 127.0.0.1
for your network interface in the Network tool (as you did in the Exercise in 6.1). Next,
return to tty3 and run the following commands:
nmcli connection down "name" (where name is the name of your network interface)
nmcli connection up "name" (where name is the name of your network interface)
nslookup server1.example.com (note the successful name resolution)
nslookup www.yahoo.com (this worked because of the forwarder configured)

4. Return to tty3 and run the following commands:


vi /var/named/example.com.dns (create several resource records of your choice)
systemctl restart named.service
dig @localhost example.com ANY (note that your new records are shown)
dnf install dhcp
vi /etc/dhcp/dhcpd.conf (use the example in this section to provide an IPv4
configuration for your network)
systemctl start dhcpd.service
ps –ef | grep dhcpd (if dhcpd is not present, you made a typo in dhcpd.conf)
firewall-cmd --add-service dhcp (allows DHCP in your firewall, discussed later)
firewall-cmd --add-service dhcp --permanent

5. On your Ubuntu Server virtual machine, log into tty2 as the root user and run ifconfig
name down ; dhclient name ; ifconfig name up (where name is your interface name) to
obtain an IP address from your DHCP server (this should obtain IP configuration from
the nearest DHCP server, which is your Fedora Workstation virtual machine).

6. Return to tty3 on your Fedora Workstation virtual machine, and run the following
commands:
cat /var/lib/dhcpd/dhcpd.leases (note the IP lease information)
less /etc/chrony.conf (note the default pool line for NTP servers)
vi /etc/chrony.conf (add the allow subnet line, where subnet is your
local IP subnet – e.g. 192.168.1/24)
systemctl restart chronyd.service
firewall-cmd --add-service ntp (allows NTP in your firewall, discussed later)
firewall-cmd --add-service ntp --permanent
chronyc sources -v (note the NTP servers queried for time)

7. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
apt install ntp
vi /etc/ntp.conf (add this line before other pool entries: pool FedoraIP iburst)
systemctl restart ntp.service
ntpq -p (note your Fedora time server listed)
- 234 -

6.11 Securing a Linux Server


There are many different ways that you can provide security for a Linux server. In 6.12
and 6.13, we’ll discuss the use of firewalls and security services, however in this section
we’ll focus on general security practices and tools.

Security is a pervasive topic that touches upon many of the different administrative topics
we’ve covered throughout this text. For example, you should assign users only the
minimum permissions necessary for resources and avoid assigning permissions to other
when possible. You should also log into Linux systems as a regular user and use privilege
escalation (su or sudo) to perform commands as root when necessary. Moreover, you
should never leave your system unattended by locking your screen or logging out of your
terminal or SSH session before leaving your computer. If there are multiple regular users
that access shells on your server, you should also restrict who can perform certain
commands on the system. For example, if regular users don’t need to schedule commands
or shell scripts, you could remove the /etc/cron.deny and /etc/at.deny files
and create empty /etc/cron.allow and /etc/at.allow files to ensure that no
users can schedule jobs with the cron or at daemons. And, of course, it’s important that
you regularly update installed software packages (dnf update or apt upgrade) to
gain the associated security fixes. Some other common security practices include the
following:

Stop Unnecessary Network Services


Stopping unnecessary network services on your server reduces your server’s attack
surface (the possible ways that a hacker can gain access to your system). You should
regular run nmap on your local system to identify any services that do not need to be
started and ensure that they are stopped and not configured to start at system startup.

Restrict Access to Services by Host


Many network services support the use of TCP wrappers, which were originally used by
the Internet Super Daemon to check whether a host was allowed to connect to the service
via lines in the /etc/hosts.allow and /etc/hosts.deny files. For example, to
force the Very Secure FTP daemon to restrict connections by host, you can set the
tcp_wrappers=YES line in /etc/vsftpd.conf and configure the appropriate
lines in /etc/hosts.allow and /etc/hosts.deny. For example, to deny all
hosts the ability to connect to vsftpd on the server except hera.trios.com and
garamas.trios.com, you could configure the following entries:

[root@localhost ~]# cat /etc/hosts.deny


vsftpd: ALL
[root@localhost ~]# cat /etc/hosts.allow
vsftpd: hera.trios.com, garamas.trios.com
[root@localhost ~]# _
- 235 -

Set a Login Banner


Login banners are messages that are displayed when a user accesses a terminal and can
include your organization’s acceptable use policy to dissuade improper system use. You
can edit the /etc/motd (message of the day) file to set a login banner.

Require Strong Passwords


Strong passwords are key to preventing unnecessary logins from bots that try duplicate or
brute force decrypted passwords on key user accounts. The Pluggable Authentication
Modules (PAM) subsystem can be used to ensure that users on the system choose strong
passwords that would be difficult for bots to guess or brute force decrypt. You can
modify the /etc/pam.d/password-auth or /etc/pam.d/system-auth file
on Fedora, or the /etc/pam.d/common-password or /etc/pam.d/common-
auth file on Ubuntu to require strong passwords. For example, to ensure that passwords
must be 12 characters long, with at least 2 numbers, 3 uppercase, 2 lowercase and are
different than the previous passwords, you can add the line password requisite
pam_cracklib.so minlen=12, dcredit=2, ucredit=3, lcredit=2,
difok=4 to one of these files.

You can also use the mkpasswd-expect command to generate strong passwords for
user accounts that you create. For example, mkpasswd-expect –l 20 –d 2 –c
3 –C 4 –s 1 –v bozo command will randomly generate a password that is 20
characters long (-l), with at least 2 digits (-d), 3 lowercase letters (-c), 4 uppercase
letters (-C) and 1 special character (-s). This password will be shown to you (-v) as
well as be configured within /etc/shadow for the bozo user account.

Enable Account Lockout


PAM can also be used to lock out accounts after a certain number of invalid logins. This
prevents bots and hackers from repeatedly guessing passwords to gain access to a system.
This can be done using one of two different PAM modules (pam_faillock.so or
pam_tally2.so). To use pam_faillock.so to lock accounts for 30 minutes (1800 seconds)
after 5 invalid attempts, you could add the line auth required
pam_faillock.so preauth deny=5 unlock_time=1800 to one of the PAM
files mentioned in the previously in the Require Strong Passwords segment. To use
pam_tally2.so to do the same, you could instead add the line auth required
pam_tally2.so deny=5 unlock_time=1800 to one of these PAM files. You
can then use either the faillock or pam_tally2 commands to view users that have
been locked out. To unlock a user before their reset time period is finished, you could add
--reset --user username to either the faillock or pam_tally2 command.
- 236 -

Provide Secure Authentication


Authenticating to an LDAP database such as Active Directory using Kerberos (discussed
earlier in Section 6.6) or forwarding authentication requests to an authentication broker
such as Remote Authentication Dial In User Service (RADIUS) or Terminal Access
Controller Access control System (TACACS+) provides an additional layer of security to
the authentication process.

Active Directory is the most common method for providing secure authentication today,
but requires that you have licensed Microsoft servers providing the service. FreeRADIUS
is a common authentication broker that you can install and use to direct authentication
requests to one of many different authentication providers (including Active Directory).
However, each service that authenticates users must be configured to forward requests to
the FreeRADIUS server.

Additionally, you can install a multi-factor authentication (MFA) software package that
works with biometric devices (fingerprint scanners, iris scanners, facial recognition), or
OTP (one time password) tokens such as RSA SecurID to provide an extra level of
authentication for users.

It is also important to ensure that network services are not run as the local root user
account (recall that Apache runs as the www-data user and group). If a network service
that runs as root is compromised, a hacker would have root access to the system. Also,
the user account that network services run as should be configured with an invalid shell
to prevent a hacker from obtaining a shell on a compromised system. Most user accounts
for network services use the shell /sbin/nologin, which prints an error message
listed in /etc/nologin.txt should someone attempt to log into it.

Monitor Login Activity


To detect successful and unsuccessful system access attempts by unauthorized users, you
can monitor log files, including /var/log/auth.log, /var/log/secure,
/var/log/audit/* and /var/log/sssd/* (depending on your distribution).
You can also list recent logins using the who /var/log/wtmp or last commands,
as well as use the lastb command to display invalid (bad) login attempts.

Set User Process Limits


A runaway process can spawn thousands of child processes in a short period of time,
slowing down your server. This is called a race condition and can be prevented by setting
a limit to the number of processes that a regular user can create. For example, the
ulimit –u 100 command prevents users from spawning more than 100 processes. To
ensure that parameter is set at system startup, edit the nproc value for all users in
/etc/security/limits.conf (nproc also be configured in this file for a specific
user or group).
- 237 -

Encrypt Partitions that Contain Sensitive Data


If a server or server storage device is stolen by a hacker, the data on each storage device
can easily read if inserted into another system. To prevent this, you can encrypt the data
on a partition using Linux Unified Key Setup (LUKS), which is the Linux equivalent of
Bitlocker. You can choose to encrypt a partition using LUKS during Linux installation,
or afterwards using the cryptsetup command.

Say, for example, that you add a storage device to your server and create a new partition
on it (e.g. /dev/sdb1). You could run the cryptsetup luksFormat
/dev/sdb1 command and specify a passphrase that will be used to decrypt the partition
when the filesystem on it is mounted (you must supply this password at each boot time in
order for the system to mount the filesystem on it). Next, you can run the cryptsetup
luksOpen /dev/sdb1 lala command to make the partition available to the LVM
device mapper as a logical volume called lala, and the mkfs –t ext4
/dev/mapper/lala to format this logical volume with the ext4 filesystem. Next, you
can mount /dev/mapper/lala to a mount point directory of your choice. To ensure
that it is automatically mounted at boot time, you must add the line lala /dev/sdb1
to the /etc/crypttab file, as well as add the appropriate entry to /etc/fstab that
mounts /dev/mapper/lala to the mount point directory you chose.

Encrypt Sensitive Files


Rather than encrypting an entire partition, you can instead encrypt key sensitive files on a
server using GNU Privacy Guard (GPG) to ensure they cannot be accessed by other
users that have permission to the file, or hackers that obtain a copy of the file. To do this,
you must first generate a GPG public/private key pair for your user using the gpg --
gen-key command, specifying your email address and a passphrase that is used to
protect your private key. This passphrase must be used when encrypting and decrypting
files using GPG, or when digitally signing files using GPG to verify their authenticity.

Next, you can encrypt and digitally sign files using the gpg command. For example, to
encrypt and digitally sign proposal1, you can run gpg --encrypt --sign –r
emailaddress proposal1 and supply your passphrase when prompted (the
emailaddress must match what you used when you generated your public/private key).
To decrypt the proposal1 file, you can run the gpg proposal1 command and supply
your private key passphrase when prompted. To prevent entering your passphrase each
time, you can use a GPG passphrase caching program, such as gpg-agent.

Run a Vulnerability Scanner


There are many utilities that can scan your system for known security vulnerabilities,
including Nessus and OpenVAS, as well as Security Information and Event
Management (SIEM) tools such as AlienVault. These utilities identify vulnerabilities
- 238 -

using the associated CVE (Common Vulnerabilities and Exposures) or CWE (Common
Weakness Enumeration) number that can be Googled to learn more about the
vulnerability and the steps needed to mitigate it.

Disable Reboot Functionality on Kiosks


On publicly-accessible Linux computers, such kiosks, malicious users will often attempt
to reboot a kiosk using the [Ctrl]+[Alt]+[Del] key combination in order to attempt to boot
another operating system. To prevent this on a legacy Linux system, you can comment
the line that contains the keyword ctrlaltdel within /etc/inittab. To prevent
this on a modern Linux system that uses Systemd, you can run the systemctl mask
ctrl-alt-del.target command (mask prevents the target from being accessed).

Monitor Network Traffic


By viewing the traffic going to your system, you’ll get a good handle on normal traffic
patterns as well as be able to identify unusual or malicious traffic, such as Distributed
Denial of Service (DDoS) attacks. To view the network traffic on eth0, you could use the
tcpdump –i eth0 command. Alternatively, you can install and use a graphical
network sniffer program such as Wireshark (which also includes the tshark command
for use within a command line shell).

Install an IDS
An Intrusion Detection System (IDS) can be used to monitor files and/or network traffic
to detect anomalies that indicate an active attack or system compromise. It is important to
interpret the output of IDS programs over time to identify normal traffic that is
inadvertently labelled as malicious (called false positives). Tripwire and Advanced
Intrusion Detection Environment (AIDE) are common IDS programs that monitor system
files for unauthorized modification by hackers, while Simple WATCHer (SWATCH) can
be used to monitor log files for indicators of system compromise. Port Sentry and Snort
monitor inbound network traffic on a Linux server for malicious connection attempts and
optionally prevent them, while DenyHosts and Fail2ban monitor log files for malicious
connection attempts, and automatically adjust firewall rules to prevent them in the future.

Exercise
1. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
nmap -sT localhost (note that telnet is not necessary)
systemctl stop telnet.socket ; systemctl disable telnet.socket
nmap -sT localhost (note that your attack surface is now lower)
tcpdump -i eth0 (press [Ctrl]+c after a few minutes)
ulimit -u (note the default value)
ulimit -u 100 ; ulimit -u
- 239 -

less /etc/security/limits.conf (read the comments and example lines)


dnf install expect
mkpasswd-expect –l 12 –d 1 –c 1 –C 1 –s 1 -v (note the strong password generated)
who /var/log/wtmp
last
lastb
gpg --gen-key (supply your email and Secret555 passphrase)
cd classfiles
gpg --encrypt --sign -r email letter
file letter* ; head -1 letter*
gpg letter.gpg (choose to overwrite letter)
head -1 letter
poweroff

2. In the Settings of your hypervisor, add an additional 8GB virtual hard disk (/dev/sdc)
to your Fedora Workstation virtual machine and start it. Log into tty3 as the root user and
run the following commands:
fdisk /dev/sdc (create a single partition that space the entire disk)
cryptsetup luksFormat /dev/sdc1 (specify lUkS-555 as the passphrase)
cryptsetup luksOpen /dev/sdc1 private
mkfs -t ext4 /dev/mapper/private
mkdir /private
mount /dev/mapper/private /private
cp -R classfiles/* /private
vi /etc/crypttab (add the line: private /dev/sdc1)
vi /etc/fstab (add the line: /dev/mapper/private /private ext4 defaults 0 0)
reboot (supply the lUkS-555 passphrase to mount /dev/sdc1)

3. Log into tty3 as the root user and run the lsblk command, to verify that /dev/sdc1 is
mounted as a crypt volume and then run the ls /private command to view its contents.

4. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
vi /etc/pam.d/common-auth (add the following line above the pam_unix.so line)
auth required pam_tally2.so deny=3 unlock_time=7200

5. Log into tty5 three times as woot with an incorrect password and note the warning that
your account was locked when attempting to log in a fouth time. Return to tty2 and run
the following commands:
pam_tally2 (note that woot has been locked out)
pam_tally2 --reset --user woot
apt-get install tripwire (create a passphrase of Secret555, use other defaults)
tripwire --init (this creates a database with the checksums/hashes of key system files)
tripwire --check >outputfile (this compares the checksums/hashes of files to those
within the tripwire database to see if any were modified)
less outputfile
- 240 -

6.12 Firewalls
To prevent other computers on the network from accessing specific services running on a
Linux system, you can configure a firewall. The Linux kernel implements firewall
functionality using Netfilter, which can contain collections of firewall rules that
determine the types of network traffic should be allowed to access the system. Since
Netfilter firewall rules can be complex, many Linux distributions ship with tools that can
be used to simplify their configuration. We’ll examine how to configure Netfilter directly
using iptables and nftables, as well as other tools that simplify Netfilter configuration.

NOTE: Most Linux server distributions do not enable the firewall by default. This is
because servers are typically located on a network surrounded by network-based firewalls
that perform the same functionality. Linux workstation distributions, however, typically
have a firewall enabled by default to protect against local network attacks.

Using iptables to Configure Netfilter


By using iptables, you can discard certain network packets according to chains of
firewall rules that are stored in memory. Each chain refers to a specify type of traffic:
• The INPUT chain refers to network traffic destined for your computer.
• The FORWARD chain refers to network traffic that passes through your
computer if it is configured as a router.
• The OUTPUT chain refers to network traffic that originates from your computer.

There are no firewall rules for these chains by default, but you can use the iptables
command to create them based the source/destination IP address, protocol (TCP, UDP,
ICMP), or packet status. For example, the following commands flush all previous rules
from memory, and specify that packets destined for the local computer are dropped
except for those that originate from the 131.19.0.0/16 network:

[root@localhost ~]# iptables -F


[root@localhost ~]# iptables –P INPUT DROP
[root@localhost ~]# iptables –A INPUT –s 131.19.0.0/16 –j ACCEPT

If you want an error message to be sent back to the originating host when a packet is
dropped, you can use the word REJECT in place of DROP. Some common options to the
iptables command include:
-A chain (identifies the chain)
-d address (specifies a destination address or network)
-D number (deletes a firewall rule by number – rules start at number 1)
-F chain (removes all rules for a chain, or all chains if none are specified)
-j action (specifies the action taken)
-L chain (lists rules for a chain, or all chains if none are specified)
-P chain policy (sets the default policy for a chain)
- 241 -

-p protocol (specifies the protocol)


-R number (replaces an existing firewall rule for a chain by number)
-s address (specifies the source IP address or network)
-t table (configures a non-default table – e.g. NAT or mangle table)
-dport (specifies the destination port number)
-sport (specifies the source port number)
-m match (sets a match parameter – e.g. state matches a stateful firewall)
-i interface (specifies the input interface)
-o interface (specifies the output interface)

To see the list of firewall rules for each chain, you can use the iptables –L
command. Since most traffic starts on a standard port and switches to a random
ephemeral port above 30000 for the remainder of a session, firewall rules are often
stateful to ensure that they match all related session traffic. For example, to forward all
SSH (port 22) traffic from eth0 to eth1 on your Linux router using a stateful rule, you
could run the following command:

[root@localhost ~]# iptables –A FORWARD –i eth0 –o eth1 –m state


--state NEW -dport 22 –j ACCEPT
[root@localhost ~]# _

Next, you must allow all subsequent packets that are part of an existing session:

[root@localhost ~]# iptables –A FORWARD –i eth0 –o eth1 –m state


--state ESTABLISHED,RELATED –j ACCEPT
[root@localhost ~]# _

You can also perform Network Address Translation (NAT) using Netfilter IP tables, but
you must use the special PREROUTING, OUTPUT and POSTROUTING chains. For
example, to allow internal traffic to be translated to your external public network
interface eth1 (IP address = 64.2.8.1) you could use the POSTROUTING chain and
specify Source NAT (SNAT):

[root@localhost ~]# iptables –t nat –A POSTROUTING –o eth1 –j SNAT


--to-source 64.2.8.1
[root@localhost ~]# _

Alternatively, you can specify to configure NAT using masquerading, which forwards
any internal traffic to the Internet via a single public network interface (the most common
NAT implementation). For example, to forward all traffic using masquerading through
your public network interface eth1, you can use the following command:

[root@localhost ~]# iptables –t nat –A POSTROUTING –o eth1 –j MASQ


[root@localhost ~]# _
- 242 -

If you wish to enable port forwarding on your NAT router such that external requests
addressed to port 80 are forwarded to your internal Web server (192.168.1.55), you could
use the PREROUTING chain and specify Destination NAT (DNAT):

[root@localhost ~]# iptables –t nat –A PREROUTING –i eth1


--dport 80 –j DNAT --to-destination 192.168.1.55
[root@localhost ~]# _

You can instead use the ip6tables command to configure IPv6 firewall rules. The
ipset command can also be used to define sets of IP addresses or networks; these sets
can then be used within a firewall rule to allow or block access.

To ensure your firewall rules are automatically loaded at system startup, you can use the
iptables-save >/etc/sysconfig/iptables command on a Fedora system,
or the iptables-save >/etc/iptables/rules.v4 (or rules.v6) command
on an Ubuntu system.

Using nftables to Configure Netfilter


The more recent nftables framework can also be used to configure Netfilter using a more
straightforward syntax. For example, to add an nftable that matches IPv4 traffic, add an
input chain that drops all traffic except for traffic on port 80 (HTTP) and 443 (HTTPS),
and view the results, you could run the following commands:

[root@localhost ~]# nft add table ip filter


[root@localhost ~]# nft add chain ip filter input
[root@localhost ~]# nft add rule ip filter input drop
[root@localhost ~]# nft add rule ip filter input tcp dport {80, 443} ct
state new,established accept
[root@localhost ~]# nft list table ip filter
table ip filter {
chain input {
drop
tcp dport { 80, 443 } ct state established,new accept
}
}
[root@localhost ~]# _

You can replace ip with ip6 in these commands to create firewall rules for IPv6 traffic,
or replace ip with inet to match both IPv4 and IPv6 traffic. You can also use
iptables-translate and ip6tables-translate to convert existing iptables
rules for IPv4 and IPv6 into the equivalent ones for nftables.

To ensure that nftables firewall rules are loaded at system startup, you run nft list
ruleset > /etc/nftables.conf on Ubuntu. On Fedora, you can run nft
list ruleset > /etc/nftables/filename.nft (filename of your choice)
and then add an include /etc/nftables/filename.nft line to the
/etc/sysconfig/nftables.conf.
- 243 -

NOTE: While both iptables and nftables can be used to configure Netfilter rules
simultaneously on a Linux system, it is recommended that you use one or the other for
consistency and to prevent accidental misconfiguration.

Simplifying Firewall Configuration


Different Linux distributions include different tools for simplifying the configuration of
Netfilter firewall rules. On Ubuntu, you can use the ufw (uncomplicated firewall)
command. For example, to enable the firewall, and configure it to deny all incoming
traffic except ssh, FTP and HTTP (port 80 and 443), you could run the following
commands:

[root@localhost ~]# ufw enable


Firewall is active and enabled on system startup
[root@localhost ~]# ufw default deny incoming
Default incoming policy changed to ‘deny’
(be sure to update your rules accordingly)
[root@localhost ~]# ufw default allow outgoing
Default incoming policy changed to ‘deny’
(be sure to update your rules accordingly)
[root@localhost ~]# ufw allow ssh
Rule updated
Rule updated (v6)
[root@localhost ~]# ufw allow ftp
Rule updated
Rule updated (v6)
[root@localhost ~]# ufw allow port 80,443
Rule updated
Rule updated (v6)
[root@localhost ~]# _

NOTE: To see all of the firewall rules that you’ve set, you can use the ufw status
verbose command. The ufw command saves firewall rules to /etc/default/ufw
and files under the /etc/ufw directory for automatic configuration at system startup.

Most Linux distributions, including Fedora, use the Systemd firewall daemon to manage
firewall rules. This daemon allows you to have different sets of firewall rules for different
networks (called network zones), which is useful for Linux laptops that connect to
several different wireless networks (public, home, work, and so on.). The default zone is
defined in /etc/firewalld/firewalld.conf, and custom zone configuration is
stored in /etc/firewalld/zones. Whenever you connect to a new network using a
Linux system (e.g. Tim Hortons Wi-Fi), your desktop environment asks you which
network zone you are part of (e.g. you would choose public for Tim Hortons Wi-Fi).

By default, connections are blocked by the Systemd firewall daemon unless they are
explicitly allowed. To allow SSH connections to your system in your default (current)
zone, as well as ensure that this configuration is loaded at boot time, you could run the
- 244 -

following two commands (to see a list of available service names, you can run the
firewall-cmd --get-services command):

[root@localhost ~]# firewall-cmd --add-service ssh


success
[root@localhost ~]# firewall-cmd --add-service ssh --permanent
success
[root@localhost ~]# _

Alternatively, you could set firewall exceptions by port number. For example, to allow
SSH by port (TCP port 22), you could use the following command:

[root@localhost ~]# firewall-cmd --add-port=22/tcp


success
[root@localhost ~]# firewall-cmd --add-port=22/tcp --permanent
success
[root@localhost ~]# _

You can instead use Netfilter IP tables syntax:

[root@localhost ~]# firewall-cmd --direct --add-rule ipv4 filter INPUT


0 -p tcp --dport 22 -j ACCEPT
success
[root@localhost ~]# _

To verify that SSH is allowed in your active (current) zone, you can run the following
(yes = allowed, no = blocked):

[root@localhost ~]# firewall-cmd --query-service ssh


yes
[root@localhost ~]# _

And to configure masquerading via your public network interface, you can run:

[root@localhost ~]# firewall-cmd --zone=public --add-masquerade


success
[root@localhost ~]# _

You can run firewall-cmd --get-zones to list all available network zones,
firewall-cmd --get-default-zone to list your active zone, and firewall-
cmd --set-default-zone=public to change your active zone to the public
zone. You can also run firewall-cmd --list-all to list the services enabled in
your active zone, or firewall-cmd --list-all-zones to list the services
enabled in each zone defined on the system.

Within a desktop environment, you can use the graphical Firewall tool that is available
with your Linux distribution. On Fedora systems, you can open this tool by navigating to
Activities > Show Applications > Sundry > Firewall in GNOME.
- 245 -

Exercise
1. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
iptables -L (note the default policy for each chain)
iptables –P INPUT DROP
ping gateway (where gateway is your default gateway – this should fail)
iptables -L (note the default policy for the INPUT chain)
iptables –A INPUT –s subnet -j ACCEPT (where subnet is your local IP network)
iptables -L (note the firewall rule under the INPUT chain)
ping gateway (where gateway is your default gateway – this works now
because the ping reply is now allowed into your system)
iptables –F ; iptables –P INPUT ACCEPT
iptables -L (note that your configuration has been reverted to defaults)
apt install nftables
nft add table ip filter
nft add chain ip filter input
nft add rule ip filter input drop
nft add rule ip filter input tcp dport 22 accept
nft add rule ip filter input tcp dport 80 accept
nft list table ip filter
nft list ruleset (same output since the ip table is the only one defined)
nft delete table ip filter
ufw enable
ufw status verbose
ufw allow ssh ; ufw allow ftp ; ufw allow nfs
ufw allow samba ; ufw allow http ; ufw allow postgres
ufw status verbose
ufw disable

2. On your Fedora Workstation virtual machine, log into tty3 as the root user and run the
following commands:
iptables -L
firewall-cmd --get-zones
firewall-cmd --get-services
firewall-cmd --list-all-zones
firewall-cmd --get-default-zone
firewall-cmd --get-active-zones
firewall-cmd --list-all
firewall-cmd --add-service samba
firewall-cmd --permanent --add-service samba
firewall-cmd --query-service=samba

3. Log into the GNOME desktop as the woot user and run the graphical Firewall
configuration tool (Activities > Show Applications > Sundry > Firewall). Examine the
functionality and observe your settings for the default zone.
- 246 -

6.13 Security Services


Most Linux systems contain a security service that enforces security policies to minimize
the chance that a malicious program can compromise files and processes. You may need
to modify these security policies to allow system and network service features and
functionality, or to allow processes to access certain files and directories.

SELinux
The most common security service today is Security Enhanced Linux (SELinux) which
is enabled by default on Fedora. This is because the /etc/selinux/config file sets
the SELINUX option to enforcing. Possible values for this option include:
• enforcing (policy settings are enforced by SELinux)
• permissive (SELinux generates warnings only & logs events)
• disabled (SELinux is disabled)

The SELINUXTYPE option in the /etc/selinux/config file identifies which


daemons and processes are managed by SELinux. Possible values for this option include:
• targeted (only targeted network daemons are protected)
• strict (all daemons are protected)
• minimum (only essential daemons are protected)
• mls (use custom attributes for classifying processes)

After setting the SELINUX option to enforcing or permissive, you must reboot to
relabel the files on the system (this could take quite some time). Next, you can use the
sestatus command to verify that SELinux is enabled:

[root@localhost ~]# sestatus –v | head 6


SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
Current mode: enforcing
Mode from config file: enforcing
Policy version: 26
Policy from config file: targeted
[root@localhost ~]# _

NOTE: The getenforce and setenforce commands can view and switch between
enforcement levels. For example, setenforce permissive will switch SELinux
from enforcing to permissive mode. If an issue is resolved after running this command,
then you have verified that SELinux enforcement caused the issue.

At its heart, SELinux is a labeling system. It adds a label to files, directories and
processes on the system. SELinux policies on the system determine which processes
should be allowed to access certain file and directories based on their labels, and the
Linux kernel enforces these rules.
- 247 -

NOTE: A file that has a period (.) appended to the mode within a long listing has an
SELinux label (context) set. You can view the SELinux labels for a file using the –Z
option to the ls command. To view the SELinux label for a process, you can use –Z
option to the ps command.

By default, Fedora has definitions for the targeted policy that require processes to have
the same major label type as the files that they access. SELinux labels have four main
sections (user:role:type:level):
• user identifies the user type (usually unconfined_u)
• role identifies the user category in the SELinux policy (usually object_r)
• type is the most important section, as it identifies the classification for the file,
directory or process that is enforced by policies (e.g. Apache processes run with a
type of httpd_t and can only access files and directories that have an SELinux
type starting with httpd_ in the targeted policy)
• level is an optional section that is used if you enable the MLS (Multi-Level
Security) or MCS (Multi-Category Security) components of SELinux that can be
used to define custom attributes or restrict similar process types, respectively

NOTE: The seinfo –t command displays the available types within SELinux labels.

Most SELinux problems are the result of a file or directory that has an incorrect label that
prevents certain processes from accessing it. When you copy files to other directories,
SELinux will automatically apply the correct label to that file based on the label needed
by the target directory. However, if you move a file, that file retains its original SELinux
label (which may not match the required SELinux label in the target directory). In this
case, you can use the semanage or chcon commands to change the label on the file to
the correct one, or the restorecon command to force SELinux to automatically
relabel the file with the default label for the directory that it is in. To relabel all files on a
filesystem, you can create a /.autorelabel file and reboot.

Sometimes SELinux policy settings are too strict to allow a particular functionality. For
example, if you use the userdir module in Apache you must run the setsebool –P
httpd_enable_homedirs true command to ensure that the SELinux policy
allows Apache to access home directory content. To see all of the settings you can
configure for Apache within your SELinux policy, you can use the getsebool –a |
grep httpd command.

If SELinux blocks a process that should not be blocked, you can run the audit2why
</var/log/audit/audit.log command to view a summary of each SELinux
violation. You can Google the value of comm= alongside the description under Was
caused by: from an SELinux violation in order to locate the associated setsebool
or chcon commands needed to prevent it in the future should it be allowed.
- 248 -

Alternatively, you can use the graphical SELinux Troubleshooter program in your
desktop environment to display the same information (including the associated
setsebool or chcon commands to allow it). This program also display notifications
automatically in your desktop environment when SELinux violations occur.

NOTE: You can also create SELinux policies to solve SELinux violation issues. To do
this, you must first use the audit2allow command to convert the violation into an
allowed SELinux policy file, and then add that policy file to SELinux.

AppArmor
Some systems, such as Ubuntu, use AppArmor instead of SELinux to provide the same
functionality. Restrictions for different programs on the system are called AppArmor
profiles and are stored in text files for each program under the /etc/apparmor.d/
directory (e.g. /etc/apparmor.d/usr.sbin.crond for the cron daemon).

Like SELinux, AppArmor profile restrictions can be enforced (enforce mode) or set to
generate warnings only (complain mode). However, unlike SELinux, different
AppArmor profiles can be in different modes at the same time. You can use the aa-
status command to view the processes with AppArmor profiles that are currently in
either enforce or complain mode. Any processes that are not managed by AppArmor
(because they do not have a corresponding AppArmor profile) are shown as “unconfined”
in the aa-status output and can be listed separately using the aa-unconfined
command.

You can use the aa-complain command to switch an AppArmor profile to complain
mode (e.g. aa-complain /etc/apparmor.d/usr.sbin.crond). If a file or
process problem is resolved when moving to complain mode, then the issue was caused
by AppArmor. Similarly, the aa-enforce command can be used to switch an
AppArmor profile to enforce mode, and the aa-disable command can be used to
switch an AppArmor profile to unconfined. You can install these commands on Ubuntu
using the apt install apparmor-utils command.

Most problems related to AppArmor can be solved by editing the policy settings in the
AppArmor profile (settings are well documented in these files using comments).
Alternatively, some settings may require configuring a general AppArmor parameter
within a file under the /etc/apparmor.d/tunables directory. In this case, you
would normally Google the problem to learn the parameter to modify, but you can also
search the apparmor.d man page.
- 249 -

Exercise
1. On your Fedora system, log into tty3 as the root user and run the following commands:
sestatus –v (note that SELinux is enabled and enforcing by default)
cat /etc/selinux/config
setenforce permissive
sestatus -v
setenforce enforcing
sestatus -v
ps -eZ | less
ls -lZ /var/* | less
ls -lZ classfiles (note the default type of admin_home)
mv text.err /var
ls -lZ /var (note that text.err still has the same type)
restorecon /var/text.err
ls -lZ /var (note that text.err now has a generic var type)
getsebool -a | less
less /var/log/audit/audit.log (note any SELinux violations)
audit2why </var/log/audit/audit.log | less (note the summaries and remediations)

2. Log into the GNOME desktop environment as woot and navigate to Activities > Show
Applications > SELinux Troubleshooter (type the name to access it quickly). Note the
most recent SELinux violation and click Troubleshoot to see the commands that you can
run to allow it (should it be allowed). Next, click List all Alerts to view all of the unique
SELinux violations that occurred (these are parsed from /var/log/audit/audit.log).

3. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
aa-status (note the AppArmor profiles configured in enforce mode, as well as the the
PowerShell profile that is set to complain mode and the NTP daemon process
set to enforce mode)
apt install apparmor-utils
aa-complain /usr/sbin/ntpd
aa-status
aa-enforce /usr/sbin/ntpd
aa-status
less /etc/apparmor.d/usr.sbin.ntpd
ls /etc/apparmor.d/tuneables
cat /etc/apparmor.d/tuneables/home
- 250 -

CHAPTER

Cloud Technologies
Now that we have explored Linux usage, system and network
administration, we’ll explore the different technologies used run virtual
machines and containers on Linux systems, as well as the process used to
push new Web apps to virtual machines and containers in the cloud.

7.1 Understanding the Cloud


7.2 Virtualization
7.3 Containers
7.4 Devops Workflows
- 251 -

7.1 Understanding the Cloud


Recall from Chapter 1 that the cloud is the worldwide collection of Web apps run on Web
servers (commonly called cloud servers). The public cloud consists of servers on the
Internet that can be rented commercially by anyone, whereas the private cloud consists of
servers within an organization’s private data center. Regardless of whether you are
connecting to a public or private cloud provider, there are three main ways that you can
host Web apps on a cloud server: SaaS, PaaS and IaaS.

The simplest way is to pay a cloud provider to run your custom Web app on their servers
or create a private cloud in your organization specifically to run a Web app; this is called
Software as a Service (SaaS).

Another method involves paying a cloud provider to run your containerized Web apps
within the cloud. Recall from Chapter 1 that a container is a subset of an existing
operating system that contains just enough operating system software to run a specific
Web app on an underlying operating system kernel that the containers share. Container
runtimes such as Docker can be used to execute hundreds or thousands of containers on a
single operating system installed on a cloud server. This type of structure is often called
Platform as a Service (PaaS) since you are paying to run your containers on someone
else’s platform (operating system).

A third method involves renting hardware from a cloud provider to run your own
operating systems within virtual machines (much like you did throughout the exercises in
this book). You must then configure these operating systems and the Web apps that they
run, as well as provide ongoing security and maintenance for them. This approach is
called Infrastructure as a Service (IaaS). Most public cloud providers, such as Azure
and Amazon Web Services, provide SaaS, PaaS and IaaS.

NOTE: SaaS, PaaS and IaaS are called cloud delivery models. There are many others
that you will see marketed on the Internet, but they are simply versions of SaaS, PaaS or
IaaS. For example, Mobile as a Service (MaaS) is just mobile device management
software hosted on a cloud provider that you can pay to use (SaaS). As a result, these
other cloud delivery models are collectively referred to as Anything as a Service (XaaS).

So where does Linux fit into SaaS, PaaS and IaaS? Since Linux doesn’t require expensive
operating system licensing in addition to cloud provider costs, it’s the operating system
that is most commonly used by SaaS, PaaS and IaaS. Hosting operating systems within
the cloud that require expensive licensing will drive up the cost of the service that the
Web app provides to customers, and result in the Web app being non-competitive in the
market. Since Linux is easily customizable, you can create very small Linux containers
that use a minimal amount of resources in a cloud data center, and many PaaS providers
allow you to choose from a large selection of small, pre-configured Linux containers that
contain the supporting open source software needed by your Web app. Additionally, most
- 252 -

IaaS providers offer several free pre-configured Linux operating system templates that
you can easily copy to create preconfigured Linux servers on their cloud within seconds.

When you purchase space to run a virtual machine (e.g. AWS EC2) or a container (e.g.
AWS Elastic Beanstalk), you must also purchase storage. The virtual machine or
container should use regular filesystem storage (called block storage), but the Web apps
that you run on your virtual machines or containers could be configured to access object
storage on the cloud provider using the HTTP protocol. Object storage is much cheaper
than block storage as it is stored in a binary large object (BLOB) instead of a formatted
filesystem. It is well suited for cloud apps that need to store thousands of pictures and
messages, but not for databases, and other traditional server service storage that require
block storage.

7.2 Virtualization
Throughout this book, you have run Linux as a guest operating system within a virtual
machines running on a hypervisor on your PC. This is essentially the same process that
you use to implement IaaS on a public cloud provider, or in your organization’s private
cloud on a server hypervisor such as Microsoft Hyper-V or VMWare ESXi.

However, the Linux kernel includes a hypervisor called Kernel Virtual Machine (KVM)
that works alongside the Qemu libraries to run Linux, UNIX and Windows virtual
machines. Thus, you can install Linux bare metal on server hardware and run virtual
machines on it using KVM/Qemu with near-native speed. To create and manage virtual
machines on Linux using KVM/Qemu, you can install the Virtual Machine Manager
package from a software repository, which includes the virt-manager tool that can
create and manage virtual machines within a desktop environment (shown below) as well
as the virsh command that can manage virtual machines within a command line shell.
- 253 -

Alternatively, you can use the Boxes application within the GNOME desktop
environment to create and manage KVM/Qemu virtual machines. GNOME boxes uses
the SPICE protocol to graphically connect to each virtual machine, but you can instead
configure GNOME boxes to use VNC.

KVM/Qemu virtual machines support the same types of virtual networks that you
learned in previous courses with Hyper-V and Oracle VirtualBox (bridged/external,
NAT/internal, private/local). Similarly, when creating virtual hard disk files to store the
virtual filesystems for your virtual machine, you can choose to allocate the disk space
immediately (thick provisioning) or create a dynamically-expanding virtual hard disk file
that allocates disk space as needed (thin provisioning).

You can also create virtual machine templates that can be copied to create new virtual
machines. This can be a .ova (open virtualization appliance) file that includes a pre-
installed virtual hard disk file (e.g. .vhdx) alongside a .ovf (open virtualization
format) XML file that stores the settings for the virtual machine (amount of RAM, and so
on.). You can easily create .ova files using the Oracle VirtualBox hypervisor, as well as
import .ova files into Oracle VirtualBox. For example, Kali Linux is available in .ova
format for easy import into Oracle VirtualBox. However .ova files are not specific to
Oracle VirtualBox. You can import .ova files into many different virtualization platforms,
including cloud virtualization services, such as Amazon Cloud Services.

Exercise
1. Optionally install Fedora Workstation Linux natively on your system in a dual-boot
configuration with your existing host operating system (the Fedora installation process
will detect your existing operating system, allow you to resize its storage to create Linux
partitions, as well as create entries in GRUB to dual boot both operating systems).

2. Next, use the Boxes tool in your GNOME desktop to create and run another virtual
machine of your choice (Linux or Windows).
- 254 -

7.3 Containers
Linux containers provide the basis for PaaS, and are the primary method used to Web
apps in the cloud. Containers are often referred to as sandboxes and contain just enough
data to run an app (including the app dependencies and the associated data). The
container runs on an underlying operating system kernel via a container runtime, but is
isolated from other apps on the server, much like as if it were running on its own server.
Because you don’t need a separate virtual machine (full operating system) for each Web
app, containers allow you to run multiple Web apps with far fewer underlying system
resources. This makes them ideal for running Web apps in cloud environments. Nealy all
major Web apps created today, including Netflix, Facebook, Twitter, and Google, are run
within containers.

But there’s another reason why containers are very popular for running apps in the cloud:
they are easier and faster to develop for. Because containers contain all the app
dependencies and associated data for the app itself, they are not dependent on the
underlying operating system, and can be run on any system that has a conainer runtime,
including a developer workstation. This allows you to create a Web app within a
container on your local developer workstation, and then push the container to a host in
the cloud without having to worry about having the exact same underlying Linux
operating system configuration on your local computer and cloud host.

The Docker daemon is the most common container runtime today, but many others exist.
Nearly all Docker configuration is performed using the docker command (also called
the docker client). You can run containers based on a container image that someone else
created, or you can use docker compose to create your own container image that
includes your Web app, push this container image to a private part of Docker Hub (an
online Docker container image repository), and then pull it down to a PaaS cloud
provider such as Amazon Web Services or Microsoft Azure that will run it on their
container runtime.

To learn how Docker works, we’ll explore it interactively in the following exercise,
explaining each concept along the way.

Exercise
1. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
snap install docker
docker search busybox | less
docker pull busybox
docker images

These three docker commands search for and pull the latest copy of the official busybox
container image from Docker Hub. It is an official image since it has a single name under
the root of Docker Hub (e.g. docker pull jasoneckert/busybox would instead pull a
- 255 -

user-contributed busybox image). Container images can also have different versions,
much like git repositories. For example, docker pull busybox is essentially the same as
docker pull busybox:latest (latest version of the busybox). However, you can pull older
versions too; docker pull ubuntu:18.04 would pull an older version of the official
Ubuntu container image, whereas docker pull ubuntu would pull the latest version. You
can also remove container images that are no longer needed – for example, docker rmi
busybox would remove the busybox container image.

2. Run the docker run busybox echo Hello World command. This runs a copy of the
core busybox container (which is stored in /var/snap/docker/common/var-lib-docker/),
tells it to run the command echo Hello World, and then kill the container in memory.

3. Run the docker ps command note there are no running containers. Next, type docker
ps -a and note that it remembers the container you ran briefly but gives it a random name
because you didn’t supply one.

4. Run the following commands to run a copy of the busybox container, as well as an
interactive shell in a tty terminal. When you exit this shell, your container is killed in
memory:
docker run -it busybox sh
ls
ls /etc
ls /bin
ps -ef
exit
docker ps
docker ps -a

5. Run the docker container prune command to remove all history of stopped containers
(that you don’t want to reuse in the future). Next, obtain the official Apache container
from Docker Hub by running the following commands:
docker search apache | less
docker pull httpd
docker images

6. Run the following commands:


docker run -d -P --name site1 httpd
docker port site1
docker ps

The -d option of the docker run command detaches the container from your terminal and
keeps the container running until you stop it, and the -P option maps exposed ports to an
auto-incremented port number above 32768. Since this is a Web server that listens on
port 80 (HTTP), we often have to map it to another port number since we’ll be planning
on running several of these containers on the same computer and they can’t all listen on
- 256 -

port 80. Alternatively, you could manually map port numbers using the -p option; for
example, -p 31999:80 would manually map port 80 to 31999 for this particular container.

7. Open a Web browser on your Windows or macOS PC and navigate to


http://UbuntuIP:port where port is the port of your site1 container exposed to the
underlying Ubuntu operating system (e.g. http://192.168.1.106:32768) and note the
default “It works!” webpage.

8. Run the following commands to create another container called site2:


docker run -d -P --name site2 httpd
docker port site2
docker ps

9. To ensure that site2 has a different webpage, connect to this second container
interactively and modify the index.html file using the following commands:
docker exec -it site2 sh
cd htdocs
ls
cat index.html
echo “<html><body><h1>Site 2 works!</h1></body></html>” > index.html
exit

10. Open a Web browser on your Windows or macOS PC and navigate to


http://UbuntuIP:port where port is the port of your site2 container and note the “Site 2
works!” webpage.

11. Stop your two containers and remove your container runtime history using the
following commands:
docker stop site1
docker stop site2
docker ps
docker ps -a
docker container prune
docker ps -a

12. As an added challenge, create a shell script that runs an endless while loop to start as
many copies of the httpd container as you can. Use the docker ps command alongside
performance tools (e.g. free, top, or the Cockpit portal) to monitor the resources of your
Ubuntu virtual machine and stop the shell script when the amount of RAM is close to
exhaustion. How many containers were you able to run? This is a good example of why
containers and PaaS are extensively used in cloud environments. Rather than creating a
large Netflix app that accepts millions of connections, you can create a small Netflix app
that accepts a single connection (which is easier) and use containers to run it millions of
times, as each user connects. When users disconnect, the Netflix app and associated
container are destroyed. This scalability is key for large cloud services such as Netflix.
- 257 -

13. Developers often start with a pre-built container image, add their Web app to it, and
then push that image to Docker Hub. To illustrate this process without creating an actual
Web app, we’ll create another container image based on httpd, but with a different
webpage, run it locally, and then push it to Docker Hub. First create a free account on
Docker Hub (https://hub.docker.com) and follow the instructions to create a free public
repository called webapp. Next, create an access token (password equivalent for
uploading to your Docker Hub repository) by navigating to Account Settings > Security
> New Access Token (you’ll need to copy this token and use it as a password when
connecting to Docker Hub later on).

14. Run mkdir webappcontainer && cd webappcontainer to create and enter a


directory for our new container image. Next, run vi index.html, add the following
contents and save your changes when finished:
<html>
<body>
<h1>This is a custom app!</h1>
</body>
</html>

15. Run vi Dockerfile, add the following contents and save your changes when finished:
FROM httpd
COPY ./index.html htdocs/index.html

16. Run the following commands to create a new image called webapp (based on httpd
but with the new index.html from the current directory), view your new image locally,
and then upload it to your Docker Hub repository. Make sure you replace jasoneckert
with your own Docker Hub username:
cd webappcontainer
docker build -t jasoneckert/webapp .
docker images
docker login -u jasoneckert #Paste your Docker Hub access token when prompted!
docker push jasoneckert/webapp:latest

17. To verify that your container serves the new webpage, run docker run -d -p 80:80 --
name webapp jasoneckert/webapp (replace jasoneckert with your Docker Hub
username) and then navigate to http://127.0.0.1 in your Web browser. Run docker stop
webapp when finished.

18. Explore the Docker help options by running the following commands:
docker --help
docker run --help
docker exec --help
- 258 -

7.4 Devops Workflows


Developers typically create Web apps on their own developer workstations but have to
continually run and test them on servers in the cloud. For a large software development
project, this may mean pushing different versions of a Web app to a production server in
the cloud over a dozen times in a single day. The process I’ve just described is called a
CD (continuous deployment) or a devops (developer operations) workflow since the
person that installed and managed these components was Linux system operator that
supported the development process. Let’s examine a sample traditional devops workflow:

The first step is to get the Web app code into the cloud. Since developers publish their
code to a code repository such as Git or GitHub, this is the most common way to copy the
code to the cloud environment. This code is often collaboratively developed by several
different developers, each of whom have their own Git branch that must be merged into
the master branch before being sent to the cloud. As a result, this component of the CD
process is called CI (continuous integration). Next, an orchestration tool (e.g. Jenkins)
will grab that code as soon as it reaches the code repository and build it using a software
build tool (e.g. Maven for Java source code).

The orchestration tool will then create a brand new virtual machine or container that
meets the exact needs of the Web app using an automation tool (e.g. Ansible, Puppet,
Chef, SaltStack, Terraform, Kickstart or Cloud-init) and then copy the compiled code to
that virtual machine or container and execute it. These automation tools often combine
the functionality of infrastructure automation (create virtual machines or containers)
together with configuration management (modifying the configuration of a newly-
installed virtual machine or container). To perform their tasks, automation tools use a set
of attributes within a YAML file that identify the components the container or virtual
machine must have installed, as well as any other configuration information. Some of
these tools (e.g., Puppet, Chef, SaltStack) require that you install an agent on each virtual
machine or container, whereas other tools (e.g. Ansible) are agentless; they use SSH to
perform all configuration (or remote PowerShell for Windows systems).
- 259 -

NOTE: The combination of infrastructure automation and configuration management is


often called Infrastructure as Code (IaC), and the associated virtual machines and
containers are referred to as the IaC inventory.

NOTE: It is important that you don’t confuse automation and configuration with
orchestration. Automation and configuration describe a task (or series of tasks)
accomplished without human intervention. Orchestration describes the arranging and
coordination of automated tasks, ultimately resulting in a consolidated process or
workflow (in this case, a devops workflow).

NOTE: To see how automation can be performed with Ubuntu Cloud-init within
Amazon Web Services visit: https://www.youtube.com/watch?v=-zL3BdbKyGY

At this point, the Web app is now running on a private test VM/container in the cloud
and can be tested. If problems are found during the testing phase, the test VM/container is
destroyed by the orchestration tool and the whole process is repeated. If no problems are
found, then the test container (or virtual machine) replaces the existing publicly-
accessible production VM/container, and the world now has version 2.0 of the Web app!

This process has evolved tremendously over the past decade, and nowadays developers
perform many of the tasks that were previously performed by devops (a process called
shifting left since developers are always listed on the left of any devops workflow
diagram). Today, a devops workflow typically involves the components shown in the
following revised diagram:
- 260 -

We still have CI, and it almost always involves Git (instead of other version control
systems). However, Web apps are almost always deployed in containers instead of VMs,
and developers build their Web apps into containers for local testing before pushing them
to a container repository (also called a container registry), such as Docker Hub.

Kubernetes (also called K8s) then pulls those containers into a cloud environment where
they can be deployed. Kubernetes is the most common orchestration and automation tool
today (although it can be used alongside other automation tools like Ansible). It can
deploy and manage both test and live containers, as well as scale them to run on many
different servers as needed.

The term devop now refers to a developer that builds a containerized Web app and
deploys it to the cloud, whereas Site Reliability Engineer (SRE) is used to describe the
systems administrator that manages the containerized Web app once it reaches the cloud.
SREs often fix problems with Web apps once they are running in the cloud, as well as
configure and mange the other components in a cloud datacenter (storage, networking,
authentication, security and so on).

Two other recent terms are also worth noting:


• Gitops refers to CD that is performed automatically via instructions that are part
of the Web app code. GitHub Actions and GitLab Runners are two common
Gitops systems today. A developer simply adds an extra text file (in YAML
format) that specifies the appropriate CD configuration to their Web app code
stored in the GitHub or GitLab code repository to have it automatically deployed
on a cloud provider when changes are made. In this section’s exercise, you’ll
perform Gitops with a sample app using GitLab Shared (free) Runners.
• Devsecops is a term used to describe the incorporation of security measures in all
CI and CD components. There are many different security products and scanners
on the market designed specifically for devops workflows.

The example software products discussed in this section are part of a larger suite of
available products that you can view at the Cloud Native Computing Foundation’s
Interactive Landscape page (https://landscape.cncf.io/). You are not expected to
understand how to use all of these tools; instead, you simply need to be aware of the
devops workflow process and concepts involved. Each organization chooses only the
products they need in their particular devops workflow (usually 2-5 tools). For smaller
projects, a devops workflow may only contain a few components, or be integrated into
your CI platform.
- 261 -

Exercise
1. Open a Web browser on your Windows or macOS PC, navigate to
https://gitlab.com and create a free account.

2. Next, log into gitlab.com and create a new project (Projects > Your projects > New
project), give it a project name of workflowtest, ensure that it is Private, and click
Create project. Observe the instructions at the bottom of the page regarding getting
code to your new project from an existing folder (it should look familiar to GitHub).

To test a sample developer workflow, we’ll download the sample JavaScript Node.js app
available on GitLab.com; for simplicity I’ve hosted it on my server, which is allowed in
the license provided by the developer.

3. On your Ubuntu Server virtual machine, log into tty2 as the root user and run the
following commands:
wget http://triosdevelopers.com/jason.eckert/trios/nodejs-master.tar.gz
tar -zxvf nodejs-master.tar.gz
cd nodejs-master
less .gitlab-ci.yml

Note the following in the .gitlab-ci.yml file:


image: node:4.2.2 # Tells docker to download and run the Web app on the docker
# image called node (version 4.2.2) from Docker Hub
test_async: # The first job that will be run by the GitLab runner (the script:
# section lists the commands that are used to execute the
# JavaScript Web app
test_db: # The second job that will be run by the GitLab runner, which
# requires a second postgres database (version 9.5.0) container

git init
git remote add origin https://gitlab.com/YourGitLabUsername/workflowtest.git
git add .
git commit –m “first commit”
git push –u origin master

4. Return back to your GitHub account in your Web browser and access your project
(Projects > Your projects > workflowtest). Next, navigate to the CI/CD icon on the left
of the screen and click Run Pipeline and then Create Pipeline This will execute your
.gitlab-ci.yml file in a shared runner and show you the two test jobs within. When they
are finished running, you can click on them to see the results (click Passed, followed by
the appropriate test).

Developers working on smaller projects often push their code up to GitLab and simply
tell GitLab to execute it within a docker container pulled from Docker Hub (GitLab
runner). Since these containers are simply virtualized apps, they should run the same on
any production server.
- 262 -

7.5 Microservices & Kubernetes


The program that is run within a container is often called a microservice because it is
designed to provide a single function. A Web app may consist of a single microservice,
or be comprised of several different microservices that work together, with each
microservice running in their own container. For example, you could create a JavaScript
Web app in one container that works alongside a Python app in another container that
analyzes the data from the JavaScript Web app using machine learning (artificial
intelligence). And the data that these apps work with can be sent to a PostgreSQL
database app running in yet another container that stores data on a persistent storage
volume available on the network (e.g. NFS share) or offered directly by the cloud
provider.

NOTE: Apps running in different containers can communicate directly with each other,
or via a special proxy service called a service mesh that provides additional security and
monitoring capabilities (often called observability).

NOTE: By specifying the configuration for multiple containers, a single Dockerfile


can be used alongside docker compose to create all of the containers that are needed
to comprise a complex Web app.

Being able to run different parts of your Web app in different containers makes it easier
to develop, fix and evolve – and is why microservice architecture is popular today. For
example, to enhance the machine learning features in our previous example, developers
only need to modify and redeploy the Python app container.

Moreover, individual containers can be flexibly scaled to service more client connections.
Since public cloud providers have ample hardware in their datacenters to scale your
microservices thousands of times easily, they are also called hyperscalers.

NOTE: By keeping each component as small as possible, and combining multiple


components to form larger services, containerized microservices are an example of the
UNIX philosophy (https://en.wikipedia.org/wiki/Unix_philosophy) in practice.
- 263 -

Kubernetes is the industry-standard software used to deploy, scale and manage containers
within a public or private cloud environment today. A Kubernetes cluster includes a
series of virtual machines called nodes. The Kubernetes control plane includes one or
more master nodes that provide services for managing the cluster. All other nodes in the
cluster are controlled by the master nodes via a kubelet service, and have a container
runtime for running containers. The Web apps that you run on each node are called pods
and may consist of one or more related containers and optional persistent storage
volumes. A sample Kubernetes node running 4 pods is shown below:

NOTE: Not all containers in a pod may be involved in providing Web app functionality.
For example, you may have a container in a pod that merely serves to monitor the pod, or
provide service mesh capabilities – these containers are often called sidecar containers.
Another example are ambassador containers, which merely serve to proxy requests from
other containers in the pod to services outside of the pod (object storage, external APIs,
Cloudflare tunnels, etc.).

When you deploy a Web app to a Kubernetes cluster, you configure a deployment that
tells Kubernetes how to start/manage/scale the containers in a pod on each node, as well
as a service that allows pods to be accessed from outside of their nodes (each node also
has a kube-proxy component that works alongside the kubelet to route traffic outside of
the node). To provide external access to a service from outside of the cluster, you need to
set up either a load balancer or ingress controller. Public cloud providers often provide
their own load balancer services that route a public IP address to your service for a fee.
Alternatively, you can use an ingress controller within your cluster to route a public IP
- 264 -

address from your cloud provider to a service. A Kubernetes cluster acts as a “black box”
that only allows the outside world to access the services you expose via a load balancer or
ingress controller.

Since a Kubernetes cluster allows other programs to interact with it using an API, you
can use a wide variety of different software programs to manage Kubernetes. The control
plane provides this API functionality, as well as the controllers and schedulers used to
manage access to Web apps, and the etcd database that stores all Kubernetes
configuration.

Developers often test their containerized Web apps on their local workstation using a
single-node Kubernetes cluster that contains a single master node that also runs all pods
via a container runtime. After successfully testing Web apps on this cluster, developers
push their container images to a container registry (e.g. Docker Hub), where they are
pulled down into a cloud-hosted K8S cluster and deployed.

In the following exercise, you’ll configure Kubernetes to run and scale the webapp
container you created earlier by installing the following software on your Windows or
macOS host system (much like a developer does):
• Docker Desktop (which includes the Docker container runtime)
• Minikube (a pre-configured single-node Kubernetes cluster)
• kubectl (the main command used to interact with Kubernetes - it’s official
pronunciation is cue-bee-cuttle)
• Helm (a package manager for Kubernetes)
• Lens (a visual management and monitoring tool for Kubernetes)
• Prometheus (a Kubernetes data collection tool)
• Grafana (a tool that visually displays data collected by Prometheus)
- 265 -

Exercise
1. Before running a Linux container on your macOS or Windows system, you need to
have an underlying Linux operating system and container runtime (previously, we’ve run
the Docker container runtime only within our Ubuntu virtual machine). As a result, we’ll
install Docker directly on your macOS or Windows system.

On macOS, Docker starts a Linux VM using Apple’s Hyperkit framework in order to run
the container. On Windows, Docker will first start the Linux operating system built into
the Windows Subsystem for Linux (WSL2). If you are using Windows and don’t already
have WSL2 installed, open the Windows Terminal (Admin) app on your Windows
system and run the following commands:
wsl --install
wsl --set-default-version 2

2. Install the Docker Desktop app on your macOS or Windows system from
https://www.docker.com/products/docker-desktop. Docker Desktop automatically
installs the Docker container runtime (containerd) and automatically starts it when you
start the Docker Desktop app.

3. Start the Docker Desktop app but close the app window afterwards. The Docker
container runtime will continue to run in the background and you can interact with it
from the Docker icon in your notification area (Windows) or menu bar (macOS).

4. Install the Homebrew package manager on your macOS host using the instructions
available at https://brew.sh, or the Chocolatey package manager on your Windows host
using the instructions available at https://chocolatey.org.

5. Open the Terminal app on your macOS system or the Windows Terminal (Admin)
app on your Windows system and run the following commands to install Minikube and
kubectl, as well as start a Minikube Kubernetes cluster and display information about it:
brew install minikube kubectl (on macOS)
choco install minikube (on Windows)
minikube start
kubectl get nodes

6. Run the following commands to create a deployment for our Web app called webapp,
expose it outside of the node via a service, and access it on your local system using the
default Web browser (replace jasoneckert with your Docker Hub username):
kubectl create deployment webapp --image=jasoneckert/webapp:latest
kubectl expose deployment webapp --type=NodePort --port=80
minikube service webapp (keep this running!)

The kubectl expose deployment command creates a service to expose the deployment
(NodePort exposes the port on every cluster node), the minikube service command tells
Minikube to connect a tunnel to the exposed service from your host operating system
(127.0.0.1) on a random port. It also opens the default Web browser and connects to
- 266 -

http://127.0.0.1:port to test access to the Web app. You can also use minikube stop to
shut down the Minikube VM/WSL2 if you no longer want to run the Kubernetes cluster.

7. Open a new tab in your Terminal app and run the following commands:
minikube ip (shows the IP address of the Minikube VM/WSL2)
kubectl get deployment
kubectl describe pod -l app=webapp

8. Kubernetes treats all containers that comprise a deployment as a traffic-balanced unit


that can scale. To ensure that you have 3 pods for redundancy in case a single pod
running your Web app goes down, run kubectl edit deployment webapp. This opens the
deployment configuration in your default text editor. Change the Replicas=1 line to
Replicas=3 and save your changes. Next, verify that Kubernetes has automatically scaled
up your Web app by running the following commands (you should see 3 copies of your
pod running within a few seconds):
kubectl get deployment webapp
kubectl get pods -l app=webapp
kubectl logs -f -l app=webapp --prefix=true (this shows you events in the Web app)
kubectl get service webapp -o yaml (note the session affinity and load balancing info)

9. Kubernetes can also perform autoscaling of pods based on the amount of traffic going
to your Web app – this is called horizontal pod autoscaling (HPA). To do this, you must
first install the metrics server. Normally this is installed using Helm (discussed later), but
if you are using Minikube, you can run the following commands to view and install the
metrics server:
minikube addons list | grep metrics-server (if you are using macOS)
minikube addons list | select-string metrics-server (if you are using Windows)
minikube addons enable metrics-server

10. After a few minutes, you can run kubectl top nodes to view statistics that are
collected. Next, you can use the following to create and view a HPA configuration for
your Web app that automatically scales from 1 to 8 pods when a consistent trend of more
than 50% of the CPU is consumed:
kubectl autoscale deployment webapp --min=1 --max=8 --cpu-percent=50
kubectl get hpa webapp

NOTE: Over time, Web app developers will make a new container image available (e.g.
jasoneckert/webapp:2.0 on Docker Hub). To update your Kubernetes cluster to use the
new image in production, you can either edit your deployment (kubectl edit deployment
webapp), modify the Image= line and save your changes, or run kubectl set image
deployment/webapp webapp=jasoneckert/webapp:2.0. Kubernetes will immediately
start replacing the pods with your new image in sequence until all of them are upgraded.
If an upgraded image causes stability issues, you can revert to the previous image using
kubectl rollout undo deployment webapp. You can also use kubectl rollout history
deployment webapp to view rollout history.
- 267 -

11. Up until now, you’ve had to access the pods that comprise your webapp using
http://127.0.0.1:port from your local system via a Minikube tunnel. In order to provide
external access to these pods, you need to set up either a load balancer or an ingress
controller. To use a load balancer, you need to pay your cloud provider for the service.
Next, you can edit your service, change NodePort to LoadBalancer, and supply any
additional configuration required by the cloud provider. The cloud provider then gives
you an external IP that you can use when creating a DNS record for your Web app.
Alternatively, you can install and configure an ingress controller, such as Nginx,
HAProxy or Traefik.

We’ll install an Nginx ingress controller in our Kubernetes cluster and configure it to
allow external access to our Web app. The easiest way to install additional components in
a Kubernetes cluster is by using the Helm package manager, which uses helm charts to
store the information needed to add/configure the appropriate software. There are many
different repositories on the Internet that provide helm charts for different Kubernetes
components. The following commands install the Helm package manger, add the Nginx
Helm repository, and install the Nginx ingress controller:
brew install helm (on macOS)
choco install kubernetes-helm (on Windows)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx

12. Run the kubectl get service ingress-nginx-controller command and note that you do
not have an external IP address. At this point, you would normally have a public external
IP provided by the cloud provider, but since we are using Minikube, we must first
emulate a cloud provider connection to your Kubernetes cluster in Minikube using an
external IP of 127.0.0.1. To do this, run minikube tunnel and supply your
macOS/Windows user password when prompted (keep this running!).

13. Open a new tab in your Terminal app and run kubectl get service ingress-nginx-
controller to view your external IP of 127.0.0.1.

14. To emulate DNS resolution, edit your hosts file using the following command and
append site2.com to the line that starts with 127.0.0.1, saving your changes when
finished:
sudo vi /etc/hosts (on macOS)
notepad C:\Windows\System32\drivers\etc\hosts (on Windows)

15. Next, create a webapp-ingress.yml text file in your current directory that contains the
following lines:
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: webapp-ingress
namespace: default
- 268 -

annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: site2.com
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: webapp
port:
number: 80

This file is called a manifest file. Manifests are JSON- or YAML-formatted text files that
contain configuration information that you can apply to your Kubernetes cluster. This
particular manifest creates an ingress service that links Nginx to webapp on port 80.

16. To apply this manifest, run the kubectl apply -f webapp-ingress.yml. Next, you can
run kubectl get ingress to view your ingress configuration and enter http://site2.com in
your Web browser to access your Web app.

NOTE: You can still access the Web app using http://127.0.0.1:port. To prevent this
(i.e., force everyone to access the Web app via the Nginx ingress controller only), simply
edit your deployment and change NodePort to ClusterIP.

14. Most administrators use a combination of command line tools and templating tools
(e.g. Terraform) for managing Kubernetes, but use graphical tools for monitoring it.
There are many cloud-based monitoring tools (e.g. Datadog) that can be integrated into
Kubernetes for a fee, as well as free tools that you can install directly in your Kubernetes
cluster, such as Prometheus and Grafana. Prometheus monitors the events in your cluster
and sends the data to Grafana for visualization. To install both Prometheus and Grafana,
run the following commands to install the Prometheus stack using a helm chart:
helm repo add prometheus-community https://prometheus-
community.github.io/helm-charts
helm repo update
helm install prometheus prometheus-community/kube-prometheus-stack

This will install a series of pods, including a pod called prometheus-grafana. Since
these pods take several minutes to start, watch the output of kubectl get pods
periodically to know when they are ready. One they are ready, run the following
commands to expose a service that can be used to access Grafana on port 3000:
kubectl expose service prometheus-grafana --type=NodePort --target-port=3000
--name=pgservice
minikube service prometheus-grafana
- 269 -

This opens your default Web browser to access it. Log into the Grafana Web app as the
user admin (default password is prom-operator) and view the different available
monitoring templates by navigating to Dashboards > Browse (click on each one).

15. You can also use graphical apps to monitor and manage Kubernetes. The most
common app for this is Lens, which is quite powerful. Install the Lens app on your
macOS or Windows host from https://k8slens.dev and use it to explore the cluster,
nodes, pods, and deployments you’ve configured previously. The statistics shown in Lens
actually come from Prometheus (they are blank otherwise).

At this point, we have only scratched the surface of Kubernetes configuration. But you
now have enough core knowledge to search for relevant information in the Kubernetes
documentation available online at https://kubernetes.io/docs/home/. Some additional
aspects of Kubernetes configuration you can explore are described in this bulleted list:
• You can integrate Kubernetes directly with a DNS provider (~Dynamic DNS)
using https://github.com/kubernetes-sigs/external-dns.
• For HTTPS/TLS, you can connect your ingress controller to https://cert-
manager.io so that it can automatically get certificates for each service. You can
install it using Helm or a downloadable manifest you can apply (see instructions
on the website for details on either method). Next, follow the instructions to
connect to a CA (e.g. Letsencrypt) and modify your ingress controller settings to
list cert-manager. Of course, you’ll also need a publicly-resolvable DNS record
for your cluster for this to work.
• Cronjobs are often used by Web apps to do things like reindexing DBs,
maintenance tasks, clearing caches, and so on. Kubernetes has a CronJob resource
that can do this. Simply create a manifest that lists the cron schedule, as well as a
container it can spawn (e.g. Alpine/Busybox) to perform the commands you
specify.
• For persistent block storage needed by containers, you need to create a PVC
(Persistent Volume Claim) to create a resource that doesn’t disappear when you
restart your cluster. Many block storage volumes can only attach to 1 pod at time,
which makes it difficult to scale. Rook/Ceph and GlusterFS can do this but are
complex to configure. NFS is a simple method that can be used to access
persistent block storage. To use NFS, install and configure the NFS Client
Provisioner and configure it to connect to an NFS share on another container or
NFS server. If the block storage is only used for hosting databases, there are many
different persistent and cloud native solutions (e.g. CockroachDB) available on
the market that may be worth the money depending on your use case.
• Namespaces limit the scope of resources in Kubernetes. You’ve been using the
default namespace for everything so far, but in a production environment you
typically create namespaces for related resources that comprise a Web app. You
can use kubectl create namespace lala to create a lala namespace, and add -n
lala to other kubectl commands to limit their functionality to that namespace. If
you run kubectl delete namespace lala, all resources associated with that
namespace will also be deleted.
- 270 -

APPENDIX A
aA
Working with macOS
As discussed in Chapter 1, macOS is a UNIX flavour. However, macOS is also 80% open
source software. The underlying operating system is an open source operating system
called Darwin that is largely maintained by the BSD UNIX community. Apple adds the
remaining 20% closed source components that comprise the graphical desktop and
frameworks.

The kernel that macOS uses is called XNU (X is not UNIX). XNU is one-third MACH
3.0 microkernel, one-third BSD UNIX kernel (filesystem & networking), and one-third
I/O Kit (an Apple driver framework). The XNU kernel and Darwin system are directly
evolved from NeXTSTEP UNIX. When Apple purchased NeXT from Steve Jobs in the
late 1990s, they essentially rebranded NeXTSTEP UNIX as Mac OS X (now called
macOS). When you develop for macOS/iOS, you still use programming objects that
inherit from NSObject (NeXT Software Object).

NOTE: Prior to macOS Sierra (10.12), macOS was called Mac OS X (10.0 - 10.11). Mac
OS 9 and earlier (1980s-1990s) were not based on NeXTSTEP UNIX.

As shown below, macOS seems very similar to Linux. However, the app shortcut bar
(called the Dock) is at the bottom and visible by default (the GNOME Dock is at the left
and you must click Activities to see it).
- 271 -

The leftmost icon shown on the Dock is the Finder (file browser app), and nearly all
system configuration can be performed by opening the System Preferences app (cog
wheel icon). In addition to configuring desktop, network and firewall settings, you can
also start network services such as SSH, FTP file sharing, and the Apache Web server
using System Preferences.

You can navigate to Finder > Applications > Utilities > Terminal to open a command
line shell. macOS uses the Z shell by default, but you can change it to Bash using the
chsh -s /bin/bash command.

Your macOS user account is a member of the admin group (equivalent to the wheel
group in Linux) and has permission to run any command as root using sudo (via a line
in /etc/sudoers). However, if you wish to switch to the root user using su – to
perform system administration, you must first assign the root user a password using the
dsenableroot command.

The directory structure is very similar to Linux systems with some noticeable differences
(e.g. home directories are under /Users instead of /home). Following is a list of
common macOS directories:

/Applications Stores most user programs


/bin Contains binary programs that any user may execute
/dev Contain device files used to reference system devices
/etc Contains most system configuration (symlink to /private/etc)
/Library Contains program libraries, documentation, and app settings
/Network Contains libraries and information from remote systems
/private Contains OS information that users should not normally view
/sbin Contains superuser (root) executables
/System Contains most system configuration & operating system files
/tmp Contains temporary files used by apps (symlink to /private/tmp)
/Users Default location for all regular user home directories
/User Information Contains system documentation (symlink to
/Library/Documentation)
/usr Stores most executables (/usr/bin, /usr/sbin) and their
documentation (/usr/share/man, /usr/share/doc)
/var Contains log files and spool/content directories
/Volumes Contains subdirectories used for mounting devices to

NOTE: Only the /Applications, /Library, /System, /User Information,


and /Users folders are shown in the graphical desktop by default. All other directories
are hidden from normal view and may be viewed using a shell. Additionally, there is a
Library folder in each home directory that stores user-specific program libraries,
documentation, and app settings.
- 272 -

Each application in macOS is contained within a single directory called a bundle.


Bundles are directories with an .app extension that appear as program icons in the
Finder. When double-clicked, they launch the app contained within. You can use the
open command to run a bundle from the command prompt. For example, open
/Applications/Firefox.app will start the Firefox Web browser. Like a Flatpak
or Snap package, bundles contains all executable files, configuration files, and
dependencies needed by an app. If you drag a bundle to the Trash, you will remove all
files associated with the app (other than user-specific files and settings stored in your
home directory). To look inside a bundle, run the cd bundlename.app command, or
right-click the bundle in the Finder and choose Show Package Contents.

To view system events, macOS provides a central graphical log file viewer called
Console (/Applications/Utilities/Console) that contains shortcuts to all
major log files on the system. The /Applications/Utilities/Activity
Monitor program is the macOS equivalent of the top command in Linux and the
Task Manager in Windows. It displays system statistics process information but can also
be used to kill processes. Alternatively, you can right-click an app icon on the Dock and
choose Force Quit to kill a process or use the [Cmd]+[Option]+[Esc] key
combination to do the same. The [Cmd]key on a Mac is the same as the Windows key
on a PC. You can hold down the [Cmd]+R keys during startup to interact with the
macOS Recovery Tool in the event you need to repair or reinstall macOS from across the
Internet.

Storage devices in macOS contain a single partition that is sliced into file systems. By
default, the block device file /dev/disk0 refers to the whole partition on the first
storage device, /dev/disk0s1 refers to the first slice (which usually contains the boot
loader) and /dev/disk0s3 refers to the third slice (which usually contains the /
filesystem). macOS uses the Apple File System (APFS) to format slices. When you insert
removable media (e.g. DVD or USB flash drive), the filesystem is automatically mounted
to a subdirectory of /Volumes and a symlink to it is placed in your Finder.

Network devices also have different names in macOS compared to Linux. The first wired
or wireless Ethernet network interface is called en0, the second is called en1, and so on.

Nearly all commands (and command options) in Linux are identical in macOS. However,
there are some notable additions and differences, as shown below in alphabetical order:

caffeinate Prevents a macOS system from entering power save / sleep mode
defaults Sets macOS system preferences
diskutil Creates, formats and checks macOS filesystems
ditto Same as the cp command (also in macOS)
dscl Manages users (and groups) on the system
dseditgroup Manages groups on the system
dsenableroot Enables the root user by setting a password for it
jobs Unlike the same command in Linux, it summons Steve Jobs ;-)
- 273 -

launchctl Starts and stops macOS daemons and system components


(equivalent to systemctl on Linux)
networksetup Configures network interface settings
newfs_type Creates filesystems (type = exfat, apfs, and so on.)
nvram Modifies Macintosh boot firmware
open Executes a macOS application bundle
pdisk Create Apple disk partitions.
pkgutil Manages and queries macOS software packages
pmset Configures macOS power management settings
scutil Changes network locations (for firewall zones)
security Configures password storage and certificates
softwareupdate Updates macOS system software
spctl Modifies and displays macOS SecAssessment settings (the macOS
equivalent to SELinux)
sw_vers Displays the version of macOS
sysctl Modifies system settings
system_profiler Displays macOS system hardware and software information
systemsetup Configures macOS system and graphical display information
vm_stat The macOS equivalent of vmstat
- 274 -

APPENDIX B
aA
Working with FreeBSD
FreeBSD has the fastest network and service stack of all operating systems today (more
so than Linux). This is why it is the choice of server for many organizations that rely on
very fast, low-latency communication, such as Netflix. As with macOS, FreeBSD UNIX
is very similar to Linux with some key differences, the most notable one being that nearly
all system configuration is stored in a single text file called /etc/rc.conf for
simplicity. While there is no graphical installation, the installation process is
straightforward and similar to installing a server-based Linux distro.

NOTE: During a FreeBSD installation, ensure that you choose the local_unbound
package when prompted if you want to cache DNS lookups locally, as FreeBSD doesn’t
have a built-in local DNS resolver that does this.

The FreeBSD directory structure is almost identical to Linux. However, regular user
home directories are located under /usr/home instead of /home. Most standard UNIX
commands (e.g. ls, chmod, find) are identical, with a few that have slightly different
options in the manual pages. In the following sections, we’ll describe some key
differences by topic area.

System & Kernel Configuration


/etc/rc.conf contains nearly all system configuration, including IP configuration,
hostname, default gateway, daemons that should start at boot time, and so on. Lines
within this file have parameter=value syntax and are easy to edit using a text editor,
such as vi. You can also use the sysrc -a command to show all of the configured
values in /etc/rc.conf, or the sysrc parameter=value command to modify
or add configuration.

FreeBSD stores the default parameters for configuration files in a defaults subdirectory.
For example, /etc/defaults/rc.conf stores a large number of system-configured
defaults that are overridden by /etc/rc.conf. Never change the entries in
/etc/defaults/rc.conf; instead, just override them by adding the same lines to
/etc/rc.conf with the values you want. There’s also an /etc/rc.conf.d
directory that software packages can add files to that set system parameters.

As with Linux, drivers can be compiled into the kernel or inserted as modules. To view
modules inserted into the kernel, you can use kldstat. You can also load and unload
modules manually. For example kldload linprocfs.ko loads the Linux procfs
filesystem module in the kernel, whereas kldunload linprocfs.ko would unload
- 275 -

it. To make sure this module gets loaded automatically at system startup, you could add
the following line to the /boot/loader.conf file:

[cmd=]kldload /boot/kernel/linprocfs.ko[/cmd]

The FreeBSD kernel also has many properties and parameters that you can view and
configure. You can use the kenv command to view the currently-configured parameters
on your system, or the sysctl -o -a command to view all available parameters and
their default values. The sysctl command can also be used to view specific
parameters. For example sysctl kern displays all parameters starting with kern,
whereas sysctl kern.securelevel displays the current value of the kernel
security level. Alternatively, to see your CPU model, you could run sysctl
hw.model. To ensure that a kernel parameter is configured at system startup, you can
add the appropriate line to the /etc/sysctl.conf file.

System Startup & Daemons


Just like the GRUB2 boot loader on Linux, FreeBSD has an interactive boot loader called
boot0. It displays a menu for 10 seconds by default that allows you to enter system rescue
mode or modify kernel values manually, among other things. If you are repairing a
system, a copy of useful binary programs is stored under the /rescue directory and
made available to boot0. Boot loader configuration is stored in /boot/loader.conf
(as well as /boot/defaults/loader.conf) and uses the same syntax as
/etc/rc.conf.

Once the kernel is loaded by boot0, the init daemon parses the large /etc/rc script to
start the other daemons that you specified within /etc/rc.conf by executing the
appropriate daemon scripts under the /etc/rc.d directory. There also are other scripts
executed by init at boot time. For example, /etc/netstart configures the network
according to the parameters you specified in /etc/rc.conf.

After your system has booted, you can view the /var/run/dmesg.boot file to see
the hardware detected and modules loaded by your kernel or view the
/var/log/messages file to view the daemons and components that were started by
init, including any associated errors.

Storage Devices, Partitions and Slices


FreeBSD uses different device files for storage as well as different methods for
partitioning and creating filesystems. To see a list of the physical storage devices you
have, you can use either the camcontrol devlist or geom disk list
command. Some sample device files for these storage devices include:
/dev/cd0 (first CD/DVD)
/dev/da0 (first SCSI/SAS disk or USB drive, which emulates SCSI)
- 276 -

/dev/ada0 (first IDE/SATA disk)


/dev/nvme0 (first NVMe SSD)
/dev/nvme0ns1 (first namespace on the first NVMe SSD)

Say, for example, you have one SATA SSD in your system that has a GPT partition table.
FreeBSD will likely create three partitions on it during the installation:
/dev/ada0p1 (usually a 512KB FreeBSD boot partition or UEFI boot partition)
/dev/ada0p2 (usually a swap partition)
/dev/ada0p3 (rest of disk - usually given to ZFS, or mounted to / if you use UFS)

If you have older storage devices that use an MBR partition table, each primary partition
is called a slice in FreeBSD, and further subdivided into up to 7 device nodes using a
special BSD disk label. For example, the first slice on /dev/ada0 could be subdivided
into 4 device nodes, with each one assigned a letter:
/dev/ada0s1a (first device node in the first slice on ada0)
/dev/ada0s1b (second device node in the first slice on ada0)
/dev/ada0s1c (third device node in the first slice on ada0)
/dev/ada0s1d (fourth device node in the first slice on ada0)

You can view your disk configuration, as well as create and manage partitions using the
gpart command. For example, gpart show -p ada0 displays partitions on ada0,
whereas gpart show -l ada0 displays labels on ada0 (these labels match files
under the /dev/gpt directory). To display the partition labels for disks on the system,
you can run the glabel list command.

Filesystems
The only two filesystems that are commonly used on FreeBSD for storage are the legacy
UNIX File System (UFS) and ZFS (the standard filesystem on FreeBSD). After creating
partitions on a GPT disk (or slices and device nodes on an MBR disk), you can use the
following commands create and work with UFS filesystems:
newfs (creates a UFS filesystem)
growfs (extends the size of a UFS filesystem)
tunefs (tunes UFS filesystem parameters)
mksnap_ffs (creates a UFS filesystem snapshot)
fsck (checks a UFS filesystem for errors)

Normally, you’d use ZFS instead of UFS on a FreeBSD system for its enterprise features,
including corruption protection and device fault tolerance. The same zpool and zfs
commands you used on Linux to configure ZFS can also be used on FreeBSD. For
example, to create a raidz volume called lala from the space on 3 different SCSI disks
and put a ZFS filesystem on it, you could use the zpool create lala raidz
/dev/da1p1 /dev/da2p1 /dev/da3p1 command.
- 277 -

Some other example zpool and zfs commands include:


zpool status lala (view status of the lala volume)
zpool list (lists all ZFS volumes)
zpool get free (displays free space information from all ZFS volumes)
zfs create lala/stuff (creates a new ZFS filesystem under the lala volume)
zfs list (displays all ZFS volumes and where they are mounted)
zfs get compression (displays compression setting for all ZFS volumes)
zfs set compression=lz4 lala/stuff (sets compression for lala/stuff)

When you run the zfs list command on a newly-installed system, you’ll see that
there is one ZFS volume called zroot that is created by the FreeBSD installer. This
volume contains many other ZFS filesystems underneath it for different system
directories:
zroot/ROOT/default is mounted to /
zroot/usr is mounted to /usr
zroot/usr/home is mounted to /usr/home
zroot/var is mounted to /var
and so on.

The reason that zroot/ROOT/default is mounted to the root of the system is


because FreeBSD supports different boot environments if you take ZFS snapshots of the
/ filesystem. Before performing a risky configuration, you could take a snapshot of your
system called zroot/ROOT/May2 and then revert to it if your risky configuration fails.
You can even choose a previous boot environment at the FreeBSD boot loader menu
when you start the system. Following are some useful boot environment commands:
pkg install beadm (installs the boot environment package)
beadm create May2 (create snapshot of system called May2)
zfs list (you should see zroot/ROOT/default and zroot/ROOT/May2)
beadm activate May2 && reboot (reverts system to May2 snapshot)

There is also a /etc/fstab file that mounts non-ZFS filesystems at system startup just
as you’d expect on a Linux system. If you use ZFS exclusively, /etc/fstab just
activates the swap partition only. And just as Linux has udev rules for restricting access
to storage devices, you can add lines to /etc/devfs.conf or /etc/devfs.rules
to do this on FreeBSD.

The only other filesystem-related difference between Linux and FreeBSD are filesystem
attributes, which are called filesystem flags in FreeBSD, and can be set at the system or
user level. For example, chflags sunlink file sets the system unlink flag on a
file, to prevent file deletion (same as the immutable attribute on Linux), whereas
chkflags nosunlink file unsets it. You can use ls -lo file to display the
filesystem flags set on a file.
- 278 -

Users and Groups


As on Linux systems, FreeBSD stores user configuration in /etc/passwd (readable by
everyone) but converts it to a /etc/pwd.db database for fast system access. However,
instead of using the /etc/shadow file like Linux does, FreeBSD stores all user and
password configuration in /etc/master.passwd (readable by root only) and
converts it to /etc/spwd.db for fast system access. Groups are stored in
/etc/group, but there is there is no sudo functionality; instead, you must be part of
the wheel (big wheel) group to use the su command to run commands as root or obtain a
root shell. Default home directory files for new users are copied from
/usr/share/skel. You can also create rules to allow or prevent user access in the
/etc/login.access file, as well as define user classes for accessing system
resources in the /etc/login.conf file.

Common commands to create and manage users include:


adduser (creates a user – defaults values are taken from /etc/adduser.conf)
adduser -C (creates the /etc/adduser.conf file with values you specify)
rmuser (removes a user)
pw useradd/userdel/usermod/lock/unlock (creates & manages users)
chpass (modifies settings for a user using the vi editor)
vipw (edits /etc/master.passwd using vi and rebuilds /etc/spwd.db)

Software and Daemons


Installing and managing packages on FreeBSD is just as easy as using the Red Hat or
Debian package managers on a Linux system. Instead of dnf or apt, you use the pkg
command:
pkg update (updates package list from online repository)
pkg search bash (searches online repository for bash packages)
pkg install bash (installs bash package from online repository)
pkg upgrade bash (upgrades bash to latest version)
pkg info bash (displays package details)
pkg info -l bash (displays package file contents)
pkg check bash (checks bash package content for missing/corrupted files)
pkg lock bash (prevents modification or removal of package)
pkg remove bash (removes bash package)
pkg clean (cleans up files in the package repository, /var/cache/pkg)
pkg autoremove (auto removes unneeded dependency packages)
pkg which /usr/local/bin/bash (displays package the bash file belongs to)
freebsd-update fetch (downloads latest version of FreeBSD)
freebsd-update install (installs latest version of FreeBSD)
- 279 -

After installing a daemon package, you must also configure it to start at system startup by
adding a line to the /etc/rc.conf file. For example, after installing the apache24
package (for the Apache Web server daemon), you could start it at system startup by
adding the apache24_enable="YES" line to /etc/rc.conf.

The configuration files for any daemons that you install are under /etc or
/usr/local/etc. For example, you’ll find the httpd.conf configuration file for
Apache in the /usr/local/etc/apache24 directory on FreeBSD. You can also
manage daemons using the same service command used in Linux systems prior to
Systemd:
service -e (displays daemons that are enabled and order they are started at boot)
service sshd stop/start/restart (stops/starts/restarts the SSH daemon)
service sshd onestart (starts SSH daemon if not listed in /etc/rc.conf)
service sshd extracommands (displays more options for the SSH daemon)

Performance
You can monitor the performance of your FreeBSD system using the same vmstat and
top commands you’ve used in Linux (the FreeBSD top command also lists ZFS
performance statistics), as well as monitor disk performance using the gstat command.
Similarly, there are many commands in FreeBSD to monitor network statistics, including:
netstat -w 1 -d (displays packet stats every 1 second)
netstat -na -f inet (displays active IPv4 connections)
netstat -na -f inet6 (displays active IPv6 connections)
netstat -m (displays tunable memory buffer information for IP stack)
sockstat -4 (displays IPv4 sockets)
sockstat -6 (displays IPv6 sockets)

Other Differences
To compile software from source in FreeBSD, you run the portsnap auto command
to download the source code for the ready-to-compile ports tree from the FreeBSD
repository to the /usr/ports/ directory and then use the appropriate make commands
to compile and install it on your system.

If you want to configure a firewall, there are three firewall systems to choose from
(https://docs.freebsd.org/en/books/handbook/), but the most common one is PF from
OpenBSD. You place your rules in /etc/pf.conf and use the pfctl command to
control the firewall. You can also use the Blacklist Daemon (blacklistd) to block
undesired connections (or too many connections). You can use blacklistctl to
control blacklistd, as well as list connection rules in /etc/blacklistd.conf.

FreeBSD jails are one of the earliest examples of operating system virtualization using
containers. To create a FreeBSD jail, you download a userland (filesystem) tarball from
- 280 -

the FreeBSD repository and extract it to a directory of your choice (e.g.


/jails/container1). Next, you add a paragraph to the /etc/jail.conf file
that configures the jail parameters (IP address, and so on.). Finally, you can start and
manage your jail using a wide variety of different commands, including:
service jail start container1 (starts the container1 jail)
service jail stop container1 (stops the container1 jail)
jls (views all jails running on the system)
jexec container1 command (executes a command in container1)
pkg -j container1 install apache24 (installs Apache in container1)

If you start the NFS file sharing daemons (installed by default) by adding the appropriate
entries to /etc/rc.conf, you can add lines to /etc/exports to share out
directories on your system. However, you can instead automatically share ZFS
filesystems using NFS. These filesystems are listed in /etc/zfs/exports. For
example, to share out the /usr/home directory using NFS, you could use the zfs
set sharenfs=on zroot/usr/home command.

FreeBSD uses the original System Log Daemon (syslogd) to log system events to log
files under /var using the entries in /etc/syslog.conf, as well as uses
newsyslog to rotate log files according to rules in /etc/newsyslog.conf.

NOTE: FreeBSD is primarily a server-focused operating system. While you can use
FreeBSD as a workstation by installing X.org and a desktop environment such as
GNOME, there is limited support for video card and laptop device hardware in FreeBSD
compared to Linux. Thus, Linux is a more popular choice for workstations.

You might also like