Raspberry Pi : WiFi in the CLI

This tutorial is based on a Raspberry Pi 3B running Raspbian Buster. WiFi is one of three built in network interfaces on the Pi board.

ifconfig

While ifconfig is considered deprecated, it is still included with the Buster release of Raspbian. In Debian Buster, it has been replaced by the ip command. When issued by itself, it will display information about the available network interfaces. It does not require root privileges.

pi@raspberrypi:~$ ifconfig
eth0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
	ether b8:27:eb:00:4a:1c txqueuelen 1000 (Ethernet)
	RX packets 0 bytes 0 (0.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 0 bytes 0 (0.0 B)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
	inet 127.0.0.1 netmask 255.0.0.0
	inet6 ::1 prefixlen 128 scopeid 0x10
	loop txqueuelen 1000 (Local Loopback)
	RX packets 0 bytes 0 (0.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 0 bytes 0 (0.0 B)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 

wlan0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
	ether b8:27:eb:55:1f:49 txqueuelen 1000 (Ethernet)
	RX packets 0 bytes 0 (0.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 0 bytes 0 (0.0 B)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
pi@raspberrypi:~$

To view only output relating to the wireless interface we can specificy wlan0 in the command string:

pi@raspberrypi:~$ ifconfig wlan0
wlan0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
	ether b8:27:eb:55:1f:49 txqueuelen 1000 (Ethernet)
	RX packets 0 bytes 0 (0.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 0 bytes 0 (0.0 B)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
pi@raspberrypi:~$

As you can see from the output, the wireless network interface, identified as wlan0, is up but not associated with a network. Ifconfig can also control the link state of the interface, either up or down. To bring the wlan0 interface down the following command can be issued:

pi@raspberrypi:~$ sudo ifconfig wlan0 down
pi@raspberrypi:~$ ifconfig
eth0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
	ether b8:27:eb:00:4a:1c txqueuelen 1000 (Ethernet)
	RX packets 0 bytes 0 (0.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 0 bytes 0 (0.0 B)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
	inet 127.0.0.1 netmask 255.0.0.0
	inet6 ::1 prefixlen 128 scopeid 0x10
	loop txqueuelen 1000 (Local Loopback)
	RX packets 0 bytes 0 (0.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 0 bytes 0 (0.0 B)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
pi@raspberrypi:~$

By executing ifconfig again, you can see that wlan0 is no longer listed in the output. To bring the interface back up, up arrow through the bash history and change the down to an up:

pi@raspberrypi:~$ sudo ifconfig wlan0 up
pi@raspberrypi:~$ ifconfig wlan0
wlan0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
	ether b8:27:eb:55:1f:49 txqueuelen 1000 (Ethernet)
	RX packets 0 bytes 0 (0.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 0 bytes 0 (0.0 B)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
pi@raspberrypi:~$

iproute2

As ifconfig is now considered deprecated, iproute2 is the preferred tool for managing network interfaces. It is a collection of utilities that replace a number of commands of which ifconfig is one. To view information about the available network interfaces, the ip command can be used with the address modifier. In the following example the wlan0 link state is currently down.

pi@raspberrypi:~$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether b8:27:eb:00:4a:1c brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether b8:27:eb:55:1f:49 brd ff:ff:ff:ff:ff:ff
pi@raspberrypi:~$

To view only information about a specific interface we can utilize the show modifier:

pi@raspberrypi:~$ ip address show eth0
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether b8:27:eb:00:4a:1c brd ff:ff:ff:ff:ff:ff
pi@raspberrypi:~$

In the eth0 example, we can see that there is no network cable plugged in (no carrier), Broadcast and Multicast are enabled, and the link state is up. To bring the wireless interface up, we can use the link set modifiers with the ip command.

pi@raspberrypi:~$ sudo ip link set wlan0 up
pi@raspberrypi:~$ ip address show wlan0
3: wlan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether b8:27:eb:55:1f:49 brd ff:ff:ff:ff:ff:ff

As with ifconfig, we can bring the interface back down again by specifying “down” in the command.

iwlist

To view available wireless networks use the iwlist command. The output can be rather verbose, so pipe the results through grep to clean it up:

pi@raspberrypi:~$ sudo iwlist wlan0 scan | grep ESSID
                  ESSID: "garden"
                  ESSID: "cafe"
                  ESSID: "office"
pi@raspberrypi:~$

The resulting output will contain a list of available wireless networks by SSID. For this example the network is called “garden”. If you are connecting to a network that does not broadcast its SSID, you can still connect with an additional step discussed later in this tutorial.

Raspbian uses a program called WPA_Supplicant to manage credentials for wireless network connections. To view the current configuration use the less command:

pi@raspberrypi:~$ less /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant Group=netdev
update_config=1
country=US

/etc/wpa_supplicant/wpa_supplicant.conf (END)

wpa_passphrase

To add a network configuration, use the wpa_passphrase tool. The wpa_passphrase command takes the ssid and password as its arguments, and generates a configuration entry. To add the configuration to the wpa_supplicant.conf file, use an output redirection. If you are logged in as root, you can use the following command:

pi@raspberrypi:~$ wpa_passphrase garden turnip22 >> /etc/wpa_supplicant/wpa_supplicant.conf

If you are logged in as the default user, pi, things get complicated. Because the conf file was created at the time of installation, it is owned by root. If you were to run the command using sudo, the sudo privileges would only apply to the wpa_passphrase command itself and not to the redirect.

pi@raspberrypi:~$ sudo wpa_passphrase garden turnip22 >> /etc/wpa_supplicant/wpa_supplicant.conf
bash: /etc/wpa_supplicant/wpa_supplicant.conf: Permission denied
pi@raspberrypi:~$

Because the user pi does not have write privileges for the file, you will get a permission denied error. To get around this we can pass the entire command to bash as a string. Use sudo to run bash with root privileges, and bash will execute the contents of the string:

pi@raspberrypi:~$ sudo bash -c “wpa_passphrase garden turnip22 >> /etc/wpa_supplicant/wpa_supplicant.conf”
pi@raspberrypi:~$

If you look again at the conf file, a network configuration has been added. It contains the ssid, the passkey in plain text, and it’s encrypted equivalent. The hash (#) character in front of the plain text key indicates that the line is being treated as a comment and is therefore not interpreted.

pi@raspberrypi:~$ less /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant Group=netdev
update_config=1
country=US

network={
        ssid="garden"
        #psk="turnip22"
        psk=68a1949037d37d3761579259f6665bcec21c9ac163fb8c1c91b602509b36d873039
}

/etc/wpa_supplicant/wpa_supplicant.conf (END)

To begin editing delete the plain text version of the passkey. If you are connecting to a network whose SSID is hidden, add the following line after the SSID in the network configuration:

scan_ssid=1

If you intend to connect to multiple networks, you can enforce a preferred connection order by setting a priority value. The default value is 0. The network with the highest positive integer value will connect first.

pi@raspberrypi:~$ less /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant Group=netdev
update_config=1
country=US

network={
        ssid="garden"
        scan_ssid=1
        psk=68a1949037d37d3761579259f6665bcec21c9ac163fb8c1c91b602509b36d873039
        priority=1
}

/etc/wpa_supplicant/wpa_supplicant.conf (END)

To get the Pi to connect to the network without rebooting, run daemon-reload and then restart the dhcpcd service.

pi@raspberrypi:~$ sudo systemctl daemon-reload
pi@raspberrypi:~$ sudo systemctl restart dhcpcd

To verify connectivity use the ifconfig command again:

pi@raspberrypi:~$ ifconfig wlan0
wlan0: flags=4163<UP,BROADCAST,MULTICAST> mtu 1500
        inet 10.10.1.2 netmask 255.255.255.0 broadcast 10.10.1.255
        inet6 fe80::7c1a:d5f5:de23:3a3 prefixlen 64 scopeid 0x20<link>
	ether b8:27:eb:55:1f:49 txqueuelen 1000 (Ethernet)
	RX packets 1 bytes 576 (576.0 B)
	RX errors 0 dropped 0 overruns 0 frame 0
	TX packets 24 bytes 3876 (3.7 KiB)
	TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
pi@raspberrypi:~$

Linux : Bash Scripting

Scripting is a great way to both solve complex problems, and make some of the more routine tasks of maintaining a linux system less inconvenient. A Shell script is an interpreted program, written for a Shell, using the allowed functions and syntax of that shell. In our case we will be working with the Bash Shell. To start let’s create a directory for our script, and then a new file.

[user@localhost ~]$ mkdir scripting
[user@localhost ~]$ cd scripting
[user@localhost ~]$ touch test.sh
[user@localhost ~]$

We can then open the new file up in vi and immediately ad this as the first line:

#!/bin/bash

This line indicates that this script is meant to be interpreted by the Bash Shell. Next lets get the script to do something. Lets get it to output “Hello World.”

#!/bin/bash
echo "Hello World."

While this program now contains the minimum requirements to run as a Bash Script, we cannot run it yet without giving the file executable permission. We will do that with the chmod command as thus:

[user@localhost ~]$ chmod +x test.sh
[user@localhost ~]$

Once that is done we may execute the program! To execute a script from the present working directory we will need to place a ./ in front of the script name to signify the current directory. If running a script from a different directory one can simply use the path. After hitting enter the program will run as instructed.

[user@localhost ~]$ ./test.sh
Hello World.
[user@localhost ~]$

Congratulations! You have written your first shell script, and become part of a larger tradition of code learning!

Adding On

After you get over the initial excitement of echoing text to the screen, you realize your script hasn’t actually done anything except, echo text to the screen. Suppose we’d like to have our script do more than parrot a static string? Let’s expand it by having it tell us which day of the week it is.

To get this information we can use the date function. The date function can be used alone or with modifiers.

[user@localhost ~]$ date
Sat Oct 10 01:23:46 EDT 2020
[user@localhost ~]$

To display only the day of the week, we can use the %A modifier.

[user@localhost ~]$ date +%A
Saturday
[user@localhost ~]$

We can utilize the date function in our script, by assigning the output of the command to a variable, and then adding the variable to an echo statement.

#!/bin/bash
day=$(date +%A)
echo "Hello World."
echo "Today is $day."

If we now run our script, we will see the current day of the week.

[user@localhost ~]$ ./test.sh
Hello World.
Today is Saturday.
[user@localhost ~]$

User Input

We can make our script interactive, by using the read command to assign user input to a variable. In our case the variable is called name:

#!/bin/bash
day=$(date +%A)
echo "Enter your name:"
read name
echo "Hello $name."
echo "Today is $day."

If you run the script again, you will see that it pauses to allow a response to be typed in:

[user@localhost ~]$ ./test.sh
Enter your name:
_

Typing your name and hitting enter causes the script to continue its execution:

[user@localhost ~]$ ./test.sh
Enter your name:
Bentley
Hello Bentley.
Today is Saturday.
[user@localhost ~]$

Getting Practical

Suppose you are running a web development server, with a small population of virtual hosts. You could backup up your sites by issuing a zip command for each one, or you could write a script to do this as a batch process. Before we start writing a script, let’s first consider the tedious method:

[user@localhost ~]$ zip -r /var/www/backups/client.domain.com-2020-10-13-21-32.zip /var/www/client.domain.com/

Running this command will accomplish the task of creating a zip archive of the virtual host directory client.domain.com, but the command will need to be reissued for every site. Additionally both the site directory and the time stamp will need to be typed manually each time.

What our script needs to do is identify all virtual host directories, and run a zip command for each one, inserting a current time stamp into the name of each archive file, while saving to our backups location. To accomplish the first of those tasks, we can modify the ls command to display only certain folders in our /var/www directory:

[user@localhost ~]$ ls /var/www/
backups beagle.domain.com cgi-bin client.domain.com gibson.domain.com html
[user@localhost ~]$

If we use the ls command unmodified we see every directory, including our three virtual hosts. We can use a regular expression to display only directories whose name contains .domain.com and the -d flag to show those directory names themselves, rather than their file contents.

[user@localhost ~]$ ls -d /var/www/*.domain.com
/var/www/beagle.domain.com /var/www/client.domain.com /var/www/gibson.domain.com
[user@localhost ~]$

While the output from the command contains only virtual host directories, it also includes the full path, which we won’t need for naming our archive. To get a list of directory names only, we can pipe our ls output through the xargs command:

[user@localhost ~]$ ls -d /var/www/*.domain.com | xargs -n 1 basename
beagle.domain.com
client.domain.com
gibson.domain.com
[user@localhost ~]$

We can start our script by assigning the output from our modified ls command to an array:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))

We can then use a for loop to do an action for each name in the array. For testing we will have our script print each virtual host name in order:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))
for i in "${virtualHosts[@]}"
do
echo $i
done

If you were to run the script as it is, you would get an output identical to the output of the modified ls command directly, except that each line represents an iteration of the for loop:

[user@localhost ~]$ ./backupScript.sh
beagle.domain.com
client.domain.com
gibson.domain.com
[user@localhost ~]$

To get our time stamp we can modify the date command:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))
for i in "${virtualHosts[@]}"
do
timeStamp=$(date +%Y-%m-%d-%H-%M)
echo $i $timeStamp
done
[user@localhost ~]$ ./backupScript.sh
beagle.domain.com 2020-10-13-22-49
client.domain.com 2020-10-13-22-49
gibson.domain.com 2020-10-13-22-49
[user@localhost ~]$

We can put it all together by placing a zip command in our for loop, referencing our variables in appropriate spaces:

#!/bin/sh
virtualHosts=( $(ls -d /var/www/*.domain.com | xargs -n 1 basename))
for i in "${virtualHosts[@]}"
do
timeStamp=$(date +%Y-%m-%d-%H-%M)
zip -r /var/www/backups/$i-$timeStamp.zip /var/www/$i
done

If we run the script now, three zip archives should appear in our backups directory:

[user@localhost ~]$ ./backupScript.sh
..
[user@localhost ~]$ ls /var/www/backups
beagle.domain.com-2020-10-13-23-01.zip client.domain.com-2020-10-13-23-01.zip gibson.domain.com-2020-10-13-23-01.zip
[user@localhost ~]$

Linux : Scheduling Tasks

Often times one may choose to run a command line process or script at a specific time and date. Two commands that one might consider for this purpose are At and Crontab respectively, with the latter being the more complex of the two.

At

At is a relatively straightforward affair, that allows one to execute one or more commands on a single date and time. Many systems come with At installed, but some, such as Raspbian, a flavour of Debian designed to run on the Raspberry Pi, do not. To begin enter a command as thus:

>at 5:00 PM Tue

This will then put you into an At prompt. Enter a command at the prompt. When you are finished typing the first command hit enter. If you wish to type another command do so now. If not type control+d to finish. It will add a to the last line signifying “End of Task”.

At>touch /home/admin/file.txt
At>
job 1 at 2017-02-14 17:00

In the example a new empty file named “file.txt” will be created in the /home/admin directory at 5:00 PM on Tuesday. There is also a way to use At without entering its command prompt. You can use At as a single line command by piping the output from an echo statement through it.

>echo “touch /home/admin/file2.txt” | at 4:20 PM

Crontab

Like the At command, Crontab allows one to dictate when a process or script is executed, but rather than simply running once, it runs repeatedly at a specified interval. This is particularly useful for maintenance tasks such as running backups, or monitoring tasks such as tracking system load averages.

When Crontab is invoked for the first time, a new crontab file is created and opened, for the current user, in the systems default text editor. Each line is separate scheduled event, with timing based on 6 values in order: Minute (0-59), Hour (0-23), Day of the Month (1-31), Month of the Year (1-12), day of the Week (1-7), and Year (1900+) respectively. In the case of Day of the Week it is important to note that the week in this case starts on Monday. To invoke crontab, use the following command:

>crontab -e

To run a backup script once every Sunday at 2am one might ad a line as follows:

0 2 * * 7 * bash /home/admin/scripts/backup.sh

Both At and Crontab can be configured in many more complicated and useful ways. To learn more check out their Man pages.

CentOS wget

WGET is an incredibly useful command which allows one to transfer files from a remote host. In its unmodified form, wget will only return files with public permissions. Any files that would require authentication for access will be ignored.

[user@localhost ~]$ wget http://host.net/Turnip.jpg
--2021-01-21 20:05:35--  http://host.net/Turnip.jpg
Resolving host.net (host.net)... FE80::abcd:abcd:abcd:abcd, 169.254.10.27
Connecting to host.net (host.net)|FE80::abcd:abcd:abcd:abcd... connected.
HTTP request sent, awaiting response... 200 OK
Length: 116772 (114K) [image/jpeg]
Saving to: ‘Turnip.jpg’

100%[======================================>] 116,772     --.-K/s   in 0.06s

2021-01-21 20:05:35 (1.77 MB/s) - ‘Turnip.jpg’ saved [116772/116772]

[user@localhost ~]$

In this example wget is retrieving the publicly visible file turnip.jpg from host.net, saving it to whatever directory this sessions happens to be working in. In order to have access to all files we will need to incorporte some sort of authentication method. The following example uses wget in conjuction with ftp.

[user@localhost ~]$ wget ftp://user:password@host.net/nonPublicFile.txt

While this method does work, it is considered bad practice, as the password will be saved as clear text in your command-line history.

[user@localhost ~]$ wget --ask-password ftp://user@host.net/nonPublicFile.txt

This use of the command is much more secure, as it prompts the user to enter the password at the time of execution.

Moving beyond downloading single files where we explicitly know the full name, we can use wildcards such as the * (asterisk) to modify the path in much the same way that they can be applied when working with local files.

[user@localhost ~]$ wget host.net/directory/*

In this case, one will download all publicly visible files that reside in the given directory. wget also has a number of its own modifiers for these purposes.

[user@localhost ~]$ wget -r host.net/directory/

In this case the -r flag indicates that wget should retrieve all publicly visible files within the given directory recursively. While this functionality provides some improvement upon its preceding method, it is still limitted to 5 levels of recursion.

[user@localhost ~]$ wget -m host.net/directory/

In this case the -m flag indicates mirror, meaning that wget will get all publicly visible files in the process of mirroring the directory/file structure of the given path.

An interesting note on the behavior of the -m flag is that while it will not download any files from levels above the specified path, it will still replicate the full folder structure. Take the following example.

[user@localhost ~]$ wget -m host.net/htdocs/images

In this case you will still only get a mirror of files and directories inside the images directory, but the folder that shows up in your local directory will actually be named host.net in which an htdocs directory will live, which holds a mirror of the targeted directory. If there are other directories or files in the htdocs directory on the remote host, they will be ignored.

CentOS MYSQL : Exporting and Importing

The default file location for MYSQL in CentOS is /var/lib/mysql. The following tutorial will assume that you are working from this directory.

Export a database using the following command:

[user@localhost ~]$ mysqldump -u root -p dbname > file.sql

Import a file to an existing mysql database using the following command:

[user@localhost ~]$ mysql -u root -p dbname < file.sql

In both cases you will be prompted for the root password you established when setting up the mysql service.

CentOS 6.5 WordPress Permissions

When you first create an instance of WordPress on your own server, you may run into issues relating to file permission and ownership. When you attempt to add media, install plugins, or update the core, you will be brought to a screen requesting the ftp credentials for your host.

A common terrible advice solution for this issue would be to set your folder permissions to 777. This will in fact solve the problem, but it will also create a security hole large enough to drive a dump truck through. I recommend never doing this.

The better way to deal with this is through both permissions and ownership. The ideal permissions setting for your wp-content directory is 655. This can be accomplished through the following command:

#chmod -R 655 /var/www/html/wp-content

Parsing this out: the commend is chmod or “change modify”, the -R flag tells the command to act recursively, the 655 is the permission level, and the last part is of course the directory to which to the command is being applied.

The next step is to change ownership. Note this will not affect your ability to edit these files, as root will always have permissions, but it will allow wordpress to do its job. Since we want to be able to update the wordpress core itself in addition to media, plugins, and themes, we will apply this change to the root directory of the site.

#chown -R apache /var/www/html/

Now when you go back into the backend of your site you will be able to do all of your upload and installation tasks without a problem.

CentOS 6.5 and WordPress Tutorial