在CentOS上部署lamp环境

Date:2013-05-21 Author:阿债 Category:Linux Tag:linux, mysql, php, nginx

描述

这里的lamp中web server用的是nginx而不是apache,但还是用传统的叫法lamp。 环境 CentOS 6.4,我的是64位,但在32位上依然可用。

StackOverflow下载一个调试bash的脚本lib.trap.sh, 我把它放在本文最后。

使用的版本如下:

脚本deploy_lamp.sh

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#!/bin/bash

source "lib.trap.sh"


username="ryan"
if [[ -z "$username" ]]; then
    username=`whoami` #当前用户名
fi
cpu_cores=`cat /proc/cpuinfo | gawk '/cpu cores/{n+=1} END{print n}'` #CPU内核数
if [[ `uname -m` =~ "x86_64" ]]; then
    sys64bit="True" #64位系统
else
    sys64bit=""
fi
if [[ `id -un mysql` != "mysql" ]]; then
    groupadd -f mysql
    useradd -r -g mysql mysql
fi

yum install -y gcc gcc-c++ make cmake automake autoconf binutils
yum install -y gawk sed vim wget
yum install -y git httpd-tools
yum install -y zlib-devel ncurses-devel readline-devel
yum install -y bzip2-devel gmp-devel openssl-devel curl-devel freetype-devel
yum install -y libxml2-devel libpng-devel libjpeg-devel libicu-devel libc-client-devel
yum install -y libtool libtool-ltdl-devel


function download()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    if [ ! -d "ngx_http_lower_upper_case" ]; then
        git clone https://github.com/replay/ngx_http_lower_upper_case.git
    fi
    if [ ! -f "pcre-8.32.tar.gz" ]; then
        wget http://sourceforge.net/projects/pcre/files/pcre/8.32/pcre-8.32.tar.gz
    fi
    if [ ! -f "nginx-1.4.1.tar.gz" ]; then
        wget http://nginx.org/download/nginx-1.4.1.tar.gz
    fi
    if [ ! -f "freetds-stable.tgz" ]; then
        wget ftp://ftp.astron.com/pub/freetds/stable/freetds-stable.tgz
    fi
    if [ ! -f "libmcrypt-2.5.8.tar.gz" ]; then
        wget -O libmcrypt-2.5.8.tar.gz http://downloads.sourceforge.net/project/mcrypt/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gz?use_mirror=nchc
    fi
    if [ ! -f "php-5.4.14.tar.gz" ]; then
        wget -O php-5.4.14.tar.gz http://cn2.php.net/get/php-5.4.14.tar.gz/from/this/mirror
    fi
    if [ ! -f "mysql-5.6.11.tar.gz" ]; then
        wget http://cdn.mysql.com/Downloads/MySQL-5.6/mysql-5.6.11.tar.gz
    fi
}


function ins_mysql()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    rm -rf mysql-5.6.11
    tar xzf mysql-5.6.11.tar.gz
    cd mysql-5.6.11
    #安装全部字符集请使用 -DEXTRAT_CHARSETS=all
    #ENABLED_LOCAL_INFILE选项为“允许从本地导入数据”
    cmake . \
        -DCMAKE_INSTALL_PREFIX=/opt/mysql-5.6.11 \
        -DINSTALL_DATADIR=/opt/mysql-5.6.11/data \
        -DDEFAULT_CHARSET=utf8 \
        -DDEFAULT_COLLATION=utf8_general_ci \
        -DEXTRAT_CHARSETS=latin1,gbk \
        -DENABLED_LOCAL_INFILE=1
    make && make install
    #大于半小时的漫长等待中......
    cd ..
}

function ini_mysql()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    #必要的软连接和目录
    ln -sf /opt/mysql-5.6.11/bin/mysqld_safe /usr/bin/mysqld_safe
    ln -sf /opt/mysql-5.6.11/bin/mysql /usr/bin/mysql
    ln -sf /opt/mysql-5.6.11/bin/mysqldump /usr/bin/mysqldump
    ln -sf /opt/mysql-5.6.11/bin/mysqlimport /usr/bin/mysqlimport
    ln -sf /opt/mysql-5.6.11/bin/mysql_config /usr/bin/mysql_config
    mkdir -p /var/run/mysqld
    chmod -R 777 /var/run/mysqld
    chown -R mysql:mysql /opt/mysql-5.6.11/data/

    #初始化MySQL
    /opt/mysql-5.6.11/scripts/mysql_install_db --user=mysql --basedir=/opt/mysql-5.6.11 --datadir=/opt/mysql-5.6.11/data
    #64位Linux
    if [ -n "$sys64bit" ]; then
      ln -sf /opt/mysql-5.6.11/lib  /opt/mysql-5.6.11/lib64
    fi
    #设置配置文件
    if [ ! -f /opt/mysql-5.6.11/my.cnf ]; then
        cp /opt/mysql-5.6.11/support-files/my-default.cnf /opt/mysql-5.6.11/my.cnf
    fi
    rm -f /etc/my.cnf
    sed -i '/# basedir =/cbasedir = \/opt\/mysql-5.6.11' /opt/mysql-5.6.11/my.cnf
    sed -i '/# datadir =/cdatadir = \/opt\/mysql-5.6.11\/data' /opt/mysql-5.6.11/my.cnf
    sed -i '/# port =/cport = 3306' /opt/mysql-5.6.11/my.cnf
    #sed -i '/# innodb_buffer_pool_size = 128M/cinnodb_buffer_pool_size = 256M' /opt/mysql-5.6.11/my.cnf
    sed -i '$alog-error=/var/log/mysqld.log' /opt/mysql-5.6.11/my.cnf
    sed -i '$apid-file=/var/run/mysqld/mysqld.pid' /opt/mysql-5.6.11/my.cnf
    sed -i '$G' /opt/mysql-5.6.11/my.cnf
    sed -i '$a[mysqld_safe]' /opt/mysql-5.6.11/my.cnf
    sed -i '$alog-error=/var/log/mysqld.log' /opt/mysql-5.6.11/my.cnf
    sed -i '$apid-file=/var/run/mysqld/mysqld.pid' /opt/mysql-5.6.11/my.cnf
    ln -sf /opt/mysql-5.6.11/my.cnf /etc/my.cnf

    #设置系统服务
    rm -f /etc/init.d/mysql
    cp /opt/mysql-5.6.11/support-files/mysql.server /etc/init.d/mysql
    sed -i '/^basedir=/cbasedir=\/opt\/mysql-5.6.11' /etc/init.d/mysql
    sed -i '/^datadir=/cdatadir=\/opt\/mysql-5.6.11\/data' /etc/init.d/mysql
    #重启mysql服务
    /etc/init.d/mysql start
}


function ini_mysql_user()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"

    #或者使用/opt/mysql-5.6.11/bin/mysql_secure_installation
    local rootpass=toor
    local dbapass=changeme
    mysql -u root --password="" mysql << EOD
    DELETE FROM \`user\` WHERE User='';
    DELETE FROM \`db\` WHERE User='';
    SET PASSWORD FOR 'root'@'localhost'=PASSWORD('$rootpass');
    SET PASSWORD FOR 'root'@'127.0.0.1'=PASSWORD('$rootpass');
    SET PASSWORD FOR 'root'@'::1'=PASSWORD('$rootpass');
    SET PASSWORD FOR 'root'@'`hostname`'=PASSWORD('$rootpass');
    GRANT ALL PRIVILEGES ON \`db\\_%\`.* TO 'dba'@'localhost' IDENTIFIED BY '$dbapass' WITH GRANT OPTION;
    GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER,INDEX ON \`db\\_%\`.* TO 'dba'@'192.168.0.%' IDENTIFIED BY '$dbapass' WITH GRANT OPTION;
    FLUSH PRIVILEGES;
    UPDATE user SET File_priv='Y' WHERE User='dba';
    FLUSH PRIVILEGES;
    GRANT FILE ON *.* TO 'dba'@'localhost';
    GRANT FILE ON *.* TO 'dba'@'192.168.0.%';
    FLUSH PRIVILEGES;
EOD

    #手工停止
    /opt/mysql-5.6.11/bin/mysqladmin --password="$rootpass" shutdown
    /etc/init.d/mysql start
    #更新
    /opt/mysql-5.6.11/bin/mysql_upgrade --password="$rootpass"
    #手工启动
    #mysqld_safe --user=mysql &
    /etc/init.d/mysql restart
}


function ins_php_pre()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    rm -rf freetds-0.91
    rm -rf libmcrypt-2.5.8

    if [ ! -d "/opt/freetds-0.91" ]; then
        tar xzf freetds-stable.tgz
        cd freetds-0.91
        ./configure --prefix=/opt/freetds-0.91 --with-tdsver=8.0 --enable-msdblib
        make && make install
        cd ..
        #64位系统下(CentOS 6.3有这个问题,其他发行版不知道是不是也这样),还需要
        if [ -n "$sys64bit" ]; then
            ln -sf /opt/freetds-0.91/lib /opt/freetds-0.91/lib64
        fi
    fi

    if [ ! -e "/usr/local/lib/libmcrypt.so.4" ]; then
        tar xzf libmcrypt-2.5.8.tar.gz
        cd libmcrypt-2.5.8
        ./configure
        make && make install
        cd ..
        if [ -n "$sys64bit" ]; then
            ln -sf /usr/local/lib/libmcrypt.so.4.4.8 /usr/lib64/libmcrypt.so.4
        else
            ln -sf /usr/local/lib/libmcrypt.so.4.4.8 /usr/lib/libmcrypt.so.4
        fi
    fi
}


function ins_php()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    rm -rf php-5.4.14

    #如果configure报错Note that the MySQL client library is not bundled anymore.
    #请将/opt/mysql-5.6.11/lib 建立软连接 /opt/mysql-5.6.11/lib64
    #64位Linux
    if [ -n "$sys64bit" ]; then
        ln -sf /opt/mysql-5.6.11/lib /opt/mysql-5.6.11/lib64
        gnu_x86_64="--build=x86_64-redhat-linux-gnu --host=x86_64-redhat-linux-gnu --with-libdir=lib64"
    else
        gnu_x86_64=""
    fi

    tar xzf php-5.4.14.tar.gz
    cd php-5.4.14
    #注意:$gnu_x86_64两边不要加引号或双引号
    ./configure --prefix=/opt/php-5.4.14 \
    --with-mysql=/opt/mysql-5.6.11 --with-mysqli=/opt/mysql-5.6.11/bin/mysql_config \
    --with-pdo-mysql=/opt/mysql-5.6.11 --with-mysql-sock=/tmp/mysql.sock \
    --with-mcrypt=/usr/local --with-zlib \
    --with-pic --with-curl=shared --with-freetype-dir --with-png-dir \
    --with-gettext=shared --with-gmp=shared --with-iconv --with-jpeg-dir --with-png-dir \
    --with-openssl --with-libxml-dir --with-pcre-regex \
    --with-kerberos --with-imap --with-imap-ssl \
    --with-pear --with-gd --enable-gd-native-ttf --enable-calendar=shared \
    --enable-exif --enable-ftp --enable-sockets --enable-bcmath=shared \
    --enable-pcntl --enable-intl --enable-mbstring \
    --enable-zip --with-bz2=shared \
    --enable-sysvsem --enable-sysvshm --enable-sysvmsg \
    --without-unixODBC --enable-mbregex --enable-embed \
    --enable-tokenizer --disable-phar --with-sqlite3 \
    --enable-fpm --with-fpm-user="$username" --with-fpm-group="$username" \
    --with-layout=GNU --with-mssql=/opt/freetds-0.91 $gnu_x86_64
    make && make install
    cd ..
}


function ini_php()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    #建立软连接
    rm -rf /usr/sbin/php-fpm
    rm -rf /usr/bin/pear
    rm -rf /usr/bin/pecl
    rm -rf /usr/bin/php*
    ln -s /opt/php-5.4.14/bin/php /usr/bin/php
    ln -s /opt/php-5.4.14/bin/phpize /usr/bin/phpize
    ln -s /opt/php-5.4.14/bin/pear /usr/bin/pear
    ln -s /opt/php-5.4.14/bin/pecl /usr/bin/pecl
    ln -s /opt/php-5.4.14/bin/php-config /usr/bin/php-config
    ln -s /opt/php-5.4.14/sbin/php-fpm /usr/sbin/php-fpm
    #配置文件
    mkdir -p /var/run/php-fpm/
    cp php-5.4.14/php.ini-production /opt/php-5.4.14/etc/php.ini
    cp /opt/php-5.4.14/etc/php-fpm.conf.default /opt/php-5.4.14/etc/php-fpm.conf

    #修改配置
    sed -i '/^max_execution_time/cmax_execution_time = 600' /opt/php-5.4.14/etc/php.ini
    sed -i '/^error_reporting/cerror_reporting = E_ALL & ~E_DEPRECATED & ~E_NOTICE' /opt/php-5.4.14/etc/php.ini
    sed -i '/^display_errors/cdisplay_errors = On' /opt/php-5.4.14/etc/php.ini
    sed -i '/^display_startup_errors/cdisplay_startup_errors = On' /opt/php-5.4.14/etc/php.ini
    sed -i '/^track_errors/ctrack_errors = On' /opt/php-5.4.14/etc/php.ini
    sed -i '/^upload_max_filesize/cupload_max_filesize = 20M' /opt/php-5.4.14/etc/php.ini
    sed -i '/^;date.timezone/cdate.timezone = Asia/Shanghai' /opt/php-5.4.14/etc/php.ini

    #修改php-fpm的进程数
    sed -i "/^;daemonize/cdaemonize = yes" /opt/php-5.4.14/etc/php-fpm.conf
    sed -i "/^listen = /clisten = /var/run/php-fpm/php-fpm.sock" /opt/php-5.4.14/etc/php-fpm.conf
    sed -i '/^pm.max_children/cpm.max_children = 30' /opt/php-5.4.14/etc/php-fpm.conf
    sed -i '/^pm.start_servers/cpm.start_servers = 2' /opt/php-5.4.14/etc/php-fpm.conf
    sed -i '/^pm.min_spare_servers/cpm.min_spare_servers = 2' /opt/php-5.4.14/etc/php-fpm.conf
    sed -i '/^pm.max_spare_servers/cpm.max_spare_servers = 30' /opt/php-5.4.14/etc/php-fpm.conf

    mkdir -p /etc/php5/fpm/
    ln -s /opt/php-5.4.14/etc/php.ini /etc/php5/fpm/php.ini
    ln -s /opt/php-5.4.14/etc/php-fpm.conf /etc/php5/fpm/php-fpm.conf
}


function ins_php_ext()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    #添加常用扩展
    if [ ! -f "/opt/php-5.4.14/lib/php/20100525/apc.so" ]; then
        printf "\n" | pecl install apc
        sed -i '1aextension=apc.so' /opt/php-5.4.14/etc/php.ini
    fi
    if [ ! -f "/opt/php-5.4.14/lib/php/20100525/xdebug.so" ]; then
        pecl install xdebug
        #ThreadSafe版本,带有_ts
        sed -i '1azend_extension_ts=xdebug.so' /opt/php-5.4.14/etc/php.ini
    fi
    if [ ! -d "/opt/php-5.4.14/share/pear/PHPUnit" ]; then
        pear upgrade pear && pear install phpunit
    fi
    if [ ! -f "/opt/php-5.4.14/bin/phing" ]; then
        pear channel-discover pear.phing.info
        pear install phing/phing
        ln -sf /opt/php-5.4.14/bin/phing /usr/bin/phing
    fi
}


function ins_nginx()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    rm -rf pcre-8.32
    rm -rf nginx-1.4.1
    tar xzf pcre-8.32.tar.gz
    tar xzf nginx-1.4.1.tar.gz
    cd nginx-1.4.1
    ./configure --prefix=/opt/nginx-1.4.1 --with-pcre=../pcre-8.32 --add-module=../ngx_http_lower_upper_case
    make && make install
    cd ..
}


function ini_nginx()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    cd /opt/nginx-1.4.1/conf/
    #替换主配置
    sed -i "/^#user/cuser  $username;" nginx.conf
    sed -i "/^worker_processes/cworker_processes  $cpu_cores;" nginx.conf
    sed -i "s/^#pid/pid/" nginx.conf
    sed -i "/^[[:space:]]*#log_format/{N;N;s/\([[:space:]]*\)#/\1/g;h;s/main/sess/;s/'\;$/ \"\$var_sessid\"'\;/;G}" nginx.conf
    sed -i '/^[[:space:]]*#gzip/c\    gzip  on;\n    gzip_min_length  1k;' nginx.conf
    sed -i '/^[[:space:]]* server {/,$d' nginx.conf
    sed -i '$a\    include sites/*.conf;\n}' nginx.conf
    #增加一个PHP配置
    mkdir sites authes
    touch sites/test.conf
    #将最后面的site配置文件写入上面的文件
    chmod -R 777 sites
    cd -

    ln -sf /opt/nginx-1.4.1/sbin/nginx /usr/sbin/nginx
}


function ini_nginx_site()
{
    echo ""
    echo ""
    echo "$FUNCNAME start ......"
    cat > /opt/nginx-1.4.1/conf/sites/template.conf.bak << EOD
server {
    listen           80;
    server_name      test.example.com;
    charset          utf-8;
    root             /home/ryan/projects/test;

    # case insensitive
    #lower \$lower_uri "\$uri";
    #try_files \$uri \$lower_uri;

    set \$var_sessid "-";
    if ( \$http_cookie ~* "PHPSESSID=(\S+)(;.*|\$)")
    {
        set \$var_sessid \$1;
    }
    access_log  logs/test.access.log  main;

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    #error_page   500 502 503 504  /50x.html;
    #location = /50x.html {
    #    root   html;
    #}

    if (\$request_uri ~* ^.*\\.(svn|git|hg|bzr|cvs).*\$) {
        return 404;
    }

    location ~* ^.+\\.(css|js|jpg|png|gif|ico|swf|pdf|txt|xlsx)\$ {
        access_log off;
        expires    30d;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location / {
        #auth_basic            "Restricted";
        #auth_basic_user_file  authes/test;
        try_files \$uri  \$uri/  /index.php?\$query_string;
        #fastcgi_pass   127.0.0.1:9000;
        fastcgi_pass    unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index   index.php;
        include         fastcgi_params;
        fastcgi_param   SCRIPT_FILENAME  \$document_root\$fastcgi_script_name;
    }

    #location ~* /\$ {
    #    index    index.html;
    #}
}
EOD
    chkconfig mysql on
}


softdir="/home/$username/soft/"
mkdir -p $softdir
cd $softdir
download
[ -d "/opt/mysql-5.6.11" ] || { ins_mysql; ini_mysql; ini_mysql_user; }
[ -d "/opt/php-5.4.14" ] || { ins_php_pre; ins_php; ini_php; ins_php_ext; }
[ -d "/opt/nginx-1.4.1" ] || { ins_nginx; ini_nginx; ini_nginx_site; }

exit 0

脚本lib.trap.sh

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#!/bin/bash

lib_name='trap'
lib_version=20121026

stderr_log="/dev/shm/stderr.log"

#
# TO BE SOURCED ONLY ONCE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

if test "${g_libs[$lib_name]+_}"; then
    return 0
else
    if test ${#g_libs[@]} == 0; then
        declare -A g_libs
    fi
    g_libs[$lib_name]=$lib_version
fi


#
# MAIN CODE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable
set -o errexit   ## set -e : exit the script if any statement returns a non-true return value

exec 2>"$stderr_log"


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: EXIT_HANDLER
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function exit_handler ()
{
    local error_code="$?"

    test $error_code == 0 && return;

    #
    # LOCAL VARIABLES:
    # ------------------------------------------------------------------
    #    
    local i=0
    local regex=''
    local mem=''

    local error_file=''
    local error_lineno=''
    local error_message='unknown'

    local lineno=''


    #
    # PRINT THE HEADER:
    # ------------------------------------------------------------------
    #
    # Color the output if it's an interactive terminal
    test -t 1 && tput bold; tput setf 4                                 ## red bold
    echo -e "\n(!) EXIT HANDLER:\n"


    #
    # GETTING LAST ERROR OCCURRED:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    #
    # Read last file from the error log
    # ------------------------------------------------------------------
    #
    if test -f "$stderr_log"
        then
            stderr=$( tail -n 1 "$stderr_log" )
            rm "$stderr_log"
    fi

    #
    # Managing the line to extract information:
    # ------------------------------------------------------------------
    #

    if test -n "$stderr"
        then        
            # Exploding stderr on :
            mem="$IFS"
            local shrunk_stderr=$( echo "$stderr" | sed 's/\: /\:/g' )
            IFS=':'
            local stderr_parts=( $shrunk_stderr )
            IFS="$mem"

            # Storing information on the error
            error_file="${stderr_parts[0]}"
            error_lineno="${stderr_parts[1]}"
            error_message=""

            for (( i = 3; i <= ${#stderr_parts[@]}; i++ ))
                do
                    error_message="$error_message "${stderr_parts[$i-1]}": "
            done

            # Removing last ':' (colon character)
            error_message="${error_message%:*}"

            # Trim
            error_message="$( echo "$error_message" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
    fi

    #
    # GETTING BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
    _backtrace=$( backtrace 2 )


    #
    # MANAGING THE OUTPUT:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    local lineno=""
    regex='^([a-z]{1,}) ([0-9]{1,})$'

    if [[ $error_lineno =~ $regex ]]

        # The error line was found on the log
        # (e.g. type 'ff' without quotes wherever)
        # --------------------------------------------------------------
        then
            local row="${BASH_REMATCH[1]}"
            lineno="${BASH_REMATCH[2]}"

            echo -e "FILE:\t\t${error_file}"
            echo -e "${row^^}:\t\t${lineno}\n"

            echo -e "ERROR CODE:\t${error_code}"             
            test -t 1 && tput setf 6                                    ## white yellow
            echo -e "ERROR MESSAGE:\n$error_message"


        else
            regex="^${error_file}\$|^${error_file}\s+|\s+${error_file}\s+|\s+${error_file}\$"
            if [[ "$_backtrace" =~ $regex ]]

                # The file was found on the log but not the error line
                # (could not reproduce this case so far)
                # ------------------------------------------------------
                then
                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    echo -e "ERROR MESSAGE:\n${stderr}"

                # Neither the error line nor the error file was found on the log
                # (e.g. type 'cp ffd fdf' without quotes wherever)
                # ------------------------------------------------------
                else
                    #
                    # The error file is the first on backtrace list:

                    # Exploding backtrace on newlines
                    mem=$IFS
                    IFS='
                    '
                    #
                    # Substring: I keep only the carriage return
                    # (others needed only for tabbing purpose)
                    IFS=${IFS:0:1}
                    local lines=( $_backtrace )

                    IFS=$mem

                    error_file=""

                    if test -n "${lines[1]}"
                        then
                            array=( ${lines[1]} )

                            for (( i=2; i<${#array[@]}; i++ ))
                                do
                                    error_file="$error_file ${array[$i]}"
                            done

                            # Trim
                            error_file="$( echo "$error_file" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
                    fi

                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    if test -n "${stderr}"
                        then
                            echo -e "ERROR MESSAGE:\n${stderr}"
                        else
                            echo -e "ERROR MESSAGE:\n${error_message}"
                    fi
            fi
    fi

    #
    # PRINTING THE BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 7                                            ## white bold
    echo -e "\n$_backtrace\n"

    #
    # EXITING:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 4                                            ## red bold
    echo "Exiting!"

    test -t 1 && tput sgr0 # Reset terminal

    exit "$error_code"
}
trap exit_handler EXIT                                                  # ! ! ! TRAP EXIT ! ! !
trap exit ERR                                                           # ! ! ! TRAP ERR ! ! !


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: BACKTRACE
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function backtrace
{
    local _start_from_=0

    local params=( "$@" )
    if (( "${#params[@]}" >= "1" ))
        then
            _start_from_="$1"
    fi

    local i=0
    local first=false
    while caller $i > /dev/null
    do
        if test -n "$_start_from_" && (( "$i" + 1   >= "$_start_from_" ))
            then
                if test "$first" == false
                    then
                        echo "BACKTRACE IS:"
                        first=true
                fi
                caller $i
        fi
        let "i=i+1"
    done
}

return 0