5
\$\begingroup\$

The following program is comprised of three files and is aimed to be used as a Debian-Apache-MySQL-PHP-HTTPS version-agnostic environment bootstrapper.

It is aimed to be used on raw Debian systems (anything that doesn't come with the OS wasn't installed) and to establish web domain associated web applications.

The program

File 1

This file contains fundamental installation and/or configuration commands and comprised of two parts:

  • The first part is a cat heredocument for .profile aimed to declare some global modes, variables and functions ("global" as to effect all shell sessions) that from my experience are harmless although global.

  • The second part is a "sourcing" of .profile to ensure the variables will take effect in the very first shell session in which they are declared at and also after every booting of Debian.

File 1 code is as follows (clarifications available below the code block).

#!/bin/bash

cat <<-EOF >> "$HOME"/.profile
    set -x
    complete -r

    export war="/var/www/html"
    export dmp="phpminiadmin"

    export -f war ssr tmd # Create execution shortcuts to the following functions:

    war() {
        cd $war/
    }

    ssr() {
        chown -R www-data:www-data "$war"/
        chmod -R a-x,a=rX,u+w "$war"/
        systemctl restart apache*
        chmod -R 000 "$war"/"$dmp"/
    }
    tmd() {
        chmod -R a-x,a=rX,u+w "$war"/"$dmp"/
        echo "chmod -R 000 "$war"/"$dmp"/" | at now + 1 hours
    }
EOF

source "$HOME"/.profile 2>/dev/null

File 1 modes

  • The mode set -x means constant working in full debug mode
  • The mode complete -r means constant removal of messy output of programmable completion (by calling to functions, etc) common in full debug mode

File 1 variables

  • The war variable's value reflects a user's preferred Web Application Root directory
  • The dmp variable's value reflects a user's preferred Database Management Program (such as phpMiniAdmin)

File 1 functions

  • The function war means something like "navigate to Web Application Root easy and fast"
  • The function ssr means Secured Server Restart:; that is, restart web server with repeating basic security directives that might have been mistakenly changed, as well as allowing temporary management of MySQL database by a database management program
  • The function tmd means Temporarily Manage Database and is useful after DB-manager security lock by ssr()

File 2

This file contains basic application installation and/or configuration commands.

#!/bin/bash

apt update -y
apt upgrades ufw sshguard unattended-upgrades wget curl git zip unzip tree -y
ufw --force enable
ufw allow 22,25,80,443

apt install lamp-server^ python-certbot-apache
curl -sS https://getcomposer.org/installer -o composer-setup.php
php composer-setup.php --install-dir=/usr/local/bin --filename=composer
a2enmod http2 deflate expires

File 3

This file uses to create an Apache virtual host and associated files.

This file should be executed only after creating a CMS-contexed database on top of MySQL program, based on a single pattern of data, the web domain which also uses as the name for:

  • Web application DB user
  • Web application DB instance
  • Web application directory (explained in following chapter)

The file:

#!/bin/bash

read -p "Have you created db credentials already?" yn
case $yn in
    [Yy]* ) break;;
    [Nn]* ) exit;;
    * ) echo "Please create db credentials and then comeback;";;
esac

function read_and_verify  {
    read -p "$1:" tmp1
    read -p "$2:" tmp2
    if [ "$tmp1" != "$tmp2" ]; then
        echo "Values unmatched. Please try again."; return 2
    else
        read "$1" <<< "$tmp1"
    fi
}

read_and_verify domain "Please enter the domain of your web application twice" 
read_and_verify dbrootp "Please enter the app DB root password twice" 
read_and_verify dbuserp "Please enter the app DB user password twice"

cat <<-EOF > /etc/apache2/sites-available/$domain_2.conf
    <VirtualHost *:80>
        ServerAdmin admin@"$domain_2"
        ServerName ${domain_2}
        ServerAlias www.${domain_2}
        DocumentRoot $war/${domain_2}
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
    </VirtualHost>
EOF

ln -sf /etc/apache2/sites-available/"$domain_2".conf /etc/apache2/sites-enabled/
certbot --apache -d "$domain_2" -d www."$domain_2"

Possible appendix - install Drupal

Because installing a composer-driven drupal project with a local Drush was the original aim of this program, I append the following:

composer create-project drupal-composer/drupal-project "$war"/"$domain"
cp "$drt/$domain"/wp-config-sample.php "$war/$domain"/wp-config.php
drush --root="$war" --uri="$domain" pm install redirect token metatag draggableviews
drush --root="$war" --uri="$domain" en language content_translation redirect token metatag draggableviews

Notes

  • The program doesn't cover backups because I personally believe that every hosting provider and that includes dedicating hosting, whether semi (commercial) or full (private), should include a reliable, most preferably communally maintained automatic daily backup mechanism, alongside a manual backup standardized routine procedure (say doing manual backup each quarter or half a year).
  • The program doesn't cover web application upgrades because I personally believe that every CMS should include an upgrade mechanism of its own by default, without bestowing upon users the need do maximal upgrade automation from backend.

My question

How would you revise this Debian-Apache-MySQL-PHP-HTTPS version-agnostic environment bootstrapper?

\$\endgroup\$
6
\$\begingroup\$

Firstly, it's wrong to write a Bash shebang at the start of .profile, for two reasons:

  1. It's not an executable script, but intended to be sourced from other shell environments
  2. .profile is sourced by the user's shell, which need not be Bash.

The bashisms should be rewritten if possible; those that can't (such as complete -r) should be migrated to .bash_profile.


These look a little odd:

    chmod -R a-x,a=rX,u+w "$war"/"$dmp"/
    echo "chmod -R 000 "$war"/"$dmp"/" | at now + 1 hours

In the first, there's no need to leave double-quoted string for the / characters. In the second, we definitely don't want argument splitting within the variables. I'd rewrite those two lines as:

    chmod -R a-x,a=rX,u+w "$war/$dmp/"
    echo "chmod -R 000 $war/$dmp/" | at now + 1 hours

curl -sS https://getcomposer.org/installer -o composer-setup.php
php composer-setup.php --install-dir=/usr/local/bin --filename=composer

Firstly, we don't check that curl succeeded before depending on its results. Consider set -e for that.

Secondly, and more critically, there's no verification that what we downloaded is safe to execute. I'd expect at least a simple SHA checksum before executing that PHP program.


read -p "Have you created db credentials already?" yn
case $yn in
    [Yy]* ) break;;
    [Nn]* ) exit;;
    * ) echo "Please create db credentials and then comeback;";;
esac

Firstly, a simple spelling mistake - "come back" has lost its space. Secondly, it seems wrong to exit with no message when n is entered, but to assume n when an invalid reply is given.

I'd expect something like:

read -p "Have you created db credentials already? [y/N] " yn
case "$yn" in
    [Yy]* ) true ;;
    [Nn]* ) echo >&2 "Please create db credentials and repeat this command;"; exit ;;
    * ) echo >&2 "Please respond with Y or N."; exit 1 ;;
esac

Have you got the prompts right here?

function read_and_verify  {
    read -p "$1:" tmp1
    read -p "$2:" tmp2
    if [ "$tmp1" != "$tmp2" ]; then
        echo "Values unmatched. Please try again."; return 2
    else
        read "$1" <<< "$tmp1"
    fi
}

read_and_verify domain "Please enter the domain of your web application twice" 
read_and_verify dbrootp "Please enter the app DB root password twice" 
read_and_verify dbuserp "Please enter the app DB user password twice"

The first read, where the user is prompted with the variable name is unexpected; that's not in the user's world model, and the second prompt then asks for the same thing (in different words) "twice":

domain:foo
Please enter the domain of your web application twice:foo

I think the prompt should be formed from $2 each time:

read_and_verify()  {
    read -p "Please enter $2: " tmp1
    read -p "Please enter $2 again to confirm: " tmp2
    if [ "$tmp1" != "$tmp2" ]; then
        echo >&2 "Values unmatched. Please try again."; return 2
    fi

    declare "$1=$tmp1"
}

read_and_verify domain "the domain of your web application" 
read_and_verify dbrootp "the app DB root password" 
read_and_verify dbuserp "the app DB user password"
Please enter the domain of your web application: foo
Please enter the domain of your web application again to confirm: foo

All that said, requiring user to re-type (or to copy-paste) these values is annoying, and no more effective than re-printing them and asking for confirmation:

read_and_verify()  {
    read -p "Please enter $2: " "$1"
    read -p "Please confirm $2: ${!1} [y/N] " yn
    case "$yn" in
        [Yy]*) true ;;
        *) echo >&2 "Cancelled." return 2
    esac
}
Please enter the domain of your web application: foo
Please confirm the domain of your web application: foo [y/N] y

In fact, I might shuffle things around, to gather all the inputs, and present a single summary for confirmation:

ask_yn() {
    while read -p "$* [y/n] " -n1
    do
       case "$REPLY" in
            ?) echo ;;&
            [Yy]) return 0 ;;
            [Nn]) return 1 ;;
       esac
    done
}

until
    read -p 'Please enter the domain of your web application: ' domain
    read -p 'Please enter the app DB root password: ' dbrootp
    read -p 'Please enter the app DB user password: ' dbuserp
    printf '%s: "%s"\n' \
           'Application domain' "$domain" \
           'Database root password' "$dbrootp" \
           'Database user password' "$dbuserp"
    ask_yn 'Are these details correct?'
do
    true
done

There's one case where there's value in asking for the same input twice, and that's when the user is setting an unseen value (such as when using the passwd program). The reasoning there is that a mistake can't be detected or easily corrected after the fact (because you're locked out of your account until/unless you can re-create the same typo).

Here, we're just storing values, and if we find the values are wrong, we can easily fix them up. (In fact, we could even test the passwords work before accepting the settings, by making a simple connection to the database).

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Hi, thanks; I never meant to put Bash shebang at the start of .profile; it was just for copy-pasting the content as script. Maybe I should edit to state that so I won't be further interpreted this way. Regarding the prompts; it seems to me from echo test for all three prompt comparisons that it works --- I do get example for them. Regarding your last sentence in the answer --- I misunderstood what copy-pasting is not more effective than printing? Please example this, \$\endgroup\$ – JohnDoea Nov 27 '19 at 14:30
  • \$\begingroup\$ I think I understand everything besides "unexpected"; do you mean you found the phrasing bad and that in any case it's just better to re-print the value before the user and confirm? \$\endgroup\$ – JohnDoea Nov 27 '19 at 21:18
  • \$\begingroup\$ The "unexpected" part is as a user, as shown in the exchange I show immediately after that sentence. The first prompt is domain (the shell variable name), rather than something in the user's world model, and the second prompt asks for the same thing to be entered twice (some users will interpret that to mean they should enter example.com example.com there. The alternatives I show are intended to be clearer/easier for the user. \$\endgroup\$ – Toby Speight Nov 28 '19 at 8:31
  • \$\begingroup\$ I've expanded that description, and also suggested a single confirmation using a do/until loop. I hope that's helpful. \$\endgroup\$ – Toby Speight Nov 28 '19 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.