6
\$\begingroup\$

Simple bash script to create apache2 virtualhost for localhost. Can be used for public subdomains on developer server with changes - "replace .localhost".

Tested on Ubuntu, but should work where dependency met.

Not for production, for development purposes only.

Use zenity for gui and pkexec for root permissions, so can be run without terminal, however some terminal output persist.

/etc/hosts file looks like: (so allowing add any subdomain)

127.0.0.1 localhost *.localhost

Note the wildcard, script doesn't create domains.

I'm not very experienced at bash scripting. Review and improve security, compatibility, maybe also some features with domains.

It's also on GitHub:(Updated) https://github.com/LeonidMew/CreateVirtualHost

#!/bin/bash
WEBROOT="/home/leonid/Web/" # root folder where subfolders for virtualhosts created
APACHEHOST="/etc/apache2/sites-available/050-" # prefix for virtualhost config file
A2ENSITE="050-" # short prefix for virtualhost config file
TMPHOST="/tmp/a2host-" # tmp prefix for virtualhost config while editing or rejecting
if ((`which zenity|wc -w` == 0)) # check dependency
then
 echo "Error: zenity not installed."
 exit
fi
if [ "$USER" == "root" ]
then
 zenity --error --text="You should not run this script as root but as user going to edit web files."
 exit
fi
HOST=`zenity --forms --add-entry=Name --text='Create virtualhost (= Folder name,case sensitive)'`
words=$( wc -w <<<"$HOST" )
if (($words == "0" || $words > 1)) # this not check for fully qualified sub domain name. ".localhost" added
then
 zenity --error --text="More then one word for sub domain or empty"
 exit
fi
HOSTFILE="$APACHEHOST$HOST"
HOSTFILE=$HOSTFILE".conf" # apache virtualhost config file
DIR="$WEBROOT$HOST" # folder used as document root for virtualhost
# virtualhost template 
cat >$TMPHOST$HOST <<EOF
<VirtualHost *:80>
 ServerAdmin webmaster@localhost
 DocumentRoot $DIR
 ServerName $HOST.localhost
 ServerAlias $HOST.localhost
 <Directory "$DIR">
 AllowOverride All
 Require local
 </Directory>
 # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
 # error, crit, alert, emerg.
 LogLevel warn
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
EOF
# edit virtualhost config
TEXT=`zenity --text-info --filename=$TMPHOST$HOST --editable`
words=$( wc -w <<<"$TEXT" )
if (($words == 0))
then
 echo "Cancel"
 rm $TMPHOST$HOST
 exit
fi
echo "$TEXT" > $TMPHOST$HOST
A2ENSITE=$A2A2ENSITE$HOST".conf" # params for a2ensite
echo "execute root tools with pkexec to create virtualhost"
[ -d "$DIR" ] || mkdir -p "$DIR"
pkexec /bin/bash <<EOF
chgrp www-data "$DIR"
chmod u=rwX,g=rX,o= "$DIR"
mv $TMPHOST$HOST $HOSTFILE
chown root:root $HOSTFILE
chmod u=rw,g=r,o=r $HOSTFILE
a2ensite $A2ENSITE
EOF
asked Feb 28, 2019 at 17:24
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

Notes:

  • Quote your variables. Ref Security implications of forgetting to quote a variable in bash/POSIX shells
  • Don't use ALLCAPS varnames. It's too easy to overwrite important shell variables like PATH.
  • A2ENSITE=$A2A2ENSITE$HOST".conf" -- I don't see the A2A2ENSITE variable anywhere
    • this is a perhaps a corollary of the ALLCAPS vars problem: they can be hard to read.
  • For Command Substitution, don't use backticks, use $( ... ). That syntax is (IMO) easier to read, and there are other advantages, such as nestability.
  • if ((`which zenity|wc -w` == 0)) -- use the bash builtin type command to see if there is a zenity command available: type -p zenity will return an unsuccessful exit status if there's no zenity in your path:

    if ! type -p zenity >/dev/null
    

    Although I don't really see the need for zenity. It would be super frustrating for the user who doesn't have it, being prevented from using your script. And the technical user who would be comfortable installing it is the type of user who doesn't need the bells and whistles, IMO.

  • To check if a string is empty, you don't need to call out to wc.

    Not this:

    TEXT=`zenity --text-info --filename=$TMPHOST$HOST --editable`
    words=$( wc -w <<<"$TEXT" )
    if (($words == 0))
    

    but this:

    text=$(zenity --text-info --filename="$filename" --editable)
    if [ -z "$text" ] # cancel if empty
    
  • use mktemp for temp files

    tmphost=$(mktemp)
    

    And just use "$tmphost" instead of $TMPHOST$HOST

  • you can tell bash to automatically delete the temp file when it exits:

    trap "rm $tmphost" EXIT
    
  • validate user input for host: a case statement might make sense here:

    host=$(zenity --forms --add-entry=Name --text='Create virtualhost (= Folder name,case sensitive)')
    case "$host" in
     "") zenity --error --text="Bad input: empty"; exit 1 ;;
     *"*"*) zenity --error --text="Bad input: wildcard"; exit 1 ;;
     *[[:space:]]*) zenity --error --text="Bad input: whitespace"; exit 1 ;;
    esac
    
  • I applaud your use of here-documents

  • use if [ "$(id -un)" = "root" ] instead of the USER variable.
  • mkdir -p silently does nothing if the directory already exists, so you don't need to test -d

Perhaps you want this:

#!/bin/bash
webroot="/home/leonid/Web/" # root folder where subfolders for virtualhosts created
apachehost="/etc/apache2/sites-available/050-" # prefix for virtualhost config file
a2ensite="050-" # short prefix for virtualhost config file
tmphost=$(mktemp)
trap "rm $tmphost" EXIT
if [ "$USER" == "root" ]
then
 echo "You should not run this script as root but as user going to edit web files." >&2
 exit 1
fi
read -p"Create virtualhost (= Folder name,case sensitive)" -r host
case "$host" in
 "") echo "Bad input: empty" >&2; exit 1 ;;
 *"*"*) echo "Bad input: wildcard" >&2; exit 1 ;;
 *[[:space:]]*) echo "Bad input: whitespace" >&2; exit 1 ;;
esac
# braces only for readability
hostfile="${apachehost}${host}.conf" # apache virtualhost config file
dir="${webroot}${host}" # folder used as document root for virtualhost
# virtualhost template 
cat >"$tmphost" <<EOF
<VirtualHost *:80>
 ServerAdmin webmaster@localhost
 DocumentRoot $dir
 ServerName $host.localhost
 ServerAlias $host.localhost
 <Directory "$dir">
 AllowOverride All
 Require local
 </Directory>
 # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
 # error, crit, alert, emerg.
 LogLevel warn
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
EOF
# edit virtualhost config
editor=${VISUAL:-$EDITOR}
if [ -z "$editor" ]
then
 echo "edit '$tmphost' to your liking, then hit Enter"
 read -p "I'll wait ... "
else
 "$editor" "$tmphost"
fi
# probably want some validating here that the user has not broken the config
echo "execute root tools with pkexec to create virtualhost"
mkdir -p "$dir"
pkexec /bin/bash <<EOF
chgrp www-data "$dir"
chmod u=rwX,g=rX,o= "$dir"
mv "$tmphost" "$hostfile"
chown root:root "$hostfile"
chmod u=rw,g=r,o=r "$hostfile"
a2ensite "${a2ensite}${host}.conf"
EOF

Responding to your questions:

  1. "determine if running in a terminal?" Yes with this obscure test:

    if [ -t 0 ]; then echo "in a terminal"; fi
    

    That tests file descriptor 0, which is stdin. If you're launching your script as a GUI, that test should be false.

  2. editor=${VISUAL:-$EDITOR} sets the editor variable to the user's $VISUAL variable, or if that's not set, to the $EDITOR variable. Many programs use this to determine the user's preferred "terminal" editor. vim and emacs are two common values there. If neither of those are set, then the user gets to go edit that however he chooses.

  3. "is read terminal only?" Yes

If you're going to aim for GUI and text versions, I'd use one script, but make sure all the common code is put into functions so you don't have to duplicate your code. For example:

get_virtual_host() {
 if [ -t 0 ]; then
 read -p "Create virtualhost (= Folder name,case sensitive)" -r host
 else
 host=$(zenity --forms --add-entry=Name --text='Create virtualhost (= Folder name,case sensitive)')
 fi
 case "$host" in
 "") echo "Bad input: empty" >&2; exit 1 ;;
 *"*"*) echo "Bad input: wildcard" >&2; exit 1 ;;
 *[[:space:]]*) echo "Bad input: whitespace" >&2; exit 1 ;;
 esac
 echo "$host"
}
host=$(get_virtual_host)
answered Feb 28, 2019 at 18:49
\$\endgroup\$
2
  • \$\begingroup\$ Great! Ill manage two versions - gui and your - terminal. Going to merge security and readability changes. Is it possible to determine if script is running without terminal? Whats unclear is editor=${VISUAL:-$EDITOR}` I haven't this variables set, so script is running in terminal only, without editor. Also read... is it terminal only command? \$\endgroup\$ Commented Feb 28, 2019 at 19:22
  • \$\begingroup\$ Merged and updated version in git. If you may look over it - I can add another question. \$\endgroup\$ Commented Mar 1, 2019 at 1:01

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.