|
| 1 | +#!/bin/bash |
| 2 | +# |
| 3 | +# Usage: |
| 4 | +# $ create_django_project_run_env <appname> |
| 5 | + |
| 6 | +# error exit function |
| 7 | +function error_exit |
| 8 | +{ |
| 9 | + echo "$1" 1>&2 |
| 10 | + exit 1 |
| 11 | +} |
| 12 | + |
| 13 | +# check if we're being run as root |
| 14 | +if [ "$EUID" -ne 0 ]; then |
| 15 | + echo "Please run as root" |
| 16 | + exit |
| 17 | +fi |
| 18 | + |
| 19 | +# conventional values that we'll use throughout the script |
| 20 | +APPNAME=$1 |
| 21 | +DOMAINNAME=$2 |
| 22 | +GROUPNAME=webapps |
| 23 | +# app folder name under /webapps/<appname>_project |
| 24 | +APPFOLDER=$1_project |
| 25 | +APPFOLDERPATH=/$GROUPNAME/$APPFOLDER |
| 26 | +# prerequisite standard packages. If any of these are missing, |
| 27 | +# script will attempt to install it. If installation fails, it will abort. |
| 28 | +LINUX_PREREQ=('git' 'build-essential' 'python-dev' 'nginx' 'postgresql' 'libpq-dev' 'python-pip') |
| 29 | +PYTHON_PREREQ=('virtualenv' 'supervisor') |
| 30 | + |
| 31 | +# check appname was supplied as argument |
| 32 | +if [ "$APPNAME" == "" ] || [ "$DOMAINNAME" == "" ]; then |
| 33 | + echo "Usage:" |
| 34 | + echo " $ create_django_project_run_env <project> <domain>" |
| 35 | + echo |
| 36 | + exit 1 |
| 37 | +fi |
| 38 | + |
| 39 | +# test prerequisites |
| 40 | +echo "Checking if required packages are installed..." |
| 41 | +declare -a MISSING |
| 42 | +for pkg in "${LINUX_PREREQ[@]}" |
| 43 | + do |
| 44 | + echo "Installing '$pkg'..." |
| 45 | + apt-get -y install $pkg |
| 46 | + if [ $? -ne 0 ]; then |
| 47 | + echo "Error installing system package '$pkg'" |
| 48 | + exit 1 |
| 49 | + fi |
| 50 | + done |
| 51 | + |
| 52 | +for ppkg in "${PYTHON_PREREQ[@]}" |
| 53 | + do |
| 54 | + echo "Installing Python package '$ppkg'..." |
| 55 | + pip install $ppkg |
| 56 | + if [ $? -ne 0 ]; then |
| 57 | + echo "Error installing python package '$ppkg'" |
| 58 | + exit 1 |
| 59 | + fi |
| 60 | + done |
| 61 | + |
| 62 | +if [ ${#MISSING[@]} -ne 0 ]; then |
| 63 | + echo "Following required packages are missing, please install them first." |
| 64 | + echo ${MISSING[*]} |
| 65 | + exit 1 |
| 66 | +fi |
| 67 | + |
| 68 | +echo "All required packages are installed!" |
| 69 | + |
| 70 | +# create the app folder |
| 71 | +echo "Creating app folder '$APPFOLDERPATH'..." |
| 72 | +mkdir -p /$GROUPNAME/$APPFOLDER || error_exit "Could not create app folder" |
| 73 | + |
| 74 | +# test the group 'webapps' exists, and if it doesn't create it |
| 75 | +getent group $GROUPNAME |
| 76 | +if [ $? -ne 0 ]; then |
| 77 | + echo "Creating group '$GROUPNAME' for automation accounts..." |
| 78 | + groupadd --system $GROUPNAME || error_exit "Could not create group 'webapps'" |
| 79 | +fi |
| 80 | + |
| 81 | +# create the app user account, same name as the appname |
| 82 | +grep "$APPNAME:" /etc/passwd |
| 83 | +if [ $? -ne 0 ]; then |
| 84 | + echo "Creating automation user account '$APPNAME'..." |
| 85 | + useradd --system --gid $GROUPNAME --shell /bin/bash --home $APPFOLDERPATH $APPNAME || error_exit "Could not create automation user account '$APPNAME'" |
| 86 | +fi |
| 87 | + |
| 88 | +# change ownership of the app folder to the newly created user account |
| 89 | +echo "Setting ownership of $APPFOLDERPATH and its descendents to $APPNAME:$GROUPNAME..." |
| 90 | +chown -R $APPNAME:$GROUPNAME $APPFOLDERPATH || error_exit "Error setting ownership" |
| 91 | +# give group execution rights in the folder; |
| 92 | +# TODO: is this necessary? why? |
| 93 | +chmod g+x $APPFOLDERPATH || error_exit "Error setting group execute flag" |
| 94 | + |
| 95 | +# install python virtualenv in the APPFOLDER |
| 96 | +echo "Creating environment setup for django app..." |
| 97 | +su -l $APPNAME << 'EOF' |
| 98 | +pwd |
| 99 | +echo "Setting up python virtualenv..." |
| 100 | +virtualenv -p python3 . || error_exit "Error installing virtual environment to app folder" |
| 101 | +source ./bin/activate |
| 102 | +# upgrade pip |
| 103 | +pip install --upgrade pip || error_exist "Error upgrading pip to the latest version" |
| 104 | +# install prerequisite python packages for a django app using pip |
| 105 | +echo "Installing base python packages for the app..." |
| 106 | +# Standard django packages which will be installed. If any of these fail, script will abort |
| 107 | +DJANGO_PKGS=('django' 'psycopg2' 'gunicorn' 'setproctitle') |
| 108 | +for dpkg in "${DJANGO_PKGS[@]}" |
| 109 | + do |
| 110 | + echo "Installing $dpkg..." |
| 111 | + pip install $dpkg || error_exit "Error installing $dpkg" |
| 112 | + done |
| 113 | +# create the default folders where we store django app's resources |
| 114 | +echo "Creating static file folders..." |
| 115 | +mkdir logs run ssl static media || error_exit "Error creating static folders" |
| 116 | +
|
| 117 | +EOF |
| 118 | + |
| 119 | +# generate secret key |
| 120 | +echo "Generating Django secret key..." |
| 121 | +DJANGO_SECRET_KEY=`openssl rand -base64 48` |
| 122 | +if [ $? -ne 0 ]; then |
| 123 | + error_exit "Error creating secret key." |
| 124 | +fi |
| 125 | +echo $DJANGO_SECRET_KEY > $APPFOLDERPATH/.django_secret_key |
| 126 | +chown $APPNAME:$GROUPNAME $APPFOLDERPATH/.django_secret_key |
| 127 | + |
| 128 | +echo "Creating gunicorn startup script..." |
| 129 | +cat > /tmp/prepare_env.sh << EOF |
| 130 | +DJANGODIR=$APPFOLDERPATH/$APPNAME # Django project directory |
| 131 | +DJANGO_SETTINGS_MODULE=$APPNAME.settings # settings file for the app |
| 132 | +
|
| 133 | +export DJANGO_SETTINGS_MODULE=\$DJANGO_SETTINGS_MODULE |
| 134 | +export PYTHONPATH=\$DJANGODIR:\$PYTHONPATH |
| 135 | +export SECRET_KEY=`cat $APPFOLDERPATH/.django_secret_key` |
| 136 | +export DB_PASSWORD=`cat $APPFOLDERPATH/.django_db_password` |
| 137 | +
|
| 138 | +cd $APPFOLDERPATH |
| 139 | +source ./bin/activate |
| 140 | +EOF |
| 141 | +mv /tmp/prepare_env.sh $APPFOLDERPATH |
| 142 | +chown $APPNAME:$GROUPNAME $APPFOLDERPATH/prepare_env.sh |
| 143 | + |
| 144 | +cat > /tmp/gunicorn_start.sh << EOF |
| 145 | +#!/bin/bash |
| 146 | +# Makes the following assumptions: |
| 147 | +# |
| 148 | +# 1. All applications are located in a subfolder within /webapps |
| 149 | +# 2. Each app gets a dedicated subfolder <appname> under /webapps. This will |
| 150 | +# be referred to as the app folder. |
| 151 | +# 3. The group account 'webapps' exists and each app is to be executed |
| 152 | +# under the user account <appname>. |
| 153 | +# 4. The app folder and all its recursive contents are owned by |
| 154 | +# <appname>:webapps. |
| 155 | +# 5. The django app is stored under /webapps/<appname>/<appname> folder. |
| 156 | +# |
| 157 | +
|
| 158 | +cd $APPFOLDERPATH |
| 159 | +source ./prepare_env.sh |
| 160 | +
|
| 161 | +SOCKFILE=$APPFOLDERPATH/run/gunicorn.sock # we will communicte using this unix socket |
| 162 | +USER=$APPNAME # the user to run as |
| 163 | +GROUP=$GROUPNAME # the group to run as |
| 164 | +NUM_WORKERS=3 # how many worker processes should Gunicorn spawn |
| 165 | +DJANGO_WSGI_MODULE=$APPNAME.wsgi # WSGI module name |
| 166 | +
|
| 167 | +echo "Starting $APPNAME as \`whoami\`" |
| 168 | +
|
| 169 | +# Create the run directory if it doesn't exist |
| 170 | +RUNDIR=\$(dirname \$SOCKFILE) |
| 171 | +test -d \$RUNDIR || mkdir -p \$RUNDIR |
| 172 | +
|
| 173 | +# Start your Django Unicorn |
| 174 | +# Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon) |
| 175 | +exec ./bin/gunicorn \${DJANGO_WSGI_MODULE}:application \ |
| 176 | + --name $APPNAME \ |
| 177 | + --workers \$NUM_WORKERS \ |
| 178 | + --user=\$USER --group=\$GROUP \ |
| 179 | + --bind=unix:\$SOCKFILE \ |
| 180 | + --log-level=debug \ |
| 181 | + --log-file=- |
| 182 | +EOF |
| 183 | + |
| 184 | +# move the script to app folder |
| 185 | +mv /tmp/gunicorn_start.sh $APPFOLDERPATH |
| 186 | +chown $APPNAME:$GROUPNAME $APPFOLDERPATH/gunicorn_start.sh |
| 187 | +chmod u+x $APPFOLDERPATH/gunicorn_start.sh |
| 188 | + |
| 189 | +# create the PostgreSQL database and associated role for the app |
| 190 | +# Database and role name would be the same as the <appname> argument |
| 191 | +echo "Creating secure password for database role..." |
| 192 | +DBPASSWORD=`openssl rand -base64 32` |
| 193 | +if [ $? -ne 0 ]; then |
| 194 | + error_exit "Error creating secure password for database role." |
| 195 | +fi |
| 196 | +echo $DBPASSWORD > $APPFOLDERPATH/.django_db_password |
| 197 | +chown $APPNAME:$GROUPNAME $APPFOLDERPATH/.django_db_password |
| 198 | +echo "Creating PostgreSQL role '$APPNAME'..." |
| 199 | +su postgres -c "createuser -S -D -R -w $APPNAME" |
| 200 | +echo "Changing password of database role..." |
| 201 | +su postgres -c "psql -c \"ALTER USER $APPNAME WITH PASSWORD '$DBPASSWORD';\"" |
| 202 | +echo "Creating PostgreSQL database '$APPNAME'..." |
| 203 | +su postgres -c "createdb --owner $APPNAME $APPNAME" |
| 204 | + |
| 205 | +# create nginx template in /etc/nginx/sites-available |
| 206 | +mkdir -p /etc/nginx/sites-available |
| 207 | +APPSERVERNAME=$APPNAME |
| 208 | +APPSERVERNAME+=_gunicorn |
| 209 | +cat > /etc/nginx/sites-available/$APPNAME.conf << EOF |
| 210 | +upstream $APPSERVERNAME { |
| 211 | + server unix:$APPFOLDERPATH/run/gunicorn.sock fail_timeout=0; |
| 212 | +} |
| 213 | +server { |
| 214 | + listen 80; |
| 215 | + server_name $DOMAINNAME; |
| 216 | +
|
| 217 | + client_max_body_size 5M; |
| 218 | + keepalive_timeout 5; |
| 219 | + underscores_in_headers on; |
| 220 | +
|
| 221 | + access_log $APPFOLDERPATH/logs/nginx-access.log; |
| 222 | + error_log $APPFOLDERPATH/logs/nginx-error.log; |
| 223 | +
|
| 224 | + location /media { |
| 225 | + alias $APPFOLDERPATH/media; |
| 226 | + } |
| 227 | + location /static { |
| 228 | + alias $APPFOLDERPATH/static; |
| 229 | + } |
| 230 | + location /static/admin { |
| 231 | + alias $APPFOLDERPATH/lib/python3.5/site-packages/django/contrib/admin/static/admin/; |
| 232 | + } |
| 233 | + # This would redirect http site access to HTTPS. Uncomment to enable |
| 234 | + #location / { |
| 235 | + # rewrite ^ https://\$http_host\$request_uri? permanent; |
| 236 | + #} |
| 237 | + # To make the site pure HTTPS, comment the following section while |
| 238 | + # uncommenting the above section. Also uncoment the HTTPS section |
| 239 | + location / { |
| 240 | + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; |
| 241 | + proxy_set_header Host \$http_host; |
| 242 | + proxy_redirect off; |
| 243 | + proxy_pass http://$APPSERVERNAME; |
| 244 | + } |
| 245 | +} |
| 246 | +
|
| 247 | +# Uncomment this if you want to enable HTTPS access. Also, remember to install |
| 248 | +# the site certificate, either purcahased or generated. |
| 249 | +#server { |
| 250 | +# listen 443 default ssl; |
| 251 | +# server_name $DOMAINNAME; |
| 252 | +# |
| 253 | +# client_max_body_size 5M; |
| 254 | +# keepalive_timeout 5; |
| 255 | +# |
| 256 | +# ssl_certificate /etc/nginx/ssl/cert_chain.crt; |
| 257 | +# ssl_certificate_key $APPFOLDERPATH/ssl/$DOMAINNAME.key; |
| 258 | +# |
| 259 | +# access_log $APPFOLDERPATH/logs/nginx-access.log; |
| 260 | +# error_log $APPFOLDERPATH/logs/nginx-error.log; |
| 261 | +# |
| 262 | +# location /media { |
| 263 | +# alias $APPFOLDERPATH/media; |
| 264 | +# } |
| 265 | +# location /static { |
| 266 | +# alias $APPFOLDERPATH/static; |
| 267 | +# } |
| 268 | +# location /static/admin { |
| 269 | +# alias $APPFOLDERPATH/lib/python3.5/site-packages/django/contrib/admin/static/admin/; |
| 270 | +# } |
| 271 | +# location / { |
| 272 | +# proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; |
| 273 | +# proxy_set_header Host \$http_host; |
| 274 | +# proxy_set_header X-Forwarded-Proto \$scheme; |
| 275 | +# proxy_redirect off; |
| 276 | +# proxy_pass http://$APPSERVERNAME; |
| 277 | +# } |
| 278 | +#} |
| 279 | +EOF |
| 280 | +# make a symbolic link to the nginx conf file in sites-enabled |
| 281 | +ln -s /etc/nginx/sites-available/$APPNAME.conf /etc/nginx/sites-enabled/$APPNAME |
| 282 | + |
| 283 | +# copy supervisord.conf |
| 284 | +cp ./supervisord.conf /etc || error_exit "Error copying supervisord.conf" |
| 285 | + |
| 286 | +# create the supervisor application conf file |
| 287 | +mkdir -p /etc/supervisor |
| 288 | +cat > /etc/supervisor/$APPNAME.conf << EOF |
| 289 | +[program:$APPNAME] |
| 290 | +command = $APPFOLDERPATH/gunicorn_start.sh |
| 291 | +user = $APPNAME |
| 292 | +stdout_logfile = $APPFOLDERPATH/logs/gunicorn_supervisor.log |
| 293 | +redirect_stderr = true |
| 294 | +EOF |
| 295 | + |
| 296 | +# create supervisord init.d script that can be controlled with service |
| 297 | +echo "Setting up supervisor to autostart during bootup..." |
| 298 | +cp ./supervisord /etc/init.d || error_exit "Error copying /etc/init.d/supervisord" |
| 299 | +# enable execute flag on the script |
| 300 | +chmod +x /etc/init.d/supervisord || error_exit "Error setting execute flag on supervisord" |
| 301 | +# create the entries in runlevel folders to autostart supervisord |
| 302 | +update-rc.d supervisord defaults || error_exit "Error configuring supervisord to autostart" |
| 303 | + |
| 304 | +# now create a quasi django project that can be run using a GUnicorn script |
| 305 | +echo "Installing quasi django project..." |
| 306 | +su -l $APPNAME << EOF |
| 307 | +source ./bin/activate |
| 308 | +django-admin.py startproject $APPNAME |
| 309 | +EOF |
| 310 | + |
| 311 | +# now start the supervisord daemon |
| 312 | +service supervisord start || error_exit "Error starting supervisord" |
| 313 | +# reload nginx so that requests to domain are redirected to the gunicorn process |
| 314 | +nginx -s reload || error_exit "Error reloading nginx. Check configuration files" |
| 315 | + |
| 316 | +echo "Done!" |
0 commit comments