WP-CLI

This tutorial is based on CentOS 7, but many of the commands are applicable to other flavors of Linux.

Getting Started

WP-CLI, as one may gather from the name, is a command line interface for installing and managing instances of WordPress. It is not included with any standard Linux distros, so we will need to install it manually.

[user@localhost ~]$ yum install wp-cli

When using WP-CLI, it must be run either from a directory in which wordpress is already installed, or a directory in which one intends to install wordpress. Additionally one may use a modifier to specify what directory WP-CLI will be working with. Without the modifier WP-CLI will run commands in the current working directory. In the following example we will attempt to check the wordpress core version of a possible wordpress instance in the user home directory using the path modifier.

[user@localhost ~]$ pwd
/home/user
[user@localhost ~]$ wp core version --path='/home/user'
Error: This does not seem to be a WordPress installation.
Pass --path=`path/to/wordpress` or run `wp core download`.
[user@localhost ~]$

Notice the error generated by the fact that no instance of wordpress is present. The error does give us a solution however; it shows us how to download the wordpress core to a given directory. Let’s create a new folder in /var/www and download wordpress there.

[user@localhost ~]$ mkdir /var/www/wordpress-site
[user@localhost ~]$ cd /var/www/wordpress-site
[user@localhost ~]$ wp core download
Downloading WordPress 5.8.2 (en_US)...
md5 hash verified: 1c6bfc773fd0dac60b1fbf6fcbf3599e
Success: WordPress downloaded.
[user@localhost ~]$ ls
index.php        wp-blog-header.php    wp-includes        wp-settings.php
license.txt      wp-comments-post.php  wp-links-opml.php  wp-signup.php
readme.html      wp-config-sample.php  wp-load.php        wp-trackback.php
wp-activate.php  wp-content            wp-login.php       xmlrpc.php
wp-admin         wp-cron.php           wp-mail.php
[user@localhost wordpress-site]$

Now when we check the WordPress core version, some information will show up:

[user@localhost wordpress-site]$ wp core version
5.8.2
[user@localhost wordpress-site]$

Let’s drill down further into our new WordPress instance. We can look at the list of default plugins with the following command:

[user@localhost wordpress-site]$ wp plugin list
Error: 'wp-config.php' not found.
Either create one manually or use `wp config create`.
[user@localhost wordpress-site]$

Notice that we get an error because this instance of WordPress is not fully set up! If we run the suggested command “wp config create’ without any parameters, the new error will include a list of required parameters.

[user@localhost wordpress-site]$ wp config create
Error: Parameter errors:
 missing --dbname parameter (Set the database name.)
 missing --dbuser parameter (Set the database user.)
[user@localhost wordpress-site]$

While viewing error messages may not be the most direct way to learn code, seeing and understanding these errors now will make it less shocking if you encounter them in the wild. To move forward we will need to create a database for our new site.

[user@localhost wordpress-site]$ cd /var/lib/mysql
[user@localhost mysql]$ mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2782
Server version: 5.5.68-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> create database wpsite;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> create user 'wpsite'@'localhost' identified by 'p455w0rd';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> grant all privileges on wpsite.* to 'wpsite'@'localhost';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> flush privileges;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> exit
Bye
[user@localhost mysql]$

Now that we have a database, we can properly create our wp-config file. Notice that we have included additional information such as the database passowrd, the database hostname, the WordPress database prefix, and the locale. While some of these options may contain default values, negating the need for them to be explicitly set, they are included to demonstrate the format of the options. A list of locale codes can be found at https://translate.wordpress.org/.

[user@localhost mysql]$ cd /var/www/wordpress-site
[user@localhost wordpress-site]$ wp config create --dbname=wpsite --dbuser=wpsite --dbpass=p455w0rd --dbhost=localhost --dbprefix=wp_ --locale=en_US
Success: Generated 'wp-config.php' file.
[user@localhost wordpress-site]$

We can view the new file via the less command:

[user@localhost wordpress-site]$ less wp-config.php

<\\?php
/**
 * The base configuration for WordPress
 *
 * The wp-config.php creation script uses this file during the
 * installation. You don't have to use the web site, you can
 * copy this file to "wp-config.php" and fill in the values.
 *
 * This file contains the following configurations:
 *
 * * MySQL settings
 * * Secret keys
 * * Database table prefix
 * * ABSPATH
 *
 * @link https://codex.wordpress.org/Editing_wp-config.php
 *
 * @package WordPress
 */

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wpsite' );

/** MySQL database username */
define( 'DB_USER', 'wpsite' );

/** MySQL database password */
define( 'DB_PASSWORD', 'p455w0rd' );

/** MySQL hostname */
define( 'DB_HOST', 'localhost' );

/** Database Charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );

/** The Database Collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );

/**
 * Authentication Unique Keys and Salts.
 *
wp-config.php

We have one more critical task to perform, before WP-CLI will allow us to manage the install. We need to set a site title and an administrator user. If we run the command "wp core install" without any modifiers, we will again get an error with a list of required parameters:

[user@localhost wordpress-site]$ wp core install
Error: Parameter errors:
 missing --url parameter (The address of the new site.)
 missing --title parameter (The title of the new site.)
 missing --admin_user parameter (The name of the admin user.)
 missing --admin_email parameter (The email address for the admin user.)
[user@localhost wordpress-site]$

Running the command with properly included parameters yields success:

[user@localhost wordpress-site]$ wp core install --url=http://wordpress-site.com --title=Test --admin_user=admin --admin_password=4dm1np455w0rd --admin_email=admin@host.com --skip-email
Success: WordPress installed successfully.
[user@localhost wordpress-site]$

Plugins

Now that our site is fully set up, let's take a look at the installed plugins.

[user@localhost wordpress-site]$ wp plugin list
+---------+----------+--------+---------+
| name    | status   | update | version |
+---------+----------+--------+---------+
| akismet | inactive | none   | 4.2.1   |
| hello   | inactive | none   | 1.7.2   |
+---------+----------+--------+---------+
[user@localhost wordpress-site]$

To activate a plugin we can use the following command:

[user@localhost wordpress-site]$  wp plugin activate hello
Plugin 'hello' activated.
Success: Activated 1 of 1 plugins.
[user@localhost wordpress-site]$ wp plugin list
+---------+----------+--------+---------+
| name    | status   | update | version |
+---------+----------+--------+---------+
| akismet | inactive | none   | 4.2.1   |
| hello   | active   | none   | 1.7.2   |
+---------+----------+--------+---------+
[user@localhost wordpress-site]$

To deactivate a plugin we can modify the command as such:

[user@localhost wordpress-site]$  wp plugin deactivate hello
Plugin 'hello' deactivated.
Success: Deactivated 1 of 1 plugins.
[user@localhost wordpress-site]$ wp plugin list
+---------+----------+--------+---------+
| name    | status   | update | version |
+---------+----------+--------+---------+
| akismet | inactive | none   | 4.2.1   |
| hello   | inactive | none   | 1.7.2   |
+---------+----------+--------+---------+
[user@localhost wordpress-site]$

To remove a plugin use the following form:

[user@localhost wordpress-site]$  wp plugin delete hello
Success: Deleted 1 of 1 plugins.
[user@localhost wordpress-site]$ wp plugin list
+---------+----------+--------+---------+
| name    | status   | update | version |
+---------+----------+--------+---------+
| akismet | inactive | none   | 4.2.1   |
+---------+----------+--------+---------+
[user@localhost wordpress-site]$

Let's now search available plugins for something to install. We can do so with the following query:

[user@localhost wordpress-site]$ wp plugin search elementor
Success: Showing 10 of 1010 plugins.
+----------------------------------------------------------------------+-------------------------------------+--------+
| name                                                                 | slug                                | rating |
+----------------------------------------------------------------------+-------------------------------------+--------+
| Elementor Website Builder                                            | elementor                           | 94     |
| Essential Addons for Elementor                                       | essential-addons-for-elementor-lite | 98     |
| Elementor Header & Footer Builder                                | header-footer-elementor             | 98     |
| Premium Addons for Elementor                                         | premium-addons-for-elementor        | 98     |
| ElementsKit Elementor addons (Header & Footer Builder, Mega Menu | elementskit-lite                    | 96     |
|  Builder, Layout Library)                                            |                                     |        |
| Happy Addons for Elementor (Mega Menu, Post Grid, Woocommerce Produc | happy-elementor-addons              | 96     |
| t Grid, Table, Event Calendar, Slider Elementor Widget)              |                                     |        |
| Unlimited Elements For Elementor (Free Widgets, Addons, Templates)   | unlimited-elements-for-elementor    | 96     |
| Livemesh Addons for Elementor                                        | addons-for-elementor                | 96     |
| CoDesigner – The Best Elementor Addon to Customize WooCommerce       | woolementor                         | 88     |
| OoohBoi Steroids for Elementor                                       | ooohboi-steroids-for-elementor      | 98     |
+----------------------------------------------------------------------+-------------------------------------+--------+
[user@localhost wordpress-site]$

Let's install the main elementor plugin and then activate it.

[user@localhost wordpress-site]$ wp plugin install elementor
Installing Elementor Website Builder (3.5.0)
Downloading installation package from https://downloads.wordpress.org/plugin/elementor.3.5.0.zip...
Unpacking the package...
Installing the plugin...
Plugin installed successfully.
Success:  Installed 1 of 1 plugins.
[user@localhost wordpress-site]$ wp plugin activate elementor
Plugin 'elementor' activated.
Success: Activated 1 of 1 plugins.
[user@localhost wordpress-site]$  wp plugin list
+-----------+----------+--------+---------+
| name      | status   | update | version |
+-----------+----------+--------+---------+
| akismet   | inactive | none   | 4.2.1   |
| elementor | active   | none   | 3.5.0   |
| wordfence | inactive | none   | 7.5.7   |
+-----------+----------+--------+---------+
[user@localhost wordpress-site]$

We can view more information about the plugin we just installed using the get modifier:

[user@localhost wordpress-site]$ wp plugin get elementor
+-------------+-------------------------------------------------------------------------------------------------------+
| Field       | Value                                                                                                 |
+-------------+-------------------------------------------------------------------------------------------------------+
| name        | elementor                                                                                             |
| title       | Elementor                                                                                             |
| author      | Elementor.com                                                                                         |
| version     | 3.5.0                                                                                                 |
| description | The Elementor Website Builder has it all: drag and drop page builder, pixel perfect design, mobile re |
|             | sponsive editing, and more. Get started now!                                                          |
| status      | active                                                                                                |
+-------------+-------------------------------------------------------------------------------------------------------+
[user@localhost wordpress-site]$

Themes

The wp theme options can be used in much the same way the wp plugin options. Here we will look up Astra, the current most popular WordPress theme for 2021, install it, and then activate it:

[user@localhost wordpress-site]$ wp theme list
+-----------------+----------+--------+---------+
| name            | status   | update | version |
+-----------------+----------+--------+---------+
| twentynineteen  | inactive | none   | 2.1     |
| twentytwenty    | inactive | none   | 1.8     |
| twentytwentyone | active   | none   | 1.4     |
+-----------------+----------+--------+---------+
[user@localhost wordpress-site]$ wp theme search astra
Success: Showing 5 of 5 themes.
+------------------+-----------------+--------+
| name             | slug            | rating |
+------------------+-----------------+--------+
| Astra            | astra           | 98     |
| Astral           | astral          | 98     |
| Dark Mode for A. | dark-mode-for-a | 0      |
| PressBook News   | pressbook-news  | 100    |
| Blush            | blush           | 20     |
+------------------+-----------------+--------+
[user@localhost wordpress-site]$ wp theme install astra
Installing Astra (3.7.5)
Downloading installation package from https://downloads.wordpress.org/theme/astra.3.7.5.zip...
Unpacking the package...
Installing the theme...
Theme installed successfully.
Success: Installed 1 of 1 themes.
[user@localhost wordpress-site]$ wp theme activate astra
Success: Switched to 'Astra' theme.
[user@localhost wordpress-site]$ wp theme get astra
+----------------+----------------------------------------------------------------------------------------------------+
| Field          | Value                                                                                              |
+----------------+----------------------------------------------------------------------------------------------------+
| name           | Astra                                                                                              |
| title          | Astra                                                                                              |
| version        | 3.7.5                                                                                              |
| status         | active                                                                                             |
| parent_theme   |                                                                                                    |
| template_dir   | /var/www/wordpress-site/wp-content/themes/astra                                                    |
| stylesheet_dir | /var/www/wordpress-site/wp-content/themes/astra                                                    |
| template       | astra                                                                                              |
| stylesheet     | astra                                                                                              |
| screenshot     | screenshot.jpg                                                                                     |
| description    | Astra is fast, fully customizable & beautiful WordPress theme suitable for blog, personal port |
|                | folio, business website and WooCommerce storefront. It is very lightweight (less than 50KB on fron |
|                | tend) and offers unparalleled speed. Built with SEO in mind, Astra comes with Schema.org code inte |
|                | grated and is Native AMP ready so search engines will love your site. It offers special features a |
|                | nd templates so it works perfectly with all page builders like Elementor, Beaver Builder, Visual C |
|                | omposer, SiteOrigin, Divi, etc. Some of the other features: # WooCommerce Ready # Responsive # RTL |
|                |  & Translation Ready # Extendible with premium addons # Regularly updated # Designed, Develope |
|                | d, Maintained & Supported by Brainstorm Force. Looking for a perfect base theme? Look no furth |
|                | er. Astra is fast, fully customizable and WooCommerce ready theme that you can use for building an |
|                | y kind of website!                                                                                 |
| author         | Brainstorm Force                                                         |
| tags           | ["custom-menu","custom-logo","entertainment","one-column","two-columns","left-sidebar","e-commerce |
|                | ","right-sidebar","custom-colors","editor-style","featured-images","full-width-template","microfor |
|                | mats","post-formats","rtl-language-support","theme-options","threaded-comments","translation-ready |
|                | ","blog"]                                                                                          |
| theme_root     | /var/www/wordpress-site/wp-content/themes                                                          |
| theme_root_uri | http://wordpress-site.com/wp-content/themes                                                        |
+----------------+----------------------------------------------------------------------------------------------------+
[user@localhost wordpress-site]$ wp theme list
+-----------------+----------+--------+---------+
| name            | status   | update | version |
+-----------------+----------+--------+---------+
| astra           | active   | none   | 3.7.5   |
| twentynineteen  | inactive | none   | 2.1     |
| twentytwenty    | inactive | none   | 1.8     |
| twentytwentyone | inactive | none   | 1.4     |
+-----------------+----------+--------+---------+
[user@localhost wordpress-site]$

Notice the get modifier can be used with themes in the same way it is used with plugins.

Updates

The following command formats will work for updating the WordPress Core, Plugins, and Themes respectively:

[user@localhost wordpress-site]$ wp core update
Success: WordPress is up to date.
[user@localhost wordpress-site]$ wp plugin update elementor
Success: Plugin already updated.
[user@localhost wordpress-site]$ wp theme update astra
Success: Theme already updated.
[user@localhost wordpress-site]$

To check if there is an available WordPress core update, the check-update option maybe used:

[user@localhost wordpress-site]$ wp core check-update
Success: WordPress is at the latest version.
[user@localhost wordpress-site]$

Working with the Database

The "wp db" command may be used to perform database related tasks. For example the "export" sub-command can be used to save copies of the database as an sql file. After the sub-command the first option is the new file name with full path, which in my case is where I save db backups. The last option indicates the path to the WordPress instance whose database is being backed up.

[user@localhost wordpress-site]$ wp db export /var/lib/mysql/backups/wordpress-site.sql --path='/var/www/wordpress-site/'
Success: Exported to '/var/lib/mysql/backups/wordpress-site.sql'.
[user@localhost wordpress-site]$

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