From 433c87d8aa2566dc65b33fdae0324513439f7144 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 17 Nov 2017 17:26:30 -0800 Subject: [PATCH 001/334] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a915254..87e6cfc 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Deploy -[![release](https://img.shields.io/badge/release-v3.6.4-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) +[![release](https://img.shields.io/badge/release-v3.6.5-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) [![Build Status](https://travis-ci.org/EMRL/deploy.svg?branch=master)](https://travis-ci.org/EMRL/deploy) `deploy` is a shell script designed to speed up and automate project deployment. Its main focus is Wordpress websites, but it can be used with any code repository. From 749b5bedaa27d9af0ba64edbaf07deada829673f Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 21 Nov 2017 17:31:38 -0800 Subject: [PATCH 002/334] Resolve #109 --- deploy.sh | 5 +++-- etc/.deployrc | 0 etc/deploy.sh | 5 +++++ etc/html/civil/approval.php | 0 etc/html/civil/approve.html | 0 etc/html/civil/client.html | 0 etc/html/civil/digest/commit.html | 0 etc/html/civil/digest/footer.html | 0 etc/html/civil/digest/reference.html | 0 etc/html/civil/digest/wrap.html | 0 etc/html/civil/error.html | 0 etc/html/civil/footer.html | 0 etc/html/civil/header.html | 0 etc/html/civil/remote/index.html | 0 etc/html/civil/remote/nolog.html | 0 etc/html/civil/stats/index.html | 0 etc/html/civil/theme.conf | 0 etc/html/default/approval.php | 0 etc/html/default/approve.html | 0 etc/html/default/client.html | 0 etc/html/default/digest/commit.html | 0 etc/html/default/digest/footer.html | 0 etc/html/default/digest/wrap.html | 0 etc/html/default/error.html | 0 etc/html/default/footer.html | 2 +- etc/html/default/header.html | 0 etc/html/default/remote/index.html | 0 etc/html/default/remote/nolog.html | 0 etc/html/default/stats/_analytics.html | 0 etc/html/default/stats/index.html | 0 etc/html/default/theme.conf | 0 lib/approval.sh | 0 lib/digest.sh | 0 lib/display-styles.sh | 0 lib/package-manager.sh | 0 lib/permissions-fix.sh | 0 lib/report.sh | 9 ++++++++- lib/smart-commits.sh | 0 lib/user-feedback.sh | 0 lib/wp-acf.sh | 0 lib/wp-plugins.sh | 0 lib/wp-wordfence.sh | 0 lib/wp.sh | 0 lib/yes-no.sh | 0 44 files changed, 17 insertions(+), 4 deletions(-) mode change 100644 => 100755 etc/.deployrc mode change 100644 => 100755 etc/html/civil/approval.php mode change 100644 => 100755 etc/html/civil/approve.html mode change 100644 => 100755 etc/html/civil/client.html mode change 100644 => 100755 etc/html/civil/digest/commit.html mode change 100644 => 100755 etc/html/civil/digest/footer.html mode change 100644 => 100755 etc/html/civil/digest/reference.html mode change 100644 => 100755 etc/html/civil/digest/wrap.html mode change 100644 => 100755 etc/html/civil/error.html mode change 100644 => 100755 etc/html/civil/footer.html mode change 100644 => 100755 etc/html/civil/header.html mode change 100644 => 100755 etc/html/civil/remote/index.html mode change 100644 => 100755 etc/html/civil/remote/nolog.html mode change 100644 => 100755 etc/html/civil/stats/index.html mode change 100644 => 100755 etc/html/civil/theme.conf mode change 100644 => 100755 etc/html/default/approval.php mode change 100644 => 100755 etc/html/default/approve.html mode change 100644 => 100755 etc/html/default/client.html mode change 100644 => 100755 etc/html/default/digest/commit.html mode change 100644 => 100755 etc/html/default/digest/footer.html mode change 100644 => 100755 etc/html/default/digest/wrap.html mode change 100644 => 100755 etc/html/default/error.html mode change 100644 => 100755 etc/html/default/footer.html mode change 100644 => 100755 etc/html/default/header.html mode change 100644 => 100755 etc/html/default/remote/index.html mode change 100644 => 100755 etc/html/default/remote/nolog.html mode change 100644 => 100755 etc/html/default/stats/_analytics.html mode change 100644 => 100755 etc/html/default/stats/index.html mode change 100644 => 100755 etc/html/default/theme.conf mode change 100644 => 100755 lib/approval.sh mode change 100644 => 100755 lib/digest.sh mode change 100644 => 100755 lib/display-styles.sh mode change 100644 => 100755 lib/package-manager.sh mode change 100644 => 100755 lib/permissions-fix.sh mode change 100644 => 100755 lib/smart-commits.sh mode change 100644 => 100755 lib/user-feedback.sh mode change 100644 => 100755 lib/wp-acf.sh mode change 100644 => 100755 lib/wp-plugins.sh mode change 100644 => 100755 lib/wp-wordfence.sh mode change 100644 => 100755 lib/wp.sh mode change 100644 => 100755 lib/yes-no.sh diff --git a/deploy.sh b/deploy.sh index 2b8786f..ccec14c 100755 --- a/deploy.sh +++ b/deploy.sh @@ -62,7 +62,7 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME TASKUSER CLIENTID \ CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN REFRESHTOKEN PROFILEID \ METRIC RESULT ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO REPORTURL \ - CLIENTCONTACT <<< "" + CLIENTCONTACT INCLUDEHOSTING <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} ${DEVGROUP} ${APACHEUSER} ${APACHEGROUP} ${TO} ${FROM} ${SUBJECT} ${EMAILERROR} @@ -77,7 +77,8 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} ${REFRESHTOKEN} ${PROFILEID} ${METRIC} ${RESULT}" "${ALLOWROOT} - ${SHORTEMAIL} ${DIGESTCOVER} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT}" > /dev/null + ${SHORTEMAIL} ${DIGESTCOVER} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} + ${INCLUDEHOSTING}" > /dev/null # Internal variables read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \ urlFile htmlFile htmlSendmail htmlEmail clientEmail textSendmail deployPath \ diff --git a/etc/.deployrc b/etc/.deployrc old mode 100644 new mode 100755 diff --git a/etc/deploy.sh b/etc/deploy.sh index 23bc4b7..0e8a43a 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -33,6 +33,11 @@ # First and last name of the primary contact for this client # CLIENTCONTACT="First Last" +# Include hosting as a line item on monthly reports? If set to TRUE, the report +# line item will read "Monthly web hosting"; customize the text included in +# report by setting it to any other value. +# INCLUDEHOSTING="TRUE" + # If you need to send logfiles and email alerts to address(es) other # than those configured globally, enter them below. # TO="notify@client.com" diff --git a/etc/html/civil/approval.php b/etc/html/civil/approval.php old mode 100644 new mode 100755 diff --git a/etc/html/civil/approve.html b/etc/html/civil/approve.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/client.html b/etc/html/civil/client.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/digest/commit.html b/etc/html/civil/digest/commit.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/digest/footer.html b/etc/html/civil/digest/footer.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/digest/reference.html b/etc/html/civil/digest/reference.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/digest/wrap.html b/etc/html/civil/digest/wrap.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/error.html b/etc/html/civil/error.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/footer.html b/etc/html/civil/footer.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/header.html b/etc/html/civil/header.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/remote/index.html b/etc/html/civil/remote/index.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/remote/nolog.html b/etc/html/civil/remote/nolog.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/stats/index.html b/etc/html/civil/stats/index.html old mode 100644 new mode 100755 diff --git a/etc/html/civil/theme.conf b/etc/html/civil/theme.conf old mode 100644 new mode 100755 diff --git a/etc/html/default/approval.php b/etc/html/default/approval.php old mode 100644 new mode 100755 diff --git a/etc/html/default/approve.html b/etc/html/default/approve.html old mode 100644 new mode 100755 diff --git a/etc/html/default/client.html b/etc/html/default/client.html old mode 100644 new mode 100755 diff --git a/etc/html/default/digest/commit.html b/etc/html/default/digest/commit.html old mode 100644 new mode 100755 diff --git a/etc/html/default/digest/footer.html b/etc/html/default/digest/footer.html old mode 100644 new mode 100755 diff --git a/etc/html/default/digest/wrap.html b/etc/html/default/digest/wrap.html old mode 100644 new mode 100755 diff --git a/etc/html/default/error.html b/etc/html/default/error.html old mode 100644 new mode 100755 diff --git a/etc/html/default/footer.html b/etc/html/default/footer.html old mode 100644 new mode 100755 index 0fe4e6f..e2557fb --- a/etc/html/default/footer.html +++ b/etc/html/default/footer.html @@ -10,7 +10,7 @@ diff --git a/etc/html/default/header.html b/etc/html/default/header.html old mode 100644 new mode 100755 diff --git a/etc/html/default/remote/index.html b/etc/html/default/remote/index.html old mode 100644 new mode 100755 diff --git a/etc/html/default/remote/nolog.html b/etc/html/default/remote/nolog.html old mode 100644 new mode 100755 diff --git a/etc/html/default/stats/_analytics.html b/etc/html/default/stats/_analytics.html old mode 100644 new mode 100755 diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html old mode 100644 new mode 100755 diff --git a/etc/html/default/theme.conf b/etc/html/default/theme.conf old mode 100644 new mode 100755 diff --git a/lib/approval.sh b/lib/approval.sh old mode 100644 new mode 100755 diff --git a/lib/digest.sh b/lib/digest.sh old mode 100644 new mode 100755 diff --git a/lib/display-styles.sh b/lib/display-styles.sh old mode 100644 new mode 100755 diff --git a/lib/package-manager.sh b/lib/package-manager.sh old mode 100644 new mode 100755 diff --git a/lib/permissions-fix.sh b/lib/permissions-fix.sh old mode 100644 new mode 100755 diff --git a/lib/report.sh b/lib/report.sh index 740b991..fd2984d 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -29,8 +29,15 @@ function createReport() { # Try to setup the month/day count LASTDY=`cal ${PRVMTH} ${PRVYR} | egrep "28|29|30|31" |tail -1 |awk '{print $NF}'` + if [[ -n "${INCLUDEHOSTING}" ]] && [[ "${INCLUDEHOSTING}" != "FALSE" ]]; then + if [[ "${INCLUDEHOSTING}" == "TRUE" ]]; then + INCLUDEHOSTING="Monthly web hosting" + fi + echo "" >> "${statFile}" + fi + #if [[ $(git log --before={'date "+%Y-%m-01"'} --after=${PRVYR}-${PRVMTH}-31) ]]; then - git log --all --no-merges --first-parent --before={'date "+%Y-%m-01"'} --after="${PRVYR}-${PRVMTH}-31 00:00" --pretty=format:"" > "${statFile}" + git log --all --no-merges --first-parent --before={'date "+%Y-%m-01"'} --after="${PRVYR}-${PRVMTH}-31 00:00" --pretty=format:"" >> "${statFile}" # If it's an empty report, this empty row will keep the javascript from breaking. Kludgy I know. if [[ ! -s "${statFile}" ]]; then diff --git a/lib/smart-commits.sh b/lib/smart-commits.sh old mode 100644 new mode 100755 diff --git a/lib/user-feedback.sh b/lib/user-feedback.sh old mode 100644 new mode 100755 diff --git a/lib/wp-acf.sh b/lib/wp-acf.sh old mode 100644 new mode 100755 diff --git a/lib/wp-plugins.sh b/lib/wp-plugins.sh old mode 100644 new mode 100755 diff --git a/lib/wp-wordfence.sh b/lib/wp-wordfence.sh old mode 100644 new mode 100755 diff --git a/lib/wp.sh b/lib/wp.sh old mode 100644 new mode 100755 diff --git a/lib/yes-no.sh b/lib/yes-no.sh old mode 100644 new mode 100755 From a1f2917efc277cafb7ae66aa5605c31e0bf2b086 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 21 Nov 2017 17:34:47 -0800 Subject: [PATCH 003/334] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 216b658..81d972a 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Added +- Option to include web hosting as a line item on monthly reports + ## [3.6.5] - 11-17-2017 ### Added - Monthly reports can now be generated using `deploy --report [project name]` From c25acbb1bfca4715a5d5fa08f7b39c0b8d30a009 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 22 Nov 2017 14:27:26 -0800 Subject: [PATCH 004/334] Resolves #106, resolves #111 --- CHANGELOG.md | 2 + etc/html/default/digest/footer.html | 2 +- etc/html/default/remote/index.html | 2 +- etc/html/default/remote/nolog.html | 2 +- etc/html/default/report/css/print.css | 3 ++ etc/html/default/report/css/style.css | 30 +++++++++-- etc/html/default/report/header.html | 76 +++++++++++++++------------ etc/html/default/report/js/edit.js | 4 +- lib/log-handling.sh | 4 +- lib/report.sh | 8 +-- 10 files changed, 85 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d972a..153400e 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Option to include web hosting as a line item on monthly reports +### Changed +- Item descriptions are now editable in reports ## [3.6.5] - 11-17-2017 ### Added diff --git a/etc/html/default/digest/footer.html b/etc/html/default/digest/footer.html index 11cb420..5658922 100755 --- a/etc/html/default/digest/footer.html +++ b/etc/html/default/digest/footer.html @@ -19,7 +19,7 @@ diff --git a/etc/html/default/remote/index.html b/etc/html/default/remote/index.html index 0389094..ce60ec6 100755 --- a/etc/html/default/remote/index.html +++ b/etc/html/default/remote/index.html @@ -92,7 +92,7 @@

Any Quest

diff --git a/etc/html/default/remote/nolog.html b/etc/html/default/remote/nolog.html index bfb9843..52969a2 100755 --- a/etc/html/default/remote/nolog.html +++ b/etc/html/default/remote/nolog.html @@ -81,7 +81,7 @@ diff --git a/etc/html/default/report/css/print.css b/etc/html/default/report/css/print.css index 6781c2f..65433c5 100755 --- a/etc/html/default/report/css/print.css +++ b/etc/html/default/report/css/print.css @@ -1,3 +1,6 @@ +table tr { + border-bottom: solid 1px black; +} #hiderow, .delete { display: none; diff --git a/etc/html/default/report/css/style.css b/etc/html/default/report/css/style.css index d44f792..37999dc 100755 --- a/etc/html/default/report/css/style.css +++ b/etc/html/default/report/css/style.css @@ -23,6 +23,13 @@ table th { border: 1px solid black; padding: 5px; } +table thead { + display: table-header-group; +} +table tr { + page-break-inside: avoid; + page-break-after: auto; +} #header { height: 15px; width: 100%; @@ -124,6 +131,8 @@ table th { width: 100%; margin: 30px 0 0 0; border: 1px solid black; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; } #items th { text-align: left; @@ -182,11 +191,21 @@ table th { text-align: center; } textarea:hover, -textarea:focus, #items td.total-value textarea:hover, -#items td.total-value textarea:focus, +#items td.total-value textarea:focus, +div.editable:hover { + background-color: none; + outline: none !important; + border-bottom: none; + box-shadow: inset 0 -1px 0px 0px #E6E6E6; +} +textarea:focus, +div.editable:focus { + outline: none !important; + box-shadow: inset 0 -1px 0px 0px #bbb; +} .delete:hover { - background-color: #EEFF88; + color: #ffffff; } .delete-wpr { position: relative; @@ -203,4 +222,7 @@ textarea:focus, top: 0px; left: -22px; font-size: 12px; -} \ No newline at end of file +} +.editable { + outline: none; +} diff --git a/etc/html/default/report/header.html b/etc/html/default/report/header.html index 0cdcc63..ec6e499 100755 --- a/etc/html/default/report/header.html +++ b/etc/html/default/report/header.html @@ -3,43 +3,51 @@ - - Monthly Maintenance Report for {{PROJCLIENT}} - - - - - + + Monthly Maintenance Report for {{PROJCLIENT}} + + + + + + -
-
- {{LASTMONTH}} maintenance report for -
-

{{PROJCLIENT}}
- -

+
+
+ {{LASTMONTH}} maintenance report for +
+

{{PROJCLIENT}}
+ +

-
-

This report for the month of {{LASTMONTH}} includes notes on system updates, security patches, and other code changes on {{PRODURL}}

-
+
+

+ This report for the month of {{LASTMONTH}} includes notes on system updates, security patches, and other code changes on {{PRODURL}} +

+
-
- - - - - - - - -
Invoice #
Date
- + + + + + -
Invoice #
- - - - - + + + + +
ItemDescription
Date
+ + + + + + + + + + diff --git a/etc/html/default/report/js/edit.js b/etc/html/default/report/js/edit.js index 2f20ac7..cf95d27 100755 --- a/etc/html/default/report/js/edit.js +++ b/etc/html/default/report/js/edit.js @@ -105,7 +105,7 @@ $(document).ready(function() { $("#paid").blur(update_balance); $("#addrow").click(function(){ - $(".item-row:last").after(''); + $(".item-row:last").after(''); if ($(".delete").length > 0) $(".delete").show(); bind(); }); @@ -136,4 +136,4 @@ $(document).ready(function() { $("#date").val(print_today()); -}); \ No newline at end of file +}); diff --git a/lib/log-handling.sh b/lib/log-handling.sh index 1ec33d3..da22732 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -173,7 +173,7 @@ function htmlBuild() { fi } -# Remote log function; this really neexds to be rewritten +# Remote log function; this really needs to be rewritten function postLog() { if [[ "${REMOTELOG}" == "TRUE" ]]; then # Post to localhost by simply copying files @@ -416,8 +416,8 @@ function htmlDir() { mkdir "${LOCALHOSTPATH}/${APP}/report"; errorChk mkdir "${LOCALHOSTPATH}/${APP}/report/css"; errorChk mkdir "${LOCALHOSTPATH}/${APP}/report/js"; errorChk + fi cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${LOCALHOSTPATH}/${APP}/report"; errorChk cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${LOCALHOSTPATH}/${APP}/report"; errorChk - fi fi } diff --git a/lib/report.sh b/lib/report.sh index fd2984d..2b8c004 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -30,14 +30,16 @@ function createReport() { LASTDY=`cal ${PRVMTH} ${PRVYR} | egrep "28|29|30|31" |tail -1 |awk '{print $NF}'` if [[ -n "${INCLUDEHOSTING}" ]] && [[ "${INCLUDEHOSTING}" != "FALSE" ]]; then + # If INCLUDEHOSTING is equal to something other than TRUE, its value + # will be used as the text string in the report if [[ "${INCLUDEHOSTING}" == "TRUE" ]]; then - INCLUDEHOSTING="Monthly web hosting" + INCLUDEHOSTING="Web hosting for the month of ${LASTMONTH}" fi - echo "" >> "${statFile}" + echo "" >> "${statFile}" fi #if [[ $(git log --before={'date "+%Y-%m-01"'} --after=${PRVYR}-${PRVMTH}-31) ]]; then - git log --all --no-merges --first-parent --before={'date "+%Y-%m-01"'} --after="${PRVYR}-${PRVMTH}-31 00:00" --pretty=format:"" >> "${statFile}" + git log --all --no-merges --first-parent --before={'date "+%Y-%m-01"'} --after="${PRVYR}-${PRVMTH}-31 00:00" --pretty=format:"" >> "${statFile}" # If it's an empty report, this empty row will keep the javascript from breaking. Kludgy I know. if [[ ! -s "${statFile}" ]]; then From 98aacb836f3049f8ba3a3d5812daa0e7ab58b947 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 28 Nov 2017 13:43:14 -0800 Subject: [PATCH 005/334] Resolve #108 --- CHANGELOG.md | 2 ++ deploy.sh | 2 +- lib/ssh-check.sh | 6 +++--- lib/utilities.sh | 7 ++++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 153400e..65faba2 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Option to include web hosting as a line item on monthly reports ### Changed - Item descriptions are now editable in reports +### Fixed +- Fixed a bug that kept SSH keys from being properly checked ## [3.6.5] - 11-17-2017 ### Added diff --git a/deploy.sh b/deploy.sh index ccec14c..9b4c6d4 100755 --- a/deploy.sh +++ b/deploy.sh @@ -538,7 +538,7 @@ if [[ ! -f "${WORKPATH}/${APP}/.queued" ]]; then fi # Disable SSH check for those that will never need it -if [[ "${NOKEY}"="TRUE" ]]; then +if [[ "${NOKEY}" == "TRUE" ]]; then DISABLESSHCHECK="TRUE" fi diff --git a/lib/ssh-check.sh b/lib/ssh-check.sh index 51628e3..c59ed81 100755 --- a/lib/ssh-check.sh +++ b/lib/ssh-check.sh @@ -6,6 +6,8 @@ # SSH checks for both Bitbucket and Github ############################################################################### +# TODO: Rewrite this to store git@domain stuff in a variable, allow for other +# repohosts to work (Gitlab etc.) and shorten the entire function function sshChk() { if [[ "${NOKEY}" != "TRUE" ]]; then trace "Checking SSH configuration" @@ -18,9 +20,7 @@ function sshChk() { trace "git@bitbucket.org: OK" fi elif [[ "${REPOHOST}" == *"github"* ]]; then - ssh -oStrictHostKeyChecking=no git@github.org &> /dev/null; errorStatus - #ssh -T git@github.com &> /dev/null; errorStatus - + ssh -oStrictHostKeyChecking=no git@github.org &> /dev/null; errorStatus if [[ "${EXITCODE}" != "0" ]]; then error "git@github.org: SSH check failed (Error code ${EXITCODE})" else diff --git a/lib/utilities.sh b/lib/utilities.sh index c7a19a5..f7b2aa5 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -37,9 +37,9 @@ function go() { analyticsTest; quickExit fi - # Test SSH key authentication + # Test SSH key authentication using the --ssh-check flag if [[ "${SSHTEST}" == "1" ]]; then - if [[ "${NOKEY}" != "TRUE" ]]; then + if [[ "${NOKEY}" != "TRUE" ]] && [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then notice "Checking SSH Configuration..." sshChk else @@ -140,7 +140,8 @@ function depCheck() { if yesno --default yes "Would you like to edit the configuration file now? [Y/n] "; then nano "${APPRC}" clear; sleep 1 - quickExit + $(basename ${APP}) && exit + # exec "/usr/local/bin/deploy ${STARTUP} ${APP}" fi info "You can change configuration later by editing ${APPRC}" fi From 637a5482b5b40223aad73dd30da63f1f357b4ce9 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 28 Nov 2017 17:49:38 -0800 Subject: [PATCH 006/334] Remote log hosting enabled --- CHANGELOG.md | 1 + deploy.sh | 20 ++-- etc/deploy.sh | 31 ++++++ lib/digest.sh | 12 ++- lib/log-handling.sh | 249 -------------------------------------------- lib/mail-log.sh | 190 +++++++++++++++++++++++++++++++++ lib/post-log.sh | 124 ++++++++++++++++++++++ lib/statistics.sh | 62 ++++++----- lib/wp.sh | 2 +- 9 files changed, 401 insertions(+), 290 deletions(-) create mode 100755 lib/mail-log.sh create mode 100755 lib/post-log.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 65faba2..cb331bd 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Option to include web hosting as a line item on monthly reports +- Remote servers can now host project logs, digests, statistics, and reports ### Changed - Item descriptions are now editable in reports ### Fixed diff --git a/deploy.sh b/deploy.sh index 9b4c6d4..0896d15 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.5" +VERSION="3.6.6rc" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LASTMONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" @@ -58,10 +58,10 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ PROJNAME PROJCLIENT DEVURL PRODURL REPO MASTER PRODUCTION COMMITMSG DEPLOY \ DONOTDEPLOY TASK CHECKBRANCH ACTIVECHECK CHECKTIME GARBAGE WFCHECK ACFKEY \ WFOFF REMOTELOG POSTTOLOCALHOST LOCALHOSTPATH DIGESTEMAIL DIGESTSLACK DIGESTURL \ - CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS LOGMSG \ - EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME TASKUSER CLIENTID \ - CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN REFRESHTOKEN PROFILEID \ - METRIC RESULT ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO REPORTURL \ + CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD SSHCMD \ + LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME TASKUSER \ + CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN REFRESHTOKEN \ + PROFILEID METRIC RESULT ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO REPORTURL \ CLIENTCONTACT INCLUDEHOSTING <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} @@ -73,9 +73,9 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${CHECKTIME} ${GARBAGE} ${WFCHECK} ${ACFKEY} ${WFOFF} ${REMOTELOG} ${POSTTOLOCALHOST} ${LOCALHOSTPATH} ${DIGESTEMAIL} ${DIGESTSLACK} ${DIGESTURL} ${CLIENTLOGO} ${REMOTEURL} ${SCPPOST} ${SCPUSER} ${SCPHOST} - ${SCPHOSTPATH} ${SCPPASS} ${LOGMSG} ${EXPIRELOGS} ${SERVERCHECK} - ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} ${CLIENTID} - ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} + ${SCPHOSTPATH} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} ${EXPIRELOGS} + ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} + ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} ${REFRESHTOKEN} ${PROFILEID} ${METRIC} ${RESULT}" "${ALLOWROOT} ${SHORTEMAIL} ${DIGESTCOVER} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} ${INCLUDEHOSTING}" > /dev/null @@ -91,7 +91,7 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile AUTHORNAME GRAVATAR IMGFILE SIZE RND ANALYTICSMSG digestSendmail MINAUSER \ MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX QUEUED \ DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile CURMTH \ - CURYR PRVMTH PRVYR LASTDY <<< "" + CURYR PRVMTH PRVYR LASTDY TMP <<< "" echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile} ${trshFile} ${statFile} ${urlFile} ${htmlFile} ${htmlSendmail} ${htmlEmail} ${clientEmail} ${textSendmail} ${deployPath} ${etcLocation} ${libLocation} @@ -106,7 +106,7 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${digestSendmail} ${MINAUSER} ${MINADOMAIN} ${SSHTARGET} ${SSHSTATUS} ${REMOTEFILE} ${GREETING} ${LOGSUFFIX} ${QUEUED} ${DISABLESSHCHECK} ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} ${CURMTH} ${CURYR} - ${PRVMTH} ${PRVYR} ${LASTDY}" > /dev/null + ${PRVMTH} ${PRVYR} ${LASTDY} ${TMP}" > /dev/null # Options function flags() { diff --git a/etc/deploy.sh b/etc/deploy.sh index 0e8a43a..45db2e1 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -166,8 +166,39 @@ # Enable the settings below to post on WARNiNG and/or ERROR. # SLACKERROR="FALSE" +# Logging +# +# Post HTML logs to remote server. This needs to be set to "TRUE" even you +# are only posting to LOCALHOST. +# REMOTELOG="TRUE" +# +# Define the root url where the deploy log will be accessible with no +# trailing slash +# REMOTEURL="http://deploy.domain.com" +# +# If using HTML logs, define which template you'd like to use. HTML templates +# are stored in separate folders in /etc/deploy/html. The value used below +# should be the folder name of your template. +# REMOTETEMPLATE="default" +# +# Post logs via SCP +# SCPPOST="TRUE" +# SCPUSER="user" +# SCPHOST="hostname.com" +# SCPHOSTPATH="/full/path/to/file" +# +# DANGER DANGER: If for some reason you absolutely can't use an SSH key you +# can configure your password here +# SCPPASS="password" +# # Post commit logs to this URL. # POSTURL="" +# +# If you're posting logs to a place on the same machine you're deploying from, +# set POSTTOLOCALHOST to "TRUE" and define the path where you want to store +# the HTML logs. +# LOCALHOSTPOST="TRUE" +# LOCALHOSTPATH="/var/www/production/deploy" # Google Analytics # diff --git a/lib/digest.sh b/lib/digest.sh index 5ec2458..744d96c 100755 --- a/lib/digest.sh +++ b/lib/digest.sh @@ -16,10 +16,15 @@ function createDigest() { AUTHOREMAIL=$(echo $AUTHOR | cut -d\| -f1 | tr -d '[[:space:]]' | tr '[:upper:]' '[:lower:]') AUTHORNAME=$(echo $AUTHOR | cut -d\| -f2) GRAVATAR="http://www.gravatar.com/avatar/$(echo -n $AUTHOREMAIL | md5sum)?d=404&s=200" - IMGFILE="${LOCALHOSTPATH}/${APP}/avatar/$AUTHORNAME.png" - # if [[ ! -f $IMGFILE ]]; then # If you wanna cache? + if [[ "${SCPPOST}" != "TRUE" ]]; then + IMGFILE="${LOCALHOSTPATH}/${APP}/avatar/$AUTHORNAME.png" + else + if [[ ! -d "/tmp/avatar" ]]; then + umask 077 && mkdir /tmp/avatar &> /dev/null + fi + IMGFILE="/tmp/avatar/${AUTHORNAME}.png" + fi curl -fso "${IMGFILE}" "${GRAVATAR}" - # fi done # Attempt to get analytics @@ -59,7 +64,6 @@ function createDigest() { # Git some stats # git log --no-merges --since="7 days ago" --reverse --stat | grep -Eo "[0-9]{1,} files? changed" | grep -Eo "[0-9]{1,}" | awk "{ sum += \$1 } END { print sum }" - processHTML if [[ -z "${RESULT}" ]] || [[ "${RESULT}" == "0" ]] || [[ "${SIZE}" == "0" ]]; then diff --git a/lib/log-handling.sh b/lib/log-handling.sh index da22732..6657f88 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -172,252 +172,3 @@ function htmlBuild() { processHTML fi } - -# Remote log function; this really needs to be rewritten -function postLog() { - if [[ "${REMOTELOG}" == "TRUE" ]]; then - # Post to localhost by simply copying files - if [[ "${LOCALHOSTPOST}" == "TRUE" ]] && [[ -f "${htmlFile}" ]]; then - # Check that directory exists - htmlDir - - # Post the file - if [[ -n "${REMOTEFILE}" ]] && [[ "${REPORT}" != "1" ]]; then #&& [[ -n "${COMMITHASH}" ]]; then - cp "${htmlFile}" "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" - chmod a+rw "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null - fi - - # Post the digest - if [[ "${DIGEST}" == "1" ]]; then - REMOTEFILE="digest-${EPOCH}.html" - cp "${htmlFile}" "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" - chmod a+rw "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null - DIGESTURL="${REMOTEURL}/${APP}/${REMOTEFILE}" - fi - - # Post the report - if [[ "${REPORT}" == "1" ]]; then - REMOTEFILE="${EPOCH}.php" - cp "${htmlFile}" "${LOCALHOSTPATH}/${APP}/report/${REMOTEFILE}" - chmod a+rw "${LOCALHOSTPATH}/${APP}/report/${REMOTEFILE}" &> /dev/null - REPORTURL="${REMOTEURL}/${APP}/report/${REMOTEFILE}" - fi - - # Remove logs older then X days - if [[ -n "${EXPIRELOGS}" ]]; then - find "${LOCALHOSTPATH}/${APP}"* -mtime +"${EXPIRELOGS}" -exec rm {} \; &> /dev/null - fi - fi - - # Send the files through SCP (not yet enabled) - if [[ "${SCPPOST}" == "TRUE" ]]; then - if [[ -n "${SCPPASS}" ]]; then - sshpass -p "${SCPPASS}" scp -o StrictHostKeyChecking=no "${htmlFile}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}${COMMITHASH}.html" &> /dev/null - else - scp "${htmlFile}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/{COMMITHASH}.html" &> /dev/null - fi - fi - fi -} - -function mailLog() { - # Only send email if a commit has been made, an approval is required, or there has been an error - if [[ -n "${COMMITHASH}" ]] || [[ "${message_state}" == "ERROR" ]] || [[ "${message_state}" == "APPROVAL NEEDED" ]] || [[ "${AUTOMATE}" == "1" ]]; then - - # If using --current, use the REPO value instead of the APP (current directory) - if [[ "${CURRENT}" == "1" ]]; then - APP="${REPO}" - fi - - if [[ "${EMAILHTML}" == "TRUE" ]]; then - # Send the email - ( - echo "Sender: ${FROM}" - echo "From: ${FROM} <${FROM}>" - echo "Reply-To: ${FROM} <${FROM}>" - echo "To: ${TO}" - echo "Subject: [${message_state}] ${SUBJECT} - ${APP}" - echo "Content-Type: text/html" - echo - echo "${htmlSendmail}"; - ) | "${MAILPATH}"/sendmail -t - else - # Compile and send text format email - textSendmail=$(<"${logFile}") - ( - echo "Sender: ${FROM}" - echo "From: ${FROM} <${FROM}>" - echo "Reply-To: ${FROM} <${FROM}>" - echo "To: ${TO}" - echo "Subject: [${message_state}] ${SUBJECT} - ${APP}" - echo "Content-Type: text/plain" - echo - echo "${textSendmail}"; - ) | "${MAILPATH}"/sendmail -t - fi - fi - - # Is this a digest email? - if [[ -n "${DIGESTEMAIL}" ]] && [[ "${DIGEST}" == "1" ]] && [[ -n "${digestSendmail}" ]]; then - # Send the email - ( - echo "Sender: ${FROM}" - echo "From: EMRL <${FROM}>" - echo "Reply-To: ${FROM} <${FROM}>" - echo "To: ${DIGESTEMAIL}" - echo "Subject: ${PROJNAME} updates for the week of ${WEEKOF}" - echo "Content-Type: text/html" - echo - echo "${digestSendmail}"; - ) | "${MAILPATH}"/sendmail -t - fi -} - -function emailTest() { - console "Testing email..." - if [[ -z "${TO}" ]]; then - warning "No recipient address found."; emptyLine - cleanUp; exit 1 - else - # Send HTML mail - ( - echo "Sender: ${FROM}" - echo "From: ${FROM} <${FROM}>" - echo "Reply-To: ${FROM} <${FROM}>" - echo "To: ${TO}" - echo "Subject: [TESTING] ${SUBJECT} - ${APP}" - echo "Content-Type: text/html" - echo - echo "This is a test HTML email from deploy ${VERSION}.
" - echo "Current user is ${DEV}

"; - echo - echo "Project Information
" - echo "Name: ${PROJNAME}
" - echo "Client: ${PROJCLIENT}
" - echo "Logo: ${CLIENTLOGO}
" - echo "Digest email(s): ${DIGESTEMAIL}
" - echo "Staging URL: ${DEVURL}
" - echo "Production URL: ${PRODURL}

" - echo - echo "Git Configuration
" - echo "Repo: ${REPOHOST}/${REPO}
" - echo "Master branch: ${MASTER}
" - echo "Production branch: ${PRODUCTION}
" - echo "Auto merge: ${AUTOMERGE}
" - echo "File Stashing: ${STASH}
" - echo "Force branch checking: ${CHECKBRANCH}

" - echo - echo "Wordpress Setup
" - echo "Wordpress root: ${WPROOT}
" - echo "Wordpress application: ${WPAPP}
" - echo "Wordpress system: ${WPSYSTEM}

" - echo - echo "Deployment Configuration
" - echo "Deploy command: ${DEPLOY}
" - echo "Disallow deployment: ${DONOTDEPLOY}

" - echo - echo "Integration
" - echo "Task #: ${TASK}
" - echo "Task user: ${TASKUSER}
" - echo "Task time: ${ADDTIME}
" - echo "Post to Slack: ${POSTTOSLACK}
" - echo "Post errors to Slack: ${SLACKERROR}
" - echo "Google Analytics ID: ${PROFILEID}"; - ) | "${MAILPATH}"/sendmail -t - # Send Text mail - ( - echo "Sender: ${FROM}" - echo "From: ${FROM} <${FROM}>" - echo "Reply-To: ${FROM} <${FROM}>" - echo "To: ${TO}" - echo "Subject: [TESTING] ${SUBJECT} - ${APP}" - echo "Content-Type: text/plain" - echo - echo "This is a test TEXT email from deploy ${VERSION} (https://github.com/EMRL/deploy/)" - echo "Current user is ${DEV}"; - echo - echo "Project Information" - echo "-------------------" - echo "Name: ${PROJNAME}" - echo "Client: ${PROJCLIENT}" - echo "Logo: ${CLIENTLOGO}" - echo "Digest email(s): ${DIGESTEMAIL}" - echo "Staging URL: ${DEVURL}" - echo "Production URL: ${PRODURL}" - echo - echo "Git Configuration" - echo "-----------------" - echo "Repo: ${REPOHOST}/${REPO}" - echo "Master branch: ${MASTER}" - echo "Production branch: ${PRODUCTION}" - echo "Auto merge: ${AUTOMERGE}" - echo "File Stashing: ${STASH}" - echo "Force branch checking: ${CHECKBRANCH}" - echo - echo "Wordpress Setup" - echo "-----------------------" - echo "Wordpress root: ${WPROOT}" - echo "Wordpress application: ${WPAPP}" - echo "Wordpress system: ${WPSYSTEM}" - echo - echo "Deployment Configuration" - echo "------------------------" - echo "Deploy command: ${DEPLOY}" - echo "Disallow deployment: ${DONOTDEPLOY}" - echo - echo "Integration" - echo "-----------" - echo "Task #: ${TASK}" - echo "Task user: ${TASKUSER}" - echo "Task time: ${ADDTIME}" - echo "Post to Slack: ${POSTTOSLACK}" - echo "Post errors to Slack: ${SLACKERROR}" - echo "Google Analytics ID: ${PROFILEID}"; - ) | "${MAILPATH}"/sendmail -t - fi - - # If an integration is setup, let's test it - if [[ ! -z "${TASK}" ]]; then - sleep 2 - if [[ "${POSTEMAILHEAD}${TASK}${POSTEMAILTAIL}" == ?*@?*.?* ]]; then - console "Testing integration to ${POSTEMAIL}" - ( - if [[ -z "${TASKUSER}" ]] || [[ -z "${ADDTIME}" ]]; then - echo "From: ${FROM}" - else - echo "From: ${TASKUSER}" - fi - echo "Reply-To: ${FROM} <${FROM}>" - echo "To: ${POSTEMAILHEAD}${TASK}${POSTEMAILTAIL}" - echo "Subject: [TESTING] ${SUBJECT} - ${APP}" - echo "Content-Type: text/plain" - echo - echo "This is a test email integration from deploy ${VERSION}" - echo "(https://github.com/EMRL/deploy/)" - ) | "${MAILPATH}"/sendmail -t - quietExit - else - console "Integration email address ${POSTEMAILHEAD}${TASK}${POSTEMAILTAIL} does not look valid"; quietExit - fi - fi -} - -function htmlDir() { - if [[ ! -d "${LOCALHOSTPATH}/${APP}" ]]; then - mkdir "${LOCALHOSTPATH}/${APP}" - fi - - if [[ ! -d "${LOCALHOSTPATH}/${APP}/avatar" ]]; then - mkdir "${LOCALHOSTPATH}/${APP}/avatar" - fi - - if [[ "${message_state}" == "REPORT" ]]; then - if [[ ! -d "${LOCALHOSTPATH}/${APP}/report" ]]; then - mkdir "${LOCALHOSTPATH}/${APP}/report"; errorChk - mkdir "${LOCALHOSTPATH}/${APP}/report/css"; errorChk - mkdir "${LOCALHOSTPATH}/${APP}/report/js"; errorChk - fi - cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${LOCALHOSTPATH}/${APP}/report"; errorChk - cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${LOCALHOSTPATH}/${APP}/report"; errorChk - fi -} diff --git a/lib/mail-log.sh b/lib/mail-log.sh new file mode 100755 index 0000000..42be1df --- /dev/null +++ b/lib/mail-log.sh @@ -0,0 +1,190 @@ +#!/bin/bash +# +# mail-log.sh +# +############################################################################### +# Mail handling +############################################################################### +trace "Loading mail handler" + +function mailLog() { + # Only send email if a commit has been made, an approval is required, or there has been an error + if [[ -n "${COMMITHASH}" ]] || [[ "${message_state}" == "ERROR" ]] || [[ "${message_state}" == "APPROVAL NEEDED" ]] || [[ "${AUTOMATE}" == "1" ]]; then + + # If using --current, use the REPO value instead of the APP (current directory) + if [[ "${CURRENT}" == "1" ]]; then + APP="${REPO}" + fi + + if [[ "${EMAILHTML}" == "TRUE" ]]; then + # Send the email + ( + echo "Sender: ${FROM}" + echo "From: ${FROM} <${FROM}>" + echo "Reply-To: ${FROM} <${FROM}>" + echo "To: ${TO}" + echo "Subject: [${message_state}] ${SUBJECT} - ${APP}" + echo "Content-Type: text/html" + echo + echo "${htmlSendmail}"; + ) | "${MAILPATH}"/sendmail -t + else + # Compile and send text format email + textSendmail=$(<"${logFile}") + ( + echo "Sender: ${FROM}" + echo "From: ${FROM} <${FROM}>" + echo "Reply-To: ${FROM} <${FROM}>" + echo "To: ${TO}" + echo "Subject: [${message_state}] ${SUBJECT} - ${APP}" + echo "Content-Type: text/plain" + echo + echo "${textSendmail}"; + ) | "${MAILPATH}"/sendmail -t + fi + fi + + # Is this a digest email? + if [[ -n "${DIGESTEMAIL}" ]] && [[ "${DIGEST}" == "1" ]] && [[ -n "${digestSendmail}" ]]; then + # Send the email + ( + echo "Sender: ${FROM}" + echo "From: EMRL <${FROM}>" + echo "Reply-To: ${FROM} <${FROM}>" + echo "To: ${DIGESTEMAIL}" + echo "Subject: ${PROJNAME} updates for the week of ${WEEKOF}" + echo "Content-Type: text/html" + echo + echo "${digestSendmail}"; + ) | "${MAILPATH}"/sendmail -t + fi +} + +function emailTest() { + console "Testing email..." + if [[ -z "${TO}" ]]; then + warning "No recipient address found."; emptyLine + cleanUp; exit 1 + else + # Send HTML mail + ( + echo "Sender: ${FROM}" + echo "From: ${FROM} <${FROM}>" + echo "Reply-To: ${FROM} <${FROM}>" + echo "To: ${TO}" + echo "Subject: [TESTING] ${SUBJECT} - ${APP}" + echo "Content-Type: text/html" + echo + echo "This is a test HTML email from deploy ${VERSION}.
" + echo "Current user is ${DEV}

"; + echo + echo "Project Information
" + echo "Name: ${PROJNAME}
" + echo "Client: ${PROJCLIENT}
" + echo "Logo: ${CLIENTLOGO}
" + echo "Digest email(s): ${DIGESTEMAIL}
" + echo "Staging URL: ${DEVURL}
" + echo "Production URL: ${PRODURL}

" + echo + echo "Git Configuration
" + echo "Repo: ${REPOHOST}/${REPO}
" + echo "Master branch: ${MASTER}
" + echo "Production branch: ${PRODUCTION}
" + echo "Auto merge: ${AUTOMERGE}
" + echo "File Stashing: ${STASH}
" + echo "Force branch checking: ${CHECKBRANCH}

" + echo + echo "Wordpress Setup
" + echo "Wordpress root: ${WPROOT}
" + echo "Wordpress application: ${WPAPP}
" + echo "Wordpress system: ${WPSYSTEM}

" + echo + echo "Deployment Configuration
" + echo "Deploy command: ${DEPLOY}
" + echo "Disallow deployment: ${DONOTDEPLOY}

" + echo + echo "Integration
" + echo "Task #: ${TASK}
" + echo "Task user: ${TASKUSER}
" + echo "Task time: ${ADDTIME}
" + echo "Post to Slack: ${POSTTOSLACK}
" + echo "Post errors to Slack: ${SLACKERROR}
" + echo "Google Analytics ID: ${PROFILEID}"; + ) | "${MAILPATH}"/sendmail -t + # Send Text mail + ( + echo "Sender: ${FROM}" + echo "From: ${FROM} <${FROM}>" + echo "Reply-To: ${FROM} <${FROM}>" + echo "To: ${TO}" + echo "Subject: [TESTING] ${SUBJECT} - ${APP}" + echo "Content-Type: text/plain" + echo + echo "This is a test TEXT email from deploy ${VERSION} (https://github.com/EMRL/deploy/)" + echo "Current user is ${DEV}"; + echo + echo "Project Information" + echo "-------------------" + echo "Name: ${PROJNAME}" + echo "Client: ${PROJCLIENT}" + echo "Logo: ${CLIENTLOGO}" + echo "Digest email(s): ${DIGESTEMAIL}" + echo "Staging URL: ${DEVURL}" + echo "Production URL: ${PRODURL}" + echo + echo "Git Configuration" + echo "-----------------" + echo "Repo: ${REPOHOST}/${REPO}" + echo "Master branch: ${MASTER}" + echo "Production branch: ${PRODUCTION}" + echo "Auto merge: ${AUTOMERGE}" + echo "File Stashing: ${STASH}" + echo "Force branch checking: ${CHECKBRANCH}" + echo + echo "Wordpress Setup" + echo "-----------------------" + echo "Wordpress root: ${WPROOT}" + echo "Wordpress application: ${WPAPP}" + echo "Wordpress system: ${WPSYSTEM}" + echo + echo "Deployment Configuration" + echo "------------------------" + echo "Deploy command: ${DEPLOY}" + echo "Disallow deployment: ${DONOTDEPLOY}" + echo + echo "Integration" + echo "-----------" + echo "Task #: ${TASK}" + echo "Task user: ${TASKUSER}" + echo "Task time: ${ADDTIME}" + echo "Post to Slack: ${POSTTOSLACK}" + echo "Post errors to Slack: ${SLACKERROR}" + echo "Google Analytics ID: ${PROFILEID}"; + ) | "${MAILPATH}"/sendmail -t + fi + + # If an integration is setup, let's test it + if [[ ! -z "${TASK}" ]]; then + sleep 2 + if [[ "${POSTEMAILHEAD}${TASK}${POSTEMAILTAIL}" == ?*@?*.?* ]]; then + console "Testing integration to ${POSTEMAIL}" + ( + if [[ -z "${TASKUSER}" ]] || [[ -z "${ADDTIME}" ]]; then + echo "From: ${FROM}" + else + echo "From: ${TASKUSER}" + fi + echo "Reply-To: ${FROM} <${FROM}>" + echo "To: ${POSTEMAILHEAD}${TASK}${POSTEMAILTAIL}" + echo "Subject: [TESTING] ${SUBJECT} - ${APP}" + echo "Content-Type: text/plain" + echo + echo "This is a test email integration from deploy ${VERSION}" + echo "(https://github.com/EMRL/deploy/)" + ) | "${MAILPATH}"/sendmail -t + quietExit + else + console "Integration email address ${POSTEMAILHEAD}${TASK}${POSTEMAILTAIL} does not look valid"; quietExit + fi + fi +} diff --git a/lib/post-log.sh b/lib/post-log.sh new file mode 100755 index 0000000..1154cc5 --- /dev/null +++ b/lib/post-log.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# +# post-log.sh +# +############################################################################### +# Handles posting logs to localhost as well as remote hosts +############################################################################### +trace "Loading post functions" + +# Remote log function; this really needs to be rewritten +function postLog() { + if [[ "${REMOTELOG}" == "TRUE" ]]; then + + # Post to localhost by simply copying files + if [[ "${LOCALHOSTPOST}" == "TRUE" ]] && [[ -f "${htmlFile}" ]]; then + + # Check that directory exists + htmlDir + + # Post the file + if [[ -n "${REMOTEFILE}" ]] && [[ "${REPORT}" != "1" ]]; then #&& [[ -n "${COMMITHASH}" ]]; then + cp "${htmlFile}" "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" + chmod a+rw "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null + fi + + # Post the digest + if [[ "${DIGEST}" == "1" ]]; then + REMOTEFILE="digest-${EPOCH}.html" + cp "${htmlFile}" "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" + chmod a+rw "${LOCALHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null + DIGESTURL="${REMOTEURL}/${APP}/${REMOTEFILE}" + fi + + # Post the report + if [[ "${REPORT}" == "1" ]]; then + REMOTEFILE="${EPOCH}.php" + cp "${htmlFile}" "${LOCALHOSTPATH}/${APP}/report/${REMOTEFILE}" + chmod a+rw "${LOCALHOSTPATH}/${APP}/report/${REMOTEFILE}" &> /dev/null + REPORTURL="${REMOTEURL}/${APP}/report/${REMOTEFILE}" + fi + + # Statistics + if [[ "${PROJSTATS}" == "1" ]]; then + [[ ! -d "${LOCALHOSTPATH}/${APP}" ]] && mkdir "${LOCALHOSTPATH}/${APP}" + [[ ! -d "${LOCALHOSTPATH}/${APP}/stats" ]] && mkdir "${LOCALHOSTPATH}/${APP}/stats" + cp -R "/tmp/stats" "${LOCALHOSTPATH}/${APP}" + chmod -R a+rw "${deployPath}/html/${HTMLTEMPLATE}/stats" &> /dev/null + fi + + # Remove logs older then X days + if [[ -n "${EXPIRELOGS}" ]]; then + find "${LOCALHOSTPATH}/${APP}"* -mtime +"${EXPIRELOGS}" -exec rm {} \; &> /dev/null + fi + fi + + # Send the files through SCP (not yet enabled) + if [[ "${SCPPOST}" == "TRUE" ]] && [[ -f "${htmlFile}" ]]; then + + # Setup up the proper command, depending on whether we're using key or password + if [[ -n "${SCPPASS}" ]]; then + TMP=$(<$SCPPASS) + SCPPASS="${TMP}" + SCPCMD="sshpass -p \"${SCPPASS}\" scp -o StrictHostKeyChecking=no" + SSHCMD="sshpass -p \"${SCPPASS}\" ssh" + else + SCPCMD="scp" + SSHCMD="ssh" + fi + + # Loop through the various scenarios, make directories if needed + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}" + + if [[ "${DIGEST}" == "1" ]]; then + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/avatar" + eval "${SCPCMD} -r" "/tmp/avatar/" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}" + # Clean up your mess + if [[ -d "/tmp/avatar" ]]; then + rm -R "/tmp/avatar" + fi + fi + + if [[ "${PROJSTATS}" == "1" ]]; then + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/stats" + eval "${SCPCMD} -r" "/tmp/stats/" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}" + # Clean up your mess + if [[ -d "/tmp/stats" ]]; then + rm -R "/tmp/stats" + fi + fi + + if [[ "${REPORT}" == "1" ]]; then + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report" + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report/css" + eval "${SCPCMD} -r" "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/report" + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report/js" + eval "${SCPCMD} -r" "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/report" + fi + + # Send over the logs and set permissions + eval "${SCPCMD}" "${htmlFile}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "chmod -R 755 ${SCPHOSTPATH}/${APP}/" + fi + fi +} + +function htmlDir() { + if [[ ! -d "${LOCALHOSTPATH}/${APP}" ]]; then + mkdir "${LOCALHOSTPATH}/${APP}" + fi + + if [[ ! -d "${LOCALHOSTPATH}/${APP}/avatar" ]]; then + mkdir "${LOCALHOSTPATH}/${APP}/avatar" + fi + + if [[ "${message_state}" == "REPORT" ]]; then + if [[ ! -d "${LOCALHOSTPATH}/${APP}/report" ]]; then + mkdir "${LOCALHOSTPATH}/${APP}/report"; errorChk + mkdir "${LOCALHOSTPATH}/${APP}/report/css"; errorChk + mkdir "${LOCALHOSTPATH}/${APP}/report/js"; errorChk + fi + cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${LOCALHOSTPATH}/${APP}/report"; errorChk + cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${LOCALHOSTPATH}/${APP}/report"; errorChk + fi +} diff --git a/lib/statistics.sh b/lib/statistics.sh index e117c19..01b4522 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -8,33 +8,43 @@ trace "Loading statistics functions" function projStats() { - hash gitchart 2>/dev/null || { - error "gitchart not installed." - } - if [[ "${REMOTELOG}" == "TRUE" ]] && [[ "${LOCALHOSTPOST}" == "TRUE" ]]; then - [[ ! -d "${LOCALHOSTPATH}/${APP}" ]] && mkdir "${LOCALHOSTPATH}/${APP}" - [[ ! -d "${LOCALHOSTPATH}/${APP}/stats" ]] && mkdir "${LOCALHOSTPATH}/${APP}/stats" - notice "Generating files..." + hash gitchart 2>/dev/null || { + error "gitchart not installed." + } + if [[ "${REMOTELOG}" == "TRUE" ]]; then + # Setup up tmp work folder + if [[ ! -d "/tmp/stats" ]]; then + umask 077 && mkdir /tmp/stats &> /dev/null + fi - # Process the HTML - cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" - processHTML - cat "${htmlFile}" > "${LOCALHOSTPATH}/${APP}/stats/index.html" + notice "Generating files..." - # Create the charts - /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "${LOCALHOSTPATH}/${APP}/stats/authors.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "${LOCALHOSTPATH}/${APP}/stats/commits_day_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "${LOCALHOSTPATH}/${APP}/stats/commits_hour_day.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "${LOCALHOSTPATH}/${APP}/stats/commits_hour_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "${LOCALHOSTPATH}/${APP}/stats/commits_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "${LOCALHOSTPATH}/${APP}/stats/commits_year.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "${LOCALHOSTPATH}/${APP}/stats/commits_year_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "${LOCALHOSTPATH}/${APP}/stats/files_type.svg" &>> /dev/null & - # /usr/bin/gitchart -r "${WORKPATH}/${APP}" commits_day "${LOCALHOSTPATH}/${APP}/stats/commits_day.svg" &>> /dev/null & - spinner $! + # Process the HTML + cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" + processHTML + cat "${htmlFile}" > "/tmp/stats/index.html" - # Process primary chart color and set permissions if needed - sleep 1; find "${LOCALHOSTPATH}/${APP}/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; - chmod -R a+rw "${deployPath}/html/${HTMLTEMPLATE}/stats" &> /dev/null - fi + # Create the charts + /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "/tmp/stats/authors.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "/tmp/stats/commits_day_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "/tmp/stats/commits_hour_day.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "/tmp/stats/commits_hour_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "/tmp/stats/commits_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "/tmp/stats/commits_year.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "/tmp/stats/commits_year_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "/tmp/stats/files_type.svg" &>> /dev/null & + # /usr/bin/gitchart -r "${WORKPATH}/${APP}" commits_day "${LOCALHOSTPATH}/${APP}/stats/commits_day.svg" &>> /dev/null & + spinner $! + + # Process primary chart color and set permissions if needed + sleep 1; find "/tmp/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; + + postLog +# if [[ "${LOCALHOSTPOST}" == "TRUE" ]]; then +# [[ ! -d "${LOCALHOSTPATH}/${APP}" ]] && mkdir "${LOCALHOSTPATH}/${APP}" +# [[ ! -d "${LOCALHOSTPATH}/${APP}/stats" ]] && mkdir "${LOCALHOSTPATH}/${APP}/stats" +# cp -R "/tmp/stats" "${LOCALHOSTPATH}/${APP}" +# chmod -R a+rw "${deployPath}/html/${HTMLTEMPLATE}/stats" &> /dev/null +# fi + fi } diff --git a/lib/wp.sh b/lib/wp.sh index 97ea60f..9b65b2c 100755 --- a/lib/wp.sh +++ b/lib/wp.sh @@ -75,7 +75,7 @@ function wpPkg() { fi # If running in Wordpress update-only mode, bail out - if [[ "$UPGRADE" == "1" ]] && [[ "$UPD1" == "1" ]] && [[ "$UPD2" == "1" ]]; then + if [[ "${UPGRADE}" == "1" ]] && [[ "${UPD1}" == "1" ]] && [[ "${UPD2}" == "1" ]]; then notice "No updates available, halting." safeExit fi From 26377cd8aa08437175aa0bd18d5d6b1d1c6e0d19 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 28 Nov 2017 20:06:29 -0800 Subject: [PATCH 007/334] Resolve #112 --- lib/post-log.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/post-log.sh b/lib/post-log.sh index 1154cc5..f5f0312 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -94,10 +94,13 @@ function postLog() { eval "${SCPCMD} -r" "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/report" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report/js" eval "${SCPCMD} -r" "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/report" + eval "${SCPCMD}" "${htmlFile}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/report/${REMOTEFILE}" fi # Send over the logs and set permissions - eval "${SCPCMD}" "${htmlFile}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null + if [[ "${REPORT}" != "1" ]] && [[ "${PROJSTATS}" != "1" ]]; then + eval "${SCPCMD}" "${htmlFile}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null + fi eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "chmod -R 755 ${SCPHOSTPATH}/${APP}/" fi fi From cd4dfda042e022a085172f5dfd3159abf70b383b Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 1 Dec 2017 15:15:58 -0800 Subject: [PATCH 008/334] Resolve #113 --- deploy.sh | 4 ++-- etc/html/default/stats/index.html | 2 +- lib/post-log.sh | 36 +++++++++++++++++++------------ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/deploy.sh b/deploy.sh index 0896d15..e69c50a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -57,7 +57,7 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ POSTEMAILHEAD POSTEMAILTAIL POSTTOSLACK SLACKURL SLACKERROR POSTURL NOKEY \ PROJNAME PROJCLIENT DEVURL PRODURL REPO MASTER PRODUCTION COMMITMSG DEPLOY \ DONOTDEPLOY TASK CHECKBRANCH ACTIVECHECK CHECKTIME GARBAGE WFCHECK ACFKEY \ - WFOFF REMOTELOG POSTTOLOCALHOST LOCALHOSTPATH DIGESTEMAIL DIGESTSLACK DIGESTURL \ + WFOFF REMOTELOG LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL DIGESTSLACK DIGESTURL \ CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD SSHCMD \ LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME TASKUSER \ CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN REFRESHTOKEN \ @@ -71,7 +71,7 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${PROJNAME} ${PROJCLIENT} ${DEVURL} ${PRODURL} ${REPO} ${MASTER} ${PRODUCTION} ${COMMITMSG} ${DEPLOY} ${DONOTDEPLOY} ${TASK} ${CHECKBRANCH} ${ACTIVECHECK} ${CHECKTIME} ${GARBAGE} ${WFCHECK} ${ACFKEY} ${WFOFF} ${REMOTELOG} - ${POSTTOLOCALHOST} ${LOCALHOSTPATH} ${DIGESTEMAIL} ${DIGESTSLACK} ${DIGESTURL} + ${LOCALHOSTPOST} ${LOCALHOSTPATH} ${DIGESTEMAIL} ${DIGESTSLACK} ${DIGESTURL} ${CLIENTLOGO} ${REMOTEURL} ${SCPPOST} ${SCPUSER} ${SCPHOST} ${SCPHOSTPATH} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} ${EXPIRELOGS} ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index c02a1de..c008afa 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -111,7 +111,7 @@

Any Quest

ItemDescription
X
Item ID
X
Description
${TASK}X
Web hosting for the month of ${LASTMONTH}
${TASK}X
${INCLUDEHOSTING}
%hX
%s
%hX
%s
diff --git a/lib/post-log.sh b/lib/post-log.sh index f5f0312..e931831 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -12,7 +12,7 @@ function postLog() { if [[ "${REMOTELOG}" == "TRUE" ]]; then # Post to localhost by simply copying files - if [[ "${LOCALHOSTPOST}" == "TRUE" ]] && [[ -f "${htmlFile}" ]]; then + if [[ "${LOCALHOSTPOST}" == "TRUE" ]] && [[ -n "${LOCALHOSTPATH}" ]] && [[ -f "${htmlFile}" ]]; then # Check that directory exists htmlDir @@ -71,6 +71,8 @@ function postLog() { eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}" if [[ "${DIGEST}" == "1" ]]; then + REMOTEFILE="digest-${EPOCH}.html" + DIGESTURL="${REMOTEURL}/${APP}/${REMOTEFILE}" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/avatar" eval "${SCPCMD} -r" "/tmp/avatar/" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}" # Clean up your mess @@ -89,6 +91,8 @@ function postLog() { fi if [[ "${REPORT}" == "1" ]]; then + REMOTEFILE="${EPOCH}.php" + REPORTURL="${REMOTEURL}/${APP}/report/${REMOTEFILE}" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report/css" eval "${SCPCMD} -r" "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/report" @@ -107,21 +111,25 @@ function postLog() { } function htmlDir() { - if [[ ! -d "${LOCALHOSTPATH}/${APP}" ]]; then - mkdir "${LOCALHOSTPATH}/${APP}" - fi + # Yet another if/then to cover my ass. What a mess! + if [[ "${LOCALHOSTPOST}" == "TRUE" ]] && [[ -n "${LOCALHOSTPATH}" ]]; then - if [[ ! -d "${LOCALHOSTPATH}/${APP}/avatar" ]]; then - mkdir "${LOCALHOSTPATH}/${APP}/avatar" - fi + if [[ ! -d "${LOCALHOSTPATH}/${APP}" ]]; then + mkdir "${LOCALHOSTPATH}/${APP}" + fi - if [[ "${message_state}" == "REPORT" ]]; then - if [[ ! -d "${LOCALHOSTPATH}/${APP}/report" ]]; then - mkdir "${LOCALHOSTPATH}/${APP}/report"; errorChk - mkdir "${LOCALHOSTPATH}/${APP}/report/css"; errorChk - mkdir "${LOCALHOSTPATH}/${APP}/report/js"; errorChk + if [[ ! -d "${LOCALHOSTPATH}/${APP}/avatar" ]]; then + mkdir "${LOCALHOSTPATH}/${APP}/avatar" + fi + + if [[ "${message_state}" == "REPORT" ]]; then + if [[ ! -d "${LOCALHOSTPATH}/${APP}/report" ]]; then + mkdir "${LOCALHOSTPATH}/${APP}/report"; errorChk + mkdir "${LOCALHOSTPATH}/${APP}/report/css"; errorChk + mkdir "${LOCALHOSTPATH}/${APP}/report/js"; errorChk + fi + cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${LOCALHOSTPATH}/${APP}/report"; errorChk + cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${LOCALHOSTPATH}/${APP}/report"; errorChk fi - cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${LOCALHOSTPATH}/${APP}/report"; errorChk - cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${LOCALHOSTPATH}/${APP}/report"; errorChk fi } From d01cab4fde9d68387e0053c6c856f6639f978cab Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 1 Dec 2017 18:52:19 -0800 Subject: [PATCH 009/334] Resolve #114 --- lib/git.sh | 2 +- lib/log-handling.sh | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/git.sh b/lib/git.sh index 97cc2f3..5ee4c6f 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -324,7 +324,7 @@ function gitPushProd() { # Get the stats for this git author, just for fun function gitStats() { - if [[ "${GITSTATS}" == "TRUE" ]] && [[ "${QUIET}" != "1" ]] && [[ "${PUBLISH}" != "1" ]]; then + if [[ "${GITSTATS}" == "TRUE" ]] && [[ "${QUIET}" != "1" ]] && [[ "${PUBLISH}" != "1" ]] && [[ "${APPROVE}" != "1" ]]; then console "Calculating..." getent passwd "${USER}" | cut -d ':' -f 5 | cut -d ',' -f 1 > "${trshFile}" FULLUSER=$(<"${trshFile}") diff --git a/lib/log-handling.sh b/lib/log-handling.sh index 6657f88..c0d0b9a 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -157,12 +157,17 @@ function htmlBuild() { LOGURL="${REMOTEURL}/${APP}/${EPOCH}.${LOGSUFFIX}" REMOTEFILE="${EPOCH}.${LOGSUFFIX}" else - if [[ "${message_state}" != "SUCCESS" ]] || [[ -z "${COMMITHASH}" ]]; then - LOGURL="${REMOTEURL}/${APP}/${message_state}-${EPOCH}.${LOGSUFFIX}" - REMOTEFILE="${message_state}-${EPOCH}.${LOGSUFFIX}" + if [[ "${message_state}" == "APPROVAL NEEDED" ]]; then + LOGURL="${REMOTEURL}/${APP}/APPROVAL-${EPOCH}.${LOGSUFFIX}" + REMOTEFILE="APPROVAL-${EPOCH}.${LOGSUFFIX}" else - LOGURL="${REMOTEURL}/${APP}/${COMMITHASH}.${LOGSUFFIX}" - REMOTEFILE="${COMMITHASH}.${LOGSUFFIX}" + if [[ "${message_state}" != "SUCCESS" ]] || [[ -z "${COMMITHASH}" ]]; then + LOGURL="${REMOTEURL}/${APP}/${message_state}-${EPOCH}.${LOGSUFFIX}" + REMOTEFILE="${message_state}-${EPOCH}.${LOGSUFFIX}" + else + LOGURL="${REMOTEURL}/${APP}/${COMMITHASH}.${LOGSUFFIX}" + REMOTEFILE="${COMMITHASH}.${LOGSUFFIX}" + fi fi fi From debe19051b6ab9651a27becdfb3ae2f182d20622 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 3 Dec 2017 13:32:39 -0800 Subject: [PATCH 010/334] Improved test emails --- CHANGELOG.md | 1 + deploy.sh | 14 ++-- lib/mail-log.sh | 191 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 134 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb331bd..4d7c7b6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Remote servers can now host project logs, digests, statistics, and reports ### Changed - Item descriptions are now editable in reports +- Cleaned up email output generated using `deploy --email-test` ### Fixed - Fixed a bug that kept SSH keys from being properly checked diff --git a/deploy.sh b/deploy.sh index e69c50a..610a8bc 100755 --- a/deploy.sh +++ b/deploy.sh @@ -57,12 +57,12 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ POSTEMAILHEAD POSTEMAILTAIL POSTTOSLACK SLACKURL SLACKERROR POSTURL NOKEY \ PROJNAME PROJCLIENT DEVURL PRODURL REPO MASTER PRODUCTION COMMITMSG DEPLOY \ DONOTDEPLOY TASK CHECKBRANCH ACTIVECHECK CHECKTIME GARBAGE WFCHECK ACFKEY \ - WFOFF REMOTELOG LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL DIGESTSLACK DIGESTURL \ - CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD SSHCMD \ - LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME TASKUSER \ - CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN REFRESHTOKEN \ - PROFILEID METRIC RESULT ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO REPORTURL \ - CLIENTCONTACT INCLUDEHOSTING <<< "" + WFOFF REMOTELOG REMOTETEMPLATE LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL DIGESTSLACK \ + DIGESTURL CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD \ + SSHCMD LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME \ + TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ + REFRESHTOKEN PROFILEID METRIC RESULT ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO \ + REPORTURL CLIENTCONTACT INCLUDEHOSTING <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} ${DEVGROUP} ${APACHEUSER} ${APACHEGROUP} ${TO} ${FROM} ${SUBJECT} ${EMAILERROR} @@ -70,7 +70,7 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${POSTEMAILTAIL} ${POSTTOSLACK} ${SLACKURL} ${SLACKERROR} ${POSTURL} ${NOKEY} ${PROJNAME} ${PROJCLIENT} ${DEVURL} ${PRODURL} ${REPO} ${MASTER} ${PRODUCTION} ${COMMITMSG} ${DEPLOY} ${DONOTDEPLOY} ${TASK} ${CHECKBRANCH} ${ACTIVECHECK} - ${CHECKTIME} ${GARBAGE} ${WFCHECK} ${ACFKEY} ${WFOFF} ${REMOTELOG} + ${CHECKTIME} ${GARBAGE} ${WFCHECK} ${ACFKEY} ${WFOFF} ${REMOTELOG} ${REMOTETEMPLATE} ${LOCALHOSTPOST} ${LOCALHOSTPATH} ${DIGESTEMAIL} ${DIGESTSLACK} ${DIGESTURL} ${CLIENTLOGO} ${REMOTEURL} ${SCPPOST} ${SCPUSER} ${SCPHOST} ${SCPHOSTPATH} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} ${EXPIRELOGS} diff --git a/lib/mail-log.sh b/lib/mail-log.sh index 42be1df..e3b64e0 100755 --- a/lib/mail-log.sh +++ b/lib/mail-log.sh @@ -79,37 +79,66 @@ function emailTest() { echo "Current user is ${DEV}

"; echo echo "Project Information
" - echo "Name: ${PROJNAME}
" - echo "Client: ${PROJCLIENT}
" - echo "Logo: ${CLIENTLOGO}
" - echo "Digest email(s): ${DIGESTEMAIL}
" - echo "Staging URL: ${DEVURL}
" - echo "Production URL: ${PRODURL}

" - echo - echo "Git Configuration
" - echo "Repo: ${REPOHOST}/${REPO}
" - echo "Master branch: ${MASTER}
" - echo "Production branch: ${PRODUCTION}
" - echo "Auto merge: ${AUTOMERGE}
" - echo "File Stashing: ${STASH}
" - echo "Force branch checking: ${CHECKBRANCH}

" - echo - echo "Wordpress Setup
" - echo "Wordpress root: ${WPROOT}
" - echo "Wordpress application: ${WPAPP}
" - echo "Wordpress system: ${WPSYSTEM}

" - echo - echo "Deployment Configuration
" - echo "Deploy command: ${DEPLOY}
" - echo "Disallow deployment: ${DONOTDEPLOY}

" - echo - echo "Integration
" - echo "Task #: ${TASK}
" - echo "Task user: ${TASKUSER}
" - echo "Task time: ${ADDTIME}
" - echo "Post to Slack: ${POSTTOSLACK}
" - echo "Post errors to Slack: ${SLACKERROR}
" - echo "Google Analytics ID: ${PROFILEID}"; + [[ -n "${PROJNAME}" ]] && echo "Name: ${PROJNAME}
" + [[ -n "${PROJCLIENT}" ]] && echo "Client: ${PROJCLIENT}
" + [[ -n "${CLIENTLOGO}" ]] && echo "Logo: ${CLIENTLOGO}
" + [[ -n "${DIGESTEMAIL}" ]] && echo "Digest email(s): ${DIGESTEMAIL}
" + [[ -n "${DEVURL}" ]] && echo "Staging URL: ${DEVURL}
" + [[ -n "${PRODURL}" ]] && echo "Production URL: ${PRODURL}
" + echo "
" + # Git + if [[ -n "${REPO}" ]] || [[ -n "${MASTER}" ]] || [[ -n "${PRODUCTION}" ]] || [[ -n "${AUTOMERGE}" ]] || [[ -n "${STASH}" ]] || [[ -n "${CHECKBRANCH}" ]]; then + echo "Git Configuration
" + [[ -n "${REPO}" ]] && echo "Repo: ${REPOHOST}/${REPO}
" + [[ -n "${MASTER}" ]] && echo "Master branch: ${MASTER}
" + [[ -n "${PRODUCTION}" ]] && echo "Production branch: ${PRODUCTION}
" + [[ -n "${AUTOMERGE}" ]] && echo "Auto merge: ${AUTOMERGE}
" + [[ -n "${STASH}" ]] && echo "File Stashing: ${STASH}
" + [[ -n "${CHECKBRANCH}" ]] && echo "Force branch checking: ${CHECKBRANCH}
" + echo "
" + fi + # Wordpress + if [[ -n "${WPROOT}" ]] || [[ -n "${WPAPP}" ]] || [[ -n "${WPSYSTEM}" ]]; then + echo "Wordpress Setup
" + [[ -n "${WPROOT}" ]] && echo "Wordpress root: ${WPROOT}
" + [[ -n "${WPAPP}" ]] && echo "Wordpress application: ${WPAPP}
" + [[ -n "${WPSYSTEM}" ]] && echo "Wordpress system: ${WPSYSTEM}
" + echo "
" + fi + # Deployment + if [[ -n "${DEPLOY}" ]] || [[ -n "${DONOTDEPLOY}" ]]; then + echo "Deployment Configuration
" + [[ -n "${DEPLOY}" ]] && echo "Deploy command: ${DEPLOY}
" + [[ -n "${DONOTDEPLOY}" ]] && echo "Disallow deployment: ${DONOTDEPLOY}
" + echo "
" + fi + # Integration + if [[ -n "${TASK}" ]] || [[ -n "${TASKUSER}" ]] || [[ -n "${ADDTIME}" ]] || [[ -n "${POSTTOSLACK}" ]] || [[ -n "${SLACKERROR}" ]] || [[ -n "${PROFILEID}" ]] || [[ -n "${POSTURL}" ]]; then + echo "Integration
" + [[ -n "${TASK}" ]] && echo "Task #: ${TASK}
" + [[ -n "${TASKUSER}" ]] && echo "Task user: ${TASKUSER}
" + [[ -n "${ADDTIME}" ]] && echo "Task time: ${ADDTIME}
" + [[ -n "${POSTTOSLACK}" ]] && echo "Post to Slack: ${POSTTOSLACK}
" + [[ -n "${SLACKERROR}" ]] && echo "Post errors to Slack: ${SLACKERROR}
" + [[ -n "${POSTURL}" ]] && echo "Webhook URL: ${POSTURL}
" + [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}
" + echo "
" + fi + # Logging + if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then + echo "Logging
" + [[ -n "${REMOTELOG}" ]] && echo "Web logs: ${REMOTELOG}
" + [[ -n "${REMOTEURL}" ]] && echo "Address: ${REMOTEURL}
" + [[ -n "${EXPIRELOGS}" ]] && echo "Log expiration: ${EXPIRELOGS} days
" + [[ -n "${LOCALHOSTPOST}" ]] && echo "Save logs locally: ${LOCALHOSTPOST}
" + [[ -n "${LOCALHOSTPATH}" ]] && echo "Path to local logs: ${}LOCALHOSTPATH
" + [[ -n "${SCPPOST}" ]] && echo "Post with SCP/SSH: ${SCPPOST}
" + [[ -n "${SCPUSER}" ]] && echo "SCP user: ${SCPUSER}
" + [[ -n "${SCPHOST}" ]] && echo "Remote log host: ${SCPHOST}
" + [[ -n "${SCPHOSTPATH}" ]] && echo "Remote log path: ${SCPHOSTPATH}
" + [[ -n "${REMOTETEMPLATE}" ]] && echo "Log template: ${REMOTETEMPLATE}
" + echo "
" + fi ) | "${MAILPATH}"/sendmail -t # Send Text mail ( @@ -125,41 +154,73 @@ function emailTest() { echo echo "Project Information" echo "-------------------" - echo "Name: ${PROJNAME}" - echo "Client: ${PROJCLIENT}" - echo "Logo: ${CLIENTLOGO}" - echo "Digest email(s): ${DIGESTEMAIL}" - echo "Staging URL: ${DEVURL}" - echo "Production URL: ${PRODURL}" - echo - echo "Git Configuration" - echo "-----------------" - echo "Repo: ${REPOHOST}/${REPO}" - echo "Master branch: ${MASTER}" - echo "Production branch: ${PRODUCTION}" - echo "Auto merge: ${AUTOMERGE}" - echo "File Stashing: ${STASH}" - echo "Force branch checking: ${CHECKBRANCH}" - echo - echo "Wordpress Setup" - echo "-----------------------" - echo "Wordpress root: ${WPROOT}" - echo "Wordpress application: ${WPAPP}" - echo "Wordpress system: ${WPSYSTEM}" - echo - echo "Deployment Configuration" - echo "------------------------" - echo "Deploy command: ${DEPLOY}" - echo "Disallow deployment: ${DONOTDEPLOY}" + [[ -n "${PROJNAME}" ]] && echo "Name: ${PROJNAME}" + [[ -n "${PROJCLIENT}" ]] && echo "Client: ${PROJCLIENT}" + [[ -n "${CLIENTLOGO}" ]] && echo "Logo: ${CLIENTLOGO}" + [[ -n "${DIGESTEMAIL}" ]] && echo "Digest email(s): ${DIGESTEMAIL}" + [[ -n "${DEVURL}" ]] && echo "Staging URL: ${DEVURL}" + [[ -n "${PRODURL}" ]] && echo "Production URL: ${PRODURL}" echo - echo "Integration" - echo "-----------" - echo "Task #: ${TASK}" - echo "Task user: ${TASKUSER}" - echo "Task time: ${ADDTIME}" - echo "Post to Slack: ${POSTTOSLACK}" - echo "Post errors to Slack: ${SLACKERROR}" - echo "Google Analytics ID: ${PROFILEID}"; + # Git + if [[ -n "${REPO}" ]] || [[ -n "${MASTER}" ]] || [[ -n "${PRODUCTION}" ]] || [[ -n "${AUTOMERGE}" ]] || [[ -n "${STASH}" ]] || [[ -n "${CHECKBRANCH}" ]]; then + echo "Git Configuration" + echo "-----------------" + [[ -n "${REPO}" ]] && echo "Repo: ${REPOHOST}/${REPO}" + [[ -n "${MASTER}" ]] && echo "Master branch: ${MASTER}" + [[ -n "${PRODUCTION}" ]] && echo "Production branch: ${PRODUCTION}" + [[ -n "${AUTOMERGE}" ]] && echo "Auto merge: ${AUTOMERGE}" + [[ -n "${STASH}" ]] && echo "File Stashing: ${STASH}" + [[ -n "${CHECKBRANCH}" ]] && echo "Force branch checking: ${CHECKBRANCH}" + echo + fi + # Wordpress + if [[ -n "${WPROOT}" ]] || [[ -n "${WPAPP}" ]] || [[ -n "${WPSYSTEM}" ]]; then + echo "Wordpress Setup" + echo "---------------" + [[ -n "${WPROOT}" ]] && echo "Wordpress root: ${WPROOT}" + [[ -n "${WPAPP}" ]] && echo "Wordpress application: ${WPAPP}" + [[ -n "${WPSYSTEM}" ]] && echo "Wordpress system: ${WPSYSTEM}" + echo + fi + # Deployment + if [[ -n "${DEPLOY}" ]] || [[ -n "${DONOTDEPLOY}" ]]; then + echo "Deployment Configuration" + echo "------------------------" + [[ -n "${DEPLOY}" ]] && echo "Deploy command: ${DEPLOY}" + [[ -n "${DONOTDEPLOY}" ]] && echo "Disallow deployment: ${DONOTDEPLOY}" + echo + fi + # Integration + if [[ -n "${TASK}" ]] || [[ -n "${TASKUSER}" ]] || [[ -n "${ADDTIME}" ]] || [[ -n "${POSTTOSLACK}" ]] || [[ -n "${SLACKERROR}" ]] || [[ -n "${PROFILEID}" ]] || [[ -n "${POSTURL}" ]]; then + echo "Integration" + echo "-----------" + [[ -n "${TASK}" ]] && echo "Task #: ${TASK}" + [[ -n "${TASKUSER}" ]] && echo "Task user: ${TASKUSER}" + [[ -n "${ADDTIME}" ]] && echo "Task time: ${ADDTIME}" + [[ -n "${POSTTOSLACK}" ]] && echo "Post to Slack: ${POSTTOSLACK}" + [[ -n "${SLACKERROR}" ]] && echo "Post errors to Slack: ${SLACKERROR}" + [[ -n "${POSTURL}" ]] && echo "Webhook URL: ${POSTURL}" + [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}" + echo + fi + # Logging + if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then + echo "Logging" + echo "-------" + [[ -n "${REMOTELOG}" ]] && echo "Web logs: ${REMOTELOG}" + [[ -n "${REMOTEURL}" ]] && echo "Address: ${REMOTEURL}" + [[ -n "${EXPIRELOGS}" ]] && echo "Log expiration: ${EXPIRELOGS} days" + [[ -n "${LOCALHOSTPOST}" ]] && echo "Save logs locally: ${LOCALHOSTPOST}" + [[ -n "${LOCALHOSTPATH}" ]] && echo "Path to local logs: ${}LOCALHOSTPATH" + [[ -n "${SCPPOST}" ]] && echo "Post with SCP/SSH: ${SCPPOST}" + [[ -n "${SCPUSER}" ]] && echo "SCP user: ${SCPUSER}" + [[ -n "${SCPHOST}" ]] && echo "Remote log host: ${SCPHOST}" + [[ -n "${SCPHOSTPATH}" ]] && echo "Remote log path: ${SCPHOSTPATH}" + [[ -n "${REMOTETEMPLATE}" ]] && echo "Log template: ${REMOTETEMPLATE}s" + echo + fi + + ) | "${MAILPATH}"/sendmail -t fi From 9cfade4cbad6b2725dba001f045f4869902905fb Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 4 Dec 2017 14:02:00 -0800 Subject: [PATCH 011/334] Fix for weird new session results from Google --- CHANGELOG.md | 1 + lib/analytics.sh | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d7c7b6..a9def72 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Cleaned up email output generated using `deploy --email-test` ### Fixed - Fixed a bug that kept SSH keys from being properly checked +- Added a workaround for Google potentially displaying over 100% of user sessions as new ## [3.6.5] - 11-17-2017 ### Added diff --git a/lib/analytics.sh b/lib/analytics.sh index a89033a..222296e 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -39,7 +39,11 @@ function analytics() { fi if [[ "${RND}" == "1" ]]; then - if [[ "${SIZE}" -gt "50" ]]; then + # Sometimes Google reports confusion percentages that exceed + # 100%, let's kill those results + if [[ "${}" -gt "100" ]]; then + analyticsFail + elif [[ "${SIZE}" -gt "50" ]]; then ANALYTICSMSG="Last week ${SIZE} percent of your users were first time visitors. That's great!" else RESULT="$((100 - ${SIZE}))" From a501d52899fd3d9b9f7d748af83f4f2b69f1303c Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 4 Dec 2017 17:45:19 -0800 Subject: [PATCH 012/334] Added dependency checks to install script --- install/doinst.sh | 177 +++++++++++++++++++++++++++++++++++----------- lib/git.sh | 2 +- lib/report.sh | 86 +++++++++++----------- lib/statistics.sh | 62 ++++++++-------- 4 files changed, 212 insertions(+), 115 deletions(-) diff --git a/install/doinst.sh b/install/doinst.sh index d96ea31..a2add37 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -1,48 +1,145 @@ -#!/usr/bin/bash +#!/bin/bash # -# doinst.sh +# install.sh # -# Installs deployment files for use system-wide +############################################################################### +# Installs and configures deploy +# +# https://github.com/EMRL/deploy +############################################################################### + +# No root, no fun +if [[ "${EUID}" -ne 0 ]]; then + echo "You must have root access to install - try 'sudo install/doinst.sh'" 2>&1 + exit 1 +fi + +function check_os() { + # Try to discover the OS flavor + if [[ -f /etc/os-release ]]; then + # freedesktop.org and systemd + . /etc/os-release + OS="${NAME}" + VER="${VERSION_ID}" + elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + OS="$(lsb_release -si)" + VER="$(lsb_release -sr)" + elif [[ -f /etc/lsb-release ]]; then + # For some versions of Debian/Ubuntu without lsb_release command + . /etc/lsb-release + OS="${DISTRIB_ID}" + VER="${DISTRIB_RELEASE}" + elif [[ -f /etc/debian_version ]]; then + # Older Debian/Ubuntu/etc. + OS=Debian + VER="$(cat /etc/debian_version)" + elif [[ -f /etc/SuSe-release ]]; then + # Older SuSE/etc. + ... + elif [[ -f /etc/redhat-release ]]; then + # Older Red Hat, CentOS, etc. + ... + else + # Fall back to uname, e.g. "Linux ", also works for BSD, etc. + OS="$(uname -s)" + VER="$(uname -r)" + fi +} + +function check_program() { + printf "%-40s" "Checking for ${1}..." + whereis $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } +} + +function check_optional() { + printf "%-40s" "Checking for ${1}..." + whereis $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } +} -# Error check function function errorChk() { - EXITCODE=$?; - if [[ "${EXITCODE}" != "0" ]]; then - echo "Error ${EXITCODE}: deploy not installed." - exit "${EXITCODE}" - fi + EXITCODE=$?; + if [[ "${EXITCODE}" != "0" ]]; then + echo "Error ${EXITCODE}: deploy not installed." + exit "${EXITCODE}" + fi } -# Check for root -if [[ "${EUID}" -ne 0 ]]; then - echo "You must have root access to install - try 'sudo install/doinst.sh'" 2>&1 +echo; check_os +if [[ -n "${OS}" ]] && [[ -n "${VER}" ]]; then + echo "=> Checking operating system: "; sleep 1 + echo "${OS} ${VER}"; sleep 1 +else + # No values, crash out for now exit 1 +fi + +# Declare dependencies +dependencies=(awk cat curl echo eval gawk git grep pkill printf read sed sendmail sleep tput wget) + +# Declare optional stuff +options=(gitchart grunt npm scp ssh sshpass wp) + +message='' +fg_red=`tput setaf 1` +fg_green=`tput setaf 2` +reset=`tput sgr0` + +# Common messages +YES="${fg_green}OK${reset}" +NO="${fg_red}NO${reset}" +NOT="${fg_red}NOT FOUND${reset}" + +echo; echo "=> Checking for required dependencies:"; sleep 1 +for i in "${dependencies[@]}" ; do + check_program $i +done + +if [[ -z "${message}" ]] ; then + #echo "${fg_green}SUCCESS: System is ready!${reset}" + sleep 1 else - if [[ ! -d /etc/deploy ]] && [[ ! -d /etc/deploy/lib ]] && [[ ! -d /etc/deploy/crontab ]]; then - echo "Creating directories" - if [[ ! -d /etc/deploy ]]; then - sudo mkdir /etc/deploy; errorChk - fi - - if [[ ! -d /etc/deploy/lib ]]; then - sudo mkdir /etc/deploy/lib; errorChk - fi - - if [[ ! -d /etc/deploy/lib/crontab ]]; then - sudo mkdir /etc/deploy/lib/crontab; errorChk - fi - fi - - echo "Installing configuration files" - sudo cp -R etc/* /etc/deploy; errorChk - sudo cp etc/.deployrc /etc/deploy; errorChk - - if [[ ! -f /etc/deploy/deploy.conf ]]; then - cp /etc/deploy/deploy-example.conf /etc/deploy/deploy.conf; errorChk - fi - - cp -R lib/* /etc/deploy/lib; errorChk - cp deploy.sh /usr/local/bin/deploy; errorChk - sudo chmod 755 /usr/local/bin/deploy; errorChk - echo "Successfully installed, try typing 'deploy' for help." -fi \ No newline at end of file + echo "${fg_red}ERROR: Install missing dependencies and retry installation${reset}" + # echo ${message}; + exit 1 +fi + +echo; echo "=> Checking optional dependencies:"; sleep 1 +for k in "${options[@]}" ; do + check_optional $k +done + +if [[ -n "${message}" ]] ; then + echo "${fg_red}WARNING: Some extended functions will not be available${reset}" + # echo ${message}; +fi + +# Start the install: First check for root +echo; sleep 1 +if [[ ! -d /etc/deploy ]] && [[ ! -d /etc/deploy/lib ]] && [[ ! -d /etc/deploy/crontab ]]; then + echo "Creating directories" + if [[ ! -d /etc/deploy ]]; then + sudo mkdir /etc/deploy; errorChk + fi + + if [[ ! -d /etc/deploy/lib ]]; then + sudo mkdir /etc/deploy/lib; errorChk + fi + + if [[ ! -d /etc/deploy/lib/crontab ]]; then + sudo mkdir /etc/deploy/lib/crontab; errorChk + fi +fi + +echo "Installing configuration files" +sudo cp -R etc/* /etc/deploy; errorChk +sudo cp etc/.deployrc /etc/deploy; errorChk + +if [[ ! -f /etc/deploy/deploy.conf ]]; then + cp /etc/deploy/deploy-example.conf /etc/deploy/deploy.conf; errorChk +fi + +cp -R lib/* /etc/deploy/lib; errorChk +cp deploy.sh /usr/local/bin/deploy; errorChk +sudo chmod 755 /usr/local/bin/deploy; errorChk +echo "Successfully installed, try typing 'deploy' for help." diff --git a/lib/git.sh b/lib/git.sh index 5ee4c6f..fd89d7f 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -46,7 +46,7 @@ function gitStart() { activeChk # Try to clear out old git processes owned by this user, if they exist - killall -9 git &>> /dev/null || true + pkill -f git &>> /dev/null || true } # Checkout master diff --git a/lib/report.sh b/lib/report.sh index 2b8c004..dac791d 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -8,47 +8,47 @@ trace "Loading report handling" function createReport() { - message_state="REPORT" - htmlDir - - # Get the first and last day of last month - CURMTH="$(date +%m)" - CURYR="$(date +%Y)" - - if [[ "${CURMTH}" -eq 1 ]]; then - PRVMTH="12" - PRVYR=`expr "${CURYR}" - 1` - else PRVMTH=`expr "${CURMTH}" - 2` - PRVYR="${CURYR}" - fi - - if [[ "${PRVMTH}" -lt 10 ]]; - then PRVMTH="0${PRVMTH}" - fi - - # Try to setup the month/day count - LASTDY=`cal ${PRVMTH} ${PRVYR} | egrep "28|29|30|31" |tail -1 |awk '{print $NF}'` - - if [[ -n "${INCLUDEHOSTING}" ]] && [[ "${INCLUDEHOSTING}" != "FALSE" ]]; then - # If INCLUDEHOSTING is equal to something other than TRUE, its value - # will be used as the text string in the report - if [[ "${INCLUDEHOSTING}" == "TRUE" ]]; then - INCLUDEHOSTING="Web hosting for the month of ${LASTMONTH}" - fi - echo "
" >> "${statFile}" - fi - - #if [[ $(git log --before={'date "+%Y-%m-01"'} --after=${PRVYR}-${PRVMTH}-31) ]]; then - git log --all --no-merges --first-parent --before={'date "+%Y-%m-01"'} --after="${PRVYR}-${PRVMTH}-31 00:00" --pretty=format:"" >> "${statFile}" - - # If it's an empty report, this empty row will keep the javascript from breaking. Kludgy I know. - if [[ ! -s "${statFile}" ]]; then - echo "" > "${statFile}" - fi - - # Compile full report - cat "${deployPath}/html/${HTMLTEMPLATE}/report/header.html" "${statFile}" "${deployPath}/html/${HTMLTEMPLATE}/report/footer.html" > "${htmlFile}" - - # Filter and replace template variables - processHTML + message_state="REPORT" + htmlDir + + # Get the first and last day of last month + CURMTH="$(date +%m)" + CURYR="$(date +%Y)" + + if [[ "${CURMTH}" -eq 1 ]]; then + PRVMTH="12" + PRVYR=`expr "${CURYR}" - 1` + else PRVMTH=`expr "${CURMTH}" - 2` + PRVYR="${CURYR}" + fi + + if [[ "${PRVMTH}" -lt 10 ]]; + then PRVMTH="0${PRVMTH}" + fi + + # Try to setup the month/day count + LASTDY=`cal ${PRVMTH} ${PRVYR} | egrep "28|29|30|31" |tail -1 |awk '{print $NF}'` + + if [[ -n "${INCLUDEHOSTING}" ]] && [[ "${INCLUDEHOSTING}" != "FALSE" ]]; then + # If INCLUDEHOSTING is equal to something other than TRUE, its value + # will be used as the text string in the report + if [[ "${INCLUDEHOSTING}" == "TRUE" ]]; then + INCLUDEHOSTING="Web hosting for the month of ${LASTMONTH}" + fi + echo "" >> "${statFile}" + fi + + #if [[ $(git log --before={'date "+%Y-%m-01"'} --after=${PRVYR}-${PRVMTH}-31) ]]; then + git log --all --no-merges --first-parent --before={'date "+%Y-%m-01"'} --after="${PRVYR}-${PRVMTH}-31 00:00" --pretty=format:"" >> "${statFile}" + + # If it's an empty report, this empty row will keep the javascript from breaking. Kludgy I know. + if [[ ! -s "${statFile}" ]]; then + echo "" > "${statFile}" + fi + + # Compile full report + cat "${deployPath}/html/${HTMLTEMPLATE}/report/header.html" "${statFile}" "${deployPath}/html/${HTMLTEMPLATE}/report/footer.html" > "${htmlFile}" + + # Filter and replace template variables + processHTML } diff --git a/lib/statistics.sh b/lib/statistics.sh index 01b4522..8653e0a 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -8,43 +8,43 @@ trace "Loading statistics functions" function projStats() { - hash gitchart 2>/dev/null || { - error "gitchart not installed." - } - if [[ "${REMOTELOG}" == "TRUE" ]]; then - # Setup up tmp work folder + hash gitchart 2>/dev/null || { + error "gitchart not installed." + } + if [[ "${REMOTELOG}" == "TRUE" ]]; then + # Setup up tmp work folder if [[ ! -d "/tmp/stats" ]]; then umask 077 && mkdir /tmp/stats &> /dev/null fi - notice "Generating files..." + notice "Generating files..." - # Process the HTML - cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" - processHTML - cat "${htmlFile}" > "/tmp/stats/index.html" + # Process the HTML + cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" + processHTML + cat "${htmlFile}" > "/tmp/stats/index.html" - # Create the charts - /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "/tmp/stats/authors.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "/tmp/stats/commits_day_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "/tmp/stats/commits_hour_day.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "/tmp/stats/commits_hour_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "/tmp/stats/commits_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "/tmp/stats/commits_year.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "/tmp/stats/commits_year_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "/tmp/stats/files_type.svg" &>> /dev/null & - # /usr/bin/gitchart -r "${WORKPATH}/${APP}" commits_day "${LOCALHOSTPATH}/${APP}/stats/commits_day.svg" &>> /dev/null & - spinner $! + # Create the charts + /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "/tmp/stats/authors.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "/tmp/stats/commits_day_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "/tmp/stats/commits_hour_day.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "/tmp/stats/commits_hour_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "/tmp/stats/commits_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "/tmp/stats/commits_year.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "/tmp/stats/commits_year_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "/tmp/stats/files_type.svg" &>> /dev/null & + # /usr/bin/gitchart -r "${WORKPATH}/${APP}" commits_day "${LOCALHOSTPATH}/${APP}/stats/commits_day.svg" &>> /dev/null & + spinner $! - # Process primary chart color and set permissions if needed - sleep 1; find "/tmp/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; + # Process primary chart color and set permissions if needed + sleep 1; find "/tmp/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; - postLog -# if [[ "${LOCALHOSTPOST}" == "TRUE" ]]; then -# [[ ! -d "${LOCALHOSTPATH}/${APP}" ]] && mkdir "${LOCALHOSTPATH}/${APP}" -# [[ ! -d "${LOCALHOSTPATH}/${APP}/stats" ]] && mkdir "${LOCALHOSTPATH}/${APP}/stats" -# cp -R "/tmp/stats" "${LOCALHOSTPATH}/${APP}" -# chmod -R a+rw "${deployPath}/html/${HTMLTEMPLATE}/stats" &> /dev/null -# fi - fi + postLog +# if [[ "${LOCALHOSTPOST}" == "TRUE" ]]; then +# [[ ! -d "${LOCALHOSTPATH}/${APP}" ]] && mkdir "${LOCALHOSTPATH}/${APP}" +# [[ ! -d "${LOCALHOSTPATH}/${APP}/stats" ]] && mkdir "${LOCALHOSTPATH}/${APP}/stats" +# cp -R "/tmp/stats" "${LOCALHOSTPATH}/${APP}" +# chmod -R a+rw "${deployPath}/html/${HTMLTEMPLATE}/stats" &> /dev/null +# fi + fi } From e4e21cc76c1e94e05ed55d1b57f00f5f86e5e862 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 4 Dec 2017 17:48:51 -0800 Subject: [PATCH 013/334] Changed to command -v --- install/doinst.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/doinst.sh b/install/doinst.sh index a2add37..bb787e4 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# install.sh +# doinst.sh # ############################################################################### # Installs and configures deploy @@ -49,12 +49,12 @@ function check_os() { function check_program() { printf "%-40s" "Checking for ${1}..." - whereis $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } + command -v $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } } function check_optional() { printf "%-40s" "Checking for ${1}..." - whereis $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } + command -v $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } } function errorChk() { From f4691eb585e4db86475b2e29df2e4b12d3c272cf Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sat, 9 Dec 2017 22:30:27 -0800 Subject: [PATCH 014/334] PHP Server Monitor integration --- deploy.sh | 9 ++++- etc/deploy.sh | 25 +++++++++++-- etc/html/default/approve.html | 2 +- etc/html/default/error.html | 2 +- etc/html/default/header.html | 6 ++- etc/html/default/success.html | 2 +- lib/log-handling.sh | 1 + lib/mail-log.sh | 17 +++++++++ lib/monitor.sh | 70 +++++++++++++++++++++++++++++++++++ lib/process-html.sh | 7 +++- lib/utilities.sh | 8 ++++ 11 files changed, 137 insertions(+), 12 deletions(-) create mode 100755 lib/monitor.sh diff --git a/deploy.sh b/deploy.sh index 610a8bc..273769b 100755 --- a/deploy.sh +++ b/deploy.sh @@ -91,7 +91,8 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile AUTHORNAME GRAVATAR IMGFILE SIZE RND ANALYTICSMSG digestSendmail MINAUSER \ MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX QUEUED \ DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile CURMTH \ - CURYR PRVMTH PRVYR LASTDY TMP <<< "" + CURYR PRVMTH PRVYR LASTDY TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ + MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI <<< "" echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile} ${trshFile} ${statFile} ${urlFile} ${htmlFile} ${htmlSendmail} ${htmlEmail} ${clientEmail} ${textSendmail} ${deployPath} ${etcLocation} ${libLocation} @@ -106,7 +107,9 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${digestSendmail} ${MINAUSER} ${MINADOMAIN} ${SSHTARGET} ${SSHSTATUS} ${REMOTEFILE} ${GREETING} ${LOGSUFFIX} ${QUEUED} ${DISABLESSHCHECK} ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} ${CURMTH} ${CURYR} - ${PRVMTH} ${PRVYR} ${LASTDY} ${TMP}" > /dev/null + ${PRVMTH} ${PRVYR} ${LASTDY} ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} + ${SERVERID} ${MONITORHOURS} ${LATENCY} ${UPTIME} ${MONITORTEST} + ${MONITORAPI}" > /dev/null # Options function flags() { @@ -143,6 +146,7 @@ Other Options: --slack-test Test Slack integration --post-test Test webhook integration --analytics-test Test Google Analytics authentication + --monitor-test Test production server uptime and latency monitoring --function-list Output a list of all functions() --variable-list Output a project's declared variables @@ -204,6 +208,7 @@ while [[ ${1:-unset} = -?* ]]; do --email-test) EMAILTEST="1" ;; --post-test) POSTTEST="1" ;; --analytics-test) ANALYTICSTEST="1" ;; + --monitor-test) MONITORTEST="1" ;; --stats) PROJSTATS="1" ;; --unlock) UNLOCK="1" ;; --repair) REPAIR="1"; FORCE="1"; MERGE="1"; STASH="TRUE"; VERBOSE="1" ;; diff --git a/etc/deploy.sh b/etc/deploy.sh index 45db2e1..61de5ee 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -200,20 +200,39 @@ # LOCALHOSTPOST="TRUE" # LOCALHOSTPATH="/var/www/production/deploy" +# Server Monitoring +# +# Uptime and average latency can be included in logs, digests, and reports when +# integrating with PHP Server Monitor, and an add-on API. +# See https://github.com/EMRL/deploy/wiki/Integration for more information. +# +# Full API URL +# MONITORURL="https://your.phpservermonitor.com/api/monitorapi.php" +# +# Email/password of the user that will access the API. Password can be stored in +# a file outside of the project repo for security reasons +# MONITORUSER="user@domain.com" +# MONITORPASS="/path/to/password/file" +# +# Server ID to monitor. When viewing the server on your web console, your URL will +# look something like https://monitor.com/?&mod=server&action=view&id=3 - in this +# case SERVERID would be "3" (notice the &id=3 at the end of the URL) +# SERVERID="###" + # Google Analytics # # API credentials # CLIENTID="#############################################.apps.googleusercontent.com" # CLIENTSECRET="########################" # REDIRECTURI="http://localhost" - +# # OAuth authorization will expire after one hour, but will be updated when needed # if the tokens below are configured correctly # AUTHORIZATIONCODE="##############################################" - +# # Tokens # ACCESSTOKEN="#################################################################################################################################" # REFRESHTOKEN="##################################################################" - +# # Google Analytics ID # PROFILEID="########" diff --git a/etc/html/default/approve.html b/etc/html/default/approve.html index e658d7e..3016d05 100755 --- a/etc/html/default/approve.html +++ b/etc/html/default/approve.html @@ -1,4 +1,4 @@ -

Proposed commit from {{USER}}
+

Proposed commit from {{USER}}
{{NOTES}}

diff --git a/etc/html/default/error.html b/etc/html/default/error.html index 2c3e39a..5288396 100755 --- a/etc/html/default/error.html +++ b/etc/html/default/error.html @@ -1,4 +1,4 @@ -

ERROR: +

ERROR: {{NOTES}}

diff --git a/etc/html/default/header.html b/etc/html/default/header.html index 43412e8..4537e1b 100755 --- a/etc/html/default/header.html +++ b/etc/html/default/header.html @@ -61,8 +61,10 @@ {{PROJCLIENT}} -

Date: {{NOW}}
- Project: {{PROJNAME}} ({{PROJCLIENT}})
+

Date: {{NOW}}
+ Project: {{PROJNAME}} ({{PROJCLIENT}})
Staging URL: {{DEVURL}}
Production URL: {{PRODURL}}

+
Uptime: {{UPTIME}}%
+
Latency: {{LATENCY}}s
\ No newline at end of file diff --git a/etc/html/default/success.html b/etc/html/default/success.html index 7c9c872..787a54e 100755 --- a/etc/html/default/success.html +++ b/etc/html/default/success.html @@ -1,4 +1,4 @@ -

Commit {{COMMITHASH}} by {{USER}} +

Commit {{COMMITHASH}} by {{USER}}
Notes: {{NOTES}}

diff --git a/lib/log-handling.sh b/lib/log-handling.sh index c0d0b9a..55dde5d 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -174,6 +174,7 @@ function htmlBuild() { # Insert the full deployment logfile & button it all up if [[ "${REPORT}" != "1" ]]; then cat "${logFile}" "${deployPath}/html/${HTMLTEMPLATE}/footer.html" >> "${htmlFile}" + # There's probably a better place for this. processHTML fi } diff --git a/lib/mail-log.sh b/lib/mail-log.sh index e3b64e0..301a858 100755 --- a/lib/mail-log.sh +++ b/lib/mail-log.sh @@ -124,6 +124,14 @@ function emailTest() { [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}
" echo "
" fi + # Server monitoring + if [[ -n "${MONITORURL}" ]] || [[ -n "${MONITORUSER}" ]] || [[ -n "${SERVERID}" ]]; then + echo "Server Monitoring
" + [[ -n "${TASK}" ]] && echo "Monitor URL: ${MONITORURL}
" + [[ -n "${TASK}" ]] && echo "User: ${MONITORUSER}
" + [[ -n "${TASK}" ]] && echo "Server ID: ${SERVERID}
" + echo "
" + fi # Logging if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then echo "Logging
" @@ -203,6 +211,15 @@ function emailTest() { [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}" echo fi + # Server monitoring + if [[ -n "${MONITORURL}" ]] || [[ -n "${MONITORUSER}" ]] || [[ -n "${SERVERID}" ]]; then + echo "Server Monitoring" + echo "-----------------" + [[ -n "${TASK}" ]] && echo "Monitor URL: ${MONITORURL}" + [[ -n "${TASK}" ]] && echo "User: ${MONITORUSER}" + [[ -n "${TASK}" ]] && echo "Server ID: ${SERVERID}" + echo + fi # Logging if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then echo "Logging" diff --git a/lib/monitor.sh b/lib/monitor.sh new file mode 100755 index 0000000..166988e --- /dev/null +++ b/lib/monitor.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# +# monitor.sh +# +############################################################################### +# Uses PHP Server Monitor and an add-on API to retrieve a production server's +# uptime and latency +############################################################################### +trace "Loading server monitoring" + +function server_monitor() { + # Don't bother unless all the needed variables are declared + if [[ -n "${MONITORURL}" ]] && [[ -n "${MONITORUSER}" ]] && [[ -n "${SERVERID}" ]] && [[ -n "${MONITORPASS}" ]]; then + # What kind of log is this? + if [[ "${REPORT}" == "1" ]]; then + # Last 30 days; this will not correlate exactly with the time range of the report + # which is dealing with the last calendar month, but oh well we get what we pay + # for. The PHP Monitor API has no ability to look up results in that way + MONITORHOURS="720" + else + # Default to last 7 days + MONITORHOURS="168" + fi + server_monitor_log + trace "Uptime: ${UPTIME} / Latency: ${LATENCY} (avg. over last ${MONITORHOURS} hours)" + fi +} + +function server_monitor_test() { + notice "Testing server monitor integration..." + if [[ -n "${MONITORURL}" ]] && [[ -n "${MONITORUSER}" ]] && [[ -n "${SERVERID}" ]] && [[ -n "${MONITORPASS}" ]]; then + console "Monitor URL: ${MONITORURL}" + console "User: ${MONITORUSER}" + console "Server ID: ${SERVERID}" + console "Password file: ${MONITORPASS}" + else + warning "Server monitoring is not configured; check your project's configuration file." + return + fi + + # Check last 7 days for the sake of the test + MONITORHOURS="168" + server_monitor_log + console "API: ${MONITORAPI}" + notice "Results (last 7 days)" + console "Uptime: ${UPTIME}%" + console "Latency: ${LATENCY}s" +} + +function server_monitor_log() { + # :oad the password and setup the curl command + MONITORPASS=$(<$MONITORPASS) + MONITORAPI="${MONITORURL}?tag=serveruptime&email=${MONITORUSER}&app_password=${MONITORPASS}&server_id=${SERVERID}&HoursUnit=${MONITORHOURS}" + curl -s --request GET "${MONITORAPI}" -o "${trshFile}" + # Uptime + UPTIME=$(grep -Po '"uptime":.*?[^\\]",' ${trshFile}) + UPTIME="$(cut -d ',' -f 1 <<< "${UPTIME}")" + # Isolate the value we need + UPTIME="$(sed 's/^[^:]*://g' <<< "${UPTIME}")" + # Round to two decimal places + UPTIME="$(printf '%0.2f\n' "${UPTIME}")" + + # Latency + LATENCY="$(grep -Po '"average_latency":.*?[^\\]",' ${trshFile})" + LATENCY="$(cut -d ',' -f 1 <<< "${LATENCY}")" + # Isolate the value we need + LATENCY="$(sed 's/^[^:]*://g' <<< "${LATENCY}")" + # Round to two decimal places + LATENCY="$(printf '%0.2f\n' "${LATENCY}")" +} diff --git a/lib/process-html.sh b/lib/process-html.sh index 47bb48a..4ce4391 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -8,14 +8,15 @@ trace "Loading html handling" function processHTML() { - # Clean out the variables stuff we don't need + # Clean out the stuff we don't need [[ -z "${DEVURL}" ]] && sed -i '/Staging URL:/d' "${htmlFile}" [[ -z "${PRODURL}" ]] && sed -i '/PRODURL/d' "${htmlFile}" + [[ -z "${UPTIME}" ]] && sed -i '/UPTIME/d' "${htmlFile}" + [[ -z "${LATENCY}" ]] && sed -i '/LATENCY/d' "${htmlFile}" [[ -z "${PROJCLIENT}" ]] && sed -i 's/()//' "${htmlFile}" [[ -z "${CLIENTLOGO}" ]] && sed -i '/CLIENTLOGO/d' "${htmlFile}" [[ -z "${CLIENTCONTACT}" ]] && sed -i '/CLIENTCONTACT/d' "${htmlFile}" [[ -z "${notes}" ]] && sed -i '/NOTES/d' "${htmlFile}" - [[ -z "${RESULT}" ]] || [[ "${RESULT}" == "0" ]] || [[ "${SIZE}" == "0" ]] && sed -i '/ANALYTICS/d' "${htmlFile}" [[ -z "${SMOOCHID}" ]] && sed -i '/SMOOCHID/d' "${htmlFile}" @@ -59,5 +60,7 @@ function processHTML() { -e "s^{{DIGESTCOVER}}^${DIGESTCOVER}^g" \ -e "s^{{WEEKOF}}^${WEEKOF}^g" \ -e "s^{{LASTMONTH}}^${LASTMONTH}^g" \ + -e "s^{{UPTIME}}^${UPTIME}^g" \ + -e "s^{{LATENCY}}^${LATENCY}^g" \ "${htmlFile}" } diff --git a/lib/utilities.sh b/lib/utilities.sh index f7b2aa5..c7a9014 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -37,6 +37,11 @@ function go() { analyticsTest; quickExit fi + # Test server monitoring + if [[ "${MONITORTEST}" == "1" ]]; then + server_monitor_test; quickExit + fi + # Test SSH key authentication using the --ssh-check flag if [[ "${SSHTEST}" == "1" ]]; then if [[ "${NOKEY}" != "TRUE" ]] && [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then @@ -95,6 +100,9 @@ function go() { if [[ "${REQUIREAPPROVAL}" == "TRUE" ]] && [[ -f "${WORKPATH}/${APP}/.queued" ]] && [[ -f "${WORKPATH}/${APP}/.approved" ]]; then notice "Processing outstanding approval..." fi + + # Weird spot for this I know + server_monitor } function fixIndex() { From d2265e2d22d4718bf187268fa20a9e3a47a284e3 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 13 Dec 2017 00:30:53 -0800 Subject: [PATCH 015/334] Temp cleanup --- etc/html/default/digest/header.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etc/html/default/digest/header.html b/etc/html/default/digest/header.html index ce3735c..79a8391 100755 --- a/etc/html/default/digest/header.html +++ b/etc/html/default/digest/header.html @@ -46,6 +46,7 @@
+ + + + + + + + + + + + + + + + + +
From 8b090900919080a0ba92ee12e7ad8e32baece41b Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 13 Dec 2017 14:28:57 -0800 Subject: [PATCH 016/334] awk or gawk? --- lib/git.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git.sh b/lib/git.sh index fd89d7f..84c5c13 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -330,6 +330,6 @@ function gitStats() { FULLUSER=$(<"${trshFile}") git log --author="${FULLUSER}" --pretty=tformat: --numstat | \ # The single quotes were messing with trying to line break this one - gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "Your total lines of code contributed so far: %s\n(+%s added | -%s deleted)\n",loc,add,subs }' - + awk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "Your total lines of code contributed so far: %s\n(+%s added | -%s deleted)\n",loc,add,subs }' - fi } From ba3c364d2c5cd58e72fc82d462ca762671c71d7b Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 18 Dec 2017 09:31:26 -0800 Subject: [PATCH 017/334] Trimming decimal points that equal .00 from monitor reports --- install/doinst.sh | 4 ++-- lib/analytics.sh | 2 +- lib/monitor.sh | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/install/doinst.sh b/install/doinst.sh index bb787e4..2e35155 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -75,7 +75,7 @@ else fi # Declare dependencies -dependencies=(awk cat curl echo eval gawk git grep pkill printf read sed sendmail sleep tput wget) +dependencies=(awk cat curl echo eval git grep pkill printf read sed sendmail sleep tput wget) # Declare optional stuff options=(gitchart grunt npm scp ssh sshpass wp) @@ -110,7 +110,7 @@ for k in "${options[@]}" ; do done if [[ -n "${message}" ]] ; then - echo "${fg_red}WARNING: Some extended functions will not be available${reset}" + echo "${fg_red}WARNING: Some extended functionality may not be available${reset}" # echo ${message}; fi diff --git a/lib/analytics.sh b/lib/analytics.sh index 222296e..0f02057 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -41,7 +41,7 @@ function analytics() { if [[ "${RND}" == "1" ]]; then # Sometimes Google reports confusion percentages that exceed # 100%, let's kill those results - if [[ "${}" -gt "100" ]]; then + if [[ "${SIZE}" -gt "100" ]]; then analyticsFail elif [[ "${SIZE}" -gt "50" ]]; then ANALYTICSMSG="Last week ${SIZE} percent of your users were first time visitors. That's great!" diff --git a/lib/monitor.sh b/lib/monitor.sh index 166988e..85d2a2b 100755 --- a/lib/monitor.sh +++ b/lib/monitor.sh @@ -48,7 +48,7 @@ function server_monitor_test() { } function server_monitor_log() { - # :oad the password and setup the curl command + # Load the password and setup the curl command MONITORPASS=$(<$MONITORPASS) MONITORAPI="${MONITORURL}?tag=serveruptime&email=${MONITORUSER}&app_password=${MONITORPASS}&server_id=${SERVERID}&HoursUnit=${MONITORHOURS}" curl -s --request GET "${MONITORAPI}" -o "${trshFile}" @@ -59,6 +59,10 @@ function server_monitor_log() { UPTIME="$(sed 's/^[^:]*://g' <<< "${UPTIME}")" # Round to two decimal places UPTIME="$(printf '%0.2f\n' "${UPTIME}")" + # Lop off the .00 if we're at 100% + if [[ "${UPTIME}" == "100.00" ]]; then + UPTIME="100" + fi # Latency LATENCY="$(grep -Po '"average_latency":.*?[^\\]",' ${trshFile})" From 6c5442785768cdb85ddf4bc122b683547bdbcded Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 27 Dec 2017 23:48:26 -0800 Subject: [PATCH 018/334] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9def72..ed6bea5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Server health information now included in email/html logs via integration with [PHP Server Monitor](http://www.phpservermonitor.org/) using this [API](https://github.com/skydiver/PHP-Server-Monitor-API) - Option to include web hosting as a line item on monthly reports - Remote servers can now host project logs, digests, statistics, and reports ### Changed From 0ca8a7676e581096cd89cc0b3628b882d2679296 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 28 Dec 2017 00:16:52 -0800 Subject: [PATCH 019/334] Server monitor integration --- etc/html/default/digest/footer.html | 2 +- etc/html/default/footer.html | 2 +- etc/html/default/header.html | 4 +++- etc/html/default/stats/index.html | 2 +- lib/monitor.sh | 13 ++++++++----- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/etc/html/default/digest/footer.html b/etc/html/default/digest/footer.html index 5658922..5d22d9a 100755 --- a/etc/html/default/digest/footer.html +++ b/etc/html/default/digest/footer.html @@ -19,7 +19,7 @@ diff --git a/etc/html/default/footer.html b/etc/html/default/footer.html index e2557fb..ec82169 100755 --- a/etc/html/default/footer.html +++ b/etc/html/default/footer.html @@ -10,7 +10,7 @@ diff --git a/etc/html/default/header.html b/etc/html/default/header.html index 4537e1b..03e0f37 100755 --- a/etc/html/default/header.html +++ b/etc/html/default/header.html @@ -1,3 +1,4 @@ + @@ -67,4 +68,5 @@ Production URL:{{PRODURL}}

Uptime: {{UPTIME}}%
-
Latency: {{LATENCY}}s
\ No newline at end of file +
Latency: {{LATENCY}}s
+ \ No newline at end of file diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index c008afa..24f0f39 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -111,7 +111,7 @@

Any Quest

diff --git a/lib/monitor.sh b/lib/monitor.sh index 85d2a2b..6af8492 100755 --- a/lib/monitor.sh +++ b/lib/monitor.sh @@ -17,9 +17,12 @@ function server_monitor() { # which is dealing with the last calendar month, but oh well we get what we pay # for. The PHP Monitor API has no ability to look up results in that way MONITORHOURS="720" - else - # Default to last 7 days + elif [[ "${DIGEST}" == "1" ]] || [[ "${PROJSTATS}" == "1" ]]; then + # Digests and reports should average the 7 days MONITORHOURS="168" + else + # Anything else defaults to 24 hours + MONITORHOURS="24" fi server_monitor_log trace "Uptime: ${UPTIME} / Latency: ${LATENCY} (avg. over last ${MONITORHOURS} hours)" @@ -39,10 +42,10 @@ function server_monitor_test() { fi # Check last 7 days for the sake of the test - MONITORHOURS="168" + MONITORHOURS="24" server_monitor_log - console "API: ${MONITORAPI}" - notice "Results (last 7 days)" + console "API: ${MONITORAPI}" + notice "Results (last 24 hours)" console "Uptime: ${UPTIME}%" console "Latency: ${LATENCY}s" } From 1997457202f76e70a4576298761ae756a7e4f953 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 31 Dec 2017 13:28:23 -0800 Subject: [PATCH 020/334] Improved report URL, resolve #119 --- CHANGELOG.md | 1 + etc/html/default/report/header.html | 2 +- lib/post-log.sh | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6bea5..347c4b4 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Item descriptions are now editable in reports - Cleaned up email output generated using `deploy --email-test` +- Report URLs are now writted as `report/YEAR-MONTH.php` instead of using a hard to remember string ### Fixed - Fixed a bug that kept SSH keys from being properly checked - Added a workaround for Google potentially displaying over 100% of user sessions as new diff --git a/etc/html/default/report/header.html b/etc/html/default/report/header.html index ec6e499..0e5a447 100755 --- a/etc/html/default/report/header.html +++ b/etc/html/default/report/header.html @@ -32,7 +32,7 @@

{{PROJCLIENT}}

- + diff --git a/lib/post-log.sh b/lib/post-log.sh index e931831..c5a6791 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -91,7 +91,7 @@ function postLog() { fi if [[ "${REPORT}" == "1" ]]; then - REMOTEFILE="${EPOCH}.php" + REMOTEFILE="${CURYR}-${CURMTH}.php" REPORTURL="${REMOTEURL}/${APP}/report/${REMOTEFILE}" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report/css" From 6a49f4b222131e2eeba7f8f75a854a39af37245f Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 1 Jan 2018 13:03:32 -0800 Subject: [PATCH 021/334] Fixed calendar year rollover, resolve #120 --- lib/report.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/report.sh b/lib/report.sh index dac791d..f4a7ba5 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -16,7 +16,7 @@ function createReport() { CURYR="$(date +%Y)" if [[ "${CURMTH}" -eq 1 ]]; then - PRVMTH="12" + PRVMTH="11" PRVYR=`expr "${CURYR}" - 1` else PRVMTH=`expr "${CURMTH}" - 2` PRVYR="${CURYR}" @@ -30,16 +30,16 @@ function createReport() { LASTDY=`cal ${PRVMTH} ${PRVYR} | egrep "28|29|30|31" |tail -1 |awk '{print $NF}'` if [[ -n "${INCLUDEHOSTING}" ]] && [[ "${INCLUDEHOSTING}" != "FALSE" ]]; then - # If INCLUDEHOSTING is equal to something other than TRUE, its value - # will be used as the text string in the report + # If INCLUDEHOSTING is equal to something other than TRUE (And not FALSE), + # its value will be used as the text string in the report if [[ "${INCLUDEHOSTING}" == "TRUE" ]]; then - INCLUDEHOSTING="Web hosting for the month of ${LASTMONTH}" + INCLUDEHOSTING="Web hosting for the month of ${LASTMONTH} ${PRVYR}" fi echo "" >> "${statFile}" fi - #if [[ $(git log --before={'date "+%Y-%m-01"'} --after=${PRVYR}-${PRVMTH}-31) ]]; then - git log --all --no-merges --first-parent --before={'date "+%Y-%m-01"'} --after="${PRVYR}-${PRVMTH}-31 00:00" --pretty=format:"" >> "${statFile}" + trace "git log --all --no-merges --first-parent --before=\"${CURYR}-${CURMTH}-1 00:00\" --after=\"${PRVYR}-${PRVMTH}-${LASTDY} 00:00\" --pretty=format:\"\"" + git log --all --no-merges --first-parent --before="${CURYR}-${CURMTH}-1 00:00" --after="${PRVYR}-${PRVMTH}-${LASTDY} 00:00" --pretty=format:"" >> "${statFile}" # If it's an empty report, this empty row will keep the javascript from breaking. Kludgy I know. if [[ ! -s "${statFile}" ]]; then From ee55f493add0dfbeef3160014ac45aa78eb3e788 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 1 Jan 2018 13:06:51 -0800 Subject: [PATCH 022/334] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 347c4b4..0fd0aae 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Cleaned up email output generated using `deploy --email-test` - Report URLs are now writted as `report/YEAR-MONTH.php` instead of using a hard to remember string ### Fixed -- Fixed a bug that kept SSH keys from being properly checked +- Fixed a bug with running a report on dates in the previous calendar year - Added a workaround for Google potentially displaying over 100% of user sessions as new +- Fixed a bug that kept SSH keys from being properly checked ## [3.6.5] - 11-17-2017 ### Added From 727f95b50316ed84e6afa546e23cac3b8a8279d0 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 2 Jan 2018 16:33:46 -0800 Subject: [PATCH 023/334] Report format tweak --- etc/html/default/report/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/html/default/report/header.html b/etc/html/default/report/header.html index 0e5a447..e6e0b1f 100755 --- a/etc/html/default/report/header.html +++ b/etc/html/default/report/header.html @@ -32,7 +32,7 @@

{{PROJCLIENT}}

Invoice #Invoice Ref. #
${TASK}X
${INCLUDEHOSTING}
%hX
%s
%hX
%s
%hX
%s
- + From 011129e2cd2a19fcacdcef67792b02a3bb8225b6 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 8 Jan 2018 09:25:17 -0800 Subject: [PATCH 024/334] Analytics payload bug, resolve #122 --- lib/analytics.sh | 1 + lib/digest.sh | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/analytics.sh b/lib/analytics.sh index 0f02057..206fbac 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -82,6 +82,7 @@ function analyticsData() { SIZE="$(printf "%.0f\n" "${RESULT}")" } +# If no other results are worht displaying, fall back to displaying hits function analyticsFail() { METRIC="hits" analyticsData diff --git a/lib/digest.sh b/lib/digest.sh index 744d96c..6ccb967 100755 --- a/lib/digest.sh +++ b/lib/digest.sh @@ -66,8 +66,13 @@ function createDigest() { # git log --no-merges --since="7 days ago" --reverse --stat | grep -Eo "[0-9]{1,} files? changed" | grep -Eo "[0-9]{1,}" | awk "{ sum += \$1 } END { print sum }" processHTML + # Strip out useless analytics results if [[ -z "${RESULT}" ]] || [[ "${RESULT}" == "0" ]] || [[ "${SIZE}" == "0" ]]; then sed -i '/ANALYTICS/d' "${htmlFile}" + else + if [[ "${METRIC}" == "hits" ]] && [[ "${RESULT}" -lt "499" ]]; then + sed -i '/ANALYTICS/d' "${htmlFile}" + fi fi # Get the email payload ready From c3b6a2d201611286b2360c69286bfd29e6213db7 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 10 Jan 2018 21:17:52 -0800 Subject: [PATCH 025/334] New release function, resolves #123 --- CHANGELOG.md | 1 + deploy.sh | 5 +++-- lib/core.sh | 9 +++++---- lib/release-check.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100755 lib/release-check.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd0aae..90a4a2d 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Server health information now included in email/html logs via integration with [PHP Server Monitor](http://www.phpservermonitor.org/) using this [API](https://github.com/skydiver/PHP-Server-Monitor-API) - Option to include web hosting as a line item on monthly reports - Remote servers can now host project logs, digests, statistics, and reports +- New release check now runs upon launch ### Changed - Item descriptions are now editable in reports - Cleaned up email output generated using `deploy --email-test` diff --git a/deploy.sh b/deploy.sh index 273769b..ac63e12 100755 --- a/deploy.sh +++ b/deploy.sh @@ -92,7 +92,8 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX QUEUED \ DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile CURMTH \ CURYR PRVMTH PRVYR LASTDY TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ - MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI <<< "" + MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI RELEASE RELEASENOTES \ + RELEASEURL <<< "" echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile} ${trshFile} ${statFile} ${urlFile} ${htmlFile} ${htmlSendmail} ${htmlEmail} ${clientEmail} ${textSendmail} ${deployPath} ${etcLocation} ${libLocation} @@ -109,7 +110,7 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} ${CURMTH} ${CURYR} ${PRVMTH} ${PRVYR} ${LASTDY} ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} ${SERVERID} ${MONITORHOURS} ${LATENCY} ${UPTIME} ${MONITORTEST} - ${MONITORAPI}" > /dev/null + ${MONITORAPI} ${RELEASE} ${RELEASENOTES} ${RELEASEURL}" > /dev/null # Options function flags() { diff --git a/lib/core.sh b/lib/core.sh index d43beca..ebfa86f 100755 --- a/lib/core.sh +++ b/lib/core.sh @@ -7,10 +7,11 @@ ############################################################################### function coreApp() { - depCheck # Check that required commands are available - gitStart # Check for a valid git project and get set up - lock # Create lock file - go # Start a deployment work session + depCheck # Check that required commands are available + release_check # Check for newer version at Github + gitStart # Check for a valid git project and get set up + lock # Create lock file + go # Start a deployment work session if [[ "${DIGEST}" == "1" ]]; then createDigest elif [[ "${REPORT}" == "1" ]]; then diff --git a/lib/release-check.sh b/lib/release-check.sh new file mode 100755 index 0000000..c384584 --- /dev/null +++ b/lib/release-check.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# +# release-check.sh +# +############################################################################### +# Check to see if there is a new release version on Github +############################################################################### + +function release_check() { + # Only check for a newer release when someone is at the console + if [[ "${FORCE}" != "1" ]]; then + # Get the release tag + trace "Checking for deploy updates..." + RELEASE="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep \"tag_name\")" + # Remove the extra garbage + RELEASE="${RELEASE#*v}" + RELEASE="$(printf '%s' "${RELEASE}" | sed 's/",//g')" + + # Compare versions + if version_compare "${RELEASE}" "${VERSION}"; then + # Get release notes + RELEASENOTES="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep \"body\")" + # Remove the extra garbage + RELEASENOTES="$(printf '%s' "${RELEASENOTES}" | sed 's^\"body\": \"^^g')" + RELEASENOTES="${RELEASENOTES//\"}" + RELEASENOTES=${RELEASENOTES#" "} + + # User feedback + info "\r\nNew release found: ${RELEASE}" + printf "${RELEASENOTES}" | fold --spaces -w 78; emptyLine + # Update? + if yesno --default yes "Would you like to download now? [Y/n] "; then + # Get latest + RELEASEURL="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep tarball_url | cut -d '"' -f 4)" + curl -Ls "${RELEASEURL}" -o "deploy-${RELEASE}.tar.gz" + # Eventually I'll have a full install here but for now we'll bail + console "Try 'tar zxvf deploy-${RELEASE}.tar.gz' and then 'sudo doinst.sh' from the archive root directory." + quietExit + fi + fi + fi +} + +function version_compare() { + test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; +} From 15db0e5584de4446f4f72496dae5b67f5d604d7b Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sat, 13 Jan 2018 11:04:12 -0800 Subject: [PATCH 026/334] Release 3.6.6 --- CHANGELOG.md | 7 +++++-- README.md | 8 ++++---- deploy.sh | 2 +- lib/invoice.sh | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100755 lib/invoice.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a4a2d..4d274b0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [3.6.6] - 01-13-2018 ### Added - Server health information now included in email/html logs via integration with [PHP Server Monitor](http://www.phpservermonitor.org/) using this [API](https://github.com/skydiver/PHP-Server-Monitor-API) - Option to include web hosting as a line item on monthly reports @@ -179,7 +179,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Old monolithic script rewritten -[Unreleased]: https://github.com/EMRL/deploy/compare/v3.6.5...HEAD +[Unreleased]: https://github.com/EMRL/deploy/compare/v3.6.6...HEAD +[3.6.8]: https://github.com/EMRL/deploy/compare/v3.6.7...3.6.8 +[3.6.7]: https://github.com/EMRL/deploy/compare/v3.6.6...3.6.7 +[3.6.6]: https://github.com/EMRL/deploy/compare/v3.6.5...3.6.6 [3.6.5]: https://github.com/EMRL/deploy/compare/v3.6.4...3.6.5 [3.6.4]: https://github.com/EMRL/deploy/compare/v3.6...v3.6.4 [3.6]: https://github.com/EMRL/deploy/compare/v3.5.7...v3.6 diff --git a/README.md b/README.md index 87e6cfc..adae3e4 100755 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ # Deploy -[![release](https://img.shields.io/badge/release-v3.6.5-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) +[![release](https://img.shields.io/badge/release-v3.6.6-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) [![Build Status](https://travis-ci.org/EMRL/deploy.svg?branch=master)](https://travis-ci.org/EMRL/deploy) `deploy` is a shell script designed to speed up and automate project deployment. Its main focus is Wordpress websites, but it can be used with any code repository. -[![asciicast](https://asciinema.org/a/mMCid9O2BK7JrpocQuSl3CRkP.png)](https://asciinema.org/a/mMCid9O2BK7JrpocQuSl3CRkP?t=0) +[Changelog](https://github.com/EMRL/deploy/blob/master/CHANGELOG.md) This script is in daily use at [EMRL](http://emrl.com), an advertising, design, and development agency in northern California. If you have any questions, please feel free to contact us. Please note that our documentation here is nearly useless, and there is quite a bit of setup involved in getting this running reliably. A full setup guide is coming soon™. -[View Changelog](https://github.com/EMRL/deploy/blob/master/CHANGELOG.md) - ## Startup Options ``` @@ -52,6 +50,8 @@ Other Options: --variable-list Output a project's declared variables ``` +[![asciicast](https://asciinema.org/a/mMCid9O2BK7JrpocQuSl3CRkP.png)](https://asciinema.org/a/mMCid9O2BK7JrpocQuSl3CRkP?t=0) + ## How It Works Basically, this thing is a wrapper that simplifies web app deployment from a development or staging environment to a production server. At the moment is mostly focused on Wordpress projects but in it should work for other stuff too. diff --git a/deploy.sh b/deploy.sh index ac63e12..faf8e70 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.6rc" +VERSION="3.6.6" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LASTMONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" diff --git a/lib/invoice.sh b/lib/invoice.sh new file mode 100755 index 0000000..31a784b --- /dev/null +++ b/lib/invoice.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# invoice.sh +# +############################################################################### +# Handles creating invoices and integration with InvoiceNinja +############################################################################### + +function create_invoice() { + # Will create the invoice payload + trace "This is an empty function" +} + +function send_invoice() { + # Hit the InvoiceNinja API and create the invoice + # + # Things that need to be defined: + # API URL + # client_id + # + # And then we'll loop through + # product + # notes + # cost + # qty + # + # Example to create an invoice: + # + # curl -X POST https://invoice.com/api/v1/invoices -H "Content-Type:application/json" \ + # -d '{"client_id":"1", "invoice_items":[{"product_key": "ITEM", "notes":"Test", "cost":10, "qty":1}]}' \ + # -H "X-Ninja-Token: ###################################" + trace "This is an empty function" +} \ No newline at end of file From 5c0b341e5f4133483dea0f49f880a70a195a955d Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 14 Jan 2018 23:39:00 -0800 Subject: [PATCH 027/334] Some functions renamed --- deploy.sh | 2 +- install/doinst.sh | 20 ++++++++++---------- lib/active-check.sh | 2 +- lib/analytics.sh | 18 +++++++++--------- lib/approval.sh | 2 +- lib/core.sh | 16 ++++++++-------- lib/deployment.sh | 12 ++++++------ lib/digest.sh | 2 +- lib/display-styles.sh | 2 +- lib/error-check.sh | 12 ++++++------ lib/git.sh | 30 +++++++++++++++--------------- lib/mail-log.sh | 4 ++-- lib/permissions-fix.sh | 10 +++++----- lib/post-integration.sh | 4 ++-- lib/post-log.sh | 10 +++++----- lib/quit.sh | 18 +++++++++--------- lib/report.sh | 2 +- lib/slack.sh | 4 ++-- lib/ssh-check.sh | 4 ++-- lib/utilities.sh | 6 +++--- lib/webhook.sh | 6 +++--- lib/wp-acf.sh | 2 +- lib/wp-plugins.sh | 4 ++-- 23 files changed, 96 insertions(+), 96 deletions(-) diff --git a/deploy.sh b/deploy.sh index faf8e70..e7f97c3 100755 --- a/deploy.sh +++ b/deploy.sh @@ -564,7 +564,7 @@ if [[ "${MERGE}" == "1" ]] && [[ -z "${PRODUCTION}" ]]; then fi # Execute the deploy process -coreApp +core # Trapping stuff trap userExit INT diff --git a/install/doinst.sh b/install/doinst.sh index 2e35155..da2d401 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -57,7 +57,7 @@ function check_optional() { command -v $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } } -function errorChk() { +function error_check() { EXITCODE=$?; if [[ "${EXITCODE}" != "0" ]]; then echo "Error ${EXITCODE}: deploy not installed." @@ -119,27 +119,27 @@ echo; sleep 1 if [[ ! -d /etc/deploy ]] && [[ ! -d /etc/deploy/lib ]] && [[ ! -d /etc/deploy/crontab ]]; then echo "Creating directories" if [[ ! -d /etc/deploy ]]; then - sudo mkdir /etc/deploy; errorChk + sudo mkdir /etc/deploy; error_check fi if [[ ! -d /etc/deploy/lib ]]; then - sudo mkdir /etc/deploy/lib; errorChk + sudo mkdir /etc/deploy/lib; error_check fi if [[ ! -d /etc/deploy/lib/crontab ]]; then - sudo mkdir /etc/deploy/lib/crontab; errorChk + sudo mkdir /etc/deploy/lib/crontab; error_check fi fi echo "Installing configuration files" -sudo cp -R etc/* /etc/deploy; errorChk -sudo cp etc/.deployrc /etc/deploy; errorChk +sudo cp -R etc/* /etc/deploy; error_check +sudo cp etc/.deployrc /etc/deploy; error_check if [[ ! -f /etc/deploy/deploy.conf ]]; then - cp /etc/deploy/deploy-example.conf /etc/deploy/deploy.conf; errorChk + cp /etc/deploy/deploy-example.conf /etc/deploy/deploy.conf; error_check fi -cp -R lib/* /etc/deploy/lib; errorChk -cp deploy.sh /usr/local/bin/deploy; errorChk -sudo chmod 755 /usr/local/bin/deploy; errorChk +cp -R lib/* /etc/deploy/lib; error_check +cp deploy.sh /usr/local/bin/deploy; error_check +sudo chmod 755 /usr/local/bin/deploy; error_check echo "Successfully installed, try typing 'deploy' for help." diff --git a/lib/active-check.sh b/lib/active-check.sh index 4f1dcad..6ddf2f1 100755 --- a/lib/active-check.sh +++ b/lib/active-check.sh @@ -7,7 +7,7 @@ ############################################################################### trace "Loading activity checking" -function activeChk() { +function active_check() { if [[ "${FORCE}" == "1" ]] && [[ "${UPGRADE}" == "1" ]] && [[ "${QUIET}" == "1" ]] && [[ "${ACTIVECHECK}" = "TRUE" ]]; then trace "Checking for active files" active_files=$(find "${WORKPATH}/${APP}" -mmin -"${CHECKTIME}" ! -path "${WORKPATH}/${APP}/public/app/wflogs/*" ! -path "${WORKPATH}/${APP}/.git/*" ! -path "${WORKPATH}/${APP}/.git") diff --git a/lib/analytics.sh b/lib/analytics.sh index 206fbac..eb3be70 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -28,13 +28,13 @@ function analytics() { ACCESSTOKEN="$(awk -F\" '{print $4}' "${trshFile}")" # Grab data from Google - analyticsData + ga_data if [[ "${RND}" == "0" ]]; then if [[ "${RESULT}" -gt "499" ]]; then ANALYTICSMSG="You had ${SIZE} hits in the last week. Awesome!" else - analyticsFail + ga_fail fi fi @@ -42,7 +42,7 @@ function analytics() { # Sometimes Google reports confusion percentages that exceed # 100%, let's kill those results if [[ "${SIZE}" -gt "100" ]]; then - analyticsFail + ga_fail elif [[ "${SIZE}" -gt "50" ]]; then ANALYTICSMSG="Last week ${SIZE} percent of your users were first time visitors. That's great!" else @@ -55,7 +55,7 @@ function analytics() { if [[ "${SIZE}" -ge "30" ]]; then ANALYTICSMSG="You had traffic from ${SIZE} organic searches last week. Not bad!" else - analyticsFail + ga_fail fi fi @@ -64,7 +64,7 @@ function analytics() { if [[ "${RESULT}" -gt "2" ]]; then ANALYTICSMSG="Last week visitors averaged over ${RESULT} minutes each on your site. Nice!" else - analyticsFail + ga_fail fi fi @@ -72,24 +72,24 @@ function analytics() { if [[ "${SIZE}" -ge "20" ]]; then ANALYTICSMSG="Your site had ${SIZE} social media interactions in the last week!" else - analyticsFail + ga_fail fi fi } -function analyticsData() { +function ga_data() { RESULT=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:$METRIC&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) SIZE="$(printf "%.0f\n" "${RESULT}")" } # If no other results are worht displaying, fall back to displaying hits -function analyticsFail() { +function ga_fail() { METRIC="hits" analyticsData ANALYTICSMSG="You had ${SIZE} hits in the last week." } -function analyticsTest() { +function ga_test() { emptyLine if [[ -z "${CLIENTID}" ]] || [[ -z "${CLIENTSECRET}" ]]; then warning "Define API project" diff --git a/lib/approval.sh b/lib/approval.sh index b078c9f..502c9a8 100755 --- a/lib/approval.sh +++ b/lib/approval.sh @@ -35,7 +35,7 @@ function approve() { git add "${QUEUED}" | tee --append "${logFile}" fi done < "${WORKPATH}/${APP}/.queued" - git commit -m "${notes}" &>> "${logFile}"; errorChk + git commit -m "${notes}" &>> "${logFile}"; error_check trace "Commit message: ${notes}" } diff --git a/lib/core.sh b/lib/core.sh index ebfa86f..d46cf79 100755 --- a/lib/core.sh +++ b/lib/core.sh @@ -6,16 +6,16 @@ # The core application ############################################################################### -function coreApp() { - depCheck # Check that required commands are available - release_check # Check for newer version at Github - gitStart # Check for a valid git project and get set up - lock # Create lock file - go # Start a deployment work session +function core() { + dependency_check # Check that required commands are available + release_check # Check for newer version at Github + gitStart # Check for a valid git project and get set up + lock # Create lock file + go # Start a deployment work session if [[ "${DIGEST}" == "1" ]]; then - createDigest + create_digest elif [[ "${REPORT}" == "1" ]]; then - createReport + create_report else srvCheck # Check that servers are up and running if [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then diff --git a/lib/deployment.sh b/lib/deployment.sh index 8e94a88..f04c7cc 100755 --- a/lib/deployment.sh +++ b/lib/deployment.sh @@ -23,9 +23,9 @@ function preDeploy() { emptyLine trace "Stashing dirty files" if [[ "${VERBOSE}" == "1" ]] && [[ "${QUIET}" != "1" ]]; then - git stash | tee --append "${logFile}"; errorChk + git stash | tee --append "${logFile}"; error_check else - git stash >> "${logFile}"; errorChk + git stash >> "${logFile}"; error_check fi currentStash="1" else @@ -55,7 +55,7 @@ function pkgDeploy() { emptyLine if [[ -n "${DEPLOY}" ]]; then # Add ssh keys and double check directoy - cd "${WORKPATH}/${APP}" || errorChk + cd "${WORKPATH}/${APP}" || error_check if [[ "${INCOGNITO}" != "TRUE" ]]; then trace "Launching deployment" else @@ -73,18 +73,18 @@ function pkgDeploy() { # If we don't require approval to push to live, keep going if [[ "${FORCE}" = "1" ]] || yesno --default yes "Deploy to live server? [Y/n] "; then # Test deployment command before running - deployChk + deploy_check # Deploy via deployment command specified in configuration if [[ "${VERBOSE}" == "1" ]] && [[ "${INCOGNITO}" != "TRUE" ]]; then eval "${DEPLOY}" | tee --append "${logFile}" - errorChk + error_check else if [[ "${QUIET}" != "1" ]]; then eval "${DEPLOY}" &>> "${logFile}" & spinner $! else eval "${DEPLOY}" &>> "${logFile}" - errorChk + error_check fi fi diff --git a/lib/digest.sh b/lib/digest.sh index 6ccb967..9f9e97a 100755 --- a/lib/digest.sh +++ b/lib/digest.sh @@ -7,7 +7,7 @@ ############################################################################### trace "Loading digest handling" -function createDigest() { +function create_digest() { message_state="DIGEST" htmlDir diff --git a/lib/display-styles.sh b/lib/display-styles.sh index 9426318..e95c3e0 100755 --- a/lib/display-styles.sh +++ b/lib/display-styles.sh @@ -71,7 +71,7 @@ function error() { fi echo -e "\nERROR: $*" >> "${logFile}" error_msg="$*" - errorExit + error_exit } function warning() { diff --git a/lib/error-check.sh b/lib/error-check.sh index 0414c30..40820af 100755 --- a/lib/error-check.sh +++ b/lib/error-check.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# errorChk() +# error_check() # ############################################################################### # Handles various exit code checking @@ -8,16 +8,16 @@ trace "Loading error checking" # Try to get exit/error code, with a hard stop on fail -function errorChk() { +function error_check() { EXITCODE=$?; if [[ "${EXITCODE}" != 0 ]]; then warning "Exiting on error code ${EXITCODE}" error_msg="Exited on error code ${EXITCODE}" - errorExit + error_exit fi } -function deployChk() { +function deploy_check() { if [[ "${DEPLOY}" == *"mina"* ]]; then # && [[ "${DEPLOY}" != *"bundle"* ]]; then DEPLOYTEST="mina --simulate deploy" # Get variables organized @@ -102,14 +102,14 @@ function deployChk() { if [[ "${EXITCODE}" != 0 ]]; then warning "Deployment exited due to a configuration problem (Error ${EXITCODE})" error_msg="Deployment exited due to a configuration problem (Error ${EXITCODE})" - errorExit + error_exit fi trace "OK" fi } # Try to get exit/error code, with a hard stop on fail -function errorStatus() { +function error_status() { EXITCODE=$?; if [[ "${EXITCODE}" != 0 ]]; then error_msg="WARNING: Error code ${EXITCODE}" diff --git a/lib/git.sh b/lib/git.sh index 84c5c13..4fabcea 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -17,7 +17,7 @@ function gitStart() { info "${WORKPATH}/${APP} is not a valid directory." exit 1 else - cd "${WORKPATH}/${APP}" || errorChk + cd "${WORKPATH}/${APP}" || error_check fi # Check that .git exists @@ -43,7 +43,7 @@ function gitStart() { fi # Check for active files - activeChk + active_check # Try to clear out old git processes owned by this user, if they exist pkill -f git &>> /dev/null || true @@ -106,9 +106,9 @@ function gitStage() { if [[ "${FORCE}" = "1" ]] || yesno --default yes "Stage files? [Y/n] "; then trace "Staging files" if [[ "${VERBOSE}" -eq 1 ]]; then - git add -A | tee --append "${logFile}"; errorChk + git add -A | tee --append "${logFile}"; error_check else - git add -A &>> "${logFile}"; errorChk + git add -A &>> "${logFile}"; error_check fi else trace "Exiting without staging files"; userExit @@ -179,7 +179,7 @@ function gitCommit() { trace "Queuing commit message" echo "${notes}" > "${WORKPATH}/${APP}/.queued" else - git commit -m "${notes}" &>> "${logFile}"; errorChk + git commit -m "${notes}" &>> "${logFile}"; error_check trace "Commit message: ${notes}" fi fi @@ -196,7 +196,7 @@ function gitPushMstr() { trace "Pushing ${MASTER}"; fixIndex emptyLine if [[ "${VERBOSE}" -eq 1 ]]; then - git push | tee --append "${logFile}"; errorChk + git push | tee --append "${logFile}"; error_check else if [[ "${FORCE}" = "1" ]] || yesno --default yes "Push ${MASTER} branch? [Y/n] "; then if [[ "${NOKEY}" != "TRUE" ]]; then @@ -205,10 +205,10 @@ function gitPushMstr() { spinner $! info "Success. " else - git push &>> "${logFile}"; errorChk + git push &>> "${logFile}"; error_check fi else - git push &>> "${logFile}"; errorChk + git push &>> "${logFile}"; error_check fi else safeExit @@ -224,14 +224,14 @@ function gitChkProd() { notice "Checking out ${PRODUCTION} branch..."; fixIndex if [[ "${VERBOSE}" -eq 1 ]]; then - git checkout "${PRODUCTION}" | tee --append "${logFile}"; errorChk + git checkout "${PRODUCTION}" | tee --append "${logFile}"; error_check else if [[ "${QUIET}" != "1" ]]; then git checkout "${PRODUCTION}" &>> "${logFile}" & spinner $! info "Success. " else - git checkout "${PRODUCTION}" &>> "${logFile}"; errorChk + git checkout "${PRODUCTION}" &>> "${logFile}"; error_check fi fi @@ -243,7 +243,7 @@ function gitChkProd() { if [[ "${FORCE}" = "1" ]] || yesno --default yes "Current branch is ${current_branch} and should be ${PRODUCTION}, try again? [Y/n] "; then if [[ "${current_branch}" = "${MASTER}" ]]; then [[ -f "${gitLock}" ]] && rm "${gitLock}" - git add .; git checkout "${PRODUCTION}" &>> "${logFile}" #; errorChk + git add .; git checkout "${PRODUCTION}" &>> "${logFile}" #; error_check fi else safeExit @@ -269,7 +269,7 @@ function gitMerge() { # Clear out the index.lock file, cause reasons [[ -f "${gitLock}" ]] && rm "${gitLock}" # Bonus add, just because. Ugh. - # git add -A; errorChk + # git add -A; error_check if [[ "${VERBOSE}" -eq 1 ]]; then git merge "${MASTER}" | tee --append "${logFile}" else @@ -278,7 +278,7 @@ function gitMerge() { git merge "${MASTER}" &>> "${logFile}" & showProgress else - git merge "${MASTER}"&>> "${logFile}"; errorChk + git merge "${MASTER}"&>> "${logFile}"; error_check fi fi fi @@ -292,7 +292,7 @@ function gitPushProd() { trace "Push ${PRODUCTION}"; fixIndex emptyLine if [[ "${VERBOSE}" -eq 1 ]]; then - git push | tee --append "${logFile}"; errorChk + git push | tee --append "${logFile}"; error_check trace "OK" else if [[ "${FORCE}" = "1" ]] || yesno --default yes "Push ${PRODUCTION} branch? [Y/n] "; then @@ -302,7 +302,7 @@ function gitPushProd() { spinner $! info "Success. " else - git push &>> "${logFile}"; errorChk + git push &>> "${logFile}"; error_check fi sleep 1 if [[ $(git status --porcelain) ]]; then diff --git a/lib/mail-log.sh b/lib/mail-log.sh index 301a858..fb41ded 100755 --- a/lib/mail-log.sh +++ b/lib/mail-log.sh @@ -60,11 +60,11 @@ function mailLog() { fi } -function emailTest() { +function email_test() { console "Testing email..." if [[ -z "${TO}" ]]; then warning "No recipient address found."; emptyLine - cleanUp; exit 1 + clean_up; exit 1 else # Send HTML mail ( diff --git a/lib/permissions-fix.sh b/lib/permissions-fix.sh index 2918ec7..4c48b94 100755 --- a/lib/permissions-fix.sh +++ b/lib/permissions-fix.sh @@ -13,7 +13,7 @@ function permFix() { # /lib is obsolete for future repositories if [[ -d "$WORKPATH/$APP/lib" ]]; then - sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/lib; #errorChk + sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/lib; #error_check info " ${APP}/lib/" else sleep 1 @@ -28,13 +28,13 @@ function permFix() { sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/.gitmodules; > /dev/null; fi - sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/.git; #errorChk + sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/.git; #error_check info " ${APP}/.git" - sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/public/system; #errorChk + sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/public/system; #error_check info " ${APP}/public/system/" - sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/public/app; #errorChk + sudo chown -R "${DEVUSER}"."${DEVGROUP}" "${WORKPATH}"/"${APP}"/public/app; #error_check info " ${APP}/public/app/" - sudo chown -R "${APACHEUSER}"."${APACHEGROUP}" "${WORKPATH}"/"${APP}"/public/app; #errorChk + sudo chown -R "${APACHEUSER}"."${APACHEGROUP}" "${WORKPATH}"/"${APP}"/public/app; #error_check info " ${APP}/public/app/plugins" fi } diff --git a/lib/post-integration.sh b/lib/post-integration.sh index 94f5570..6aef391 100755 --- a/lib/post-integration.sh +++ b/lib/post-integration.sh @@ -8,7 +8,7 @@ trace "Loading integration" # Compile commit message with other stuff for integration -function buildLog() { +function build_log() { if [[ "${DIGEST}" != "1" ]]; then # OK let's grab the short version of the commit hash COMMITHASH="$(git rev-parse --short HEAD)"; @@ -86,7 +86,7 @@ function postCommit() { if [[ -n "$POSTEMAIL" ]]; then # Is it a valid email address? Ghetto check but better than nothing if [[ "$POSTEMAIL" == ?*@?*.?* ]]; then - buildLog; mailPost + build_log; mailPost else trace "Integration email address ${POSTEMAIL} does not look valid" fi diff --git a/lib/post-log.sh b/lib/post-log.sh index c5a6791..077d834 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -124,12 +124,12 @@ function htmlDir() { if [[ "${message_state}" == "REPORT" ]]; then if [[ ! -d "${LOCALHOSTPATH}/${APP}/report" ]]; then - mkdir "${LOCALHOSTPATH}/${APP}/report"; errorChk - mkdir "${LOCALHOSTPATH}/${APP}/report/css"; errorChk - mkdir "${LOCALHOSTPATH}/${APP}/report/js"; errorChk + mkdir "${LOCALHOSTPATH}/${APP}/report"; error_check + mkdir "${LOCALHOSTPATH}/${APP}/report/css"; error_check + mkdir "${LOCALHOSTPATH}/${APP}/report/js"; error_check fi - cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${LOCALHOSTPATH}/${APP}/report"; errorChk - cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${LOCALHOSTPATH}/${APP}/report"; errorChk + cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${LOCALHOSTPATH}/${APP}/report"; error_check + cp -R "${deployPath}/html/${HTMLTEMPLATE}/report/js" "${LOCALHOSTPATH}/${APP}/report"; error_check fi fi } diff --git a/lib/quit.sh b/lib/quit.sh index eb9eca1..a987b0a 100755 --- a/lib/quit.sh +++ b/lib/quit.sh @@ -16,17 +16,17 @@ function userExit() { mailLog fi # Clean up your mess - cleanUp; exit 0 + clean_up; exit 0 } # Quick exit, never send log. Ever. function quickExit() { # Clean up your mess - cleanUp; exit 0 + clean_up; exit 0 } # Exit on error -function errorExit() { +function error_exit() { message_state="ERROR"; makeLog # Compile log # Check email settings if [[ "${EMAILERROR}" == "TRUE" ]]; then @@ -40,7 +40,7 @@ function errorExit() { fi # Clean up your mess - cleanUp; exit 1 + clean_up; exit 1 } # Clean exit @@ -60,7 +60,7 @@ function safeExit() { else if [[ "${POSTTOSLACK}" == "TRUE" ]]; then trace "Posting to Slack" - buildLog; slackPost > /dev/null 2>&1 + build_log; slackPost > /dev/null 2>&1 fi fi @@ -68,16 +68,16 @@ function safeExit() { postWebhook # Clean up your mess - cleanUp; exit 0 + clean_up; exit 0 } # Clean exit, nothing to commit function quietExit() { # Clean up your mess - cleanUp; exit 0 + clean_up; exit 0 } -function cleanUp() { +function clean_up() { # If anything is stashed, unstash it. if [[ "${currentStash}" == "1" ]]; then trace "Unstashing files" @@ -108,7 +108,7 @@ function cleanUp() { # If Wordfence was an issue, restart the plugin if [[ "${WFOFF}" = "1" ]]; then - "${WPCLI}"/wp plugin activate --no-color wordfence &>> $logFile; errorChk + "${WPCLI}"/wp plugin activate --no-color wordfence &>> $logFile; error_check fi # Was this an approval? diff --git a/lib/report.sh b/lib/report.sh index f4a7ba5..4de845f 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -7,7 +7,7 @@ ############################################################################### trace "Loading report handling" -function createReport() { +function create_report() { message_state="REPORT" htmlDir diff --git a/lib/slack.sh b/lib/slack.sh index d6dee08..839371d 100755 --- a/lib/slack.sh +++ b/lib/slack.sh @@ -123,7 +123,7 @@ function slackPost () { if [[ "${DIGEST}" == "1" ]] && [[ -z "${GREETING}" ]]; then trace "No activity found, canceling digest." else - curl -X POST --data "payload={\"text\": \"${slack_icon} ${slack_message}\"}" "${SLACKURL}" > /dev/null 2>&1; errorStatus + curl -X POST --data "payload={\"text\": \"${slack_icon} ${slack_message}\"}" "${SLACKURL}" > /dev/null 2>&1; error_status fi } @@ -133,7 +133,7 @@ function slackTest { echo "${SLACKURL}" if [[ -z "${SLACKURL}" ]]; then warning "No Slack configuration found."; emptyLine - cleanUp; exit 1 + clean_up; exit 1 else curl -X POST --data "payload={\"text\": \"${slack_icon} Testing Slack integration of ${APP} from deploy ${VERSION}\nhttps://github.com/EMRL/deploy\"}" "${SLACKURL}" emptyLine diff --git a/lib/ssh-check.sh b/lib/ssh-check.sh index c59ed81..82ece58 100755 --- a/lib/ssh-check.sh +++ b/lib/ssh-check.sh @@ -12,7 +12,7 @@ function sshChk() { if [[ "${NOKEY}" != "TRUE" ]]; then trace "Checking SSH configuration" if [[ "${REPOHOST}" == *"bitbucket"* ]]; then - ssh -oStrictHostKeyChecking=no git@bitbucket.org &> /dev/null; errorStatus + ssh -oStrictHostKeyChecking=no git@bitbucket.org &> /dev/null; error_status if [[ "${EXITCODE}" != "0" ]]; then error "git@bitbucket.org: SSH check failed (Error code ${EXITCODE})" else @@ -20,7 +20,7 @@ function sshChk() { trace "git@bitbucket.org: OK" fi elif [[ "${REPOHOST}" == *"github"* ]]; then - ssh -oStrictHostKeyChecking=no git@github.org &> /dev/null; errorStatus + ssh -oStrictHostKeyChecking=no git@github.org &> /dev/null; error_status if [[ "${EXITCODE}" != "0" ]]; then error "git@github.org: SSH check failed (Error code ${EXITCODE})" else diff --git a/lib/utilities.sh b/lib/utilities.sh index c7a9014..495b519 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -29,7 +29,7 @@ function go() { # Email test if [[ "${EMAILTEST}" == "1" ]]; then - emailTest; quickExit + email_test; quickExit fi # Test analytics authentication @@ -110,7 +110,7 @@ function fixIndex() { if [[ "${FIXINDEX}" == "TRUE" ]]; then if [[ ! -w "${WORKPATH}/${APP}/.git/index" ]]; then trace "Index is not writable, attempting to fix..." - sudo chmod 777 "${WORKPATH}/${APP}/.git/index"; errorChk + sudo chmod 777 "${WORKPATH}/${APP}/.git/index"; error_check if [[ ! -w "${WORKPATH}/${APP}/.git/index" ]]; then error "Unable to write new index file."; fi @@ -120,7 +120,7 @@ function fixIndex() { } # Check that dependencies exist -function depCheck() { +function dependency_check() { # Is git installed? hash git 2>/dev/null || { error "deploy ${VERSION} requires git to function properly." diff --git a/lib/webhook.sh b/lib/webhook.sh index 7015a10..3b737f1 100755 --- a/lib/webhook.sh +++ b/lib/webhook.sh @@ -14,13 +14,13 @@ function postWebhook { message_state="DIGEST" payload="*${PROJNAME}* updates for the week of ${WEEKOF} (<${DIGESTURL})" # Send it - curl -X POST --data "payload={\"text\": \"${payload}\"}" "${POSTURL}" > /dev/null 2>&1; errorStatus + curl -X POST --data "payload={\"text\": \"${payload}\"}" "${POSTURL}" > /dev/null 2>&1; error_status fi # Create payload for reports if [[ "${REPORT}" == "1" ]]; then payload="Monthly report for *${PROJNAME}* created (<${REPORTURL})" - curl -X POST --data "payload={\"text\": \"${payload}\"}" "${POSTURL}" > /dev/null 2>&1; errorStatus + curl -X POST --data "payload={\"text\": \"${payload}\"}" "${POSTURL}" > /dev/null 2>&1; error_status fi fi } @@ -31,7 +31,7 @@ function postTest { echo "${POSTURL}" if [[ -z "${POSTURL}" ]]; then warning "No webhook URL found."; emptyLine - cleanUp; exit 1 + clean_up; exit 1 else curl -X POST --data "payload={\"text\": \"Testing POST integration of ${APP} from deploy ${VERSION}\nhttps://github.com/EMRL/deploy\"}" "${POSTURL}" emptyLine diff --git a/lib/wp-acf.sh b/lib/wp-acf.sh index 5ae2bfa..0880b35 100755 --- a/lib/wp-acf.sh +++ b/lib/wp-acf.sh @@ -6,7 +6,7 @@ # Handle ACF Pro updates in more reliable way than wp-cli ############################################################################### -function acfUpdate() { +function acf_update() { if grep -q "advanced-custom-fields-pro" "${wpFile}"; then ACFFILE="/tmp/acfpro.zip" # Download the ACF Pro upgrade file diff --git a/lib/wp-plugins.sh b/lib/wp-plugins.sh index f2d8c26..7d151df 100755 --- a/lib/wp-plugins.sh +++ b/lib/wp-plugins.sh @@ -33,10 +33,10 @@ function wpPlugins() { if [[ "${FORCE}" = "1" ]] || yesno --default no "Proceed with updates? [y/N] "; then # If ACFPRO needs an update, do it first via wget if [[ "${QUIET}" != "1" ]]; then - acfUpdate & + acf_update & spinner $! else - acfUpdate + acf_update fi # Let's get to work From 075c2c4fba3aff41cd9d7f9179f48f2ea9c4efb8 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 21 Jan 2018 11:26:13 -0800 Subject: [PATCH 028/334] Ongoing code cleanup, a couple small bug fixes --- CHANGELOG.md | 7 ++ deploy.sh | 49 ++++---- etc/deploy.sh | 270 +++++++++++++++++++++++----------------- install/doinst.sh | 2 +- lib/active-check.sh | 6 +- lib/analytics.sh | 103 ++++++++------- lib/approval.sh | 2 +- lib/core.sh | 2 +- lib/deployment.sh | 4 +- lib/digest.sh | 7 +- lib/display-styles.sh | 2 +- lib/error-check.sh | 2 +- lib/git.sh | 12 +- lib/invoice.sh | 2 +- lib/lock.sh | 2 +- lib/log-handling.sh | 2 +- lib/mail-log.sh | 3 +- lib/monitor.sh | 2 +- lib/package-manager.sh | 2 +- lib/permissions-fix.sh | 2 +- lib/post-integration.sh | 2 +- lib/post-log.sh | 2 +- lib/quit.sh | 2 +- lib/release-check.sh | 2 +- lib/report.sh | 2 +- lib/server-check.sh | 2 +- lib/slack.sh | 2 +- lib/smart-commits.sh | 2 +- lib/ssh-check.sh | 2 +- lib/statistics.sh | 2 +- lib/user-feedback.sh | 2 +- lib/utilities.sh | 8 +- lib/validate.sh | 4 +- lib/webhook.sh | 2 +- lib/wp-acf.sh | 6 +- lib/wp-core.sh | 2 +- lib/wp-plugins.sh | 2 +- lib/wp-wordfence.sh | 2 +- lib/wp.sh | 2 +- lib/yes-no.sh | 2 +- test/extended.sh | 0 41 files changed, 298 insertions(+), 237 deletions(-) mode change 100644 => 100755 test/extended.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d274b0..3bc9872 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Changed +- Configuration files restructred for better readability +- Many function and variable names changed for better consistency +### Fixed +- Google analytics no longer incorrectly display for projects that do not use them + ## [3.6.6] - 01-13-2018 ### Added - Server health information now included in email/html logs via integration with [PHP Server Monitor](http://www.phpservermonitor.org/) using this [API](https://github.com/skydiver/PHP-Server-Monitor-API) diff --git a/deploy.sh b/deploy.sh index e7f97c3..72c3e68 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # deploy.sh # @@ -23,7 +23,7 @@ APP="null" # Set mode set -uo pipefail -# Initialize and export all variables +# Initialize variables # Startup switches read -r APP UPGRADE SKIPUPDATE CURRENT VERBOSE QUIET STRICT DEBUG FORCE \ SLACKTEST FUNCTIONLIST VARIABLELIST AUTOMATE EMAILTEST APPROVE \ @@ -34,6 +34,7 @@ echo "${APP} ${UPGRADE} ${SKIPUPDATE} ${CURRENT} ${VERBOSE} ${QUIET} ${STRICT} ${AUTOMATE} ${EMAILTEST} ${APPROVE} ${DENY} ${PUBLISH} ${DIGEST} ${ANALYTICS} ${ANALYTICSTEST} ${PROJSTATS} ${UNLOCK} ${SSHTEST} ${TIME} ${UPDATEONLY} ${POSTTEST} ${REPORT} ${REPAIR}" > /dev/null + # Temp files read -r logFile wpFile coreFile postFile trshFile statFile urlFile <<< "" echo "${logFile} ${wpFile} ${coreFile} ${postFile} ${trshFile} ${statFile} @@ -61,7 +62,7 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ DIGESTURL CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD \ SSHCMD LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME \ TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ - REFRESHTOKEN PROFILEID METRIC RESULT ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO \ + REFRESHTOKEN PROFILEID ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO \ REPORTURL CLIENTCONTACT INCLUDEHOSTING <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} @@ -76,19 +77,18 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SCPHOSTPATH} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} ${EXPIRELOGS} ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} - ${REFRESHTOKEN} ${PROFILEID} ${METRIC} ${RESULT}" "${ALLOWROOT} + ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} ${SHORTEMAIL} ${DIGESTCOVER} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} ${INCLUDEHOSTING}" > /dev/null # Internal variables read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \ urlFile htmlFile htmlSendmail htmlEmail clientEmail textSendmail deployPath \ - etcLocation libLocation POSTEMAIL current_branch error_msg active_files notes \ + etcLocation libLocation POSTEMAIL current_branch error_msg notes \ UPDCORE TASKLOG PCA PCB PCC PCD PLUGINS slack_icon APPRC USERRC message_state \ COMMITURL COMMITHASH UPD1 UPD2 UPDATE gitLock AUTOMERGE MERGE EXITCODE \ currentStash deploy_cmd deps start_branch postSendmail SLACKUSER NOCHECK \ - ACFFILE VIEWPORT VIEWPORTPRE LOGTITLE LOGURL TIMESTAMP STARTUP WPROOT \ - WPAPP WPSYSTEM DONOTUPDATEWP gitHistory DIGESTWRAP AUTHOR AUTHOREMAIL \ - AUTHORNAME GRAVATAR IMGFILE SIZE RND ANALYTICSMSG digestSendmail MINAUSER \ + VIEWPORT VIEWPORTPRE LOGTITLE LOGURL TIMESTAMP STARTUP WPROOT \ + WPAPP WPSYSTEM DONOTUPDATEWP gitHistory ANALYTICSMSG digestSendmail MINAUSER \ MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX QUEUED \ DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile CURMTH \ CURYR PRVMTH PRVYR LASTDY TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ @@ -97,14 +97,13 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile} ${trshFile} ${statFile} ${urlFile} ${htmlFile} ${htmlSendmail} ${htmlEmail} ${clientEmail} ${textSendmail} ${deployPath} ${etcLocation} ${libLocation} - ${POSTEMAIL} ${current_branch} ${error_msg} ${active_files} ${notes} ${UPDCORE} + ${POSTEMAIL} ${current_branch} ${error_msg} ${notes} ${UPDCORE} ${TASKLOG} ${PCA} ${PCB} ${PCC} ${PCD} ${PLUGINS} ${slack_icon} ${APPRC} ${USERRC} ${message_state} ${COMMITURL} ${COMMITHASH} ${UPD1} ${UPD2} ${UPDATE} ${gitLock} ${AUTOMERGE} ${MERGE} ${EXITCODE} ${currentStash} ${deploy_cmd} ${deps} - ${start_branch} ${postSendmail} ${SLACKUSER} ${NOCHECK} ${ACFFILE} ${VIEWPORT} + ${start_branch} ${postSendmail} ${SLACKUSER} ${NOCHECK} ${VIEWPORT} ${VIEWPORTPRE} ${LOGTITLE} ${LOGURL} ${TIMESTAMP} ${STARTUP} ${WPROOT} ${WPAPP} - ${WPSYSTEM} ${DONOTUPDATEWP} ${gitHistory} ${DIGESTWRAP} ${AUTHOR} ${AUTHOREMAIL} - ${AUTHORNAME} ${GRAVATAR} ${IMGFILE} ${SIZE} ${RND} ${ANALYTICSMSG} + ${WPSYSTEM} ${DONOTUPDATEWP} ${gitHistory} ${ANALYTICSMSG} ${digestSendmail} ${MINAUSER} ${MINADOMAIN} ${SSHTARGET} ${SSHSTATUS} ${REMOTEFILE} ${GREETING} ${LOGSUFFIX} ${QUEUED} ${DISABLESSHCHECK} ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} ${CURMTH} ${CURYR} @@ -112,7 +111,7 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${SERVERID} ${MONITORHOURS} ${LATENCY} ${UPTIME} ${MONITORTEST} ${MONITORAPI} ${RELEASE} ${RELEASENOTES} ${RELEASEURL}" > /dev/null -# Options +# Display command options function flags() { echo -n "Usage: deploy [options] [target] ... @@ -272,28 +271,28 @@ fi # Fire up temporary log files. Consolidate this shit better someday, geez. # # Crash and burn -function logFail() { +function log_fail() { echo "Could not create temporary file, exiting."; exit 1 } # Main log file logFile="/tmp/$APP.log-$RANDOM.log" -(umask 077 && touch "${logFile}") || logFail -wpFile="/tmp/$APP.wp-$RANDOM.log"; (umask 077 && touch "${wpFile}" &> /dev/null) || logFail -coreFile="/tmp/$APP.core-$RANDOM.log"; (umask 077 && touch "${coreFile}" &> /dev/null) || logFail +(umask 077 && touch "${logFile}") || log_fail +wpFile="/tmp/$APP.wp-$RANDOM.log"; (umask 077 && touch "${wpFile}" &> /dev/null) || log_fail +coreFile="/tmp/$APP.core-$RANDOM.log"; (umask 077 && touch "${coreFile}" &> /dev/null) || log_fail # Start writing the logfile echo -e "Deployment logfile for ${APP^^} - $NOW\r" >> "${logFile}" echo -e "Launching deploy${STARTUP}\n" >> "${logFile}" # More crappy tmp files -postFile="/tmp/$APP.wtf-$RANDOM.log"; (umask 077 && touch "${postFile}" &> /dev/null) || logFail -trshFile="/tmp/$APP.trsh-$RANDOM.log"; (umask 077 && touch "${trshFile}" &> /dev/null) || logFail -statFile="/tmp/$APP.stat-$RANDOM.log"; (umask 077 && touch "${statFile}" &> /dev/null) || logFail -urlFile="/tmp/$APP.url-$RANDOM.log"; (umask 077 && touch "${urlFile}" &> /dev/null) || logFail -htmlFile="/tmp/$APP.log-$RANDOM.html"; (umask 077 && touch "${htmlFile}" &> /dev/null) || logFail -htmlEmail="/tmp/$APP.email-$RANDOM.html"; (umask 077 && touch "${htmlEmail}" &> /dev/null) || logFail -clientEmail="/tmp/$APP.shortemail-$RANDOM.html"; (umask 077 && touch "${clientEmail}" &> /dev/null) || logFail +postFile="/tmp/$APP.wtf-$RANDOM.log"; (umask 077 && touch "${postFile}" &> /dev/null) || log_fail +trshFile="/tmp/$APP.trsh-$RANDOM.log"; (umask 077 && touch "${trshFile}" &> /dev/null) || log_fail +statFile="/tmp/$APP.stat-$RANDOM.log"; (umask 077 && touch "${statFile}" &> /dev/null) || log_fail +urlFile="/tmp/$APP.url-$RANDOM.log"; (umask 077 && touch "${urlFile}" &> /dev/null) || log_fail +htmlFile="/tmp/$APP.log-$RANDOM.html"; (umask 077 && touch "${htmlFile}" &> /dev/null) || log_fail +htmlEmail="/tmp/$APP.email-$RANDOM.html"; (umask 077 && touch "${htmlEmail}" &> /dev/null) || log_fail +clientEmail="/tmp/$APP.shortemail-$RANDOM.html"; (umask 077 && touch "${clientEmail}" &> /dev/null) || log_fail # Path of the script; I should flip this check to make it more useful if [ -d "/etc/deploy" ]; then @@ -430,7 +429,7 @@ for var in "${REPOHOST} ${CLIENTLOGO}" "${DEVURL}" "${PRODURL}"; do done # Check variables for weird characters -validateVar +validate_conf # Are we using "smart" *cough* commits? if [[ "${SMARTCOMMIT}" == "TRUE" ]]; then diff --git a/etc/deploy.sh b/etc/deploy.sh index 61de5ee..12140db 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -6,20 +6,10 @@ # # If any value set here will override both global and per-user settings. -# The URL for this repo's hosting, with no trailing slash. For example, if -# you use Github and your repo URL looks like https://github.com/EMRL/deploy -# your REPOHOST should be set to https://github.com/EMRL (with no trailing slash) -# If most of your repos are all hosted at the same location, you may want to define -# this in either the global or user configuration files. -# REPOHOST="" -# If you have no SSH key or wish to login manually using your account name and -# password in the console, set NOKEY to exactly "TRUE" -# NOKEY="TRUE" - -# By default deploy will check for valid SSH keys; if you want to override this -# behavior, set DISABLESSHCHECK to TRUE -# DISABLESSHCHECK="FALSE" +############################################################################### +# Project Information +############################################################################### # A human readable project name # PROJNAME="Best Webapp Ever" @@ -27,33 +17,11 @@ # A human readable client name # PROJCLIENT="Client Name" -# If you are using html logfiles, define the full URL to the client's logo -# CLIENTLOGO="http://client.com/assets/img/logo.png" - -# First and last name of the primary contact for this client -# CLIENTCONTACT="First Last" - -# Include hosting as a line item on monthly reports? If set to TRUE, the report -# line item will read "Monthly web hosting"; customize the text included in -# report by setting it to any other value. -# INCLUDEHOSTING="TRUE" - -# If you need to send logfiles and email alerts to address(es) other -# than those configured globally, enter them below. -# TO="notify@client.com" - -# If you'd like to send branded HTML emails using the `deploy --digest [project]` -# command, enter the recipient's email address below. Email value can be a comma -# separated string of multiple addresses. -# DIGESTEMAIL="digest@email.com" - -# If you are using a digest theme that includes a cover image, at the URL below. -# DIGESTCOVER="http://client.com/assets/img/cover.jpg" +# Development project URL, including http:// or https:// +# DEVURL="http://devurl.com" -# If you'd like to post a Slack notification with a URL to view the weekly digest -# set the following to TRUE. IF you want to use an incoming webhook other than the -# one defined in SLACKURL below, enter that here *instead* of TRUE. -# DIGESTSLACK="TRUE" +# Production, or "Live" project URL, including http:// or https:// +# PRODURL="http://productionurl.com" # If you want to use an email template unique to this project (instead of the # globally configured template) define it below. HTML templates are stored in @@ -61,24 +29,26 @@ # folder name of your template. # HTMLTEMPLATE="default" -# Development project URL, including http:// or https:// -# DEVURL="http://devurl.com" +# If you are using html logfiles, define the full URL to the client's logo +# CLIENTLOGO="http://client.com/assets/img/logo.png" -# Production, or "Live" project URL, including http:// or https:// -# PRODURL="http://productionurl.com" + +############################################################################### +# Git Configuration +############################################################################### + +# The URL for this repo's hosting, with no trailing slash. For example, if +# you use Github and your repo URL looks like https://github.com/EMRL/deploy +# your REPOHOST should be set to https://github.com/EMRL (with no trailing +# slash) If most of your repos are all hosted at the same location, you may +# want to define this in either the global or user configuration files. +# REPOHOST="" # The exact name of the Bitbucket/Github repository # REPO="name-of-repo" -# Advanced Custom Fields Pro License -# -# Too many issues seem to crop up with the normal method of updating the Wordpress -# plugin Advanced Custom Fields Pro. Including your license key below will enable -# upgrades to happen more reliably. -# ACFKEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - -# Configure your branches. In most cases the name will be master & production. If -# you are only using a master branch, leave production undefined. +# Configure your branches. In most cases the name will be master & production. +# If you are only using a master branch, leave production undefined. # MASTER="master" # PRODUCTION="production" @@ -86,10 +56,10 @@ # PRODUCTION branches when deploying, set AUTOMERGE to TRUE. # AUTOMERGE="TRUE" -# If dirty (yet to be committed) files exist in the repo, deploy will normally not halt -# execution when running with the --automate flag. If you prefer to have the dirty files -# stashed and proceed with updates set the below value to TRUE. Files will be unstashed -# after the deployment is complete. +# If dirty (yet to be committed) files exist in the repo, deploy will normally +# not halt execution when running with the --automate flag. If you prefer to +# have the dirty files stashed and proceed with updates set the below value +# to TRUE. Files will be unstashed after the deployment is complete. # STASH="TRUE" # Define CHECKBRANCH if you only want deploy to run when the set branch is @@ -97,12 +67,24 @@ # "production", deployment will halt. # CHECKBRANCH="master" +# If you have no SSH key or wish to login manually using your account name and +# password in the console, set NOKEY to exactly "TRUE" +# +# NOKEY="TRUE" + +# By default deploy will check for valid SSH keys; if you want to override this +# behavior, set DISABLESSHCHECK to TRUE +# DISABLESSHCHECK="FALSE" + # If for some reason you'd like a default commit message. It will # always be editable before finalizing commit. # COMMITMSG="This is a default commit message" -# Wordpress configuration -# + +############################################################################### +# Wordpress Setup +############################################################################### + # Some developers employ a file structure that separates Wordpress core from # their application code. If you're using non-standard file paths, define the # root, system, and app (plugin/theme) directories below. Note that the forward @@ -110,10 +92,22 @@ # WPROOT="/public" # WPAPP="/app" # WPSYSTEM="/system" -# -# If you do not want to allow Wordpress core updates, set DONOTUPDATEWP to TRUE. + +# If you do not want to allow core updates, set DONOTUPDATEWP to TRUE. # DONOTUPDATEWP="FALSE" +# Advanced Custom Fields Pro License +# +# Too many issues seem to crop up with the normal method of updating the +# Wordpress plugin ACF Pro. Including your license key below will enable +# upgrades to happen more reliably. +# ACFKEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + + +############################################################################### +# Deployment Configuration +############################################################################### + # The command to finalize deployment of your project(s) # DEPLOY="mina deploy" @@ -121,25 +115,22 @@ # environment, set REQUIREAPPROVAL="TRUE" # REQUIREAPPROVAL="TRUE" -# If this project needs logs mailed to an address other than the one configured -# globally, set it below. -# TO="deploy@emrl.com" +# Disallow deployment; set to TRUE to enable. Double negative, it's tricky. +# DONOTDEPLOY="TRUE" -# IF INCOGNITO is set to true, log files as well as verbose output to screen -# will be stripped of details such as email addresses and system file paths. -# INCOGNITO="TRUE" -# Disallow deployment; set to TRUE to enable. Double negative, it's tricky. -# DONOTDEPLOY="TRUE" +############################################################################### +# Integration +############################################################################### -# Integration ID +# Project Management # -# This is used to post deploy logs to project management systems that can -# external email input. For examples, for our task management system +# Task#: This is used to post deploy logs to project management systems +# that can external email input. For examples, for our task management system # accepts emails in the format task-####@projects.emrl.com, with the #### # being the task identification number for the project being deployed. # TASK="task" - +# # If you wish to have automated deployments add tracked time to your project # management system, uncomment and configure the two values below. TASKUSER # should be the email address of the user that the time will be logged as, @@ -148,7 +139,7 @@ # TASKUSER="deploy@emrl.com" # ADDTIME="10m" -# Slack Integration +# Slack # # You'll need to set up an "Incoming Webhook" custom integration on the Slack # side to get this ready to roll. @@ -158,81 +149,126 @@ # # Set POSTTOSLACK to "TRUE" to enable Slack integration. # POSTTOSLACK="TRUE" - +# # Add your full Webhook URL below, including https:// # SLACKURL="https://hooks.slack.com/services/###################/########################" - +# # Normally only successful deployments are posted to Slack. # Enable the settings below to post on WARNiNG and/or ERROR. # SLACKERROR="FALSE" -# Logging +# Google Analytics +# +# API credentials +# CLIENTID="#############################################.apps.googleusercontent.com" +# CLIENTSECRET="########################" +# REDIRECTURI="http://localhost" # +# OAuth authorization will expire after one hour, but will be updated when needed +# if the tokens below are configured correctly +# AUTHORIZATIONCODE="##############################################" +# +# Tokens +# ACCESSTOKEN="#################################################################################################################################" +# REFRESHTOKEN="##################################################################" +# +# Google Analytics ID +# PROFILEID="########" + + +############################################################################### +# Server Monitoring +############################################################################### + +# Uptime and average latency can be included in logs, digests, and reports when +# integrating with PHP Server Monitor, and an add-on API. +# See https://github.com/EMRL/deploy/wiki/Integration for more information. +# +# Full API URL +# MONITORURL="https://your.phpservermonitor.com/api/monitorapi.php" +# +# Email/password of the user that will access the API. Password can be stored in +# a file outside of the project repo for security reasons +# MONITORUSER="user@domain.com" +# MONITORPASS="/path/to/password/file" +# +# Server ID to monitor. When viewing the server on your web console, your URL +# will be something like https://monitor.com/?&mod=server&action=view&id=3 - in +# this case SERVERID would be "3" (notice the &id=3 at the end of the URL) +# SERVERID="###" + + +############################################################################### +# Logging +############################################################################### + +# If you need to send logfiles and email alerts to address(es) other +# than those configured globally, enter them below. +# TO="notify@client.com" + +# IF INCOGNITO is set to true, log files as well as verbose output to screen +# will be stripped of details such as email addresses and system file paths. +# INCOGNITO="TRUE" + # Post HTML logs to remote server. This needs to be set to "TRUE" even you # are only posting to LOCALHOST. # REMOTELOG="TRUE" -# + # Define the root url where the deploy log will be accessible with no # trailing slash # REMOTEURL="http://deploy.domain.com" -# + # If using HTML logs, define which template you'd like to use. HTML templates # are stored in separate folders in /etc/deploy/html. The value used below # should be the folder name of your template. # REMOTETEMPLATE="default" -# + # Post logs via SCP # SCPPOST="TRUE" # SCPUSER="user" # SCPHOST="hostname.com" # SCPHOSTPATH="/full/path/to/file" -# + # DANGER DANGER: If for some reason you absolutely can't use an SSH key you # can configure your password here # SCPPASS="password" -# + # Post commit logs to this URL. # POSTURL="" -# + # If you're posting logs to a place on the same machine you're deploying from, # set POSTTOLOCALHOST to "TRUE" and define the path where you want to store # the HTML logs. # LOCALHOSTPOST="TRUE" # LOCALHOSTPATH="/var/www/production/deploy" -# Server Monitoring -# -# Uptime and average latency can be included in logs, digests, and reports when -# integrating with PHP Server Monitor, and an add-on API. -# See https://github.com/EMRL/deploy/wiki/Integration for more information. -# -# Full API URL -# MONITORURL="https://your.phpservermonitor.com/api/monitorapi.php" -# -# Email/password of the user that will access the API. Password can be stored in -# a file outside of the project repo for security reasons -# MONITORUSER="user@domain.com" -# MONITORPASS="/path/to/password/file" -# -# Server ID to monitor. When viewing the server on your web console, your URL will -# look something like https://monitor.com/?&mod=server&action=view&id=3 - in this -# case SERVERID would be "3" (notice the &id=3 at the end of the URL) -# SERVERID="###" -# Google Analytics -# -# API credentials -# CLIENTID="#############################################.apps.googleusercontent.com" -# CLIENTSECRET="########################" -# REDIRECTURI="http://localhost" -# -# OAuth authorization will expire after one hour, but will be updated when needed -# if the tokens below are configured correctly -# AUTHORIZATIONCODE="##############################################" -# -# Tokens -# ACCESSTOKEN="#################################################################################################################################" -# REFRESHTOKEN="##################################################################" -# -# Google Analytics ID -# PROFILEID="########" +############################################################################### +# Weekly Digests +############################################################################### + +# If you'd like to send branded HTML emails using the `deploy --digest [project]` +# command, enter the recipient's email address below. Email value can be a comma +# separated string of multiple addresses. +# DIGESTEMAIL="digest@email.com" + +# If you are using a digest theme that includes a cover image, at the URL below. +# DIGESTCOVER="http://client.com/assets/img/cover.jpg" + +# If you'd like to post a Slack notification with a URL to view the weekly digest +# set the following to TRUE. IF you want to use an incoming webhook other than the +# one defined in SLACKURL below, enter that here *instead* of TRUE. +# DIGESTSLACK="TRUE" + + +############################################################################### +# Monthly Reporting +############################################################################### + +# First and last name of the primary contact for this client +# CLIENTCONTACT="First Last" + +# Include hosting as a line item on monthly reports? If set to TRUE, the report +# line item will read "Monthly web hosting"; customize the text included in +# report by setting it to any other value. +# INCLUDEHOSTING="TRUE" diff --git a/install/doinst.sh b/install/doinst.sh index da2d401..cea846f 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # doinst.sh # diff --git a/lib/active-check.sh b/lib/active-check.sh index 6ddf2f1..d337c94 100755 --- a/lib/active-check.sh +++ b/lib/active-check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # active-check.sh # @@ -7,6 +7,10 @@ ############################################################################### trace "Loading activity checking" +# Declare needed variables +read -r active_files <<< "" +echo "${active_files}" > /dev/null + function active_check() { if [[ "${FORCE}" == "1" ]] && [[ "${UPGRADE}" == "1" ]] && [[ "${QUIET}" == "1" ]] && [[ "${ACTIVECHECK}" = "TRUE" ]]; then trace "Checking for active files" diff --git a/lib/analytics.sh b/lib/analytics.sh index eb3be70..c1c389c 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # analytics.sh # @@ -7,6 +7,10 @@ ############################################################################### trace "Loading analytics functions" +# Initializa needed variables +read -r SIZE RND METRIC RESULT <<< "" +echo "${SIZE} ${RND} ${METRIC} ${RESULT}" > /dev/null + function ga_metrics() { array[0]="hits" array[1]="percentNewSessions" @@ -19,60 +23,63 @@ function ga_metrics() { } function analytics() { - # Setup the metric we're after - ga_metrics - - # Update access token - curl -s -d "client_id=${CLIENTID}&client_secret=${CLIENTSECRET}&refresh_token=${REFRESHTOKEN}&grant_type=refresh_token" https://accounts.google.com/o/oauth2/token > "${trshFile}" - sed -i '/access_token/!d' "${trshFile}" - ACCESSTOKEN="$(awk -F\" '{print $4}' "${trshFile}")" - - # Grab data from Google - ga_data - - if [[ "${RND}" == "0" ]]; then - if [[ "${RESULT}" -gt "499" ]]; then - ANALYTICSMSG="You had ${SIZE} hits in the last week. Awesome!" - else - ga_fail + # If profile does not exist, skip it all + if [[ -n "${PROFILEID}" ]]; then + # Setup the metric we're after + ga_metrics + + # Update access token + curl -s -d "client_id=${CLIENTID}&client_secret=${CLIENTSECRET}&refresh_token=${REFRESHTOKEN}&grant_type=refresh_token" https://accounts.google.com/o/oauth2/token > "${trshFile}" + sed -i '/access_token/!d' "${trshFile}" + ACCESSTOKEN="$(awk -F\" '{print $4}' "${trshFile}")" + + # Grab data from Google + ga_data + + if [[ "${RND}" == "0" ]]; then + if [[ "${RESULT}" -gt "499" ]]; then + ANALYTICSMSG="You had ${SIZE} hits in the last week. Awesome!" + else + ga_fail + fi fi - fi - if [[ "${RND}" == "1" ]]; then - # Sometimes Google reports confusion percentages that exceed - # 100%, let's kill those results - if [[ "${SIZE}" -gt "100" ]]; then - ga_fail - elif [[ "${SIZE}" -gt "50" ]]; then - ANALYTICSMSG="Last week ${SIZE} percent of your users were first time visitors. That's great!" - else - RESULT="$((100 - ${SIZE}))" - ANALYTICSMSG="Last week ${RESULT} percent of your users were return visitors. That's great!" + if [[ "${RND}" == "1" ]]; then + # Sometimes Google reports confusion percentages that exceed + # 100%, let's kill those results + if [[ "${SIZE}" -gt "100" ]]; then + ga_fail + elif [[ "${SIZE}" -gt "50" ]]; then + ANALYTICSMSG="Last week ${SIZE} percent of your users were first time visitors. That's great!" + else + RESULT="$((100 - ${SIZE}))" + ANALYTICSMSG="Last week ${RESULT} percent of your users were return visitors. That's great!" + fi fi - fi - if [[ "${RND}" == "2" ]]; then - if [[ "${SIZE}" -ge "30" ]]; then - ANALYTICSMSG="You had traffic from ${SIZE} organic searches last week. Not bad!" - else - ga_fail + if [[ "${RND}" == "2" ]]; then + if [[ "${SIZE}" -ge "30" ]]; then + ANALYTICSMSG="You had traffic from ${SIZE} organic searches last week. Not bad!" + else + ga_fail + fi fi - fi - if [[ "${RND}" == "3" ]]; then - RESULT="$((${SIZE} / 60))" - if [[ "${RESULT}" -gt "2" ]]; then - ANALYTICSMSG="Last week visitors averaged over ${RESULT} minutes each on your site. Nice!" - else - ga_fail + if [[ "${RND}" == "3" ]]; then + RESULT="$((${SIZE} / 60))" + if [[ "${RESULT}" -gt "2" ]]; then + ANALYTICSMSG="Last week visitors averaged over ${RESULT} minutes each on your site. Nice!" + else + ga_fail + fi fi - fi - if [[ "${RND}" == "4" ]]; then - if [[ "${SIZE}" -ge "20" ]]; then - ANALYTICSMSG="Your site had ${SIZE} social media interactions in the last week!" - else - ga_fail + if [[ "${RND}" == "4" ]]; then + if [[ "${SIZE}" -ge "20" ]]; then + ANALYTICSMSG="Your site had ${SIZE} social media interactions in the last week!" + else + ga_fail + fi fi fi } @@ -85,7 +92,7 @@ function ga_data() { # If no other results are worht displaying, fall back to displaying hits function ga_fail() { METRIC="hits" - analyticsData + ga_data ANALYTICSMSG="You had ${SIZE} hits in the last week." } diff --git a/lib/approval.sh b/lib/approval.sh index 502c9a8..05f68df 100755 --- a/lib/approval.sh +++ b/lib/approval.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # approval.sh # diff --git a/lib/core.sh b/lib/core.sh index d46cf79..6f9cd9e 100755 --- a/lib/core.sh +++ b/lib/core.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # core.sh # diff --git a/lib/deployment.sh b/lib/deployment.sh index f04c7cc..27a2c45 100755 --- a/lib/deployment.sh +++ b/lib/deployment.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # deployment.sh # @@ -61,7 +61,7 @@ function pkgDeploy() { else trace "Launching deployment from ${PWD}" fi - fixIndex + fix_index # Make sure the project's deploy command is going to work deploy_cmd=$(echo "${DEPLOY}" | awk '{print $1;}') diff --git a/lib/digest.sh b/lib/digest.sh index 9f9e97a..7764b1a 100755 --- a/lib/digest.sh +++ b/lib/digest.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # digest.sh # @@ -7,6 +7,11 @@ ############################################################################### trace "Loading digest handling" +# Initializa needed variables +read -r AUTHOR AUTHOREMAIL AUTHORNAME GRAVATAR IMGFILE DIGESTWRAP <<< "" +echo "${AUTHOR} ${AUTHOREMAIL} ${AUTHORNAME} ${GRAVATAR} ${IMGFILE} + ${DIGESTWRAP}" > /dev/null + function create_digest() { message_state="DIGEST" htmlDir diff --git a/lib/display-styles.sh b/lib/display-styles.sh index e95c3e0..2553b7c 100755 --- a/lib/display-styles.sh +++ b/lib/display-styles.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # display-styles.sh # diff --git a/lib/error-check.sh b/lib/error-check.sh index 40820af..7dc10d3 100755 --- a/lib/error-check.sh +++ b/lib/error-check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # error_check() # diff --git a/lib/git.sh b/lib/git.sh index 4fabcea..c67b1fb 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # git.sh # @@ -56,7 +56,7 @@ function gitChkMstr() { else current_branch="$(git rev-parse --abbrev-ref HEAD)" if [[ "${current_branch}" != "${MASTER}" ]]; then - notice "Checking out master branch..."; fixIndex + notice "Checking out master branch..."; fix_index if [[ "${VERBOSE}" -eq 1 ]]; then git checkout master | tee --append "${logFile}" else @@ -193,7 +193,7 @@ function gitCommit() { # Push master function gitPushMstr() { if [[ -n "${MASTER}" ]]; then - trace "Pushing ${MASTER}"; fixIndex + trace "Pushing ${MASTER}"; fix_index emptyLine if [[ "${VERBOSE}" -eq 1 ]]; then git push | tee --append "${logFile}"; error_check @@ -221,7 +221,7 @@ function gitPushMstr() { function gitChkProd() { if [[ "${MERGE}" = "1" ]]; then if [[ -n "${PRODUCTION}" ]]; then - notice "Checking out ${PRODUCTION} branch..."; fixIndex + notice "Checking out ${PRODUCTION} branch..."; fix_index if [[ "${VERBOSE}" -eq 1 ]]; then git checkout "${PRODUCTION}" | tee --append "${logFile}"; error_check @@ -265,7 +265,7 @@ function gitChkProd() { function gitMerge() { if [[ "${MERGE}" = "1" ]]; then if [[ -n "${PRODUCTION}" ]]; then - notice "Merging ${MASTER} into ${PRODUCTION}..."; fixIndex + notice "Merging ${MASTER} into ${PRODUCTION}..."; fix_index # Clear out the index.lock file, cause reasons [[ -f "${gitLock}" ]] && rm "${gitLock}" # Bonus add, just because. Ugh. @@ -289,7 +289,7 @@ function gitMerge() { function gitPushProd() { if [[ "${MERGE}" = "1" ]]; then if [[ -n "${PRODUCTION}" ]]; then - trace "Push ${PRODUCTION}"; fixIndex + trace "Push ${PRODUCTION}"; fix_index emptyLine if [[ "${VERBOSE}" -eq 1 ]]; then git push | tee --append "${logFile}"; error_check diff --git a/lib/invoice.sh b/lib/invoice.sh index 31a784b..5d17c0f 100755 --- a/lib/invoice.sh +++ b/lib/invoice.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # invoice.sh # diff --git a/lib/lock.sh b/lib/lock.sh index b6f980d..cab9c3b 100755 --- a/lib/lock.sh +++ b/lib/lock.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # lock.sh # diff --git a/lib/log-handling.sh b/lib/log-handling.sh index 55dde5d..0f82e3a 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # log-handling.sh # diff --git a/lib/mail-log.sh b/lib/mail-log.sh index fb41ded..f7c90b1 100755 --- a/lib/mail-log.sh +++ b/lib/mail-log.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # mail-log.sh # @@ -237,7 +237,6 @@ function email_test() { echo fi - ) | "${MAILPATH}"/sendmail -t fi diff --git a/lib/monitor.sh b/lib/monitor.sh index 6af8492..61ebcaa 100755 --- a/lib/monitor.sh +++ b/lib/monitor.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # monitor.sh # diff --git a/lib/package-manager.sh b/lib/package-manager.sh index cddaa01..a5c7c62 100755 --- a/lib/package-manager.sh +++ b/lib/package-manager.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # package-manager # diff --git a/lib/permissions-fix.sh b/lib/permissions-fix.sh index 4c48b94..2ac444f 100755 --- a/lib/permissions-fix.sh +++ b/lib/permissions-fix.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # permissions-fix.sh # diff --git a/lib/post-integration.sh b/lib/post-integration.sh index 6aef391..2fb4593 100755 --- a/lib/post-integration.sh +++ b/lib/post-integration.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # post-integration.sh # diff --git a/lib/post-log.sh b/lib/post-log.sh index 077d834..cf3d873 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # post-log.sh # diff --git a/lib/quit.sh b/lib/quit.sh index a987b0a..3aceb9a 100755 --- a/lib/quit.sh +++ b/lib/quit.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # quit.sh # diff --git a/lib/release-check.sh b/lib/release-check.sh index c384584..dee44a7 100755 --- a/lib/release-check.sh +++ b/lib/release-check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # release-check.sh # diff --git a/lib/report.sh b/lib/report.sh index 4de845f..3bbe9f8 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # report.sh # diff --git a/lib/server-check.sh b/lib/server-check.sh index baf6639..53ee1d0 100755 --- a/lib/server-check.sh +++ b/lib/server-check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # server-check.sh # diff --git a/lib/slack.sh b/lib/slack.sh index 839371d..6b7741b 100755 --- a/lib/slack.sh +++ b/lib/slack.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # slack.sh # diff --git a/lib/smart-commits.sh b/lib/smart-commits.sh index ecd0c51..8fb32cf 100755 --- a/lib/smart-commits.sh +++ b/lib/smart-commits.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # smart-commit.sh # diff --git a/lib/ssh-check.sh b/lib/ssh-check.sh index 82ece58..7ebe208 100755 --- a/lib/ssh-check.sh +++ b/lib/ssh-check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # ssh-check.sh # diff --git a/lib/statistics.sh b/lib/statistics.sh index 8653e0a..1812bd5 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # statistics.sh # diff --git a/lib/user-feedback.sh b/lib/user-feedback.sh index 7dd3abe..4d43fe6 100755 --- a/lib/user-feedback.sh +++ b/lib/user-feedback.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # user-feedback.sh # diff --git a/lib/utilities.sh b/lib/utilities.sh index 495b519..0efe71f 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # utilities.sh # @@ -34,7 +34,7 @@ function go() { # Test analytics authentication if [[ "${ANALYTICSTEST}" == "1" ]]; then - analyticsTest; quickExit + ga_test; quickExit fi # Test server monitoring @@ -105,14 +105,14 @@ function go() { server_monitor } -function fixIndex() { +function fix_index() { # A rather brutal fix for index permissions issues if [[ "${FIXINDEX}" == "TRUE" ]]; then if [[ ! -w "${WORKPATH}/${APP}/.git/index" ]]; then trace "Index is not writable, attempting to fix..." sudo chmod 777 "${WORKPATH}/${APP}/.git/index"; error_check if [[ ! -w "${WORKPATH}/${APP}/.git/index" ]]; then - error "Unable to write new index file."; + error "Unable to write new index file." fi fi sleep 1 diff --git a/lib/validate.sh b/lib/validate.sh index ab57938..c54b77d 100755 --- a/lib/validate.sh +++ b/lib/validate.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # validate.sh # @@ -7,7 +7,7 @@ ############################################################################### trace "Loading validation tests" -function validateVar() { +function validate_conf() { array=( "${PROJNAME}" "${PROJCLIENT}" ) for i in "${array[@]}" do diff --git a/lib/webhook.sh b/lib/webhook.sh index 3b737f1..68492ac 100755 --- a/lib/webhook.sh +++ b/lib/webhook.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # webhook.sh # diff --git a/lib/wp-acf.sh b/lib/wp-acf.sh index 0880b35..e67d872 100755 --- a/lib/wp-acf.sh +++ b/lib/wp-acf.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # wp-acf.sh # @@ -6,6 +6,10 @@ # Handle ACF Pro updates in more reliable way than wp-cli ############################################################################### +# Declare needed variables +read -r ACFFILE <<< "" +echo "${ACFFILE}" > /dev/null + function acf_update() { if grep -q "advanced-custom-fields-pro" "${wpFile}"; then ACFFILE="/tmp/acfpro.zip" diff --git a/lib/wp-core.sh b/lib/wp-core.sh index a516161..a20679a 100755 --- a/lib/wp-core.sh +++ b/lib/wp-core.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # wp-core.sh # diff --git a/lib/wp-plugins.sh b/lib/wp-plugins.sh index 7d151df..043cf22 100755 --- a/lib/wp-plugins.sh +++ b/lib/wp-plugins.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # wp-plugins.sh # diff --git a/lib/wp-wordfence.sh b/lib/wp-wordfence.sh index ce369a2..8873299 100755 --- a/lib/wp-wordfence.sh +++ b/lib/wp-wordfence.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # wp-wordfence.sh # diff --git a/lib/wp.sh b/lib/wp.sh index 9b65b2c..60e0919 100755 --- a/lib/wp.sh +++ b/lib/wp.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # wp.sh # diff --git a/lib/yes-no.sh b/lib/yes-no.sh index 21c5cd5..0284dd9 100755 --- a/lib/yes-no.sh +++ b/lib/yes-no.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # yesno.sh # diff --git a/test/extended.sh b/test/extended.sh old mode 100644 new mode 100755 From 106d808b0c2bf626ee00ec22691dace4ca17ed48 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 21 Jan 2018 14:50:32 -0800 Subject: [PATCH 029/334] Fixed install dependency check, more cleanup --- deploy.sh | 13 ++-- etc/deploy.sh | 142 ++++++++++++++++++++++--------------------- install/doinst.sh | 46 ++++++++------ lib/active-check.sh | 2 +- lib/analytics.sh | 2 +- lib/approval.sh | 5 +- lib/digest.sh | 2 +- lib/log-handling.sh | 2 +- lib/process-html.sh | 9 ++- lib/report.sh | 2 +- lib/statistics.sh | 2 +- lib/user-feedback.sh | 4 ++ 12 files changed, 127 insertions(+), 104 deletions(-) diff --git a/deploy.sh b/deploy.sh index 72c3e68..4fddb82 100755 --- a/deploy.sh +++ b/deploy.sh @@ -44,13 +44,8 @@ read -r black red green yellow blue magenta cyan white endColor bold underline \ reset purple tan <<< "" echo "${black} ${red} ${green} ${yellow} ${blue} ${magenta} ${cyan} ${white} ${endColor} ${bold} ${underline} ${reset} ${purple} ${tan}" > /dev/null -# User feedback -read -r pid delay spinstr <<< "" -echo "${pid} ${delay} ${spinstr}" > /dev/null -# HTML Themes -read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID <<< "" -echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} - ${SMOOCHID}" + + # Constants and environment variables read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ EMAILHTML NOPHP FIXPERMISSIONS DEVUSER DEVGROUP APACHEUSER APACHEGROUP TO \ @@ -89,7 +84,7 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile currentStash deploy_cmd deps start_branch postSendmail SLACKUSER NOCHECK \ VIEWPORT VIEWPORTPRE LOGTITLE LOGURL TIMESTAMP STARTUP WPROOT \ WPAPP WPSYSTEM DONOTUPDATEWP gitHistory ANALYTICSMSG digestSendmail MINAUSER \ - MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX QUEUED \ + MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX \ DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile CURMTH \ CURYR PRVMTH PRVYR LASTDY TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI RELEASE RELEASENOTES \ @@ -105,7 +100,7 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${VIEWPORTPRE} ${LOGTITLE} ${LOGURL} ${TIMESTAMP} ${STARTUP} ${WPROOT} ${WPAPP} ${WPSYSTEM} ${DONOTUPDATEWP} ${gitHistory} ${ANALYTICSMSG} ${digestSendmail} ${MINAUSER} ${MINADOMAIN} ${SSHTARGET} ${SSHSTATUS} - ${REMOTEFILE} ${GREETING} ${LOGSUFFIX} ${QUEUED} ${DISABLESSHCHECK} + ${REMOTEFILE} ${GREETING} ${LOGSUFFIX} ${DISABLESSHCHECK} ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} ${CURMTH} ${CURYR} ${PRVMTH} ${PRVYR} ${LASTDY} ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} ${SERVERID} ${MONITORHOURS} ${LATENCY} ${UPTIME} ${MONITORTEST} diff --git a/etc/deploy.sh b/etc/deploy.sh index 12140db..f71914f 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -1,10 +1,9 @@ -#!/bin/bash +#!/usr/bin/env bash # # Here are examples of settings you might need to change on a per-project -# basis. This file should be placed in config/deploy.sh in your projects' -# root folder. Project settings will override both system & per-user settings. -# -# If any value set here will override both global and per-user settings. +# basis. This file should be placed in either the project's root folder, +# or in /config/. Settings configured in this file will override both +# system & user settings. ############################################################################### @@ -23,15 +22,6 @@ # Production, or "Live" project URL, including http:// or https:// # PRODURL="http://productionurl.com" -# If you want to use an email template unique to this project (instead of the -# globally configured template) define it below. HTML templates are stored in -# separate folders in /etc/deploy/html. The value used below should be the -# folder name of your template. -# HTMLTEMPLATE="default" - -# If you are using html logfiles, define the full URL to the client's logo -# CLIENTLOGO="http://client.com/assets/img/logo.png" - ############################################################################### # Git Configuration @@ -120,16 +110,16 @@ ############################################################################### -# Integration +# Notifications ############################################################################### # Project Management -# +# ------------------ # Task#: This is used to post deploy logs to project management systems -# that can external email input. For examples, for our task management system +# that can accept external email input. For examples, our task management system # accepts emails in the format task-####@projects.emrl.com, with the #### # being the task identification number for the project being deployed. -# TASK="task" +# TASK="####" # # If you wish to have automated deployments add tracked time to your project # management system, uncomment and configure the two values below. TASKUSER @@ -140,7 +130,7 @@ # ADDTIME="10m" # Slack -# +# ----- # You'll need to set up an "Incoming Webhook" custom integration on the Slack # side to get this ready to roll. # See https://YOURTEAMNAME.slack.com/apps/manage/custom-integrations to get @@ -156,46 +146,16 @@ # Normally only successful deployments are posted to Slack. # Enable the settings below to post on WARNiNG and/or ERROR. # SLACKERROR="FALSE" - -# Google Analytics # -# API credentials -# CLIENTID="#############################################.apps.googleusercontent.com" -# CLIENTSECRET="########################" -# REDIRECTURI="http://localhost" -# -# OAuth authorization will expire after one hour, but will be updated when needed -# if the tokens below are configured correctly -# AUTHORIZATIONCODE="##############################################" -# -# Tokens -# ACCESSTOKEN="#################################################################################################################################" -# REFRESHTOKEN="##################################################################" -# -# Google Analytics ID -# PROFILEID="########" - - -############################################################################### -# Server Monitoring -############################################################################### +# If you'd like to post a Slack notification with a URL to view the weekly digest +# set the following to TRUE. If you want to use an incoming webhook other than the +# one defined in SLACKURL, enter that here *instead* of TRUE. +# DIGESTSLACK="FALSE" -# Uptime and average latency can be included in logs, digests, and reports when -# integrating with PHP Server Monitor, and an add-on API. -# See https://github.com/EMRL/deploy/wiki/Integration for more information. -# -# Full API URL -# MONITORURL="https://your.phpservermonitor.com/api/monitorapi.php" -# -# Email/password of the user that will access the API. Password can be stored in -# a file outside of the project repo for security reasons -# MONITORUSER="user@domain.com" -# MONITORPASS="/path/to/password/file" -# -# Server ID to monitor. When viewing the server on your web console, your URL -# will be something like https://monitor.com/?&mod=server&action=view&id=3 - in -# this case SERVERID would be "3" (notice the &id=3 at the end of the URL) -# SERVERID="###" +# Webhooks +# -------- +# Post event notifications to this URL. +# POSTURL="" ############################################################################### @@ -206,6 +166,18 @@ # than those configured globally, enter them below. # TO="notify@client.com" +# If you want to use an email template unique to this project (instead of the +# globally configured template) define it below. HTML templates are stored in +# separate folders in /etc/deploy/html. The value used below should be the +# folder name of your template. +# HTMLTEMPLATE="default" + +# If you are using html logfiles, define the full URL to the client's logo +# CLIENTLOGO="http://client.com/assets/img/logo.png" + +# If you are using a digest theme that includes a cover image, at the URL below. +# COVER="http://client.com/assets/img/cover.jpg" + # IF INCOGNITO is set to true, log files as well as verbose output to screen # will be stripped of details such as email addresses and system file paths. # INCOGNITO="TRUE" @@ -230,12 +202,9 @@ # SCPHOSTPATH="/full/path/to/file" # DANGER DANGER: If for some reason you absolutely can't use an SSH key you -# can configure your password here +# can configure the path to a text file containing *only* your password. # SCPPASS="password" -# Post commit logs to this URL. -# POSTURL="" - # If you're posting logs to a place on the same machine you're deploying from, # set POSTTOLOCALHOST to "TRUE" and define the path where you want to store # the HTML logs. @@ -252,14 +221,6 @@ # separated string of multiple addresses. # DIGESTEMAIL="digest@email.com" -# If you are using a digest theme that includes a cover image, at the URL below. -# DIGESTCOVER="http://client.com/assets/img/cover.jpg" - -# If you'd like to post a Slack notification with a URL to view the weekly digest -# set the following to TRUE. IF you want to use an incoming webhook other than the -# one defined in SLACKURL below, enter that here *instead* of TRUE. -# DIGESTSLACK="TRUE" - ############################################################################### # Monthly Reporting @@ -272,3 +233,46 @@ # line item will read "Monthly web hosting"; customize the text included in # report by setting it to any other value. # INCLUDEHOSTING="TRUE" + + +############################################################################### +# Google Analytics +############################################################################### + +# API credentials +# CLIENTID="#############################################.apps.googleusercontent.com" +# CLIENTSECRET="########################" +# REDIRECTURI="http://localhost" +# +# OAuth authorization will expire after one hour, but will be updated when needed +# if the tokens below are configured correctly +# AUTHORIZATIONCODE="##############################################" +# +# Tokens +# ACCESSTOKEN="#################################################################################################################################" +# REFRESHTOKEN="##################################################################" +# +# Google Analytics ID +# PROFILEID="########" + + +############################################################################### +# Server Monitoring +############################################################################### + +# Uptime and average latency can be included in logs, digests, and reports when +# integrating with PHP Server Monitor, and an add-on API. +# See https://github.com/EMRL/deploy/wiki/Integration for more information. + +# Full API URL +# MONITORURL="https://your.phpservermonitor.com/api/monitorapi.php" + +# Email/password of the user that will access the API. Password can be stored in +# a file outside of the project repo for security reasons +# MONITORUSER="user@domain.com" +# MONITORPASS="/path/to/password/file" + +# Server ID to monitor. When viewing the server on your web console, your URL +# will be something like https://monitor.com/?&mod=server&action=view&id=3 - in +# this case SERVERID would be "3" (notice the &id=3 at the end of the URL) +# SERVERID="###" diff --git a/install/doinst.sh b/install/doinst.sh index cea846f..1c56d9b 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -8,6 +8,15 @@ # https://github.com/EMRL/deploy ############################################################################### +# Set mode +set -uo pipefail + +# Initialize variables +read -r OS VER EXITCODE dependencies option message fg_green fg_red reset \ + YES NO NOT i k <<< "" +echo "${OS} ${VER} ${EXITCODE} ${dependencies} ${option} ${message} ${fg_green} + ${fg_red} ${reset} ${YES} ${NO} ${NOT} ${i} ${k}" > /dev/null + # No root, no fun if [[ "${EUID}" -ne 0 ]]; then echo "You must have root access to install - try 'sudo install/doinst.sh'" 2>&1 @@ -17,7 +26,8 @@ fi function check_os() { # Try to discover the OS flavor if [[ -f /etc/os-release ]]; then - # freedesktop.org and systemd + # freedesktop.org and systemd + # shellcheck disable=SC1091 . /etc/os-release OS="${NAME}" VER="${VERSION_ID}" @@ -27,6 +37,7 @@ function check_os() { VER="$(lsb_release -sr)" elif [[ -f /etc/lsb-release ]]; then # For some versions of Debian/Ubuntu without lsb_release command + # shellcheck disable=SC1091 . /etc/lsb-release OS="${DISTRIB_ID}" VER="${DISTRIB_RELEASE}" @@ -49,12 +60,13 @@ function check_os() { function check_program() { printf "%-40s" "Checking for ${1}..." - command -v $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } -} - -function check_optional() { - printf "%-40s" "Checking for ${1}..." - command -v $1 >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } + # There's potential for non-global stuff to fail this check so here's a + # fairly kludgey way to hopefully allow those to pass + if [[ "${1}" =~ ^(wp|grunt|npm)$ ]]; then + command -v $(type -p "${1}") >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } + else + command -v "${1}" >/dev/null 2>&1 && echo "${YES}" || { echo "${NOT}"; message+="${1} "; } + fi } function error_check() { @@ -81,9 +93,9 @@ dependencies=(awk cat curl echo eval git grep pkill printf read sed sendmail sle options=(gitchart grunt npm scp ssh sshpass wp) message='' -fg_red=`tput setaf 1` -fg_green=`tput setaf 2` -reset=`tput sgr0` +fg_red="$(tput setaf 1)" +fg_green="$(tput setaf 2)" +reset="$(tput sgr0)" # Common messages YES="${fg_green}OK${reset}" @@ -92,7 +104,7 @@ NOT="${fg_red}NOT FOUND${reset}" echo; echo "=> Checking for required dependencies:"; sleep 1 for i in "${dependencies[@]}" ; do - check_program $i + check_program "${i}" done if [[ -z "${message}" ]] ; then @@ -106,7 +118,7 @@ fi echo; echo "=> Checking optional dependencies:"; sleep 1 for k in "${options[@]}" ; do - check_optional $k + check_program "${k}" done if [[ -n "${message}" ]] ; then @@ -114,9 +126,9 @@ if [[ -n "${message}" ]] ; then # echo ${message}; fi -# Start the install: First check for root +# Start the install echo; sleep 1 -if [[ ! -d /etc/deploy ]] && [[ ! -d /etc/deploy/lib ]] && [[ ! -d /etc/deploy/crontab ]]; then +if [[ ! -d /etc/deploy ]] || [[ ! -d /etc/deploy/lib ]] || [[ ! -d /etc/deploy/crontab ]]; then echo "Creating directories" if [[ ! -d /etc/deploy ]]; then sudo mkdir /etc/deploy; error_check @@ -139,7 +151,7 @@ if [[ ! -f /etc/deploy/deploy.conf ]]; then cp /etc/deploy/deploy-example.conf /etc/deploy/deploy.conf; error_check fi -cp -R lib/* /etc/deploy/lib; error_check -cp deploy.sh /usr/local/bin/deploy; error_check -sudo chmod 755 /usr/local/bin/deploy; error_check +cp -R lib/* /etc/deploy/lib || error_check +cp deploy.sh /usr/local/bin/deploy || error_check +sudo chmod 755 /usr/local/bin/deploy || error_check echo "Successfully installed, try typing 'deploy' for help." diff --git a/lib/active-check.sh b/lib/active-check.sh index d337c94..db5d06f 100755 --- a/lib/active-check.sh +++ b/lib/active-check.sh @@ -7,7 +7,7 @@ ############################################################################### trace "Loading activity checking" -# Declare needed variables +# Initialize variables read -r active_files <<< "" echo "${active_files}" > /dev/null diff --git a/lib/analytics.sh b/lib/analytics.sh index c1c389c..c0993a6 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -7,7 +7,7 @@ ############################################################################### trace "Loading analytics functions" -# Initializa needed variables +# Initialize variables read -r SIZE RND METRIC RESULT <<< "" echo "${SIZE} ${RND} ${METRIC} ${RESULT}" > /dev/null diff --git a/lib/approval.sh b/lib/approval.sh index 05f68df..cba6dfe 100755 --- a/lib/approval.sh +++ b/lib/approval.sh @@ -7,11 +7,14 @@ ############################################################################### trace "Loading approval functions" +# Initialize variables +read -r QUEUED <<< "" +echo "${QUEUED}" > /dev/null + function queue() { # Make sure there's something to do gitStatus gitCommit - #(git status --porcelain | sed '/^ D /d' | sed s/^...//) >> "${WORKPATH}/${APP}/.queued" (git status --porcelain | sed s/^...//) >> "${WORKPATH}/${APP}/.queued" info "Queuing proposed updates for approval" diff --git a/lib/digest.sh b/lib/digest.sh index 7764b1a..e9fe28c 100755 --- a/lib/digest.sh +++ b/lib/digest.sh @@ -69,7 +69,7 @@ function create_digest() { # Git some stats # git log --no-merges --since="7 days ago" --reverse --stat | grep -Eo "[0-9]{1,} files? changed" | grep -Eo "[0-9]{1,}" | awk "{ sum += \$1 } END { print sum }" - processHTML + process_html # Strip out useless analytics results if [[ -z "${RESULT}" ]] || [[ "${RESULT}" == "0" ]] || [[ "${SIZE}" == "0" ]]; then diff --git a/lib/log-handling.sh b/lib/log-handling.sh index 0f82e3a..fccd0d6 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -175,6 +175,6 @@ function htmlBuild() { if [[ "${REPORT}" != "1" ]]; then cat "${logFile}" "${deployPath}/html/${HTMLTEMPLATE}/footer.html" >> "${htmlFile}" # There's probably a better place for this. - processHTML + process_html fi } diff --git a/lib/process-html.sh b/lib/process-html.sh index 4ce4391..d90c4b5 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # process-html.sh # @@ -7,7 +7,12 @@ ############################################################################### trace "Loading html handling" -function processHTML() { +# Initialize variables +read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID <<< "" +echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} + ${SMOOCHID}" + +function process_html() { # Clean out the stuff we don't need [[ -z "${DEVURL}" ]] && sed -i '/Staging URL:/d' "${htmlFile}" [[ -z "${PRODURL}" ]] && sed -i '/PRODURL/d' "${htmlFile}" diff --git a/lib/report.sh b/lib/report.sh index 3bbe9f8..f230e55 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -50,5 +50,5 @@ function create_report() { cat "${deployPath}/html/${HTMLTEMPLATE}/report/header.html" "${statFile}" "${deployPath}/html/${HTMLTEMPLATE}/report/footer.html" > "${htmlFile}" # Filter and replace template variables - processHTML + process_html } diff --git a/lib/statistics.sh b/lib/statistics.sh index 1812bd5..0066414 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -21,7 +21,7 @@ function projStats() { # Process the HTML cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" - processHTML + process_html cat "${htmlFile}" > "/tmp/stats/index.html" # Create the charts diff --git a/lib/user-feedback.sh b/lib/user-feedback.sh index 4d43fe6..c8225c1 100755 --- a/lib/user-feedback.sh +++ b/lib/user-feedback.sh @@ -7,6 +7,10 @@ ############################################################################### trace "Loading interface" +# Initialize variables +read -r pid delay spinstr temp <<< "" +echo "${pid} ${delay} ${spinstr} %{temp}" > /dev/null + # Progress spinner; we'll see if this works function spinner() { if [[ "${QUIET}" != "1" ]]; then From 433fedb73a809d6c1301767a8a51cda7b867a6b0 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 21 Jan 2018 14:59:18 -0800 Subject: [PATCH 030/334] Fixed a dangerous bug with --repair --- deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy.sh b/deploy.sh index 4fddb82..949ba3f 100755 --- a/deploy.sh +++ b/deploy.sh @@ -206,7 +206,7 @@ while [[ ${1:-unset} = -?* ]]; do --monitor-test) MONITORTEST="1" ;; --stats) PROJSTATS="1" ;; --unlock) UNLOCK="1" ;; - --repair) REPAIR="1"; FORCE="1"; MERGE="1"; STASH="TRUE"; VERBOSE="1" ;; + --repair) REPAIR="1"; FORCE="1"; STASH="TRUE"; VERBOSE="1" ;; --no-check) NOCHECK="1" ;; --function-list) FUNCTIONLIST="1"; CURRENT="1" ;; # Spoofs --current --variable-list) VARIABLELIST="1"; CURRENT="1" ;; # Spoofs --current From df44ac2a999cd3bb8403e516815632bd1c99515f Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 21 Jan 2018 21:20:56 -0800 Subject: [PATCH 031/334] More function cleanup --- CHANGELOG.md | 2 +- deploy.sh | 30 ++++++++++++++--------------- etc/html/civil/digest/header.html | 2 +- etc/html/default/report/header.html | 4 ++-- install/doinst.sh | 12 ++++++------ lib/analytics.sh | 14 +++++++------- lib/core.sh | 4 ++-- lib/deployment.sh | 8 ++++---- lib/display-styles.sh | 2 +- lib/error-check.sh | 23 +++++++++++----------- lib/git.sh | 10 +++++----- lib/log-handling.sh | 6 +++--- lib/mail-log.sh | 2 +- lib/process-html.sh | 8 ++++---- lib/release-check.sh | 2 +- lib/report.sh | 2 +- lib/server-check.sh | 2 +- lib/slack.sh | 4 ++-- lib/ssh-check.sh | 2 +- lib/user-feedback.sh | 2 +- lib/utilities.sh | 4 ++-- lib/webhook.sh | 4 ++-- lib/wp-plugins.sh | 2 +- lib/wp-wordfence.sh | 2 +- 24 files changed, 77 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc9872..1de0394 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Changed -- Configuration files restructred for better readability +- Configuration files restructured for better readability - Many function and variable names changed for better consistency ### Fixed - Google analytics no longer incorrectly display for projects that do not use them diff --git a/deploy.sh b/deploy.sh index 949ba3f..0b3f6b4 100755 --- a/deploy.sh +++ b/deploy.sh @@ -13,7 +13,7 @@ IFS=$'\n\t' VERSION="3.6.6" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" -LASTMONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" +LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" WEEKOF="$(date -d '7 days ago' +"%B %d, %Y")" GASTART="$(date -d '7 days ago' "+%Y-%m-%d")" GAEND="$(date "+%Y-%m-%d")" @@ -45,7 +45,6 @@ read -r black red green yellow blue magenta cyan white endColor bold underline \ echo "${black} ${red} ${green} ${yellow} ${blue} ${magenta} ${cyan} ${white} ${endColor} ${bold} ${underline} ${reset} ${purple} ${tan}" > /dev/null - # Constants and environment variables read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ EMAILHTML NOPHP FIXPERMISSIONS DEVUSER DEVGROUP APACHEUSER APACHEGROUP TO \ @@ -57,7 +56,7 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ DIGESTURL CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD \ SSHCMD LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME \ TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ - REFRESHTOKEN PROFILEID ALLOWROOT SHORTEMAIL DIGESTCOVER INCOGNITO \ + REFRESHTOKEN PROFILEID ALLOWROOT SHORTEMAIL INCOGNITO \ REPORTURL CLIENTCONTACT INCLUDEHOSTING <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} @@ -73,7 +72,7 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} - ${SHORTEMAIL} ${DIGESTCOVER} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} + ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} ${INCLUDEHOSTING}" > /dev/null # Internal variables read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \ @@ -259,7 +258,7 @@ fi [[ "${VARIABLELIST}" == "1" ]] && STARTUP="${STARTUP} --variable-list" # If not trying to deploy current directory, and no repo is named in the startup command, exit -if [[ "${CURRENT}" != "1" ]] && [[ -z "${@}" ]]; then +if [[ "${CURRENT}" != "1" ]] && [[ -z "${*}" ]]; then echo "Choose a valid project, or use the --current flag to deploy from the current directory."; exit 1 fi @@ -271,23 +270,23 @@ function log_fail() { } # Main log file -logFile="/tmp/$APP.log-$RANDOM.log" +logFile="/tmp/${APP}.log-$RANDOM.log" (umask 077 && touch "${logFile}") || log_fail -wpFile="/tmp/$APP.wp-$RANDOM.log"; (umask 077 && touch "${wpFile}" &> /dev/null) || log_fail -coreFile="/tmp/$APP.core-$RANDOM.log"; (umask 077 && touch "${coreFile}" &> /dev/null) || log_fail +wpFile="/tmp/${APP}.wp-$RANDOM.log"; (umask 077 && touch "${wpFile}" &> /dev/null) || log_fail +coreFile="/tmp/${APP}.core-$RANDOM.log"; (umask 077 && touch "${coreFile}" &> /dev/null) || log_fail # Start writing the logfile echo -e "Deployment logfile for ${APP^^} - $NOW\r" >> "${logFile}" echo -e "Launching deploy${STARTUP}\n" >> "${logFile}" # More crappy tmp files -postFile="/tmp/$APP.wtf-$RANDOM.log"; (umask 077 && touch "${postFile}" &> /dev/null) || log_fail -trshFile="/tmp/$APP.trsh-$RANDOM.log"; (umask 077 && touch "${trshFile}" &> /dev/null) || log_fail -statFile="/tmp/$APP.stat-$RANDOM.log"; (umask 077 && touch "${statFile}" &> /dev/null) || log_fail -urlFile="/tmp/$APP.url-$RANDOM.log"; (umask 077 && touch "${urlFile}" &> /dev/null) || log_fail -htmlFile="/tmp/$APP.log-$RANDOM.html"; (umask 077 && touch "${htmlFile}" &> /dev/null) || log_fail -htmlEmail="/tmp/$APP.email-$RANDOM.html"; (umask 077 && touch "${htmlEmail}" &> /dev/null) || log_fail -clientEmail="/tmp/$APP.shortemail-$RANDOM.html"; (umask 077 && touch "${clientEmail}" &> /dev/null) || log_fail +postFile="/tmp/${APP}.wtf-$RANDOM.log"; (umask 077 && touch "${postFile}" &> /dev/null) || log_fail +trshFile="/tmp/${APP}.trsh-$RANDOM.log"; (umask 077 && touch "${trshFile}" &> /dev/null) || log_fail +statFile="/tmp/${APP}.stat-$RANDOM.log"; (umask 077 && touch "${statFile}" &> /dev/null) || log_fail +urlFile="/tmp/${APP}.url-$RANDOM.log"; (umask 077 && touch "${urlFile}" &> /dev/null) || log_fail +htmlFile="/tmp/${APP}.log-$RANDOM.html"; (umask 077 && touch "${htmlFile}" &> /dev/null) || log_fail +htmlEmail="/tmp/${APP}.email-$RANDOM.html"; (umask 077 && touch "${htmlEmail}" &> /dev/null) || log_fail +clientEmail="/tmp/${APP}.shortemail-$RANDOM.html"; (umask 077 && touch "${clientEmail}" &> /dev/null) || log_fail # Path of the script; I should flip this check to make it more useful if [ -d "/etc/deploy" ]; then @@ -374,6 +373,7 @@ else nano ~/.deployrc clear; sleep 1 console "Loading user configuration." + # shellcheck source=/dev/null source ~/.deployrc # quickExit else diff --git a/etc/html/civil/digest/header.html b/etc/html/civil/digest/header.html index 04af4bb..73b7a07 100755 --- a/etc/html/civil/digest/header.html +++ b/etc/html/civil/digest/header.html @@ -61,7 +61,7 @@ " >> "${statFile}" fi diff --git a/lib/server-check.sh b/lib/server-check.sh index 53ee1d0..7f3e991 100755 --- a/lib/server-check.sh +++ b/lib/server-check.sh @@ -7,7 +7,7 @@ ############################################################################### trace "Loading server checks" -function srvCheck() { +function server_check() { if [ "${SERVERCHECK}" == "TRUE" ]; then notice "Checking servers..." # Set SERVERFAIL to 0 diff --git a/lib/slack.sh b/lib/slack.sh index 6b7741b..05a1015 100755 --- a/lib/slack.sh +++ b/lib/slack.sh @@ -132,10 +132,10 @@ function slackTest { console "Testing Slack integration..." echo "${SLACKURL}" if [[ -z "${SLACKURL}" ]]; then - warning "No Slack configuration found."; emptyLine + warning "No Slack configuration found."; empty_line clean_up; exit 1 else curl -X POST --data "payload={\"text\": \"${slack_icon} Testing Slack integration of ${APP} from deploy ${VERSION}\nhttps://github.com/EMRL/deploy\"}" "${SLACKURL}" - emptyLine + empty_line fi } diff --git a/lib/ssh-check.sh b/lib/ssh-check.sh index 7ebe208..cb44293 100755 --- a/lib/ssh-check.sh +++ b/lib/ssh-check.sh @@ -8,7 +8,7 @@ # TODO: Rewrite this to store git@domain stuff in a variable, allow for other # repohosts to work (Gitlab etc.) and shorten the entire function -function sshChk() { +function ssh_check() { if [[ "${NOKEY}" != "TRUE" ]]; then trace "Checking SSH configuration" if [[ "${REPOHOST}" == *"bitbucket"* ]]; then diff --git a/lib/user-feedback.sh b/lib/user-feedback.sh index c8225c1..e3a9720 100755 --- a/lib/user-feedback.sh +++ b/lib/user-feedback.sh @@ -55,6 +55,6 @@ function showProgress() { do progressBar "${number}" ${_end} done; - emptyLine; sleep 3 + empty_line; sleep 3 fi } diff --git a/lib/utilities.sh b/lib/utilities.sh index 0efe71f..f811b4c 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -46,7 +46,7 @@ function go() { if [[ "${SSHTEST}" == "1" ]]; then if [[ "${NOKEY}" != "TRUE" ]] && [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then notice "Checking SSH Configuration..." - sshChk + ssh_check else warning "This project is not configured to use SSH keys, no check needed." fi @@ -130,7 +130,7 @@ function dependency_check() { if [[ -z "${APPRC}" ]]; then # Make sure app directory is writable if [[ -w "${WORKPATH}/${APP}" ]]; then - emptyLine; info "Project configuration not found, creating."; sleep 2 + empty_line; info "Project configuration not found, creating."; sleep 2 # If configuration directory is defined if [[ -n "${CONFIGDIR}" ]]; then if [[ ! -d "${WORKPATH}/${APP}/${CONFIGDIR}" ]]; then diff --git a/lib/webhook.sh b/lib/webhook.sh index 68492ac..22fb329 100755 --- a/lib/webhook.sh +++ b/lib/webhook.sh @@ -30,10 +30,10 @@ function postTest { console "Testing POST integration..." echo "${POSTURL}" if [[ -z "${POSTURL}" ]]; then - warning "No webhook URL found."; emptyLine + warning "No webhook URL found."; empty_line clean_up; exit 1 else curl -X POST --data "payload={\"text\": \"Testing POST integration of ${APP} from deploy ${VERSION}\nhttps://github.com/EMRL/deploy\"}" "${POSTURL}" - emptyLine + empty_line fi } diff --git a/lib/wp-plugins.sh b/lib/wp-plugins.sh index 043cf22..1f243e4 100755 --- a/lib/wp-plugins.sh +++ b/lib/wp-plugins.sh @@ -27,7 +27,7 @@ function wpPlugins() { # Display plugin list if [[ "${QUIET}" != "1" ]]; then - cat "${wpFile}"; emptyLine + cat "${wpFile}"; empty_line fi if [[ "${FORCE}" = "1" ]] || yesno --default no "Proceed with updates? [y/N] "; then diff --git a/lib/wp-wordfence.sh b/lib/wp-wordfence.sh index 8873299..802e141 100755 --- a/lib/wp-wordfence.sh +++ b/lib/wp-wordfence.sh @@ -10,7 +10,7 @@ function wfCheck() { if [[ "${WFCHECK}" == "TRUE" ]]; then if [[ -f "${WORKPATH}/${APP}${WPROOT}${WPAPP}/wflogs/config.php" ]]; then - trace "Wordfence detected"; emptyLine + trace "Wordfence detected"; empty_line warning "Wordfence firewall detected, and may cause issues with deployment." if [[ "${FORCE}" = "1" ]] || [[ "${QUIET}" = "1" ]]; then error "Deployment can not continue while Wordfence firewall is enabled." From e5d218d256cab840e0c113e31b5863e2d3f24339 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 2 Feb 2018 10:36:09 -0800 Subject: [PATCH 032/334] Date fix, resolve #124 --- deploy.sh | 8 ++--- etc/html/default/header.html | 55 +++++++++++++++++------------ etc/html/default/report/header.html | 4 +-- install/doinst.sh | 2 +- lib/process-html.sh | 2 +- lib/report.sh | 9 ++++- 6 files changed, 48 insertions(+), 32 deletions(-) diff --git a/deploy.sh b/deploy.sh index 0b3f6b4..943d5c8 100755 --- a/deploy.sh +++ b/deploy.sh @@ -84,8 +84,8 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile VIEWPORT VIEWPORTPRE LOGTITLE LOGURL TIMESTAMP STARTUP WPROOT \ WPAPP WPSYSTEM DONOTUPDATEWP gitHistory ANALYTICSMSG digestSendmail MINAUSER \ MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX \ - DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile CURMTH \ - CURYR PRVMTH PRVYR LASTDY TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ + DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile \ + TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI RELEASE RELEASENOTES \ RELEASEURL <<< "" echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile} @@ -100,8 +100,8 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${WPSYSTEM} ${DONOTUPDATEWP} ${gitHistory} ${ANALYTICSMSG} ${digestSendmail} ${MINAUSER} ${MINADOMAIN} ${SSHTARGET} ${SSHSTATUS} ${REMOTEFILE} ${GREETING} ${LOGSUFFIX} ${DISABLESSHCHECK} - ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} ${CURMTH} ${CURYR} - ${PRVMTH} ${PRVYR} ${LASTDY} ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} + ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} + ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} ${SERVERID} ${MONITORHOURS} ${LATENCY} ${UPTIME} ${MONITORTEST} ${MONITORAPI} ${RELEASE} ${RELEASENOTES} ${RELEASEURL}" > /dev/null diff --git a/etc/html/default/header.html b/etc/html/default/header.html index 03e0f37..6f155ae 100755 --- a/etc/html/default/header.html +++ b/etc/html/default/header.html @@ -1,6 +1,5 @@ - - - + + @@ -11,37 +10,48 @@ + - - - - - + @@ -49,7 +59,6 @@ " >> "${statFile}" fi From fd592f4fbaafe7eabb8f7982c7595410ccf5cfc1 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 8 Feb 2018 20:27:16 -0800 Subject: [PATCH 033/334] Resolve #118 --- CHANGELOG.md | 2 ++ deploy.sh | 4 +-- etc/deploy.sh | 18 +++++++------ lib/approval.sh | 67 +++++++++++++++++++++++++++-------------------- lib/statistics.sh | 58 ++++++++++++++++++++-------------------- 5 files changed, 82 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1de0394..8b641da 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Configuration files restructured for better readability - Many function and variable names changed for better consistency ### Fixed +- Fixed a crash that could occur when creating statistics for projects with code in approval queue +- Reports for the month of Januray now generate correctly - Google analytics no longer incorrectly display for projects that do not use them ## [3.6.6] - 01-13-2018 diff --git a/deploy.sh b/deploy.sh index 943d5c8..f18fdd2 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.6" +VERSION="3.6.7-alpha.2" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" @@ -518,7 +518,7 @@ fi # Check if a deployment is queued if [[ "${REQUIREAPPROVAL}" == "TRUE" ]] && [[ -f "${WORKPATH}/${APP}/.queued" ]]; then if [[ "${APPROVE}" != "1" ]] && [[ "${DENY}" != "1" ]]; then - error "There is changed code already queued. Approve or deny it before attempting another deployment." + queue_check fi if [[ "${DENY}" == "1" ]] || [[ -f "${WORKPATH}/${APP}/.denied" ]]; then deny diff --git a/etc/deploy.sh b/etc/deploy.sh index f71914f..6dab455 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -115,12 +115,13 @@ # Project Management # ------------------ + # Task#: This is used to post deploy logs to project management systems # that can accept external email input. For examples, our task management system # accepts emails in the format task-####@projects.emrl.com, with the #### # being the task identification number for the project being deployed. # TASK="####" -# + # If you wish to have automated deployments add tracked time to your project # management system, uncomment and configure the two values below. TASKUSER # should be the email address of the user that the time will be logged as, @@ -131,22 +132,23 @@ # Slack # ----- + # You'll need to set up an "Incoming Webhook" custom integration on the Slack # side to get this ready to roll. # See https://YOURTEAMNAME.slack.com/apps/manage/custom-integrations to get # going. Once your Slack webhook is setup, run # 'deploy --slack-test' to # test your configuration. -# + # Set POSTTOSLACK to "TRUE" to enable Slack integration. # POSTTOSLACK="TRUE" -# + # Add your full Webhook URL below, including https:// # SLACKURL="https://hooks.slack.com/services/###################/########################" -# + # Normally only successful deployments are posted to Slack. # Enable the settings below to post on WARNiNG and/or ERROR. # SLACKERROR="FALSE" -# + # If you'd like to post a Slack notification with a URL to view the weekly digest # set the following to TRUE. If you want to use an incoming webhook other than the # one defined in SLACKURL, enter that here *instead* of TRUE. @@ -243,15 +245,15 @@ # CLIENTID="#############################################.apps.googleusercontent.com" # CLIENTSECRET="########################" # REDIRECTURI="http://localhost" -# + # OAuth authorization will expire after one hour, but will be updated when needed # if the tokens below are configured correctly # AUTHORIZATIONCODE="##############################################" -# + # Tokens # ACCESSTOKEN="#################################################################################################################################" # REFRESHTOKEN="##################################################################" -# + # Google Analytics ID # PROFILEID="########" diff --git a/lib/approval.sh b/lib/approval.sh index cba6dfe..05020da 100755 --- a/lib/approval.sh +++ b/lib/approval.sh @@ -12,40 +12,49 @@ read -r QUEUED <<< "" echo "${QUEUED}" > /dev/null function queue() { - # Make sure there's something to do - gitStatus - gitCommit - (git status --porcelain | sed s/^...//) >> "${WORKPATH}/${APP}/.queued" + # Make sure there's something to do + gitStatus + gitCommit + (git status --porcelain | sed s/^...//) >> "${WORKPATH}/${APP}/.queued" - info "Queuing proposed updates for approval" - safeExit - # slackPost + info "Queuing proposed updates for approval" + safeExit + # slackPost } function approve() { - info "Approving proposed updates" - # Read proposed commit message from the first line of .queued - notes="$(head -n 1 ${WORKPATH}/${APP}/.queued)" - # Remove first line - sed -i -e "1d" "${WORKPATH}/${APP}/.queued" - # Loop through file, git add each file (line) - while read QUEUED; do - # Verify file date is older than .queued - if [[ "${QUEUED}" -nt "${WORKPATH}/${APP}/.queued" ]]; then - error "The file ${QUEUED} was modified after it was queued for approval." - sed -i '1s/^/${notes}\n/' "${QUEUED}" - else - git add "${QUEUED}" | tee --append "${logFile}" - fi - done < "${WORKPATH}/${APP}/.queued" - git commit -m "${notes}" &>> "${logFile}"; error_check - trace "Commit message: ${notes}" + info "Approving proposed updates" + # Read proposed commit message from the first line of .queued + notes="$(head -n 1 ${WORKPATH}/${APP}/.queued)" + # Remove first line + sed -i -e "1d" "${WORKPATH}/${APP}/.queued" + # Loop through file, git add each file (line) + while read QUEUED; do + # Verify file date is older than .queued + if [[ "${QUEUED}" -nt "${WORKPATH}/${APP}/.queued" ]]; then + error "The file ${QUEUED} was modified after it was queued for approval." + sed -i '1s/^/${notes}\n/' "${QUEUED}" + else + git add "${QUEUED}" | tee --append "${logFile}" + fi + done < "${WORKPATH}/${APP}/.queued" + git commit -m "${notes}" &>> "${logFile}"; error_check + trace "Commit message: ${notes}" } function deny() { - info "Denying proposed updates" - if [[ -f "${WORKPATH}/${APP}/.queued" ]]; then - rm "${WORKPATH}/${APP}/.queued" - fi - quietExit + info "Denying proposed updates" + if [[ -f "${WORKPATH}/${APP}/.queued" ]]; then + rm "${WORKPATH}/${APP}/.queued" + fi + quietExit +} + +# Check for approval queue +function queue_check() { + if [[ -f "${WORKPATH}/${APP}/.queued" ]]; then + warning "Can not continue while code is queued for approval." + + quietExit + fi } diff --git a/lib/statistics.sh b/lib/statistics.sh index 0066414..e0672b5 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -8,43 +8,45 @@ trace "Loading statistics functions" function projStats() { - hash gitchart 2>/dev/null || { - error "gitchart not installed." - } - if [[ "${REMOTELOG}" == "TRUE" ]]; then - # Setup up tmp work folder - if [[ ! -d "/tmp/stats" ]]; then - umask 077 && mkdir /tmp/stats &> /dev/null - fi + hash gitchart 2>/dev/null || { + error "gitchart not installed." + } + if [[ "${REMOTELOG}" == "TRUE" ]]; then + # Check for approval queue + queue_check - notice "Generating files..." + # Setup up tmp work folder + if [[ ! -d "/tmp/stats" ]]; then + umask 077 && mkdir /tmp/stats &> /dev/null + fi - # Process the HTML - cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" - process_html - cat "${htmlFile}" > "/tmp/stats/index.html" + notice "Generating files..." - # Create the charts - /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "/tmp/stats/authors.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "/tmp/stats/commits_day_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "/tmp/stats/commits_hour_day.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "/tmp/stats/commits_hour_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "/tmp/stats/commits_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "/tmp/stats/commits_year.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "/tmp/stats/commits_year_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "/tmp/stats/files_type.svg" &>> /dev/null & - # /usr/bin/gitchart -r "${WORKPATH}/${APP}" commits_day "${LOCALHOSTPATH}/${APP}/stats/commits_day.svg" &>> /dev/null & - spinner $! + # Process the HTML + cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" + process_html + cat "${htmlFile}" > "/tmp/stats/index.html" - # Process primary chart color and set permissions if needed - sleep 1; find "/tmp/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; + # Create the charts + /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "/tmp/stats/authors.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "/tmp/stats/commits_day_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "/tmp/stats/commits_hour_day.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "/tmp/stats/commits_hour_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "/tmp/stats/commits_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "/tmp/stats/commits_year.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "/tmp/stats/commits_year_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "/tmp/stats/files_type.svg" &>> /dev/null & + spinner $! - postLog + # Process primary chart color and set permissions if needed + sleep 1; find "/tmp/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; + + postLog # if [[ "${LOCALHOSTPOST}" == "TRUE" ]]; then # [[ ! -d "${LOCALHOSTPATH}/${APP}" ]] && mkdir "${LOCALHOSTPATH}/${APP}" # [[ ! -d "${LOCALHOSTPATH}/${APP}/stats" ]] && mkdir "${LOCALHOSTPATH}/${APP}/stats" # cp -R "/tmp/stats" "${LOCALHOSTPATH}/${APP}" # chmod -R a+rw "${deployPath}/html/${HTMLTEMPLATE}/stats" &> /dev/null # fi - fi + fi } From 5f76085eb556a0d1ff7b13f58a3cacd34eeb47e2 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 9 Feb 2018 15:02:27 -0800 Subject: [PATCH 034/334] Fix for core update version number bug, resolve #83 --- lib/wp-core.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/wp-core.sh b/lib/wp-core.sh index a20679a..1cacdcb 100755 --- a/lib/wp-core.sh +++ b/lib/wp-core.sh @@ -29,9 +29,10 @@ function wpCore() { # messing up output with PHP crap sed '/^\s*$/d' "${coreFile}" > "${trshFile}" && mv "${trshFile}" "${coreFile}"; - # Remove line breaks, value should noe equal 'version x.x.x' or - # some such. - sed ':a;N;$!ba;s/\n/ /g' "${coreFile}" > "${trshFile}" && mv "${trshFile}" "${coreFile}"; + # Remove line breaks, value should noe equal 'version x.x.x' or some such. + sed ':a;N;$!ba;s/\n/ /g' "${coreFile}" > "${trshFile}" && mv "${trshFile}" "${coreFile}" + # Remove everything up to and including the first space (in case of multiple core updates) + sed -i 's/[^ ]* //' "${coreFile}" COREUPD=$(<$coreFile) if [[ -n "${COREUPD}" ]]; then From 7c9f36587505a38c106de5a4cb1375fd6e67f709 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 9 Feb 2018 18:32:54 -0800 Subject: [PATCH 035/334] Fix for remote log expiration, resolve #125 --- CHANGELOG.md | 4 +++- deploy.sh | 2 +- lib/post-log.sh | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b641da..c1d3ca5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Configuration files restructured for better readability - Many function and variable names changed for better consistency ### Fixed +- Remote hosted log files are now correctly deleted when they expire - Fixed a crash that could occur when creating statistics for projects with code in approval queue -- Reports for the month of Januray now generate correctly +- Reports for the month of January now generate correctly +- Fixed a bug that could rarely report an incorrect Wordpress version number - Google analytics no longer incorrectly display for projects that do not use them ## [3.6.6] - 01-13-2018 diff --git a/deploy.sh b/deploy.sh index f18fdd2..a07a9c1 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.7-alpha.2" +VERSION="3.6.7-alpha.3" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" diff --git a/lib/post-log.sh b/lib/post-log.sh index cf3d873..7a53f42 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -105,6 +105,12 @@ function postLog() { if [[ "${REPORT}" != "1" ]] && [[ "${PROJSTATS}" != "1" ]]; then eval "${SCPCMD}" "${htmlFile}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/${REMOTEFILE}" &> /dev/null fi + + # Remove logs older then X days + if [[ -n "${EXPIRELOGS}" ]]; then + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "'find ${SCPHOSTPATH}/${APP}* -mtime +${EXPIRELOGS} -exec rm {} \;' &> /dev/null" + fi + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "chmod -R 755 ${SCPHOSTPATH}/${APP}/" fi fi From 4a4f836805b4848fc17884122ac95eb01e256165 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sat, 10 Feb 2018 16:30:06 -0800 Subject: [PATCH 036/334] Added integer checking function --- deploy.sh | 7 ++- lib/git.sh | 5 +- lib/{core.sh => main.sh} | 7 +-- lib/monitor.sh | 106 +++++++++++++++++++-------------------- lib/post-log.sh | 11 +++- lib/release-check.sh | 31 +++++++----- lib/statistics.sh | 58 ++++++++++----------- lib/utilities.sh | 15 ++++++ lib/wp.sh | 4 +- 9 files changed, 135 insertions(+), 109 deletions(-) rename lib/{core.sh => main.sh} (98%) diff --git a/deploy.sh b/deploy.sh index a07a9c1..c7893d3 100755 --- a/deploy.sh +++ b/deploy.sh @@ -86,8 +86,7 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX \ DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile \ TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ - MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI RELEASE RELEASENOTES \ - RELEASEURL <<< "" + MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI <<< "" echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile} ${trshFile} ${statFile} ${urlFile} ${htmlFile} ${htmlSendmail} ${htmlEmail} ${clientEmail} ${textSendmail} ${deployPath} ${etcLocation} ${libLocation} @@ -103,7 +102,7 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} ${SERVERID} ${MONITORHOURS} ${LATENCY} ${UPTIME} ${MONITORTEST} - ${MONITORAPI} ${RELEASE} ${RELEASENOTES} ${RELEASEURL}" > /dev/null + ${MONITORAPI}" > /dev/null # Display command options function flags() { @@ -558,7 +557,7 @@ if [[ "${MERGE}" == "1" ]] && [[ -z "${PRODUCTION}" ]]; then fi # Execute the deploy process -core +main # Trapping stuff trap userExit INT diff --git a/lib/git.sh b/lib/git.sh index b35b61b..a458120 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -15,7 +15,7 @@ function gitStart() { # Directory exists? if [[ ! -d "${WORKPATH}/${APP}" ]]; then info "${WORKPATH}/${APP} is not a valid directory." - exit 1 + clean_up; exit 1 else cd "${WORKPATH}/${APP}" || error_check fi @@ -25,7 +25,7 @@ function gitStart() { sleep 1 else info "There is nothing at ${WORKPATH}/${APP} to deploy." - exit 1 + clean_up; exit 1 fi # Make sure there has been at least one commit previously made @@ -123,6 +123,7 @@ function gitCommit() { # Do a dry run; check for anything to commit git commit --dry-run &>> "${logFile}" + if grep -q "nothing to commit, working directory clean" "${logFile}"; then info "Nothing to commit, working directory clean." safeExit diff --git a/lib/core.sh b/lib/main.sh similarity index 98% rename from lib/core.sh rename to lib/main.sh index 35c9776..facbe9f 100755 --- a/lib/core.sh +++ b/lib/main.sh @@ -1,17 +1,18 @@ #!/usr/bin/env bash # -# core.sh +# main.sh # ############################################################################### -# The core application +# The main application ############################################################################### -function core() { +function main() { dependency_check # Check that required commands are available release_check # Check for newer version at Github gitStart # Check for a valid git project and get set up lock # Create lock file go # Start a deployment work session + if [[ "${DIGEST}" == "1" ]]; then create_digest elif [[ "${REPORT}" == "1" ]]; then diff --git a/lib/monitor.sh b/lib/monitor.sh index 61ebcaa..240a6c5 100755 --- a/lib/monitor.sh +++ b/lib/monitor.sh @@ -9,69 +9,69 @@ trace "Loading server monitoring" function server_monitor() { - # Don't bother unless all the needed variables are declared - if [[ -n "${MONITORURL}" ]] && [[ -n "${MONITORUSER}" ]] && [[ -n "${SERVERID}" ]] && [[ -n "${MONITORPASS}" ]]; then - # What kind of log is this? - if [[ "${REPORT}" == "1" ]]; then - # Last 30 days; this will not correlate exactly with the time range of the report - # which is dealing with the last calendar month, but oh well we get what we pay - # for. The PHP Monitor API has no ability to look up results in that way - MONITORHOURS="720" - elif [[ "${DIGEST}" == "1" ]] || [[ "${PROJSTATS}" == "1" ]]; then - # Digests and reports should average the 7 days - MONITORHOURS="168" - else - # Anything else defaults to 24 hours - MONITORHOURS="24" - fi - server_monitor_log - trace "Uptime: ${UPTIME} / Latency: ${LATENCY} (avg. over last ${MONITORHOURS} hours)" - fi + # Don't bother unless all the needed variables are declared + if [[ -n "${MONITORURL}" ]] && [[ -n "${MONITORUSER}" ]] && [[ -n "${SERVERID}" ]] && [[ -n "${MONITORPASS}" ]]; then + # What kind of log is this? + if [[ "${REPORT}" == "1" ]]; then + # Last 30 days; this will not correlate exactly with the time range of the report + # which is dealing with the last calendar month, but oh well we get what we pay + # for. The PHP Monitor API has no ability to look up results in that way + MONITORHOURS="720" + elif [[ "${DIGEST}" == "1" ]] || [[ "${PROJSTATS}" == "1" ]]; then + # Digests and reports should average the 7 days + MONITORHOURS="168" + else + # Anything else defaults to 24 hours + MONITORHOURS="24" + fi + server_monitor_log + trace "Uptime: ${UPTIME} / Latency: ${LATENCY} (avg. over last ${MONITORHOURS} hours)" + fi } function server_monitor_test() { - notice "Testing server monitor integration..." - if [[ -n "${MONITORURL}" ]] && [[ -n "${MONITORUSER}" ]] && [[ -n "${SERVERID}" ]] && [[ -n "${MONITORPASS}" ]]; then - console "Monitor URL: ${MONITORURL}" - console "User: ${MONITORUSER}" - console "Server ID: ${SERVERID}" - console "Password file: ${MONITORPASS}" + notice "Testing server monitor integration..." + if [[ -n "${MONITORURL}" ]] && [[ -n "${MONITORUSER}" ]] && [[ -n "${SERVERID}" ]] && [[ -n "${MONITORPASS}" ]]; then + console "Monitor URL: ${MONITORURL}" + console "User: ${MONITORUSER}" + console "Server ID: ${SERVERID}" + console "Password file: ${MONITORPASS}" else - warning "Server monitoring is not configured; check your project's configuration file." - return + warning "Server monitoring is not configured; check your project's configuration file." + return fi # Check last 7 days for the sake of the test - MONITORHOURS="24" - server_monitor_log - console "API: ${MONITORAPI}" - notice "Results (last 24 hours)" - console "Uptime: ${UPTIME}%" - console "Latency: ${LATENCY}s" + MONITORHOURS="24" + server_monitor_log + console "API: ${MONITORAPI}" + notice "Results (last 24 hours)" + console "Uptime: ${UPTIME}%" + console "Latency: ${LATENCY}s" } function server_monitor_log() { - # Load the password and setup the curl command - MONITORPASS=$(<$MONITORPASS) + # Load the password and setup the curl command + MONITORPASS=$(<$MONITORPASS) MONITORAPI="${MONITORURL}?tag=serveruptime&email=${MONITORUSER}&app_password=${MONITORPASS}&server_id=${SERVERID}&HoursUnit=${MONITORHOURS}" - curl -s --request GET "${MONITORAPI}" -o "${trshFile}" - # Uptime - UPTIME=$(grep -Po '"uptime":.*?[^\\]",' ${trshFile}) - UPTIME="$(cut -d ',' -f 1 <<< "${UPTIME}")" - # Isolate the value we need - UPTIME="$(sed 's/^[^:]*://g' <<< "${UPTIME}")" - # Round to two decimal places - UPTIME="$(printf '%0.2f\n' "${UPTIME}")" - # Lop off the .00 if we're at 100% - if [[ "${UPTIME}" == "100.00" ]]; then - UPTIME="100" - fi + curl -s --request GET "${MONITORAPI}" -o "${trshFile}" + # Uptime + UPTIME=$(grep -Po '"uptime":.*?[^\\]",' ${trshFile}) + UPTIME="$(cut -d ',' -f 1 <<< "${UPTIME}")" + # Isolate the value we need + UPTIME="$(sed 's/^[^:]*://g' <<< "${UPTIME}")" + # Round to two decimal places + UPTIME="$(printf '%0.2f\n' "${UPTIME}")" + # Lop off the .00 if we're at 100% + if [[ "${UPTIME}" == "100.00" ]]; then + UPTIME="100" + fi - # Latency - LATENCY="$(grep -Po '"average_latency":.*?[^\\]",' ${trshFile})" - LATENCY="$(cut -d ',' -f 1 <<< "${LATENCY}")" - # Isolate the value we need - LATENCY="$(sed 's/^[^:]*://g' <<< "${LATENCY}")" - # Round to two decimal places - LATENCY="$(printf '%0.2f\n' "${LATENCY}")" + # Latency + LATENCY="$(grep -Po '"average_latency":.*?[^\\]",' ${trshFile})" + LATENCY="$(cut -d ',' -f 1 <<< "${LATENCY}")" + # Isolate the value we need + LATENCY="$(sed 's/^[^:]*://g' <<< "${LATENCY}")" + # Round to two decimal places + LATENCY="$(printf '%0.2f\n' "${LATENCY}")" } diff --git a/lib/post-log.sh b/lib/post-log.sh index 7a53f42..958b15a 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -49,7 +49,10 @@ function postLog() { # Remove logs older then X days if [[ -n "${EXPIRELOGS}" ]]; then - find "${LOCALHOSTPATH}/${APP}"* -mtime +"${EXPIRELOGS}" -exec rm {} \; &> /dev/null + is_integer "${EXPIRELOGS}" + if [[ "${integer_check}" != "1" ]]; then + find "${LOCALHOSTPATH}/${APP}"* -mtime +"${EXPIRELOGS}" -exec rm {} \; &> /dev/null + fi fi fi @@ -108,9 +111,13 @@ function postLog() { # Remove logs older then X days if [[ -n "${EXPIRELOGS}" ]]; then - eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "'find ${SCPHOSTPATH}/${APP}* -mtime +${EXPIRELOGS} -exec rm {} \;' &> /dev/null" + is_integer "${EXPIRELOGS}" + if [[ "${integer_check}" != "1" ]]; then + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "'find ${SCPHOSTPATH}/${APP}* -mtime +${EXPIRELOGS} -exec rm {} \;' &> /dev/null" + fi fi + # Set permissions eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "chmod -R 755 ${SCPHOSTPATH}/${APP}/" fi fi diff --git a/lib/release-check.sh b/lib/release-check.sh index 8a0efc7..d466217 100755 --- a/lib/release-check.sh +++ b/lib/release-check.sh @@ -6,35 +6,40 @@ # Check to see if there is a new release version on Github ############################################################################### +# Initialize variables +read -r release release_notes release_url <<< "" +echo "${active_files}" > /dev/null + function release_check() { # Only check for a newer release when someone is at the console if [[ "${FORCE}" != "1" ]]; then # Get the release tag trace "Checking for deploy updates..." - RELEASE="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep \"tag_name\")" + release="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep \"tag_name\")" # Remove the extra garbage - RELEASE="${RELEASE#*v}" - RELEASE="$(printf '%s' "${RELEASE}" | sed 's/",//g')" + release="${release#*v}" + release="$(printf '%s' "${release}" | sed 's/",//g')" # Compare versions - if version_compare "${RELEASE}" "${VERSION}"; then + if version_compare "${release}" "${VERSION}"; then # Get release notes - RELEASENOTES="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep \"body\")" + release_notes="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep \"body\")" # Remove the extra garbage - RELEASENOTES="$(printf '%s' "${RELEASENOTES}" | sed 's^\"body\": \"^^g')" - RELEASENOTES="${RELEASENOTES//\"}" - RELEASENOTES=${RELEASENOTES#" "} + release_notes="$(printf '%s' "${release_notes}" | sed 's^\"body\": \"^^g')" + release_notes="${release_notes//\"}" + release_notes=${release_notes#" "} # User feedback - info "\r\nNew release found: ${RELEASE}" - printf "${RELEASENOTES}" | fold --spaces -w 78; empty_line + info "\r\nNew release found: ${release}\r\n" + printf "${release_notes}" | fold --spaces -w 78; empty_line # Update? + empty_line if yesno --default yes "Would you like to download now? [Y/n] "; then # Get latest - RELEASEURL="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep tarball_url | cut -d '"' -f 4)" - curl -Ls "${RELEASEURL}" -o "deploy-${RELEASE}.tar.gz" + release_url="$(curl -s https://api.github.com/repos/emrl/deploy/releases/latest | grep tarball_url | cut -d '"' -f 4)" + curl -Ls "${release_url}" -o "deploy-${release}.tar.gz" # Eventually I'll have a full install here but for now we'll bail - console "Try 'tar zxvf deploy-${RELEASE}.tar.gz' and then 'sudo doinst.sh' from the archive root directory." + console "Try 'tar zxvf deploy-${release}.tar.gz' and then 'sudo doinst.sh' from the archive root directory." quietExit fi fi diff --git a/lib/statistics.sh b/lib/statistics.sh index e0672b5..23454fa 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -8,45 +8,45 @@ trace "Loading statistics functions" function projStats() { - hash gitchart 2>/dev/null || { - error "gitchart not installed." - } - if [[ "${REMOTELOG}" == "TRUE" ]]; then - # Check for approval queue - queue_check + hash gitchart 2>/dev/null || { + error "gitchart not installed." + } + if [[ "${REMOTELOG}" == "TRUE" ]]; then + # Check for approval queue + queue_check - # Setup up tmp work folder - if [[ ! -d "/tmp/stats" ]]; then - umask 077 && mkdir /tmp/stats &> /dev/null - fi + # Setup up tmp work folder + if [[ ! -d "/tmp/stats" ]]; then + umask 077 && mkdir /tmp/stats &> /dev/null + fi - notice "Generating files..." + notice "Generating files..." - # Process the HTML - cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" - process_html - cat "${htmlFile}" > "/tmp/stats/index.html" + # Process the HTML + cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" + process_html + cat "${htmlFile}" > "/tmp/stats/index.html" - # Create the charts - /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "/tmp/stats/authors.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "/tmp/stats/commits_day_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "/tmp/stats/commits_hour_day.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "/tmp/stats/commits_hour_week.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "/tmp/stats/commits_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "/tmp/stats/commits_year.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "/tmp/stats/commits_year_month.svg" &>> /dev/null & - /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "/tmp/stats/files_type.svg" &>> /dev/null & - spinner $! + # Create the charts + /usr/bin/gitchart -r "${WORKPATH}/${APP}" authors "/tmp/stats/authors.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_day_week "/tmp/stats/commits_day_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_day "/tmp/stats/commits_hour_day.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_hour_week "/tmp/stats/commits_hour_week.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_month "/tmp/stats/commits_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year "/tmp/stats/commits_year.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" commits_year_month "/tmp/stats/commits_year_month.svg" &>> /dev/null & + /usr/bin/gitchart -t "" -r "${WORKPATH}/${APP}" files_type "/tmp/stats/files_type.svg" &>> /dev/null & + spinner $! - # Process primary chart color and set permissions if needed - sleep 1; find "/tmp/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; + # Process primary chart color and set permissions if needed + sleep 1; find "/tmp/stats/" -type f -exec sed -i "s/#9999ff/${PRIMARYC}/g" {} \; - postLog + postLog # if [[ "${LOCALHOSTPOST}" == "TRUE" ]]; then # [[ ! -d "${LOCALHOSTPATH}/${APP}" ]] && mkdir "${LOCALHOSTPATH}/${APP}" # [[ ! -d "${LOCALHOSTPATH}/${APP}/stats" ]] && mkdir "${LOCALHOSTPATH}/${APP}/stats" # cp -R "/tmp/stats" "${LOCALHOSTPATH}/${APP}" # chmod -R a+rw "${deployPath}/html/${HTMLTEMPLATE}/stats" &> /dev/null # fi - fi + fi } diff --git a/lib/utilities.sh b/lib/utilities.sh index f811b4c..2ae6732 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -7,12 +7,18 @@ ############################################################################### trace "Loading utilities" +# Initialize variables +read -r integer_check <<< "" +echo "${integer_check}" > /dev/null + # Open a deployment session, ask for user confirmation before beginning function go() { if [[ "${QUIET}" != "1" ]]; then tput cnorm; fi + console "deploy ${VERSION}" + if [[ "${INCOGNITO}" != "TRUE" ]]; then console "Current working path is ${WORKPATH}/${APP}" fi @@ -119,6 +125,15 @@ function fix_index() { fi } +# Check that a variable is an integer +function is_integer() { + declare arg1="$1"; integer_check="0" + if [[ ! "${arg1}" =~ ^[0-9]+$ ]] ; then + integer_check="1" + fi +} + + # Check that dependencies exist function dependency_check() { # Is git installed? diff --git a/lib/wp.sh b/lib/wp.sh index 60e0919..622be03 100755 --- a/lib/wp.sh +++ b/lib/wp.sh @@ -48,9 +48,7 @@ function wpPkg() { # Check the logs #if grep -q "U = Update Available" "${logFile}"; then if grep -q "Available plugin updates:" "${wpFile}"; then - wpPlugins - - + wpPlugins else # Was there a database glitch? if grep -q 'plugins can not be updated' "${wpFile}"; then From f8f5caa46b04f26ab80c2b1cb57e97b6ff7162d8 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 12 Feb 2018 18:34:28 -0800 Subject: [PATCH 037/334] Added .deployrc configuration --- deploy.sh | 11 ++-- etc/.deployrc | 67 +++----------------- etc/deploy-example.conf | 133 +++++++++++++++++++++++++--------------- lib/configure.sh | 63 +++++++++++++++++++ lib/deployment.sh | 4 +- lib/git.sh | 12 ++-- lib/loader.sh | 14 ++--- lib/package-manager.sh | 4 +- 8 files changed, 178 insertions(+), 130 deletions(-) create mode 100755 lib/configure.sh diff --git a/deploy.sh b/deploy.sh index c7893d3..99eb209 100755 --- a/deploy.sh +++ b/deploy.sh @@ -204,7 +204,7 @@ while [[ ${1:-unset} = -?* ]]; do --monitor-test) MONITORTEST="1" ;; --stats) PROJSTATS="1" ;; --unlock) UNLOCK="1" ;; - --repair) REPAIR="1"; FORCE="1"; STASH="TRUE"; VERBOSE="1" ;; + --repair) REPAIR="1"; FORCE="1"; STASH="TRUE"; VERBOSE="TRUE" ;; --no-check) NOCHECK="1" ;; --function-list) FUNCTIONLIST="1"; CURRENT="1" ;; # Spoofs --current --variable-list) VARIABLELIST="1"; CURRENT="1" ;; # Spoofs --current @@ -238,7 +238,7 @@ fi [[ "${SKIPUPDATE}" == "1" ]] && STARTUP="${STARTUP} --skip-update" [[ "${TIME}" == "1" ]] && STARTUP="${STARTUP} --time" [[ "${CURRENT}" == "1" ]] && STARTUP="${STARTUP} --current" -[[ "${VERBOSE}" == "1" ]] && STARTUP="${STARTUP} --verbose" +[[ "${VERBOSE}" == "TRUE" ]] && STARTUP="${STARTUP} --verbose" [[ "${QUIET}" == "1" ]] && STARTUP="${STARTUP} --quiet" [[ "${STRICT}" == "1" ]] && STARTUP="${STARTUP} --strict" [[ "${DEBUG}" == "1" ]] && STARTUP="${STARTUP} --debug" @@ -352,6 +352,8 @@ if [[ "${VARIABLELIST}" == "1" ]]; then ( set -o posix ; set ) | cat -v; quickExit fi + + # Spam all the things! trace "Version ${VERSION}" if [[ "${INCOGNITO}" != "TRUE" ]]; then @@ -369,14 +371,15 @@ else cp "${deployPath}"/.deployrc ~/.deployrc console "User configuration file missing, creating ~/.deployrc" if yesno --default yes "Would you like to edit the configuration file now? [Y/n] "; then - nano ~/.deployrc - clear; sleep 1 + # nano ~/.deployrc + configure_user; sleep 1 console "Loading user configuration." # shellcheck source=/dev/null source ~/.deployrc # quickExit else info "You can change configuration later by editing ~/.deployrc" + clear_user fi fi diff --git a/etc/.deployrc b/etc/.deployrc index 0637c49..87b31a7 100755 --- a/etc/.deployrc +++ b/etc/.deployrc @@ -8,65 +8,12 @@ # settings will override those settings found here. Anything you leave # commented out or unset in this file will use the global configuration. -# Miscellaneous settings -# -# Activate "Smart Commits"; this feature tries to create automatic -# commit messages by parsing the log files generated during -# Wordpress updates. Set to exactly "TRUE" to activate. -# SMARTCOMMIT="TRUE" -# -# If you want to see statistics about your recent git activity, -# set this value to exactly "TRUE" -# GITSTATS="TRUE" +# Clear screen on startup, when not running with --quiet or --verbose switch +# CLEARSCREEN="{{CLEARSCREEN}}" -# Log settings -# -# Strips the bulk of nasty PHP debug messages out of the -# log files that are emailed upon deployment. -# NOPHP="TRUE" -# -# Set the address that should receive your log files -# TO="deploy@emrl.com" -# -# Email log subject line "- project name" is appended to this, creating -# a subject line such as "Project deployed - Project Name" -# SUBJECT="[EMRL] Deployment" -# -# When should email logs be sent? -# EMAILERROR="TRUE" -# EMAILSUCCESS="TRUE" -# EMAILQUIT="TRUE" +# Verbose output to console? +# VERBOSE="{{VERBOSE}}" -# If you want your emails to be sent "clean" (with no detailed logging) set -# SHORTEMAIL to TRUE. Full logs will still be posted to web logs. Error emails -# will continue to contain full logs. -# SHORTEMAIL="FALSE" - -# Send log file in HTML? -# EMAILHTML="FALSE" - -# Integration options -# -# Set values for where you'd like to post commit messages to -# via email. You can use something like Zapier to re-post that -# to whatever service you like, or if your project tracker allows -# for input directly via email like ours does, you post directly. -# Some of these options will definitely need to be set in the -# project's deploy.sh, not in a master configuration. -# -# Email from domain. Whatever you're integrating with may need -# a different From: address than that of the the machine you're -# actually deploying from. -# FROMDOMAIN="emrl.com" -# -# If you need to specify a user, other than your unix user name -# to be the in the From: email, do it here. Otherwise Leave blank. -# FROMUSER="" -# -# Post commit logs to this email address. This should probably -# be set per-project. For examples, for oour task management -# system, this email would be task-####@projects.emrl.com, with -# the #### being the task identification number for the project -# being deployed. -# POSTEMAILHEAD="task-" -# POSTEMAILTAIL="@projects.emrl.com" +# If you want to see statistics about your recent git activity, +# set this value to exactly "TRUE" +# GITSTATS="{{GITSTATS}}" diff --git a/etc/deploy-example.conf b/etc/deploy-example.conf index c5f8ecf..8f79cda 100755 --- a/etc/deploy-example.conf +++ b/etc/deploy-example.conf @@ -3,9 +3,12 @@ # deploy.conf # # Global configuration file for deploy -# -# -# Clear screen on startup, when not running with --quiet switch + +############################################################################### +# General Setup +############################################################################### + +# Clear screen on startup, when not running with --quiet or --verbose switch CLEARSCREEN="TRUE" # Set the work path. This is the directory where all your @@ -16,18 +19,30 @@ WORKPATH="/var/www/html" # root directory, usually ./config CONFIGDIR="config" +# Check to see all servers defined (repo host, staging, production, etc) are +# returning a 200OK response +SERVERCHECK="TRUE" + +# Running deploy as the root user can be dangerous so it is not allowed by +# default. Set to TRUE is really must do this. +# ALLOWROOT="FALSE" + +# When ACTIVECHECK="TRUE", when running as deploy --force --update --quit +# files will be scanned for changes made within the timeframe set in +# ACTIVETiME (in minutes) and if any changes are found, deployment will be halted. +ACTIVECHECK="TRUE" +CHECKTIME="10" + + +############################################################################### +# Git Configuration +############################################################################### + # The URL for your repository hosting, with no trailing slash. For example, # if you use Github and your repo URL looks like https://github.com/EMRL/deploy # your REPOHOST should be set to https://github.com/EMRL (with no trailing slash) # REPOHOST="https://bitbucket.org/teamname" -# Check to see all servers defined (repo host, staging, production, etc) are -# returning a 200OK response -SERVERCHECK="TRUE" - -# Path to wp-cli, with no trailing slash -WPCLI="/usr/local/bin" - # Activate "Smart Commits"; this feature tries to create automatic # commit messages by parsing the log files generated during # Wordpress updates. Set to "TRUE" to activate. @@ -35,11 +50,33 @@ SMARTCOMMIT="TRUE" # If you want to see statistics about your recent git # commits, set this value to exactly "TRUE" -GITSTATS="TRUE" +GITSTATS="TRUE" -# Running deploy as the root user can be dangerous so it is not allowed by -# default. Set to TRUE is really must do this. -# ALLOWROOT="FALSE" +# If dirty (yet to be committed) files exist in the repo, deploy will normally not halt +# execution when running with the --automate flag. If you prefer to have the dirty files +# stashed and proceed with updates set the below value to TRUE. Files will be unstashed +# after the deployment is complete. +# STASH="TRUE" + +# Clean and compress repo before starting up +# GARBAGE="TRUE" + +############################################################################### +# Wordpress +############################################################################### + +# Path to wp-cli, with no trailing slash +WPCLI="/usr/local/bin" + +# Wordfence check - Wordfence makes some crappy files that break certain commands +# because of permissions garbage. Setting this to TRUE will enable a check that +# stops deployment if evidence of these files is detected. +# WFCHECK="FALSE" + + +############################################################################### +# Permission Fixes +############################################################################### # Activate Permission Fix. With multi-user stuff going on, sometimes # permission problems may arise. This function will reset permissions @@ -50,28 +87,13 @@ GITSTATS="TRUE" # APACHEUSER="apache" # Apache user # APACHEGROUP="apache" # Apache group -# Wordfence check - Wordfence makes some crappy files that break certain commands -# because of permissions garbage. Setting this to TRUE will enable a check that -# stops deployment if evidence of these files is detected. -# WFCHECK="FALSE" - -# Clean and compress repo before starting up -# GARBAGE="TRUE" - # If you have issues with the fatal permissions errors on .git/index, set this to TRUE # FIXINDEX="TRUE" -# If dirty (yet to be committed) files exist in the repo, deploy will normally not halt -# execution when running with the --automate flag. If you prefer to have the dirty files -# stashed and proceed with updates set the below value to TRUE. Files will be unstashed -# after the deployment is complete. -# STASH="TRUE" -# When ACTIVECHECK="TRUE", when running as deploy --force --update --quit -# files will be scanned for changes made within the timeframe set in -# ACTIVETiME (in minutes) and if any changes are found, deployment will be halted. -ACTIVECHECK="TRUE" -CHECKTIME="10" +############################################################################### +# Email +############################################################################### # Define the path to your mail program, with no trailing slash. As of now, deploy # only works with sendmail. @@ -92,14 +114,6 @@ MAILPATH="/usr/sbin" # EMAILSUCCESS="TRUE" # EMAILQUIT="FALSE" -# Strips the bulk of nasty PHP debug messages out of the -# log files that are emailed upon deployment. -NOPHP="TRUE" - -# IF INCOGNITO is set to true, log files as well as verbose output to screen -# will be stripped of details such as email addresses and system file paths. -# INCOGNITO="TRUE" - # If you want your emails to be sent "clean" (with no detailed logging) set # SHORTEMAIL to TRUE. Full logs will still be posted to web logs. Error emails # will continue to contain full logs. @@ -113,9 +127,9 @@ NOPHP="TRUE" # /etc/deploy/html. The value used below should be the folder name # of your template. # HTMLTEMPLATE="default" - -# Integration options. -# + +# Integration Emails +# ------------------ # Set values for where you'd like to post commit messages to # via email. You can use something like Zapier to re-post that # to whatever service you like, or if your project tracker allows @@ -140,8 +154,11 @@ NOPHP="TRUE" # POSTEMAILHEAD="task-" # POSTEMAILTAIL="@projects.domain.com" -# Slack Integration -# + +############################################################################### +# Slack Notifications +############################################################################### + # You'll need to set up an "Incoming Webhook" custom integration on the Slack # side to get this ready to roll. # See https://YOURTEAMNAME.slack.com/apps/manage/custom-integrations to get @@ -158,7 +175,28 @@ NOPHP="TRUE" # Enable the settings below to post on WARNiNG and/or ERROR. # SLACKERROR="TRUE" -# Post HTML logs to remote server. This needs to be set to "TRUE" even you + +############################################################################### +# Webhooks +############################################################################### + +# Webhook POST URL +# POSTURL="" + + +############################################################################### +# Logging +############################################################################### + +# Strips the bulk of nasty PHP debug messages out of the +# log files that are emailed upon deployment. +NOPHP="TRUE" + +# IF INCOGNITO is set to true, log files as well as verbose output to screen +# will be stripped of details such as email addresses and system file paths. +# INCOGNITO="TRUE" + + # Post HTML logs to remote server. This needs to be set to "TRUE" even you # are only posting to LOCALHOST. # REMOTELOG="TRUE" @@ -190,6 +228,3 @@ NOPHP="TRUE" # Set the number of days before logs should be deleted. Currently this only # works for logs stored on localhost. # EXPIRELOGS="30" - -# Webhook POST URL -# POSTURL="" diff --git a/lib/configure.sh b/lib/configure.sh new file mode 100755 index 0000000..59a203c --- /dev/null +++ b/lib/configure.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# configure.sh +# +############################################################################### +# Global, user, and project configuration +############################################################################### +trace "Loading configuration" + +# Initialize variables +read -r active_files <<< "" +echo "${active_files}" > /dev/null + +function configure_project() { + trace "This is an empty function" +} + +function configure_user() { + empty_line + arg="CLEARSCREEN" + if yesno --default yes "Clear screen on startup? [Y/n] "; then + set_value + else + unset_value + fi + + arg="VERBOSE" + if yesno --default no "Always show verbose output on console? [y/N] "; then + set_value + else + unset_value + fi + + arg="GITSTATS" + if yesno --default yes "Display project statistics after every deployment? [Y/n] "; then + set_value + else + unset_value + fi + [[ "${VERBOSE}" == "TRUE" ]] && VERBOSE="1"; empty_line +} + +function configure_global() { + trace "This is an empty function" +} + +function set_value() { + sed -i -e "s^{{${arg}}}^TRUE^g" \ + -e "s^# ${arg}^${arg}^g" \ + ~/.deployrc +} + +function unset_value() { + sed -i -e "s^{{${arg}}}^FALSE^g" ~/.deployrc +} + +function clear_user() { +settings=(CLEARSCREEN VERBOSE GITSTATS) +empty_line +for arg in "${settings[@]}" ; do + unset_value +done +} diff --git a/lib/deployment.sh b/lib/deployment.sh index 594b607..8c2805f 100755 --- a/lib/deployment.sh +++ b/lib/deployment.sh @@ -22,7 +22,7 @@ function preDeploy() { if [[ "${STASH}" == "TRUE" ]] || [[ "${UPDATEONLY}" == "1" ]]; then # Bah, clunky empty_line trace "Stashing dirty files" - if [[ "${VERBOSE}" == "1" ]] && [[ "${QUIET}" != "1" ]]; then + if [[ "${VERBOSE}" == "TRUE" ]] && [[ "${QUIET}" != "1" ]]; then git stash | tee --append "${logFile}"; error_check else git stash >> "${logFile}"; error_check @@ -75,7 +75,7 @@ function pkgDeploy() { # Test deployment command before running deploy_check # Deploy via deployment command specified in configuration - if [[ "${VERBOSE}" == "1" ]] && [[ "${INCOGNITO}" != "TRUE" ]]; then + if [[ "${VERBOSE}" == "TRUE" ]] && [[ "${INCOGNITO}" != "TRUE" ]]; then eval "${DEPLOY}" | tee --append "${logFile}" error_check else diff --git a/lib/git.sh b/lib/git.sh index a458120..e59a4e1 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -57,7 +57,7 @@ function gitChkMstr() { current_branch="$(git rev-parse --abbrev-ref HEAD)" if [[ "${current_branch}" != "${MASTER}" ]]; then notice "Checking out master branch..."; fix_index - if [[ "${VERBOSE}" -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then git checkout master | tee --append "${logFile}" else if [[ "${QUIET}" != "1" ]]; then @@ -105,7 +105,7 @@ function gitStage() { empty_line if [[ "${FORCE}" = "1" ]] || yesno --default yes "Stage files? [Y/n] "; then trace "Staging files" - if [[ "${VERBOSE}" -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then git add -A | tee --append "${logFile}"; error_check else git add -A &>> "${logFile}"; error_check @@ -196,7 +196,7 @@ function gitPushMstr() { if [[ -n "${MASTER}" ]]; then trace "Pushing ${MASTER}"; fix_index empty_line - if [[ "${VERBOSE}" -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then git push | tee --append "${logFile}"; error_check else if [[ "${FORCE}" = "1" ]] || yesno --default yes "Push ${MASTER} branch? [Y/n] "; then @@ -224,7 +224,7 @@ function gitChkProd() { if [[ -n "${PRODUCTION}" ]]; then notice "Checking out ${PRODUCTION} branch..."; fix_index - if [[ "${VERBOSE}" -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then git checkout "${PRODUCTION}" | tee --append "${logFile}"; error_check else if [[ "${QUIET}" != "1" ]]; then @@ -271,7 +271,7 @@ function gitMerge() { [[ -f "${gitLock}" ]] && rm "${gitLock}" # Bonus add, just because. Ugh. # git add -A; error_check - if [[ "${VERBOSE}" -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then git merge "${MASTER}" | tee --append "${logFile}" else if [[ "${QUIET}" != "1" ]]; then @@ -292,7 +292,7 @@ function gitPushProd() { if [[ -n "${PRODUCTION}" ]]; then trace "Push ${PRODUCTION}"; fix_index empty_line - if [[ "${VERBOSE}" -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then git push | tee --append "${logFile}"; error_check trace "OK" else diff --git a/lib/loader.sh b/lib/loader.sh index d0c8e49..2dacf16 100755 --- a/lib/loader.sh +++ b/lib/loader.sh @@ -7,19 +7,19 @@ ############################################################################### # Save current contents of the terminal -if [ "${QUIET}" != "1" ]; then - if [ "${CLEARSCREEN}" == "TRUE" ]; then +if [[ "${CLEARSCREEN}" == "TRUE" ]]; then + if [[ "${QUIET}" != "1" ]] && [[ "${VERBOSE}" != "TRUE" ]]; then tput smcup; clear fi -else - if [[ "${FORCE}" != "1" ]] && [[ "${QUIET}" = "1" ]]; then - echo "To deploy using the --quiet flag, you must also use --force."; exit 1 - fi +fi + +if [[ "${FORCE}" != "1" ]] && [[ "${QUIET}" = "1" ]]; then + echo "To deploy using the --quiet flag, you must also use --force."; exit 1 fi # Creating this function first, so verbose output option is usable early function trace() { - if [[ "${VERBOSE}" == "1" ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then TIMESTAMP="$(date '+%H:%M:%S')" echo -e "$(tput setaf 3)${TIMESTAMP}$(tput sgr0) $*" echo "${TIMESTAMP} $*" >> "${logFile}" diff --git a/lib/package-manager.sh b/lib/package-manager.sh index a5c7c62..6ca656b 100755 --- a/lib/package-manager.sh +++ b/lib/package-manager.sh @@ -18,7 +18,7 @@ function pkgMgr() { if [[ "${FORCE}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then cd "${WORKPATH}"/"${APP}" || errorCheck - if [[ "${VERBOSE}" -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then /usr/local/bin/grunt build --force 2>&1 | tee --append "${trshFile}" else /usr/local/bin/grunt build --force &>> "${trshFile}" & @@ -39,7 +39,7 @@ function pkgMgr() { if [[ "${FORCE}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then cd "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}" || errorCheck - if [[ $VERBOSE -eq 1 ]]; then + if [[ "${VERBOSE}" == "TRUE" ]]; then npm run build | tee --append "${trshFile}" else npm run build &>> "${trshFile}" & From a5a98b5639b83e60e6ad964df510c6502fde4da0 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 2 Mar 2018 12:53:46 -0800 Subject: [PATCH 038/334] Improved statistic reports, resolves #116 --- CHANGELOG.md | 2 + deploy.sh | 2 +- etc/html/default/stats/_analytics.html | 224 ------------------------- etc/html/default/stats/index.html | 144 ++++++++++++++++ lib/analytics.sh | 16 +- lib/configure.sh | 22 ++- lib/process-html.sh | 11 +- lib/statistics.sh | 3 + lib/utilities.sh | 6 +- 9 files changed, 191 insertions(+), 239 deletions(-) delete mode 100755 etc/html/default/stats/_analytics.html diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d3ca5..e7cd43d 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Statistic reports now display server uptime, latency, and a few handy Google analytics stats ### Changed - Configuration files restructured for better readability - Many function and variable names changed for better consistency diff --git a/deploy.sh b/deploy.sh index 99eb209..632461a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.7-alpha.3" +VERSION="3.6.7-beta.1" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" diff --git a/etc/html/default/stats/_analytics.html b/etc/html/default/stats/_analytics.html deleted file mode 100755 index fac167b..0000000 --- a/etc/html/default/stats/_analytics.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
Invoice Ref. #Ref. #
- +
{{PROJCLIENT}}
diff --git a/etc/html/default/report/header.html b/etc/html/default/report/header.html index e6e0b1f..52cb5cf 100755 --- a/etc/html/default/report/header.html +++ b/etc/html/default/report/header.html @@ -18,7 +18,7 @@
- {{LASTMONTH}} maintenance report for + {{LAST_MONTH}} maintenance report for

{{PROJCLIENT}}
@@ -26,7 +26,7 @@

{{PROJCLIENT}}

- This report for the month of {{LASTMONTH}} includes notes on system updates, security patches, and other code changes on {{PRODURL}} + This report for the month of {{LAST_MONTH}} includes notes on system updates, security patches, and other code changes on {{PRODURL}}

diff --git a/install/doinst.sh b/install/doinst.sh index 1c56d9b..dca64b2 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -59,7 +59,7 @@ function check_os() { } function check_program() { - printf "%-40s" "Checking for ${1}..." + printf "%-40s" "Checking for ${1}..."; read -p "" -t 0.03 # There's potential for non-global stuff to fail this check so here's a # fairly kludgey way to hopefully allow those to pass if [[ "${1}" =~ ^(wp|grunt|npm)$ ]]; then @@ -79,7 +79,7 @@ function error_check() { echo; check_os if [[ -n "${OS}" ]] && [[ -n "${VER}" ]]; then - echo "=> Checking operating system: "; sleep 1 + echo "=> Checking operating system: "; sleep .3 echo "${OS} ${VER}"; sleep 1 else # No values, crash out for now @@ -87,10 +87,10 @@ else fi # Declare dependencies -dependencies=(awk cat curl echo eval git grep pkill printf read sed sendmail sleep tput wget) +dependencies=(awk cat curl echo eval git grep pkill printf read sed sendmail sleep tput) # Declare optional stuff -options=(gitchart grunt npm scp ssh sshpass wp) +options=(gitchart grunt npm scp ssh sshpass wget wp) message='' fg_red="$(tput setaf 1)" @@ -102,7 +102,7 @@ YES="${fg_green}OK${reset}" NO="${fg_red}NO${reset}" NOT="${fg_red}NOT FOUND${reset}" -echo; echo "=> Checking for required dependencies:"; sleep 1 +echo; echo "=> Checking for required dependencies:" for i in "${dependencies[@]}" ; do check_program "${i}" done @@ -116,7 +116,7 @@ else exit 1 fi -echo; echo "=> Checking optional dependencies:"; sleep 1 +echo; echo "=> Checking optional dependencies:" for k in "${options[@]}" ; do check_program "${k}" done diff --git a/lib/analytics.sh b/lib/analytics.sh index c0993a6..0645af7 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -97,7 +97,7 @@ function ga_fail() { } function ga_test() { - emptyLine + empty_line if [[ -z "${CLIENTID}" ]] || [[ -z "${CLIENTSECRET}" ]]; then warning "Define API project" console "Analytics API project not defined. Check https://console.developers.google.com/" @@ -108,7 +108,7 @@ function ga_test() { fi if [[ -z "${AUTHORIZATIONCODE}" ]]; then - emptyLine; warning "Authorization required" + empty_line; warning "Authorization required" console "Point your browser to this link: https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fanalytics&redirect_uri=${REDIRECTURI}&response_type=code&client_id=${CLIENTID}" quickExit else @@ -116,7 +116,7 @@ function ga_test() { fi if [[ -z "${ACCESSTOKEN}" ]] || [[ -z "${REFRESHTOKEN}" ]]; then - emptyLine; warning "Create an access token" + empty_line; warning "Create an access token" console "Run this command: curl -H \"Content-Type: application/x-www-form-urlencoded\" -d code=${AUTHORIZATIONCODE} -d client_id=${CLIENTID} -d client_secret=${CLIENTSECRET} -d redirect_uri=${REDIRECTURI} -d grant_type=authorization_code https://accounts.google.com/o/oauth2/token" quickExit else @@ -125,7 +125,7 @@ function ga_test() { fi if [[ -z "${PROFILEID}" ]]; then - emptyLine; warning "Missing Profile ID" + empty_line; warning "Missing Profile ID" console "Your project's profile ID is not defined." quickExit else @@ -158,12 +158,12 @@ function ga_test() { notice "Retrieving ${METRIC}..." console "Running: printf \"${METRIC} (Last 7 days): \"; curl -s \"https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:$METRIC&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN\"" # | tr , '\n' | grep \"totalsForAllResults\" | cut -d'\"' -f6" - #emptyLine; analytics + #empty_line; analytics sleep 3 printf "${METRIC} (Last 7 days): "; curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:$METRIC&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6 - emptyLine + empty_line console "Verbose output" console "--------------" curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:$METRIC&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" - emptyLine + empty_line } diff --git a/lib/core.sh b/lib/core.sh index 6f9cd9e..35c9776 100755 --- a/lib/core.sh +++ b/lib/core.sh @@ -17,9 +17,9 @@ function core() { elif [[ "${REPORT}" == "1" ]]; then create_report else - srvCheck # Check that servers are up and running + server_check # Check that servers are up and running if [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then - sshChk # Check keys + ssh_check # Check keys fi permFix # Fix permissions if [[ "${PUBLISH}" == "1" ]]; then diff --git a/lib/deployment.sh b/lib/deployment.sh index 27a2c45..594b607 100755 --- a/lib/deployment.sh +++ b/lib/deployment.sh @@ -20,7 +20,7 @@ function preDeploy() { trace "Checking for files that need stashing" # Stash the dirty bits if [[ "${STASH}" == "TRUE" ]] || [[ "${UPDATEONLY}" == "1" ]]; then # Bah, clunky - emptyLine + empty_line trace "Stashing dirty files" if [[ "${VERBOSE}" == "1" ]] && [[ "${QUIET}" != "1" ]]; then git stash | tee --append "${logFile}"; error_check @@ -29,12 +29,12 @@ function preDeploy() { fi currentStash="1" else - emptyLine + empty_line error "There are previously undeployed changes in this project, deployment can not continue." fi else - emptyLine + empty_line warning "There are previously undeployed changes in this project." if yesno --default no "View unresolved files? [y/N] "; then @@ -52,7 +52,7 @@ function preDeploy() { } function pkgDeploy() { - emptyLine + empty_line if [[ -n "${DEPLOY}" ]]; then # Add ssh keys and double check directoy cd "${WORKPATH}/${APP}" || error_check diff --git a/lib/display-styles.sh b/lib/display-styles.sh index 2553b7c..26695e6 100755 --- a/lib/display-styles.sh +++ b/lib/display-styles.sh @@ -81,7 +81,7 @@ function warning() { echo "WARNING: $*" >> "${logFile}" } -function emptyLine() { +function empty_line() { if [[ "${QUIET}" != "1" ]]; then echo "" echo "" >> "${logFile}" diff --git a/lib/error-check.sh b/lib/error-check.sh index 7dc10d3..9fc7aad 100755 --- a/lib/error-check.sh +++ b/lib/error-check.sh @@ -17,6 +17,18 @@ function error_check() { fi } +# I'm not sure why this is here, figure it out! +# Try to get exit/error code, with a hard stop on fail +function error_status() { + EXITCODE=$?; + if [[ "${EXITCODE}" != 0 ]]; then + error_msg="WARNING: Error code ${EXITCODE}" + trace "${error_msg}" + else + trace "OK" + fi +} + function deploy_check() { if [[ "${DEPLOY}" == *"mina"* ]]; then # && [[ "${DEPLOY}" != *"bundle"* ]]; then DEPLOYTEST="mina --simulate deploy" @@ -107,14 +119,3 @@ function deploy_check() { trace "OK" fi } - -# Try to get exit/error code, with a hard stop on fail -function error_status() { - EXITCODE=$?; - if [[ "${EXITCODE}" != 0 ]]; then - error_msg="WARNING: Error code ${EXITCODE}" - trace "${error_msg}" - else - trace "OK" - fi -} diff --git a/lib/git.sh b/lib/git.sh index c67b1fb..b35b61b 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -52,7 +52,7 @@ function gitStart() { # Checkout master function gitChkMstr() { if [[ -z "${MASTER}" ]]; then - emptyLine; error "deploy ${VERSION} requires a master branch to be defined."; + empty_line; error "deploy ${VERSION} requires a master branch to be defined."; else current_branch="$(git rev-parse --abbrev-ref HEAD)" if [[ "${current_branch}" != "${MASTER}" ]]; then @@ -102,7 +102,7 @@ function gitStage() { if [[ -z $(git status --porcelain) ]]; then console "Nothing to commit, working directory clean."; quietExit else - emptyLine + empty_line if [[ "${FORCE}" = "1" ]] || yesno --default yes "Stage files? [Y/n] "; then trace "Staging files" if [[ "${VERBOSE}" -eq 1 ]]; then @@ -119,7 +119,7 @@ function gitStage() { # Commit, with message function gitCommit() { # Smart commit stuff - smrtCommit; emptyLine + smrtCommit; empty_line # Do a dry run; check for anything to commit git commit --dry-run &>> "${logFile}" @@ -194,7 +194,7 @@ function gitCommit() { function gitPushMstr() { if [[ -n "${MASTER}" ]]; then trace "Pushing ${MASTER}"; fix_index - emptyLine + empty_line if [[ "${VERBOSE}" -eq 1 ]]; then git push | tee --append "${logFile}"; error_check else @@ -290,7 +290,7 @@ function gitPushProd() { if [[ "${MERGE}" = "1" ]]; then if [[ -n "${PRODUCTION}" ]]; then trace "Push ${PRODUCTION}"; fix_index - emptyLine + empty_line if [[ "${VERBOSE}" -eq 1 ]]; then git push | tee --append "${logFile}"; error_check trace "OK" diff --git a/lib/log-handling.sh b/lib/log-handling.sh index fccd0d6..9439f78 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -75,7 +75,7 @@ function makeLog() { # IF we're using HTML emails, let's get to work if [[ "${EMAILHTML}" == "TRUE" ]]; then - [[ "${message_state}" != "DIGEST" ]] && htmlBuild + [[ "${message_state}" != "DIGEST" ]] && build_html cat "${htmlFile}" > "${trshFile}" # If this is an approval email, strip out PHP @@ -102,7 +102,7 @@ function makeLog() { VIEWPORTPRE=$(expr ${VIEWPORT} - 80) # Build the html email and details pages - # htmlBuild + # build_html # Strip out the buttons that self-link sed -e "s^// BUTTON: BEGIN //-->^BUTTON HIDE^g" -i "${htmlFile}" @@ -110,7 +110,7 @@ function makeLog() { fi } -function htmlBuild() { +function build_html() { # Build out the HTML LOGSUFFIX="html" if [[ "${message_state}" == "ERROR" ]]; then diff --git a/lib/mail-log.sh b/lib/mail-log.sh index f7c90b1..295022d 100755 --- a/lib/mail-log.sh +++ b/lib/mail-log.sh @@ -63,7 +63,7 @@ function mailLog() { function email_test() { console "Testing email..." if [[ -z "${TO}" ]]; then - warning "No recipient address found."; emptyLine + warning "No recipient address found."; empty_line clean_up; exit 1 else # Send HTML mail diff --git a/lib/process-html.sh b/lib/process-html.sh index d90c4b5..e75eaf2 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -8,9 +8,9 @@ trace "Loading html handling" # Initialize variables -read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID <<< "" +read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID COVER <<< "" echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} - ${SMOOCHID}" + ${SMOOCHID} ${COVER}" function process_html() { # Clean out the stuff we don't need @@ -62,9 +62,9 @@ function process_html() { -e "s^{{REMOTEURL}}^${REMOTEURL}^g" \ -e "s^{{ANALYTICSMSG}}^${ANALYTICSMSG}^g" \ -e "s^{{STATURL}}^${REMOTEURL}\/${APP}\/stats^g" \ - -e "s^{{DIGESTCOVER}}^${DIGESTCOVER}^g" \ + -e "s^{{COVER}}^${COVER}^g" \ -e "s^{{WEEKOF}}^${WEEKOF}^g" \ - -e "s^{{LASTMONTH}}^${LASTMONTH}^g" \ + -e "s^{{LASTMONTH}}^${LAST_MONTH}^g" \ -e "s^{{UPTIME}}^${UPTIME}^g" \ -e "s^{{LATENCY}}^${LATENCY}^g" \ "${htmlFile}" diff --git a/lib/release-check.sh b/lib/release-check.sh index dee44a7..8a0efc7 100755 --- a/lib/release-check.sh +++ b/lib/release-check.sh @@ -27,7 +27,7 @@ function release_check() { # User feedback info "\r\nNew release found: ${RELEASE}" - printf "${RELEASENOTES}" | fold --spaces -w 78; emptyLine + printf "${RELEASENOTES}" | fold --spaces -w 78; empty_line # Update? if yesno --default yes "Would you like to download now? [Y/n] "; then # Get latest diff --git a/lib/report.sh b/lib/report.sh index f230e55..2f10ea1 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -33,7 +33,7 @@ function create_report() { # If INCLUDEHOSTING is equal to something other than TRUE (And not FALSE), # its value will be used as the text string in the report if [[ "${INCLUDEHOSTING}" == "TRUE" ]]; then - INCLUDEHOSTING="Web hosting for the month of ${LASTMONTH} ${PRVYR}" + INCLUDEHOSTING="Web hosting for the month of ${LAST_MONTH} ${PRVYR}" fi echo "

${TASK}X
${INCLUDEHOSTING}
- @@ -26,7 +26,7 @@

{{PROJCLIENT}}

- This report for the month of {{LAST_MONTH}} includes notes on system updates, security patches, and other code changes on {{PRODURL}} + This report for the month of {{LASTMONTH}} includes notes on system updates, security patches, and other code changes on {{PRODURL}}

diff --git a/install/doinst.sh b/install/doinst.sh index dca64b2..398d3c5 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -87,7 +87,7 @@ else fi # Declare dependencies -dependencies=(awk cat curl echo eval git grep pkill printf read sed sendmail sleep tput) +dependencies=(awk cal cat curl echo eval git grep pkill printf read sed sendmail sleep tput) # Declare optional stuff options=(gitchart grunt npm scp ssh sshpass wget wp) diff --git a/lib/process-html.sh b/lib/process-html.sh index e75eaf2..dcdf5e7 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -10,7 +10,7 @@ trace "Loading html handling" # Initialize variables read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID COVER <<< "" echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} - ${SMOOCHID} ${COVER}" + ${SMOOCHID} ${COVER}" > /dev/null function process_html() { # Clean out the stuff we don't need diff --git a/lib/report.sh b/lib/report.sh index 2f10ea1..0896cd1 100755 --- a/lib/report.sh +++ b/lib/report.sh @@ -7,6 +7,10 @@ ############################################################################### trace "Loading report handling" +# Initialize variables +read -r CURMTH CURYR PRVMTH PRVYR LASTDY <<< "" +echo "${CURMTH} ${CURYR} ${PRVMTH} ${PRVYR} ${LASTDY}" > /dev/null + function create_report() { message_state="REPORT" htmlDir @@ -18,6 +22,9 @@ function create_report() { if [[ "${CURMTH}" -eq 1 ]]; then PRVMTH="11" PRVYR=`expr "${CURYR}" - 1` + elif [[ "${CURMTH}" -eq 2 ]]; then + PRVMTH="12" + PRVYR=`expr "${CURYR}" - 1` else PRVMTH=`expr "${CURMTH}" - 2` PRVYR="${CURYR}" fi @@ -33,7 +40,7 @@ function create_report() { # If INCLUDEHOSTING is equal to something other than TRUE (And not FALSE), # its value will be used as the text string in the report if [[ "${INCLUDEHOSTING}" == "TRUE" ]]; then - INCLUDEHOSTING="Web hosting for the month of ${LAST_MONTH} ${PRVYR}" + INCLUDEHOSTING="Web hosting for the month of ${LAST_MONTH}" fi echo "

${TASK}X
${INCLUDEHOSTING}
- - - -
-
- - - - - - - - - - - - - - - - - -
-

Project Statistics BETA

- - -

Report generated June 27, 2017

-

CAA Website - (https://caanet.org/) -

- -
California Apartment Association
-
- - - - - - - -

3,456Hits[0]

- -

34%New sessions[1] 3:23Avg. time[3] - -145Visitors from search[2] - -12Social interactions[4] - - -

-
- - - - - - - -
- - -

Commits by day of week

-

- -

Commits by hour of week

-

- -

Commits by hour of day

-

- -

Commits by month of year

-

- -

Commits by year

-

- -

Commits by year/month

-

-

- -

- - -

- -

Any Questions?

-

If you have any questions, feel free to ask us anything, using the live chat function on this page, or email. Thank you!

- - - - - - - -

-
- - - - - - -
-
- - - diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index 24f0f39..e270cb6 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -68,6 +68,150 @@

+

Weekly overview

Uptime: {{UPTIME}}%
+
Latency: {{LATENCY}}s
+
+ + + + + + + +
+ + + + + + + +

Commits by day of week

diff --git a/lib/analytics.sh b/lib/analytics.sh index 0645af7..c398d2c 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -8,8 +8,8 @@ trace "Loading analytics functions" # Initialize variables -read -r SIZE RND METRIC RESULT <<< "" -echo "${SIZE} ${RND} ${METRIC} ${RESULT}" > /dev/null +read -r SIZE RND METRIC RESULT GA_HITS GA_PERCENT GA_SEARCHES GA_DURATION GA_SOCIAL <<< "" +echo "${SIZE} ${RND} ${METRIC} ${RESULT} ${GA_HITS} ${GA_PERCENT} ${GA_SEARCHES} ${GA_DURATION} ${GA_SOCIAL}" > /dev/null function ga_metrics() { array[0]="hits" @@ -87,6 +87,18 @@ function analytics() { function ga_data() { RESULT=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:$METRIC&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) SIZE="$(printf "%.0f\n" "${RESULT}")" + + if [[ "${PROJSTATS}" == "1" ]]; then + GA_HITS=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:hits&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) + GA_PERCENT=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:percentNewSessions&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) + GA_SEARCHES=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:organicSearches&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) + GA_DURATION=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:avgSessionDuration&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) + GA_SOCIAL=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:socialInteractions&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) + + # Make sure we're only dealing with integers + GA_PERCENT="$(printf "%.0f\n" "${GA_PERCENT}")" + GA_DURATION="$(printf "%.0f\n" "${GA_DURATION}")" + fi } # If no other results are worht displaying, fall back to displaying hits diff --git a/lib/configure.sh b/lib/configure.sh index 59a203c..6c99976 100755 --- a/lib/configure.sh +++ b/lib/configure.sh @@ -8,32 +8,37 @@ trace "Loading configuration" # Initialize variables -read -r active_files <<< "" -echo "${active_files}" > /dev/null +read -r value <<< "" +echo "${value}" > /dev/null function configure_project() { - trace "This is an empty function" + empty_line + arg="${PROJNAME}"; read -rp "Project name:" -e -i "${arg}" value; set_value "${value}" + arg="${PROJCLIENT}"; read -rp "Client name:" -e -i "${arg}" value; set_value "${value}" + arg="${DEVURL}"; read -rp "Development URL (including http:// or https://)" -e -i "${arg}" value; set_value "${value}" + arg="${PRODURL}"; read -rp "Production URL (including http:// or https://)" -e -i "${arg}" value; set_value "${value}" } function configure_user() { empty_line + target_file="/home/fdiebel/.deployrc" arg="CLEARSCREEN" if yesno --default yes "Clear screen on startup? [Y/n] "; then - set_value + set_value TRUE else unset_value fi arg="VERBOSE" if yesno --default no "Always show verbose output on console? [y/N] "; then - set_value + set_value TRUE else unset_value fi arg="GITSTATS" if yesno --default yes "Display project statistics after every deployment? [Y/n] "; then - set_value + set_value TRUE else unset_value fi @@ -45,8 +50,9 @@ function configure_global() { } function set_value() { - sed -i -e "s^{{${arg}}}^TRUE^g" \ - -e "s^# ${arg}^${arg}^g" \ + value="$1" + sed -i -e "s^{{${arg}}}^${value}^g" \ + -e "s^# ${arg}^${arg}^g" ~/.deployrc } diff --git a/lib/process-html.sh b/lib/process-html.sh index dcdf5e7..8dd5d47 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -22,9 +22,13 @@ function process_html() { [[ -z "${CLIENTLOGO}" ]] && sed -i '/CLIENTLOGO/d' "${htmlFile}" [[ -z "${CLIENTCONTACT}" ]] && sed -i '/CLIENTCONTACT/d' "${htmlFile}" [[ -z "${notes}" ]] && sed -i '/NOTES/d' "${htmlFile}" - [[ -z "${RESULT}" ]] || [[ "${RESULT}" == "0" ]] || [[ "${SIZE}" == "0" ]] && sed -i '/ANALYTICS/d' "${htmlFile}" [[ -z "${SMOOCHID}" ]] && sed -i '/SMOOCHID/d' "${htmlFile}" + if [[ -z "${RESULT}" ]] || [[ "${RESULT}" == "0" ]] || [[ "${SIZE}" == "0" ]]; then + sed -i '/BEGIN ANALYTICS/,/END ANALYTICS/d' "${htmlFile}" + sed -i '/ANALYTICS/d' "${htmlFile}" + fi + # Get to work sed -i -e "s^{{VIEWPORT}}^${VIEWPORT}^g" \ -e "s^{{NOW}}^${NOW}^g" \ @@ -67,5 +71,10 @@ function process_html() { -e "s^{{LASTMONTH}}^${LAST_MONTH}^g" \ -e "s^{{UPTIME}}^${UPTIME}^g" \ -e "s^{{LATENCY}}^${LATENCY}^g" \ + -e "s^{{GA_HITS}}^${GA_HITS}^g" \ + -e "s^{{GA_PERCENT}}^${GA_PERCENT}^g" \ + -e "s^{{GA_SEARCHES}}^${GA_SEARCHES}^g" \ + -e "s^{{GA_DURATION}}^${GA_DURATION}^g" \ + -e "s^{{GA_SOCIAL}}^${GA_SOCIAL}^g" \ "${htmlFile}" } diff --git a/lib/statistics.sh b/lib/statistics.sh index 23454fa..df233e0 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -22,6 +22,9 @@ function projStats() { notice "Generating files..." + # Attempt to get analytics + analytics + # Process the HTML cat "${deployPath}/html/${HTMLTEMPLATE}/stats/index.html" > "${htmlFile}" process_html diff --git a/lib/utilities.sh b/lib/utilities.sh index 2ae6732..dd2ad07 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -23,6 +23,9 @@ function go() { console "Current working path is ${WORKPATH}/${APP}" fi + # Weird spot for this I know + server_monitor + # Slack test if [[ "${SLACKTEST}" == "1" ]]; then slackTest; quickExit @@ -106,9 +109,6 @@ function go() { if [[ "${REQUIREAPPROVAL}" == "TRUE" ]] && [[ -f "${WORKPATH}/${APP}/.queued" ]] && [[ -f "${WORKPATH}/${APP}/.approved" ]]; then notice "Processing outstanding approval..." fi - - # Weird spot for this I know - server_monitor } function fix_index() { From 43d8bb80a1ee6e18ece9d5d2a766e645eb569881 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sat, 3 Mar 2018 13:21:24 -0800 Subject: [PATCH 039/334] Development is now staging, resolve #126 --- deploy.sh | 2 +- etc/deploy.sh | 2 +- etc/html/default/stats/index.html | 2 +- install/doinst.sh | 2 +- lib/configure.sh | 2 +- lib/server-check.sh | 8 ++++---- lib/statistics.sh | 2 +- lib/utilities.sh | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/deploy.sh b/deploy.sh index 632461a..db62afc 100755 --- a/deploy.sh +++ b/deploy.sh @@ -462,7 +462,7 @@ fi # General info if [[ "${INCOGNITO}" != "TRUE" ]]; then trace "Log file is ${logFile}" - trace "Development workpath is ${WORKPATH}" + trace "Project workpath is ${WORKPATH}" # Are we planning on "fixing" permissions? if [[ "${FIXPERMISSIONS}" == "TRUE" ]]; then trace "Lead developer permissions are ${DEVUSER}.${DEVGROUP}" diff --git a/etc/deploy.sh b/etc/deploy.sh index 6dab455..1773ca7 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -16,7 +16,7 @@ # A human readable client name # PROJCLIENT="Client Name" -# Development project URL, including http:// or https:// +# Staging URL, including http:// or https:// # DEVURL="http://devurl.com" # Production, or "Live" project URL, including http:// or https:// diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index e270cb6..e89adb9 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -60,7 +60,7 @@

Project Statistics BETA

-

Report generated {{NOW}}

+

Weekly report generated {{NOW}}

{{PROJNAME}} ({{PRODURL}})

diff --git a/install/doinst.sh b/install/doinst.sh index 398d3c5..f941615 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -90,7 +90,7 @@ fi dependencies=(awk cal cat curl echo eval git grep pkill printf read sed sendmail sleep tput) # Declare optional stuff -options=(gitchart grunt npm scp ssh sshpass wget wp) +options=(gitchart grunt npm scp ssh sshpass wget wkhtmltopdf wp) message='' fg_red="$(tput setaf 1)" diff --git a/lib/configure.sh b/lib/configure.sh index 6c99976..07adb89 100755 --- a/lib/configure.sh +++ b/lib/configure.sh @@ -15,7 +15,7 @@ function configure_project() { empty_line arg="${PROJNAME}"; read -rp "Project name:" -e -i "${arg}" value; set_value "${value}" arg="${PROJCLIENT}"; read -rp "Client name:" -e -i "${arg}" value; set_value "${value}" - arg="${DEVURL}"; read -rp "Development URL (including http:// or https://)" -e -i "${arg}" value; set_value "${value}" + arg="${DEVURL}"; read -rp "Staging URL (including http:// or https://)" -e -i "${arg}" value; set_value "${value}" arg="${PRODURL}"; read -rp "Production URL (including http:// or https://)" -e -i "${arg}" value; set_value "${value}" } diff --git a/lib/server-check.sh b/lib/server-check.sh index 7f3e991..0a1d83a 100755 --- a/lib/server-check.sh +++ b/lib/server-check.sh @@ -27,14 +27,14 @@ function server_check() { fi if [[ -z "${DEVURL}" ]]; then - trace "No development URL set, skipping check" + trace "No staging URL set, skipping check" else # Should return "200 OK" if all is working well if curl -sL --head "${DEVURL}" | grep "200 OK" > /dev/null; then - console " ${DEVURL} (development) ${tan}OK${endColor}"; + console " ${DEVURL} (staging) ${tan}OK${endColor}"; else - console " ${DEVURL} (development) ${red}FAIL${endColor}" - trace " ${DEVURL} (development) FAIL"; SERVERFAIL="1" + console " ${DEVURL} (staging) ${red}FAIL${endColor}" + trace " ${DEVURL} (staging) FAIL"; SERVERFAIL="1" fi fi diff --git a/lib/statistics.sh b/lib/statistics.sh index df233e0..e839025 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -7,7 +7,7 @@ ############################################################################### trace "Loading statistics functions" -function projStats() { +function project_stats() { hash gitchart 2>/dev/null || { error "gitchart not installed." } diff --git a/lib/utilities.sh b/lib/utilities.sh index dd2ad07..d844217 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -64,7 +64,7 @@ function go() { # Generate git stats if [[ "${PROJSTATS}" == "1" ]]; then - projStats; quickExit + project_stats; quickExit fi # Chill and wait for user to confirm project From 652820c88aba1c7afbcc46057098dfe8061e5bea Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 4 Mar 2018 13:31:22 -0800 Subject: [PATCH 040/334] Fix for user config creation, resolve #127 --- deploy.sh | 28 ++++++++++---------- etc/deploy.sh | 2 ++ lib/configure.sh | 5 ++-- lib/env-check.sh | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/main.sh | 1 + 5 files changed, 87 insertions(+), 16 deletions(-) create mode 100755 lib/env-check.sh diff --git a/deploy.sh b/deploy.sh index db62afc..ee32015 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.7-beta.1" +VERSION="3.6.7-beta.2" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" @@ -57,7 +57,8 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ SSHCMD LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME \ TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ REFRESHTOKEN PROFILEID ALLOWROOT SHORTEMAIL INCOGNITO \ - REPORTURL CLIENTCONTACT INCLUDEHOSTING <<< "" + REPORTURL CLIENTCONTACT INCLUDEHOSTING GLOBAL_VERSION USER_VERSION \ + PROJECT_VERSION <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} ${DEVGROUP} ${APACHEUSER} ${APACHEGROUP} ${TO} ${FROM} ${SUBJECT} ${EMAILERROR} @@ -73,7 +74,7 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} - ${INCLUDEHOSTING}" > /dev/null + ${INCLUDEHOSTING} ${GLOBAL_VERSION} ${USER_VERSION} ${PROJECT_VERSION}" > /dev/null # Internal variables read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \ urlFile htmlFile htmlSendmail htmlEmail clientEmail textSendmail deployPath \ @@ -388,33 +389,32 @@ if [[ -f "${WORKPATH}/${APP}/${CONFIGDIR}/deploy.sh" ]]; then if [[ "${INCOGNITO}" != "TRUE" ]]; then trace "Loading project configuration from ${WORKPATH}/${APP}/${CONFIGDIR}/deploy.sh" fi - # shellcheck disable=1090 - source "${WORKPATH}/${APP}/${CONFIGDIR}/deploy.sh"; APPRC="1" + project_config="${WORKPATH}/${APP}/${CONFIGDIR}/deploy.sh" # Fallback to configuration file in the root directory if it exists elif [[ -f "${WORKPATH}/${APP}/.deploy.sh" ]]; then if [[ "${INCOGNITO}" != "TRUE" ]]; then trace "Loading project configuration from ${WORKPATH}/${APP}/.deploy.sh" fi - # shellcheck disable=1090 - source "${WORKPATH}/${APP}/.deploy.sh"; APPRC="1" - # Zero out the configdir variable + project_config="${WORKPATH}/${APP}/.deploy.sh" + # Zero out the global configdir variable CONFIGDIR="" elif [[ -f "${WORKPATH}/${APP}/deploy.sh" ]]; then if [[ "${INCOGNITO}" != "TRUE" ]]; then trace "Loading project configuration from ${WORKPATH}/${APP}/deploy.sh" fi # shellcheck disable=1090 - source "${WORKPATH}/${APP}/deploy.sh"; APPRC="1" - # Zero out the configdir variable + project_config="${WORKPATH}/${APP}/deploy.sh" + # Zero out the global configdir variable CONFIGDIR="" else trace "No project configuration file found" fi -# Does a project configuration exist? -#if [[ "${APPRC}" != "1" ]]; then -# trace "No project configuration file found" -#fi +if [[ -n "${project_config}" ]]; then + # shellcheck disable=1090 + source "${project_config}" + APPRC="1" +fi # Make sure variables are set up correctly for var in "${REPOHOST} ${CLIENTLOGO}" "${DEVURL}" "${PRODURL}"; do diff --git a/etc/deploy.sh b/etc/deploy.sh index 1773ca7..2bdfbc3 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash # +# # Here are examples of settings you might need to change on a per-project # basis. This file should be placed in either the project's root folder, # or in /config/. Settings configured in this file will override both # system & user settings. +PROJECT_VERSION="3.6.7" ############################################################################### diff --git a/lib/configure.sh b/lib/configure.sh index 07adb89..593ef93 100755 --- a/lib/configure.sh +++ b/lib/configure.sh @@ -21,7 +21,8 @@ function configure_project() { function configure_user() { empty_line - target_file="/home/fdiebel/.deployrc" + #target_file="/tmp/.deployrc" + #cp "${deployPath}"/.deployrc "${target_file}" arg="CLEARSCREEN" if yesno --default yes "Clear screen on startup? [Y/n] "; then set_value TRUE @@ -52,7 +53,7 @@ function configure_global() { function set_value() { value="$1" sed -i -e "s^{{${arg}}}^${value}^g" \ - -e "s^# ${arg}^${arg}^g" + -e "s^# ${arg}^${arg}^g" \ ~/.deployrc } diff --git a/lib/env-check.sh b/lib/env-check.sh new file mode 100755 index 0000000..e5aee28 --- /dev/null +++ b/lib/env-check.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# config-check.sh +# +############################################################################### +# Check to see if configuration files needs to be updated, and update if so +############################################################################### + +# Initialize variables +read -r update_global update_user update_project env_settings project_config sed_hack <<< "" +echo "${update_global} ${update_user} ${update_project} ${project_config} ${sed_hack}" > /dev/null + +function env_check() { + # Only check when someone is at the console + if [[ "${FORCE}" != "1" ]]; then + + # Compare version numbers + if version_compare "${VERSION}" "${PROJECT_VERSION}"; then + update_project="1" + fi + + if version_compare "${VERSION}" "${USER_VERSION}"; then + update_user="1" + fi + + if version_compare "${VERSION}" "${GLOBAL_VERSION}"; then + update_global="1" + fi + + # User feedback + info "\r\nNew version (${VERSION}) requires configuration updates." + + # Update? + if yesno --default yes "Update now? [Y/n] "; then + [[ "${update_project}" == "1" ]] && update_project + else + info "Skipping update." + fi + fi +} + +function update_project() { + info "Updating ${project_config}..." + env_settings=(PROJNAME PROJCLIENT DEVURL PRODURL) + cp "${deployPath}"/deploy.sh "${trshFile}" + + # Loops through the variables + for i in "${env_settings[@]}" ; do + console "${i}" + current_setting=$(grep "${i}" "${project_config}") + + # Don't bother parsing if we know it's not needed + + # Add a check to make sure the value in the project config is not commented out, if it is + # we should leave that setting controlled by global/user file + + if [[ -n "${current_setting}" ]] && [[ -n "${i}" ]]; then + console "current_setting = ${current_setting}" + sed_hack=$(echo "sed -i -e '/${i}=.*/c ${current_setting}' ${trshFile}") + console "${sed_hack}" + eval "${sed_hack}" + fi + done + + # templorary for testing + cp "${trshFile}" ~/result.txt +} diff --git a/lib/main.sh b/lib/main.sh index facbe9f..31722d4 100755 --- a/lib/main.sh +++ b/lib/main.sh @@ -9,6 +9,7 @@ function main() { dependency_check # Check that required commands are available release_check # Check for newer version at Github + # env_check # Check for configuration files that need updating gitStart # Check for a valid git project and get set up lock # Create lock file go # Start a deployment work session From 83de1becf98fa40609ad0eed7d0c8463547a5940 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 5 Mar 2018 21:11:47 -0800 Subject: [PATCH 041/334] Release 3.6.7 --- .gitignore | 3 +- CHANGELOG.md | 8 +- README.md | 14 ++-- deploy.sh | 2 +- etc/.deployrc | 5 ++ etc/deploy-example.conf | 113 +++++++++++++------------- etc/deploy.sh | 126 +++++++++++++++-------------- lib/env-check.sh | 171 ++++++++++++++++++++++++++++++++-------- lib/main.sh | 2 +- lib/release-check.sh | 2 +- 10 files changed, 282 insertions(+), 164 deletions(-) diff --git a/.gitignore b/.gitignore index 92f4d48..9c3bcee 100755 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /*.sublime-workspace .DS_Store .deploy.sh -theme.conf \ No newline at end of file +theme.conf +*.bak \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e7cd43d..fd7b871 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## ## [3.6.6] - 03-05-2018 ### Added +- Configuration files now upgrade in place if needed when new versions of `deploy` are released - Statistic reports now display server uptime, latency, and a few handy Google analytics stats ### Changed - Configuration files restructured for better readability @@ -13,7 +14,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fixed a crash that could occur when creating statistics for projects with code in approval queue - Reports for the month of January now generate correctly - Fixed a bug that could rarely report an incorrect Wordpress version number -- Google analytics no longer incorrectly display for projects that do not use them +- Google analytics no longer incorrectly display for projects that do not use them +- User configuration files are now created more reliably ## [3.6.6] - 01-13-2018 ### Added @@ -192,7 +194,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Old monolithic script rewritten -[Unreleased]: https://github.com/EMRL/deploy/compare/v3.6.6...HEAD +[Unreleased]: https://github.com/EMRL/deploy/compare/v3.6.7...HEAD [3.6.8]: https://github.com/EMRL/deploy/compare/v3.6.7...3.6.8 [3.6.7]: https://github.com/EMRL/deploy/compare/v3.6.6...3.6.7 [3.6.6]: https://github.com/EMRL/deploy/compare/v3.6.5...3.6.6 diff --git a/README.md b/README.md index adae3e4..ba0a606 100755 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Deploy -[![release](https://img.shields.io/badge/release-v3.6.6-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) +[![release](https://img.shields.io/badge/release-v3.6.7-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) [![Build Status](https://travis-ci.org/EMRL/deploy.svg?branch=master)](https://travis-ci.org/EMRL/deploy) -`deploy` is a shell script designed to speed up and automate project deployment. Its main focus is Wordpress websites, but it can be used with any code repository. +`deploy` is designed to speed up, automate, and integrate project deployment. Its main focus is Wordpress websites, but it can be used with any code repository. [Changelog](https://github.com/EMRL/deploy/blob/master/CHANGELOG.md) @@ -54,9 +54,9 @@ Other Options: ## How It Works -Basically, this thing is a wrapper that simplifies web app deployment from a development or staging environment to a production server. At the moment is mostly focused on Wordpress projects but in it should work for other stuff too. +Basically, this thing consolidates a boatload of commands into a single command that simplifies web app deployment from a development or staging environment to a production server. -This script requires [`git`](https://git-scm.com/), and will make use of [`wp-cli`](http://wp-cli.org/), [`grunt`](http://gruntjs.com/), [`npm`](https://www.npmjs.com/), and [`mina`](http://nadarei.co/mina/) if they are installed. +`deploy` requires [`git`](https://git-scm.com/), and will make use of [`wp-cli`](http://wp-cli.org/), [`grunt`](http://gruntjs.com/), [`npm`](https://www.npmjs.com/), and [`mina`](http://nadarei.co/mina/) if they are installed. ## Installation @@ -67,15 +67,15 @@ This script requires [`git`](https://git-scm.com/), and will make use of [`wp-cl ## Configuration -Configuration is handled in the `etc/deploy.conf` file. Individual users can also keep their own settings in `~/.deployrc` +Configuration is handled globally in the `etc/deploy.conf` file. Individual users also have their own settings in `~/.deployrc` Repositories can each have their own deploy configuration. An example of this file can be [found here](https://github.com/EMRL/deploy/blob/master/etc/deploy.sh). -## Integration +## Slack For workgroups and teams that use it, `deploy` is able to integrate with Slack. You'll need to set up an "Incoming Webhook" custom integration on the Slack side to get this ready to roll. See https://YOURTEAMNAME.slack.com/apps/manage/custom-integrations to get going. Once you think you've got Slack configured, run `deploy --slack-test [project]` to test. -## Running on Autopilot +## Autopilot `deploy --automate` works well for unattended updates of Wordpress sites; great for maintaining updates via a crontab. An example cron script can be [found here](https://github.com/EMRL/deploy/blob/master/etc/cron/deploy.cron.example). Running in this mode, the project will only be deployed if there are Wordpress core or plugin updates. If other code changes are detected the project will not be auto-updated. Smart Commits must be enabled or nothing will be deployed. diff --git a/deploy.sh b/deploy.sh index ee32015..d28179e 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.7-beta.2" +VERSION="3.6.7" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" diff --git a/etc/.deployrc b/etc/.deployrc index 87b31a7..fbd237f 100755 --- a/etc/.deployrc +++ b/etc/.deployrc @@ -8,6 +8,11 @@ # settings will override those settings found here. Anything you leave # commented out or unset in this file will use the global configuration. +# This value indicates the version number when this file was last changed: +# it does not necessarily reflect deploy's current version number. +# DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP +USER_VERSION="3.6.7" + # Clear screen on startup, when not running with --quiet or --verbose switch # CLEARSCREEN="{{CLEARSCREEN}}" diff --git a/etc/deploy-example.conf b/etc/deploy-example.conf index 8f79cda..9e1d8b5 100755 --- a/etc/deploy-example.conf +++ b/etc/deploy-example.conf @@ -4,34 +4,39 @@ # # Global configuration file for deploy +# This value indicates the version number when this file was last changed: +# it does not necessarily reflect deploy's current version number. +# DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP +GLOBAL_VERSION="3.6.7" + ############################################################################### # General Setup ############################################################################### # Clear screen on startup, when not running with --quiet or --verbose switch -CLEARSCREEN="TRUE" +# CLEARSCREEN="{{CLEARSCREEN}}" # Set the work path. This is the directory where all your # repos are stored, with no trailing slash -WORKPATH="/var/www/html" +# WORKPATH="{{WORKPATH}}" # Path to repository configuration files relative to its # root directory, usually ./config -CONFIGDIR="config" +# CONFIGDIR="{{CONFIGDIR}}" # Check to see all servers defined (repo host, staging, production, etc) are # returning a 200OK response -SERVERCHECK="TRUE" +# SERVERCHECK="{{SERVERCHECK}}" # Running deploy as the root user can be dangerous so it is not allowed by # default. Set to TRUE is really must do this. -# ALLOWROOT="FALSE" +# ALLOWROOT="{{ALLOWROOT}}" # When ACTIVECHECK="TRUE", when running as deploy --force --update --quit -# files will be scanned for changes made within the timeframe set in -# ACTIVETiME (in minutes) and if any changes are found, deployment will be halted. -ACTIVECHECK="TRUE" -CHECKTIME="10" +# files will be scanned for changes made within the timeframe set in ACTIVETME +# (in minutes) and if any changes are found, deployment will be halted. +# ACTIVECHECK="{{ACTIVECHECK}}" +# CHECKTIME="{{CHECKTIME}}" ############################################################################### @@ -41,37 +46,37 @@ CHECKTIME="10" # The URL for your repository hosting, with no trailing slash. For example, # if you use Github and your repo URL looks like https://github.com/EMRL/deploy # your REPOHOST should be set to https://github.com/EMRL (with no trailing slash) -# REPOHOST="https://bitbucket.org/teamname" +# REPOHOST="{{REPOHOST}}" # Activate "Smart Commits"; this feature tries to create automatic # commit messages by parsing the log files generated during # Wordpress updates. Set to "TRUE" to activate. -SMARTCOMMIT="TRUE" +# SMARTCOMMIT="{{SMARTCOMMIT}}" # If you want to see statistics about your recent git # commits, set this value to exactly "TRUE" -GITSTATS="TRUE" +# GITSTATS="{{GITSTATS}}" # If dirty (yet to be committed) files exist in the repo, deploy will normally not halt # execution when running with the --automate flag. If you prefer to have the dirty files # stashed and proceed with updates set the below value to TRUE. Files will be unstashed # after the deployment is complete. -# STASH="TRUE" +# STASH="{{STASH}}" # Clean and compress repo before starting up -# GARBAGE="TRUE" +# GARBAGE="{{GARBAGE}}" ############################################################################### # Wordpress ############################################################################### # Path to wp-cli, with no trailing slash -WPCLI="/usr/local/bin" +# WPCLI="{{WPCLI}}" # Wordfence check - Wordfence makes some crappy files that break certain commands # because of permissions garbage. Setting this to TRUE will enable a check that # stops deployment if evidence of these files is detected. -# WFCHECK="FALSE" +# WFCHECK="{{WFCHECK}}" ############################################################################### @@ -81,14 +86,14 @@ WPCLI="/usr/local/bin" # Activate Permission Fix. With multi-user stuff going on, sometimes # permission problems may arise. This function will reset permissions # upon each deploy. Set FIXPERMISSIONS to "TRUE" to activate. -# FIXPERMISSIONS="FALSE" -# DEVUSER="developer" # Lead developer username -# DEVGROUP="users" # Lead developer group -# APACHEUSER="apache" # Apache user -# APACHEGROUP="apache" # Apache group +# FIXPERMISSIONS="{{FIXPERMISSIONS}}" +# DEVUSER="{{DEVUSER}}" # Lead developer username +# DEVGROUP="{{DEVGROUP}}" # Lead developer group +# APACHEUSER="{{APACHEUSER}}" # Apache user +# APACHEGROUP="{{APACHEGROUP}}" # Apache group # If you have issues with the fatal permissions errors on .git/index, set this to TRUE -# FIXINDEX="TRUE" +# FIXINDEX="{{FIXINDEX}}" ############################################################################### @@ -97,36 +102,36 @@ WPCLI="/usr/local/bin" # Define the path to your mail program, with no trailing slash. As of now, deploy # only works with sendmail. -MAILPATH="/usr/sbin" +# MAILPATH="{{MAILPATH}}" # Set the address the logfiles will be sent to -# TO="deploy@domain.com" +# TO="{{TO}}" # Set the address the logfiles will be sent from -# FROM="support@domain.com" +# FROM="{{FROM}}" # Email log subject line "- project name" is appended to this, # creating a subject line such as "Project deployed - Project Name" -# SUBJECT="Deployment" +# SUBJECT="{{SUBJECT}}" # When should email logs be sent? Setting to TRUE to activate. -# EMAILERROR="TRUE" -# EMAILSUCCESS="TRUE" -# EMAILQUIT="FALSE" +# EMAILERROR="{{EMAILERROR}}" +# EMAILSUCCESS="{{EMAILSUCCESS}}" +# EMAILQUIT="{{EMAILQUIT}}" # If you want your emails to be sent "clean" (with no detailed logging) set -# SHORTEMAIL to TRUE. Full logs will still be posted to web logs. Error emails +# this value to TRUE. Full logs will still be posted to web logs. Error emails # will continue to contain full logs. -# SHORTEMAIL="TRUE" +# SHORTEMAIL="{{SHORTEMAIL}}" # Send HTML emails? -# EMAILHTML="TRUE" +# EMAILHTML="{{EMAILHTML}}" # If you're sending HTML emails, define which template you'd like # to use. HTML templates are stored in separate folders in # /etc/deploy/html. The value used below should be the folder name # of your template. -# HTMLTEMPLATE="default" +# HTMLTEMPLATE="{{HTMLTEMPLATE}}" # Integration Emails # ------------------ @@ -140,19 +145,19 @@ MAILPATH="/usr/sbin" # Email from domain. Whatever you're integrating with may need # a different From: address than that of the the machine you're # actually deploying from. -# FROMDOMAIN="domain.com" +# FROMDOMAIN="{{FROMDOMAIN}}" # If you need to specify a user, other than your unix user name # to be the in the From: email, do it here. Otherwise Leave blank. -# FROMUSER="" +# FROMUSER="{{FROMUSER}}" # Post commit logs to this email address. This should probably # be set per-project. For examples, for our task management # system, this email would be task-####@projects.emrl.com, with # the #### being the task identification number for the project # being deployed. -# POSTEMAILHEAD="task-" -# POSTEMAILTAIL="@projects.domain.com" +# POSTEMAILHEAD="{{POSTEMAILHEAD}}" +# POSTEMAILTAIL="{{POSTEMAILTAIL}}" ############################################################################### @@ -166,14 +171,14 @@ MAILPATH="/usr/sbin" # test your configuration. # # Set POSTTOSLACK to "TRUE" to enable Slack integration. -# POSTTOSLACK="TRUE" +# POSTTOSLACK="{{POSTTOSLACK}}" # Add your full Webhook URL below, including https:// -# SLACKURL="https://hooks.slack.com/services/#########/#########/######" +# SLACKURL="{{SLACKURL}}" # Normally only successful deployments are posted to Slack. # Enable the settings below to post on WARNiNG and/or ERROR. -# SLACKERROR="TRUE" +# SLACKERROR="{{SLACKERROR}}" ############################################################################### @@ -181,7 +186,7 @@ MAILPATH="/usr/sbin" ############################################################################### # Webhook POST URL -# POSTURL="" +# POSTURL="{{POSTURL}}" ############################################################################### @@ -190,41 +195,41 @@ MAILPATH="/usr/sbin" # Strips the bulk of nasty PHP debug messages out of the # log files that are emailed upon deployment. -NOPHP="TRUE" +# NOPHP="{{NOPHP}}" # IF INCOGNITO is set to true, log files as well as verbose output to screen # will be stripped of details such as email addresses and system file paths. -# INCOGNITO="TRUE" +# INCOGNITO="{{INCOGNITO}}" # Post HTML logs to remote server. This needs to be set to "TRUE" even you # are only posting to LOCALHOST. -# REMOTELOG="TRUE" +# REMOTELOG="{{REMOTELOG}}" # Define the root url where the deploy log will be accessible with no # trailing slash -# REMOTEURL="http://deploy.domain.com" +# REMOTEURL="{{REMOTEURL}}" # If using HTML logs, define which template you'd like to use. HTML templates # are stored in separate folders in /etc/deploy/html. The value used below # should be the folder name of your template. -# REMOTETEMPLATE="default" +# REMOTETEMPLATE="{{REMOTETEMPLATE}}" # Post logs via SCP -# SCPPOST="TRUE" -# SCPUSER="user" -# SCPHOST="hostname.com" -# SCPHOSTPATH="/full/path/to/file" +# SCPPOST="{{SCPPOST}}" +# SCPUSER="{{SCPUSER}}" +# SCPHOST="{{SCPHOST}}" +# SCPHOSTPATH="{{SCPHOSTPATH}}" # # DANGER DANGER: If for some reason you absolutely can't use an SSH key you # can configure your password here -# SCPPASS="password" +# SCPPASS="{{SCPPASS}}" # If you're posting logs to a place on the same machine you're deploying from, # set POSTTOLOCALHOST to "TRUE" and define the path where you want to store # the HTML logs. -# LOCALHOSTPOST="TRUE" -# LOCALHOSTPATH="/var/www/production/deploy" +# LOCALHOSTPOST="{{LOCALHOSTPOST}}" +# LOCALHOSTPATH="{{LOCALHOSTPATH}}" # Set the number of days before logs should be deleted. Currently this only # works for logs stored on localhost. -# EXPIRELOGS="30" +# EXPIRELOGS="{{EXPIRELOGS}}" diff --git a/etc/deploy.sh b/etc/deploy.sh index 2bdfbc3..ad1bbeb 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -5,6 +5,10 @@ # basis. This file should be placed in either the project's root folder, # or in /config/. Settings configured in this file will override both # system & user settings. + +# This value indicates the version number when this file was last changed: +# it does not necessarily reflect deploy's current version number. +# DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP PROJECT_VERSION="3.6.7" @@ -13,16 +17,16 @@ PROJECT_VERSION="3.6.7" ############################################################################### # A human readable project name -# PROJNAME="Best Webapp Ever" +# PROJNAME="{{PROJNAME}}" # A human readable client name -# PROJCLIENT="Client Name" +# PROJCLIENT="{{PROJCLIENT}}" # Staging URL, including http:// or https:// -# DEVURL="http://devurl.com" +# DEVURL="{{DEVURL}}" # Production, or "Live" project URL, including http:// or https:// -# PRODURL="http://productionurl.com" +# PRODURL="{{PRODURL}}" ############################################################################### @@ -34,43 +38,43 @@ PROJECT_VERSION="3.6.7" # your REPOHOST should be set to https://github.com/EMRL (with no trailing # slash) If most of your repos are all hosted at the same location, you may # want to define this in either the global or user configuration files. -# REPOHOST="" +# REPOHOST="{{REPOHOST}}" # The exact name of the Bitbucket/Github repository -# REPO="name-of-repo" +# REPO="{{REPO}}" # Configure your branches. In most cases the name will be master & production. # If you are only using a master branch, leave production undefined. -# MASTER="master" -# PRODUCTION="production" +# MASTER="{{MASTER}}" +# PRODUCTION="{{PRODUCTION}}" -# Configure merge behavior. If you wish to automatically merge your MASTER and -# PRODUCTION branches when deploying, set AUTOMERGE to TRUE. -# AUTOMERGE="TRUE" +# Configure merge behavior. If you wish to automatically merge your branches +# when deploying, set AUTOMERGE to TRUE. +# AUTOMERGE="{{AUTOMERGE}}" # If dirty (yet to be committed) files exist in the repo, deploy will normally # not halt execution when running with the --automate flag. If you prefer to # have the dirty files stashed and proceed with updates set the below value # to TRUE. Files will be unstashed after the deployment is complete. -# STASH="TRUE" +# STASH="{{STASH}}" # Define CHECKBRANCH if you only want deploy to run when the set branch is # currently checked out; e.g. if CHECKBRANCH="master" and the current branch is # "production", deployment will halt. -# CHECKBRANCH="master" +# CHECKBRANCH="{{CHECKBRANCH}}" # If you have no SSH key or wish to login manually using your account name and # password in the console, set NOKEY to exactly "TRUE" # -# NOKEY="TRUE" +# NOKEY="{{NOKEY}}" # By default deploy will check for valid SSH keys; if you want to override this # behavior, set DISABLESSHCHECK to TRUE -# DISABLESSHCHECK="FALSE" +# DISABLESSHCHECK="{{DISABLESSHCHECK}}" # If for some reason you'd like a default commit message. It will # always be editable before finalizing commit. -# COMMITMSG="This is a default commit message" +# COMMITMSG="{{COMMITMSG}}" ############################################################################### @@ -81,19 +85,19 @@ PROJECT_VERSION="3.6.7" # their application code. If you're using non-standard file paths, define the # root, system, and app (plugin/theme) directories below. Note that the forward # slash is required. Just about everyone on the planet can leave this alone. -# WPROOT="/public" -# WPAPP="/app" -# WPSYSTEM="/system" +# WPROOT="{{WPROOT}}" +# WPAPP="{{WPAPP}}" +# WPSYSTEM="{{WPSYSTEM}}" # If you do not want to allow core updates, set DONOTUPDATEWP to TRUE. -# DONOTUPDATEWP="FALSE" +# DONOTUPDATEWP="{{DONOTUPDATEWP}}" # Advanced Custom Fields Pro License # # Too many issues seem to crop up with the normal method of updating the # Wordpress plugin ACF Pro. Including your license key below will enable # upgrades to happen more reliably. -# ACFKEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +# ACFKEY="{{ACFKEY}}" ############################################################################### @@ -101,14 +105,14 @@ PROJECT_VERSION="3.6.7" ############################################################################### # The command to finalize deployment of your project(s) -# DEPLOY="mina deploy" +# DEPLOY="{{DEPLOY}}" # To require approval before pushing this project's code to a live production # environment, set REQUIREAPPROVAL="TRUE" -# REQUIREAPPROVAL="TRUE" +# REQUIREAPPROVAL="{{REQUIREAPPROVAL}}" # Disallow deployment; set to TRUE to enable. Double negative, it's tricky. -# DONOTDEPLOY="TRUE" +# DONOTDEPLOY="{{DONOTDEPLOY}}" ############################################################################### @@ -122,15 +126,15 @@ PROJECT_VERSION="3.6.7" # that can accept external email input. For examples, our task management system # accepts emails in the format task-####@projects.emrl.com, with the #### # being the task identification number for the project being deployed. -# TASK="####" +# TASK="{{TASK}}" # If you wish to have automated deployments add tracked time to your project # management system, uncomment and configure the two values below. TASKUSER # should be the email address of the user that the time will be logged as, # and ADDTIME is the amount of time to be added for each deployment. Time # formats can in hh:mm (02:23) or HhMm (2h23m) format. -# TASKUSER="deploy@emrl.com" -# ADDTIME="10m" +# TASKUSER="{{TASKUSER}}" +# ADDTIME="{{ADDTIME}}" # Slack # ----- @@ -142,24 +146,24 @@ PROJECT_VERSION="3.6.7" # test your configuration. # Set POSTTOSLACK to "TRUE" to enable Slack integration. -# POSTTOSLACK="TRUE" +# POSTTOSLACK="{{POSTTOSLACK}}" # Add your full Webhook URL below, including https:// -# SLACKURL="https://hooks.slack.com/services/###################/########################" +# SLACKURL="{{SLACKURL}}" # Normally only successful deployments are posted to Slack. # Enable the settings below to post on WARNiNG and/or ERROR. -# SLACKERROR="FALSE" +# SLACKERROR="{{SLACKERROR}}" # If you'd like to post a Slack notification with a URL to view the weekly digest # set the following to TRUE. If you want to use an incoming webhook other than the # one defined in SLACKURL, enter that here *instead* of TRUE. -# DIGESTSLACK="FALSE" +# DIGESTSLACK="{{DIGESTSLACK}}" # Webhooks # -------- # Post event notifications to this URL. -# POSTURL="" +# POSTURL="{{POSTURL}}" ############################################################################### @@ -168,52 +172,52 @@ PROJECT_VERSION="3.6.7" # If you need to send logfiles and email alerts to address(es) other # than those configured globally, enter them below. -# TO="notify@client.com" +# TO="{{TO}}" # If you want to use an email template unique to this project (instead of the # globally configured template) define it below. HTML templates are stored in # separate folders in /etc/deploy/html. The value used below should be the # folder name of your template. -# HTMLTEMPLATE="default" +# HTMLTEMPLATE="{{HTMLTEMPLATE}}" # If you are using html logfiles, define the full URL to the client's logo -# CLIENTLOGO="http://client.com/assets/img/logo.png" +# CLIENTLOGO="{{CLIENTLOGO}}" # If you are using a digest theme that includes a cover image, at the URL below. -# COVER="http://client.com/assets/img/cover.jpg" +# COVER="{{COVER}}" # IF INCOGNITO is set to true, log files as well as verbose output to screen # will be stripped of details such as email addresses and system file paths. -# INCOGNITO="TRUE" +# INCOGNITO="{{INCOGNITO}}" # Post HTML logs to remote server. This needs to be set to "TRUE" even you # are only posting to LOCALHOST. -# REMOTELOG="TRUE" +# REMOTELOG="{{REMOTELOG}}" # Define the root url where the deploy log will be accessible with no # trailing slash -# REMOTEURL="http://deploy.domain.com" +# REMOTEURL="{{REMOTEURL}}" # If using HTML logs, define which template you'd like to use. HTML templates # are stored in separate folders in /etc/deploy/html. The value used below # should be the folder name of your template. -# REMOTETEMPLATE="default" +# REMOTETEMPLATE="{{REMOTETEMPLATE}}" # Post logs via SCP -# SCPPOST="TRUE" -# SCPUSER="user" -# SCPHOST="hostname.com" -# SCPHOSTPATH="/full/path/to/file" +# SCPPOST="{{SCPPOST}}" +# SCPUSER="{{SCPUSER}}" +# SCPHOST="{{SCPHOST}}" +# SCPHOSTPATH="{{SCPHOSTPATH}}" # DANGER DANGER: If for some reason you absolutely can't use an SSH key you # can configure the path to a text file containing *only* your password. -# SCPPASS="password" +# SCPPASS="{{SCPPASS}}" # If you're posting logs to a place on the same machine you're deploying from, # set POSTTOLOCALHOST to "TRUE" and define the path where you want to store # the HTML logs. -# LOCALHOSTPOST="TRUE" -# LOCALHOSTPATH="/var/www/production/deploy" +# LOCALHOSTPOST="{{LOCALHOSTPOST}}" +# LOCALHOSTPATH="{{LOCALHOSTPATH}}" ############################################################################### @@ -223,7 +227,7 @@ PROJECT_VERSION="3.6.7" # If you'd like to send branded HTML emails using the `deploy --digest [project]` # command, enter the recipient's email address below. Email value can be a comma # separated string of multiple addresses. -# DIGESTEMAIL="digest@email.com" +# DIGESTEMAIL="{{DIGESTEMAIL}}" ############################################################################### @@ -231,12 +235,12 @@ PROJECT_VERSION="3.6.7" ############################################################################### # First and last name of the primary contact for this client -# CLIENTCONTACT="First Last" +# CLIENTCONTACT="{{CLIENTCONTACT}}" # Include hosting as a line item on monthly reports? If set to TRUE, the report # line item will read "Monthly web hosting"; customize the text included in # report by setting it to any other value. -# INCLUDEHOSTING="TRUE" +# INCLUDEHOSTING="{{INCLUDEHOSTING}}" ############################################################################### @@ -244,20 +248,20 @@ PROJECT_VERSION="3.6.7" ############################################################################### # API credentials -# CLIENTID="#############################################.apps.googleusercontent.com" -# CLIENTSECRET="########################" -# REDIRECTURI="http://localhost" +# CLIENTID="{{CLIENTID}}" +# CLIENTSECRET="{{CLIENTSECRET}}" +# REDIRECTURI="{{REDIRECTURI}}" # OAuth authorization will expire after one hour, but will be updated when needed # if the tokens below are configured correctly -# AUTHORIZATIONCODE="##############################################" +# AUTHORIZATIONCODE="{{AUTHORIZATIONCODE}}" # Tokens -# ACCESSTOKEN="#################################################################################################################################" -# REFRESHTOKEN="##################################################################" +# ACCESSTOKEN="{{ACCESSTOKEN}}" +# REFRESHTOKEN="{{REFRESHTOKEN}}" # Google Analytics ID -# PROFILEID="########" +# PROFILEID="{{PROFILEID}}" ############################################################################### @@ -269,14 +273,14 @@ PROJECT_VERSION="3.6.7" # See https://github.com/EMRL/deploy/wiki/Integration for more information. # Full API URL -# MONITORURL="https://your.phpservermonitor.com/api/monitorapi.php" +# MONITORURL="{{MONITORURL}}" # Email/password of the user that will access the API. Password can be stored in # a file outside of the project repo for security reasons -# MONITORUSER="user@domain.com" -# MONITORPASS="/path/to/password/file" +# MONITORUSER="{{MONITORUSER}}" +# MONITORPASS="{{MONITORPASS}}" # Server ID to monitor. When viewing the server on your web console, your URL # will be something like https://monitor.com/?&mod=server&action=view&id=3 - in # this case SERVERID would be "3" (notice the &id=3 at the end of the URL) -# SERVERID="###" +# SERVERID="{{SERVERID}}" diff --git a/lib/env-check.sh b/lib/env-check.sh index e5aee28..784ec19 100755 --- a/lib/env-check.sh +++ b/lib/env-check.sh @@ -7,61 +7,162 @@ ############################################################################### # Initialize variables -read -r update_global update_user update_project env_settings project_config sed_hack <<< "" -echo "${update_global} ${update_user} ${update_project} ${project_config} ${sed_hack}" > /dev/null +read -r update_global update_user update_project env_settings project_config \ + sed_hack short_version global_project_version local_version \ + tmp_workpath <<< "" +echo "${update_global} ${update_user} ${update_project} ${project_config} + ${sed_hack} ${short_version} ${global_project_version} + ${local_version} ${tmp_workpath}" > /dev/null function env_check() { # Only check when someone is at the console if [[ "${FORCE}" != "1" ]]; then - + # Save the workpath to reload after this process; this is to allow a + # deployment using to --current switch to continue properly after a global + # configuration file update + tmp_workpath="${WORKPATH}" + + # Trim alpha/beta part of version number + local_version="${VERSION//-*}" + # Compare version numbers - if version_compare "${VERSION}" "${PROJECT_VERSION}"; then - update_project="1" - fi + local_version="${GLOBAL_VERSION}"; source "${deployPath}"/deploy-example.conf + [[ "${GLOBAL_VERSION}" != "${local_version}" ]] && update_global="1"; + + local_version="${USER_VERSION}"; source "${deployPath}"/.deployrc + [[ "${USER_VERSION}" != "${local_version}" ]] && update_user="1"; + + local_version="${PROJECT_VERSION}"; source "${deployPath}"/deploy.sh + [[ "${PROJECT_VERSION}" != "${local_version}" ]] && update_project="1" - if version_compare "${VERSION}" "${USER_VERSION}"; then - update_user="1" + if [[ "${update_global}" == "1" ]] || [[ "${update_user}" == "1" ]] || [[ "${update_project}" == "1" ]]; then + update_config fi - if version_compare "${VERSION}" "${GLOBAL_VERSION}"; then - update_global="1" - fi + # Reload environment variables, from global > user > project + source "${deployPath}"/deploy.conf + source ~/.deployrc + source "${project_config}" - # User feedback - info "\r\nNew version (${VERSION}) requires configuration updates." + # Restore workpath value + WORKPATH="${tmp_workpath}" - # Update? - if yesno --default yes "Update now? [Y/n] "; then - [[ "${update_project}" == "1" ]] && update_project - else - info "Skipping update." + # Trying to continue if global configuration has been changed is buggy + # if using --current switch + if [[ "${update_global}" == "1" ]]; then + console "Global configuration changed - restart your deployment." + quietExit fi fi } -function update_project() { - info "Updating ${project_config}..." - env_settings=(PROJNAME PROJCLIENT DEVURL PRODURL) - cp "${deployPath}"/deploy.sh "${trshFile}" +function update_config() { + info "\r\nNew version (${VERSION}) requires configuration updates." + if yesno --default yes "Update now? [Y/n] "; then + [[ "${update_global}" == "1" ]] && update_global + [[ "${update_user}" == "1" ]] && update_user + [[ "${update_project}" == "1" ]] && update_project + info "Updates complete."; empty_line + else + info "Skipping update." + fi +} +function update_global() { + info "Updating ${deployPath}/deploy.conf..." + env_settings=(CLEARSCREEN WORKPATH CONFIGDIR SERVERCHECK ACTIVECHECK \ + CHECKTIME REPOHOST SMARTCOMMIT GITSTATS STASH GARBAGE WPCLI WFCHECK \ + FIXPERMISSIONS DEVUSER DEVGROUP APACHEUSER APACHEGROUP FIXINDEX \ + MAILPATH TO FROM SUBJECT EMAILERROR EMAILSUCCESS EMAILQUIT SHORTEMAIL \ + EMAILHTML HTMLTEMPLATE FROMDOMAIN FROMUSER POSTEMAILHEAD POSTEMAILTAIL \ + POSTTOSLACK SLACKURL SLACKERROR POSTURL NOPHP INCOGNITO REMOTELOG \ + REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS \ + LOCALHOSTPOST LOCALHOSTPATH EXPIRELOGS) + + if [[ ! -w "${deployPath}" ]]; then + info "Requesting sudo access..." + sudo cp "${deployPath}"/deploy.conf "${deployPath}"/deploy.conf.bak + else + cp "${deployPath}"/deploy.conf "${deployPath}"/deploy.conf.bak + fi + info "Global configuration backup created at ${deployPath}/deploy.conf.bak" + + cp "${deployPath}"/deploy-example.conf "${trshFile}" + + # Reload global values + source "${deployPath}"/deploy.conf + # Loops through the variables for i in "${env_settings[@]}" ; do - console "${i}" - current_setting=$(grep "${i}" "${project_config}") + current_setting=$(grep "^${i}=" "${deployPath}"/deploy.conf.bak) + insert_values + done - # Don't bother parsing if we know it's not needed + # Install new files + env_cleanup + if [[ ! -w "${deployPath}" ]]; then + sudo cp "${trshFile}" "${deployPath}"/deploy.conf + else + cp "${trshFile}" "${deployPath}"/deploy.conf + fi +} - # Add a check to make sure the value in the project config is not commented out, if it is - # we should leave that setting controlled by global/user file +function update_user() { + info "Updating ~/.deployrc..." + env_settings=(CLEARSCREEN VERBOSE GITSTATS) + + info "User configuration backup created at ~/.deployrc.bak" + cp ~/.deployrc ~/.deployrc.bak + cp "${deployPath}"/.deployrc "${trshFile}" - if [[ -n "${current_setting}" ]] && [[ -n "${i}" ]]; then - console "current_setting = ${current_setting}" - sed_hack=$(echo "sed -i -e '/${i}=.*/c ${current_setting}' ${trshFile}") - console "${sed_hack}" - eval "${sed_hack}" - fi + # Reload user values + source ~/.deployrc + + # Loops through the variables + for i in "${env_settings[@]}" ; do + current_setting=$(grep "^${i}=" ~/.deployrc) + insert_values + done + + # Install new files + env_cleanup + cp "${trshFile}" ~/.deployrc +} + +function update_project() { + info "Updating ${project_config}..." + env_settings=(PROJNAME PROJCLIENT DEVURL PRODURL REPOHOST REPO MASTER \ + PRODUCTION AUTOMERGE STASH CHECKBRANCH NOKEY DISABLESSHCHECK COMMITMSG \ + WPROOT WPAPP WPSYSTEM DONOTUPDATEWP ACFKEY DEPLOY REQUIREAPPROVAL \ + DONOTDEPLOY TASK TASKUSER ADDTIME POSTTOSLACK SLACKURL SLACKERROR \ + DIGESTSLACK POSTURL TO HTMLTEMPLATE CLIENTLOGO COVER INCOGNITO REMOTELOG \ + REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS \ + LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL CLIENTCONTACT INCLUDEHOSTING \ + CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ + REFRESHTOKEN PROFILEID MONITORURL MONITORUSER MONITORPASS SERVERID) + + info "Project configuration backup created at ${project_config}.bak" + cp "${project_config}" "${project_config}.bak" + cp "${deployPath}"/deploy.sh "${trshFile}" + # Loops through the variables + for i in "${env_settings[@]}" ; do + current_setting=$(grep "^${i}=" "${project_config}") + insert_values done - # templorary for testing - cp "${trshFile}" ~/result.txt + # Install new files + env_cleanup + cp "${trshFile}" "${project_config}" +} + +function insert_values() { + if [[ -n "${!i}" ]]; then + sed_hack=$(echo "sed -i 's^{{${i}}}^${!i}^g' ${trshFile}; sed -i 's^# ${i}^${i}^g' ${trshFile}") + # Kludgy but works. Ugh. + eval "${sed_hack}" + fi +} + +function env_cleanup() { + sed -i "s^{{.*}}^^g" "${trshFile}" } diff --git a/lib/main.sh b/lib/main.sh index 31722d4..9c40e93 100755 --- a/lib/main.sh +++ b/lib/main.sh @@ -9,7 +9,7 @@ function main() { dependency_check # Check that required commands are available release_check # Check for newer version at Github - # env_check # Check for configuration files that need updating + env_check # Check for configuration files that need updating gitStart # Check for a valid git project and get set up lock # Create lock file go # Start a deployment work session diff --git a/lib/release-check.sh b/lib/release-check.sh index d466217..dbba475 100755 --- a/lib/release-check.sh +++ b/lib/release-check.sh @@ -46,6 +46,6 @@ function release_check() { fi } -function version_compare() { +function version_compare() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; } From 1e4b1075e8e524a91f3562165a69cd44c2240121 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 6 Mar 2018 10:53:52 -0800 Subject: [PATCH 042/334] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7b871..1f15c88 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## ## [3.6.6] - 03-05-2018 +## [3.6.6] - 03-05-2018 ### Added - Configuration files now upgrade in place if needed when new versions of `deploy` are released - Statistic reports now display server uptime, latency, and a few handy Google analytics stats From 196e0bc6ca9058d95c64c2d4dd5e2acf1f7b76ff Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 6 Mar 2018 10:54:45 -0800 Subject: [PATCH 043/334] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f15c88..d6664e1 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Item descriptions are now editable in reports - Cleaned up email output generated using `deploy --email-test` -- Report URLs are now writted as `report/YEAR-MONTH.php` instead of using a hard to remember string +- Report URLs are now formatted `report/YEAR-MONTH.php` instead of using a hard to remember string ### Fixed - Fixed a bug with running a report on dates in the previous calendar year - Added a workaround for Google potentially displaying over 100% of user sessions as new From 4798a17988075cb3a99706b5f44b5ce7b84cbabe Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 6 Mar 2018 13:50:00 -0800 Subject: [PATCH 044/334] Basic invoice creation, close #129 --- CHANGELOG.md | 6 +++++- deploy.sh | 10 ++++++---- etc/deploy.sh | 26 ++++++++++++++++++++++++++ lib/env-check.sh | 3 ++- lib/invoice.sh | 38 ++++++++++++++++++++------------------ lib/main.sh | 2 ++ lib/slack.sh | 11 +++++++++++ 7 files changed, 72 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6664e1..2abb817 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [3.6.6] - 03-05-2018 +## [Unreleased] +### Added +- Invoice creation is now available via [InvoiceNinja](https://www.invoiceninja.org/)'s API using `deploy --invoice` + +## [3.6.7] - 03-05-2018 ### Added - Configuration files now upgrade in place if needed when new versions of `deploy` are released - Statistic reports now display server uptime, latency, and a few handy Google analytics stats diff --git a/deploy.sh b/deploy.sh index d28179e..2628a87 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.6.7" +VERSION="3.7-alpha.1" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" @@ -28,12 +28,12 @@ set -uo pipefail read -r APP UPGRADE SKIPUPDATE CURRENT VERBOSE QUIET STRICT DEBUG FORCE \ SLACKTEST FUNCTIONLIST VARIABLELIST AUTOMATE EMAILTEST APPROVE \ DENY PUBLISH DIGEST ANALYTICS ANALYTICSTEST PROJSTATS UNLOCK \ - SSHTEST TIME UPDATEONLY POSTTEST REPORT REPAIR <<< "" + SSHTEST TIME UPDATEONLY POSTTEST REPORT REPAIR CREATE_INVOICE <<< "" echo "${APP} ${UPGRADE} ${SKIPUPDATE} ${CURRENT} ${VERBOSE} ${QUIET} ${STRICT} ${DEBUG} ${FORCE} ${SLACKTEST} ${FUNCTIONLIST} ${VARIABLELIST} ${AUTOMATE} ${EMAILTEST} ${APPROVE} ${DENY} ${PUBLISH} ${DIGEST} - ${ANALYTICS} ${ANALYTICSTEST} ${PROJSTATS} ${UNLOCK} - ${SSHTEST} ${TIME} ${UPDATEONLY} ${POSTTEST} ${REPORT} ${REPAIR}" > /dev/null + ${ANALYTICS} ${ANALYTICSTEST} ${PROJSTATS} ${UNLOCK} ${SSHTEST} ${TIME} + ${UPDATEONLY} ${POSTTEST} ${REPORT} ${REPAIR} ${CREATE_INVOICE}" > /dev/null # Temp files read -r logFile wpFile coreFile postFile trshFile statFile urlFile <<< "" @@ -131,6 +131,7 @@ Other Options: --report Create a monthly activity report --no-check Override active file and server checks --stats Generate project statistics pages + --invoice Create an invoice --strict Any error will halt deployment completely --debug Run in debug mode --unlock Delete expired lock files @@ -204,6 +205,7 @@ while [[ ${1:-unset} = -?* ]]; do --analytics-test) ANALYTICSTEST="1" ;; --monitor-test) MONITORTEST="1" ;; --stats) PROJSTATS="1" ;; + --invoice) CREATE_INVOICE="1" ;; --unlock) UNLOCK="1" ;; --repair) REPAIR="1"; FORCE="1"; STASH="TRUE"; VERBOSE="TRUE" ;; --no-check) NOCHECK="1" ;; diff --git a/etc/deploy.sh b/etc/deploy.sh index ad1bbeb..ba91d0f 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -243,6 +243,32 @@ PROJECT_VERSION="3.6.7" # INCLUDEHOSTING="{{INCLUDEHOSTING}}" +############################################################################### +# Invoice Ninja integration +############################################################################### + +# Full url of your Invoice Ninja host, with no trailing slash +# IN_HOST="{{IN_HOST}}" + +# API Token, created at https://yourinvoicehost.com/settings/api_tokens +# IN_TOKEN="{{IN_TOKEN}}" + +# Client ID number +# IN_CLIENT_ID="{{IN_CLIENT_ID}}" + +# Default product code +# IN_PRODUCT="{{IN_PRODUCT}}" + +# Default item cost +# IN_ITEM_COST="{{IN_ITEM_COST}}" + +# Default item quantity +# IN_ITEM_QTY="{{IN_ITEM_QTY}}" + +# Default item notes +# IN_NOTES="{{IN_NOTES}}" + + ############################################################################### # Google Analytics ############################################################################### diff --git a/lib/env-check.sh b/lib/env-check.sh index 784ec19..0897573 100755 --- a/lib/env-check.sh +++ b/lib/env-check.sh @@ -138,7 +138,8 @@ function update_project() { DIGESTSLACK POSTURL TO HTMLTEMPLATE CLIENTLOGO COVER INCOGNITO REMOTELOG \ REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS \ LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL CLIENTCONTACT INCLUDEHOSTING \ - CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ + IN_HOST IN_TOKEN IN_CLIENT_ID IN_PRODUCT IN_ITEM_COST IN_ITEM_QTY \ + IN_NOTES CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ REFRESHTOKEN PROFILEID MONITORURL MONITORUSER MONITORPASS SERVERID) info "Project configuration backup created at ${project_config}.bak" diff --git a/lib/invoice.sh b/lib/invoice.sh index 5d17c0f..7fbb9d0 100755 --- a/lib/invoice.sh +++ b/lib/invoice.sh @@ -6,28 +6,30 @@ # Handles creating invoices and integration with InvoiceNinja ############################################################################### +# Initialize variables +read -r IN_HOST IN_TOKEN IN_CLIENT_ID IN_PRODUCT IN_ITEM_COST IN_ITEM_QTY \ + IN_NOTES invoice_hack <<< "" +echo "${IN_HOST} ${IN_TOKEN} ${IN_CLIENT_ID} ${IN_PRODUCT} ${IN_ITEM_COST} + ${IN_ITEM_QTY} ${IN_NOTES} ${invoice_hack}" > /dev/null + function create_invoice() { # Will create the invoice payload + trace "Creating invoice..." + invoice_hack=$(echo "curl -X POST \"${IN_HOST}/api/v1/invoices\" -H \"Content-Type:application/json\" -d '{\"client_id\":\"${IN_CLIENT_ID}\", \"invoice_items\":[{\"product_key\": \"${IN_PRODUCT}\", \"notes\":\"${IN_NOTES}\", \"cost\":${IN_ITEM_COST}, \"qty\":1}]}' -H \"X-Ninja-Token: ${IN_TOKEN}\"") + eval "${invoice_hack}" &>> "${logFile}"; error_check + safeExit +} + +function get_current_invoice() { trace "This is an empty function" } function send_invoice() { - # Hit the InvoiceNinja API and create the invoice - # - # Things that need to be defined: - # API URL - # client_id - # - # And then we'll loop through - # product - # notes - # cost - # qty - # - # Example to create an invoice: - # - # curl -X POST https://invoice.com/api/v1/invoices -H "Content-Type:application/json" \ - # -d '{"client_id":"1", "invoice_items":[{"product_key": "ITEM", "notes":"Test", "cost":10, "qty":1}]}' \ - # -H "X-Ninja-Token: ###################################" + # curl -X POST ninja.test/api/v1/email_invoice -d '{"id":1}' -H "Content-Type:application/json" -H "X-Ninja-Token: TOKEN" trace "This is an empty function" -} \ No newline at end of file +} + +function download_invoice() { + # curl -X GET ninja.test/api/v1/download/1 -H "X-Ninja-Token: TOKEN" + trace "This is an empty function" +} diff --git a/lib/main.sh b/lib/main.sh index 9c40e93..59770e7 100755 --- a/lib/main.sh +++ b/lib/main.sh @@ -18,6 +18,8 @@ function main() { create_digest elif [[ "${REPORT}" == "1" ]]; then create_report + elif [[ "${CREATE_INVOICE}" == "1" ]]; then + create_invoice else server_check # Check that servers are up and running if [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then diff --git a/lib/slack.sh b/lib/slack.sh index 05a1015..069f69d 100755 --- a/lib/slack.sh +++ b/lib/slack.sh @@ -74,6 +74,17 @@ function slackPost () { slack_message="*${SLACKUSER}* nothing to do for ${APP}\nNOTICE: ${notes}" fi + # Create a payload for invoices + if [[ "${CREATE_INVOICE}" == "1" ]]; then + message_state="NOTICE" + # Does a production URL exit? + if [[ -n "${PRODURL}" ]]; then + slack_message="${IN_NOTES} invoice created for <${PRODURL}|${PROJNAME}>" + else + slack_message="${IN_NOTES} invoice created *${PROJNAME}*" + fi + fi + # Add a details link to online logfiles if they exist if [[ -n "${REMOTEURL}" ]] && [[ -n "${REMOTELOG}" ]]; then slack_message="${slack_message} (<${LOGURL}|Details>)" From 3404a4cafce6c796cebd5c5318b8c001c39104d1 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 6 Mar 2018 13:52:56 -0800 Subject: [PATCH 045/334] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abb817..e6e29fd 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -199,10 +199,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). [Unreleased]: https://github.com/EMRL/deploy/compare/v3.6.7...HEAD -[3.6.8]: https://github.com/EMRL/deploy/compare/v3.6.7...3.6.8 -[3.6.7]: https://github.com/EMRL/deploy/compare/v3.6.6...3.6.7 -[3.6.6]: https://github.com/EMRL/deploy/compare/v3.6.5...3.6.6 -[3.6.5]: https://github.com/EMRL/deploy/compare/v3.6.4...3.6.5 +[3.6.8]: https://github.com/EMRL/deploy/compare/v3.6.7...v3.6.8 +[3.6.7]: https://github.com/EMRL/deploy/compare/v3.6.6...v3.6.7 +[3.6.6]: https://github.com/EMRL/deploy/compare/v3.6.5...v3.6.6 +[3.6.5]: https://github.com/EMRL/deploy/compare/v3.6.4...v3.6.5 [3.6.4]: https://github.com/EMRL/deploy/compare/v3.6...v3.6.4 [3.6]: https://github.com/EMRL/deploy/compare/v3.5.7...v3.6 [3.5.7]: https://github.com/EMRL/deploy/compare/v3.5.5...v3.5.7 From 397850cd74e0c84a908f6fb94e4faeafd6e31a57 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 8 Mar 2018 17:44:04 -0800 Subject: [PATCH 046/334] Resolve #130 --- CHANGELOG.md | 2 ++ README.md | 1 + lib/git.sh | 4 ++++ lib/invoice.sh | 30 +++++++++++++++++++++++------- lib/main.sh | 1 + lib/slack.sh | 4 ++-- 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abb817..7ad8390 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Invoice creation is now available via [InvoiceNinja](https://www.invoiceninja.org/)'s API using `deploy --invoice` +### Changed +- Running with the `--automate` switch now requires the branch defined as "master" must be currently checked out. The behavior is the equivalent of setting `CHECKBRANCH="${MASTER}"` ## [3.6.7] - 03-05-2018 ### Added diff --git a/README.md b/README.md index ba0a606..bf0a003 100755 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Other Options: --report Create a monthly activity report --no-check Override active file and server checks --stats Generate project statistics pages + --invoice Create an invoice --strict Any error will halt deployment completely --debug Run in debug mode --unlock Delete expired lock files diff --git a/lib/git.sh b/lib/git.sh index e59a4e1..46179bc 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -33,6 +33,10 @@ function gitStart() { if grep -q "fatal" "${trshFile}"; then error "Unable to start deployment, push your first commit manually and try again." else + # If running --automate, force a branch check + if [[ "${AUTOMATE}" ]]; then + CHECKBRANCH="${MASTER}" + fi # If CHECKBRANCH is set, make sure current branch is correct. start_branch="$(git rev-parse --abbrev-ref HEAD)" if [[ -n "${CHECKBRANCH}" ]] && [[ "${DIGEST}" != "1" ]] && [[ "${PROJSTATS}" != "1" ]] && [[ "${EMAILTEST}" != "1" ]] && [[ "${SLACKTEST}" != "1" ]]; then diff --git a/lib/invoice.sh b/lib/invoice.sh index 7fbb9d0..2ca1024 100755 --- a/lib/invoice.sh +++ b/lib/invoice.sh @@ -8,28 +8,44 @@ # Initialize variables read -r IN_HOST IN_TOKEN IN_CLIENT_ID IN_PRODUCT IN_ITEM_COST IN_ITEM_QTY \ - IN_NOTES invoice_hack <<< "" + IN_NOTES IN_EMAIL invoice_hack current_invoice <<< "" echo "${IN_HOST} ${IN_TOKEN} ${IN_CLIENT_ID} ${IN_PRODUCT} ${IN_ITEM_COST} - ${IN_ITEM_QTY} ${IN_NOTES} ${invoice_hack}" > /dev/null + ${IN_ITEM_QTY} ${IN_NOTES} ${IN_EMAIL} ${invoice_hack} ${current_invoice}" > /dev/null function create_invoice() { # Will create the invoice payload trace "Creating invoice..." invoice_hack=$(echo "curl -X POST \"${IN_HOST}/api/v1/invoices\" -H \"Content-Type:application/json\" -d '{\"client_id\":\"${IN_CLIENT_ID}\", \"invoice_items\":[{\"product_key\": \"${IN_PRODUCT}\", \"notes\":\"${IN_NOTES}\", \"cost\":${IN_ITEM_COST}, \"qty\":1}]}' -H \"X-Ninja-Token: ${IN_TOKEN}\"") eval "${invoice_hack}" &>> "${logFile}"; error_check + + get_current_invoice + + if [[ "${IN_EMAIL}" == "TRUE" ]]; then + send_invoice + fi + + notes="${IN_NOTES} invoice (#${current_invoice}) created" safeExit } function get_current_invoice() { trace "This is an empty function" + curl -X GET "${IN_HOST}/api/v1/clients/${IN_CLIENT_ID}?include=invoices" -H "X-Ninja-Token: ${IN_TOKEN}" > "${trshFile}" + + # Many sedtastic things + sed -i '/"invoice_number"/!d' "${trshFile}" + sed -i 's/ //g' "${trshFile}" + sed -i 's/[^0-9]*//g' "${trshFile}" + + # Store current invoice number + current_invoice=$(tail -1 ${trshFile}) + trace "Invoice: ${current_invoice}" } function send_invoice() { # curl -X POST ninja.test/api/v1/email_invoice -d '{"id":1}' -H "Content-Type:application/json" -H "X-Ninja-Token: TOKEN" trace "This is an empty function" -} - -function download_invoice() { - # curl -X GET ninja.test/api/v1/download/1 -H "X-Ninja-Token: TOKEN" - trace "This is an empty function" + invoice_hack=$(echo "curl -X POST \"${IN_HOST}/api/v1/email_invoice\" -d '{\"id\":\"${current_invoice}\"}' -H "Content-Type:application/json" -H \"X-Ninja-Token: ${IN_TOKEN}\"") + trace "Trying ${invoice_hack}" + eval "${invoice_hack}" &>> "${logFile}"; error_check } diff --git a/lib/main.sh b/lib/main.sh index 59770e7..3e9025a 100755 --- a/lib/main.sh +++ b/lib/main.sh @@ -19,6 +19,7 @@ function main() { elif [[ "${REPORT}" == "1" ]]; then create_report elif [[ "${CREATE_INVOICE}" == "1" ]]; then + # create_invoice create_invoice else server_check # Check that servers are up and running diff --git a/lib/slack.sh b/lib/slack.sh index 069f69d..94e1e5b 100755 --- a/lib/slack.sh +++ b/lib/slack.sh @@ -79,9 +79,9 @@ function slackPost () { message_state="NOTICE" # Does a production URL exit? if [[ -n "${PRODURL}" ]]; then - slack_message="${IN_NOTES} invoice created for <${PRODURL}|${PROJNAME}>" + slack_message="${IN_NOTES} invoice (#${current_invoice}) created for <${PRODURL}|${PROJNAME}>" else - slack_message="${IN_NOTES} invoice created *${PROJNAME}*" + slack_message="${IN_NOTES} invoice (#${current_invoice}) created *${PROJNAME}*" fi fi From 30f1d81fd90df67a7bf2e358aad2a72e013e8853 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 19 Mar 2018 17:50:34 -0700 Subject: [PATCH 047/334] Malware scanning via Nikto --- CHANGELOG.md | 1 + deploy.sh | 15 ++++++++------ etc/deploy-example.conf | 11 +++++++++- etc/deploy.sh | 12 ++++++++++- lib/env-check.sh | 5 +++-- lib/main.sh | 2 ++ lib/scan.sh | 45 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 81 insertions(+), 10 deletions(-) create mode 100755 lib/scan.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index d5927f8..51c0ea5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Invoice creation is now available via [InvoiceNinja](https://www.invoiceninja.org/)'s API using `deploy --invoice` +- Added malware scanning using (Nikto](https://www.cirt.net/Nikto2) ### Changed - Running with the `--automate` switch now requires the branch defined as "master" must be currently checked out. The behavior is the equivalent of setting `CHECKBRANCH="${MASTER}"` diff --git a/deploy.sh b/deploy.sh index 2628a87..89a0fcd 100755 --- a/deploy.sh +++ b/deploy.sh @@ -28,12 +28,13 @@ set -uo pipefail read -r APP UPGRADE SKIPUPDATE CURRENT VERBOSE QUIET STRICT DEBUG FORCE \ SLACKTEST FUNCTIONLIST VARIABLELIST AUTOMATE EMAILTEST APPROVE \ DENY PUBLISH DIGEST ANALYTICS ANALYTICSTEST PROJSTATS UNLOCK \ - SSHTEST TIME UPDATEONLY POSTTEST REPORT REPAIR CREATE_INVOICE <<< "" + SSHTEST TIME UPDATEONLY POSTTEST REPORT REPAIR CREATE_INVOICE SCAN <<< "" echo "${APP} ${UPGRADE} ${SKIPUPDATE} ${CURRENT} ${VERBOSE} ${QUIET} ${STRICT} ${DEBUG} ${FORCE} ${SLACKTEST} ${FUNCTIONLIST} ${VARIABLELIST} ${AUTOMATE} ${EMAILTEST} ${APPROVE} ${DENY} ${PUBLISH} ${DIGEST} ${ANALYTICS} ${ANALYTICSTEST} ${PROJSTATS} ${UNLOCK} ${SSHTEST} ${TIME} - ${UPDATEONLY} ${POSTTEST} ${REPORT} ${REPAIR} ${CREATE_INVOICE}" > /dev/null + ${UPDATEONLY} ${POSTTEST} ${REPORT} ${REPAIR} ${CREATE_INVOICE} + ${SCAN}" > /dev/null # Temp files read -r logFile wpFile coreFile postFile trshFile statFile urlFile <<< "" @@ -58,7 +59,7 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ REFRESHTOKEN PROFILEID ALLOWROOT SHORTEMAIL INCOGNITO \ REPORTURL CLIENTCONTACT INCLUDEHOSTING GLOBAL_VERSION USER_VERSION \ - PROJECT_VERSION <<< "" + PROJECT_VERSION NIKTO NIKTO_CONFIG <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} ${DEVGROUP} ${APACHEUSER} ${APACHEGROUP} ${TO} ${FROM} ${SUBJECT} ${EMAILERROR} @@ -72,9 +73,9 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SCPHOSTPATH} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} ${EXPIRELOGS} ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} - ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} - ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} - ${INCLUDEHOSTING} ${GLOBAL_VERSION} ${USER_VERSION} ${PROJECT_VERSION}" > /dev/null + ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} + ${CLIENTCONTACT} ${INCLUDEHOSTING} ${GLOBAL_VERSION} ${USER_VERSION} + ${PROJECT_VERSION} ${NIKTO} ${NIKTO_CONFIG}" > /dev/null # Internal variables read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \ urlFile htmlFile htmlSendmail htmlEmail clientEmail textSendmail deployPath \ @@ -136,6 +137,7 @@ Other Options: --debug Run in debug mode --unlock Delete expired lock files --repair Repair a deployment after key failure + --scan Scan production hosts for malware issues --ssh-test Validate SSH key setup --email-test Test email configuration --slack-test Test Slack integration @@ -208,6 +210,7 @@ while [[ ${1:-unset} = -?* ]]; do --invoice) CREATE_INVOICE="1" ;; --unlock) UNLOCK="1" ;; --repair) REPAIR="1"; FORCE="1"; STASH="TRUE"; VERBOSE="TRUE" ;; + --scan) SCAN="1" ;; --no-check) NOCHECK="1" ;; --function-list) FUNCTIONLIST="1"; CURRENT="1" ;; # Spoofs --current --variable-list) VARIABLELIST="1"; CURRENT="1" ;; # Spoofs --current diff --git a/etc/deploy-example.conf b/etc/deploy-example.conf index 9e1d8b5..b7b49c9 100755 --- a/etc/deploy-example.conf +++ b/etc/deploy-example.conf @@ -7,7 +7,7 @@ # This value indicates the version number when this file was last changed: # it does not necessarily reflect deploy's current version number. # DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP -GLOBAL_VERSION="3.6.7" +GLOBAL_VERSION="3.7" ############################################################################### # General Setup @@ -233,3 +233,12 @@ GLOBAL_VERSION="3.6.7" # Set the number of days before logs should be deleted. Currently this only # works for logs stored on localhost. # EXPIRELOGS="{{EXPIRELOGS}}" + +############################################################################### +# Nikto +############################################################################### + +# If you want to make use of nikto for malware/security host scanning, define +# its full path (including command) as well as its configuration file below +# NIKTO="{{NIKTO}}" +# NIKTO_CONFIG="{{NIKTO_CONFIG}}" diff --git a/etc/deploy.sh b/etc/deploy.sh index ba91d0f..ac72a4b 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -9,7 +9,7 @@ # This value indicates the version number when this file was last changed: # it does not necessarily reflect deploy's current version number. # DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP -PROJECT_VERSION="3.6.7" +PROJECT_VERSION="3.7" ############################################################################### @@ -310,3 +310,13 @@ PROJECT_VERSION="3.6.7" # will be something like https://monitor.com/?&mod=server&action=view&id=3 - in # this case SERVERID would be "3" (notice the &id=3 at the end of the URL) # SERVERID="{{SERVERID}}" + + +############################################################################### +# Nikto +############################################################################### + +# If you want to make use of nikto for malware/security host scanning, define +# its full path (including command) as well as its configuration file below +# NIKTO="{{NIKTO}}" +# NIKTO_CONFIG="{{NIKTO_CONFIG}}" diff --git a/lib/env-check.sh b/lib/env-check.sh index 0897573..9aec05f 100755 --- a/lib/env-check.sh +++ b/lib/env-check.sh @@ -77,7 +77,7 @@ function update_global() { EMAILHTML HTMLTEMPLATE FROMDOMAIN FROMUSER POSTEMAILHEAD POSTEMAILTAIL \ POSTTOSLACK SLACKURL SLACKERROR POSTURL NOPHP INCOGNITO REMOTELOG \ REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS \ - LOCALHOSTPOST LOCALHOSTPATH EXPIRELOGS) + LOCALHOSTPOST LOCALHOSTPATH EXPIRELOGS NIKTO NIKTO_CONFIG) if [[ ! -w "${deployPath}" ]]; then info "Requesting sudo access..." @@ -140,7 +140,8 @@ function update_project() { LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL CLIENTCONTACT INCLUDEHOSTING \ IN_HOST IN_TOKEN IN_CLIENT_ID IN_PRODUCT IN_ITEM_COST IN_ITEM_QTY \ IN_NOTES CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ - REFRESHTOKEN PROFILEID MONITORURL MONITORUSER MONITORPASS SERVERID) + REFRESHTOKEN PROFILEID MONITORURL MONITORUSER MONITORPASS SERVERID NIKTO \ + NIKTO_CONFIG) info "Project configuration backup created at ${project_config}.bak" cp "${project_config}" "${project_config}.bak" diff --git a/lib/main.sh b/lib/main.sh index 3e9025a..f9bf9b0 100755 --- a/lib/main.sh +++ b/lib/main.sh @@ -21,6 +21,8 @@ function main() { elif [[ "${CREATE_INVOICE}" == "1" ]]; then # create_invoice create_invoice + elif [[ "${SCAN}" == "1" ]]; then + scan_host else server_check # Check that servers are up and running if [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then diff --git a/lib/scan.sh b/lib/scan.sh new file mode 100755 index 0000000..7b15fd0 --- /dev/null +++ b/lib/scan.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# +# nikto.sh +# +############################################################################### +# Integrates with nikto to run malware and security scans +############################################################################### + +# Initialize variables +read -r NIKTO_CONFIG <<< "" +echo "${NIKTO_CONFIG}" > /dev/null + +function scan_host() { + if [[ -z "${NIKTO}" ]]; then + return + fi + + # Check for nikto + hash "${NIKTO}" 2>/dev/null || { + error "Can't find scanning command (${NITKO})" + } + + # Get headers + # trace "Header" + # curl -X HEAD -i "${PRODURL}" &>> "${logFile}" + + # Run the scan + trace "Scanning ${PRODURL}..." + "${NIKTO}" -config "${NIKTO_CONFIG}" -nointeractive -ask no -Display VP14 -Tuning x12b -host "${PRODURL}" >> "${logFile}" & + spinner $! + message_state="NOTICE" + LOGTITLE="Malware Scan" + notes="Malware scan on ${PRODURL}" + + # Clean up the log + sed -i 's/^.*Running/Running/' "${logFile}" + sed -i 's/^.*Loaded/Loaded/' "${logFile}" + sed -i '/^V\:/d' "${logFile}" + sed -i '/^Running average\: Not enough data/d' "${logFile}" + sed -i "s/: currently in plugin 'Nikto Tests'//" "${logFile}" + + # Strip redirects, can get very spammy on a Wordpress project + sed -i '/Redirects (301) to/d' "${logFile}" + safeExit +} From 496749573a25c3e3614d398778eb9569a6307259 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 19 Mar 2018 19:00:34 -0700 Subject: [PATCH 048/334] Added simple file deployment method using scp --- CHANGELOG.md | 1 + etc/deploy.sh | 23 ++++++++++++++++++++++- lib/deployment.sh | 40 ++++++++++++++++++++++++++++++---------- lib/env-check.sh | 7 ++++--- lib/scp.sh | 25 +++++++++++++++++++++++++ lib/server-check.sh | 2 +- lib/utilities.sh | 2 +- 7 files changed, 84 insertions(+), 16 deletions(-) create mode 100755 lib/scp.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c0ea5..4f6cebe 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Invoice creation is now available via [InvoiceNinja](https://www.invoiceninja.org/)'s API using `deploy --invoice` +- Added a very simple built-in web deployment method for instances when using something like `mina` is unavailble - Added malware scanning using (Nikto](https://www.cirt.net/Nikto2) ### Changed - Running with the `--automate` switch now requires the branch defined as "master" must be currently checked out. The behavior is the equivalent of setting `CHECKBRANCH="${MASTER}"` diff --git a/etc/deploy.sh b/etc/deploy.sh index ac72a4b..687c4d9 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -104,7 +104,8 @@ PROJECT_VERSION="3.7" # Deployment Configuration ############################################################################### -# The command to finalize deployment of your project(s) +# The command to finalize deployment of your project(s); set DEPLOY="SCP" to +# use the built-in scp deployment method. # DEPLOY="{{DEPLOY}}" # To require approval before pushing this project's code to a live production @@ -114,6 +115,26 @@ PROJECT_VERSION="3.7" # Disallow deployment; set to TRUE to enable. Double negative, it's tricky. # DONOTDEPLOY="{{DONOTDEPLOY}}" +# SCP Deployment +# -------------- + +# Staging file path to copy to production host, relative to the project's root +# directory (forward slash required) +# STAGING_DEPLOY_PATH="{{STAGING_DEPLOY_PATH}}" + +# Production host info +# PRODUCTION_DEPLOY_HOST="{{PRODUCTION_DEPLOY_HOST}}" + +# Full path path to copy files to on production server +# PRODUCTION_DEPLOY_PATH="{{PRODUCTION_DEPLOY_PATH}}" + +# Deployement user info +# SCP_DEPLOY_USER="{{SCP_DEPLOY_USER}}" + +# DANGER DANGER: If for some reason you absolutely can't use an SSH key you +# can configure the path to a text file containing *only* your password. +# SCP_DEPLOY_PASS="{{SCP_DEPLOY_PASS}}" + ############################################################################### # Notifications diff --git a/lib/deployment.sh b/lib/deployment.sh index 8c2805f..3b5754f 100755 --- a/lib/deployment.sh +++ b/lib/deployment.sh @@ -63,27 +63,47 @@ function pkgDeploy() { fi fix_index - # Make sure the project's deploy command is going to work - deploy_cmd=$(echo "${DEPLOY}" | awk '{print $1;}') - hash "${deploy_cmd}" 2>/dev/null || { - warning "Your deployment command ${deploy_cmd} cannot be found."; - } + if [[ "${DEPLOY}" != "SCP" ]]; then + # Make sure the project's deploy command is going to work + deploy_cmd=$(echo "${DEPLOY}" | awk '{print $1;}') + hash "${deploy_cmd}" 2>/dev/null || { + warning "Your deployment command ${deploy_cmd} cannot be found."; + } + fi if [[ "${REQUIREAPPROVAL}" != "TRUE" ]]; then + # If we don't require approval to push to live, keep going if [[ "${FORCE}" = "1" ]] || yesno --default yes "Deploy to live server? [Y/n] "; then + # Test deployment command before running - deploy_check + if [[ "${DEPLOY}" != "SCP" ]]; then + deploy_check + fi + # Deploy via deployment command specified in configuration if [[ "${VERBOSE}" == "TRUE" ]] && [[ "${INCOGNITO}" != "TRUE" ]]; then - eval "${DEPLOY}" | tee --append "${logFile}" + if [[ "${DEPLOY}" == "SCP" ]]; then + deploy_scp + else + eval "${DEPLOY}" | tee --append "${logFile}" + fi error_check else if [[ "${QUIET}" != "1" ]]; then - eval "${DEPLOY}" &>> "${logFile}" & - spinner $! + if [[ "${DEPLOY}" == "SCP" ]]; then + deploy_scp & + spinner $! + else + eval "${DEPLOY}" &>> "${logFile}" & + spinner $! + fi else - eval "${DEPLOY}" &>> "${logFile}" + if [[ "${DEPLOY}" == "SCP" ]]; then + deploy_scp &>> "${logFile}" + else + eval "${DEPLOY}" &>> "${logFile}" + fi error_check fi fi diff --git a/lib/env-check.sh b/lib/env-check.sh index 9aec05f..65a695f 100755 --- a/lib/env-check.sh +++ b/lib/env-check.sh @@ -3,7 +3,7 @@ # config-check.sh # ############################################################################### -# Check to see if configuration files needs to be updated, and update if so +# Check to see if configuration files need to be updated, and update if so ############################################################################### # Initialize variables @@ -141,8 +141,9 @@ function update_project() { IN_HOST IN_TOKEN IN_CLIENT_ID IN_PRODUCT IN_ITEM_COST IN_ITEM_QTY \ IN_NOTES CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ REFRESHTOKEN PROFILEID MONITORURL MONITORUSER MONITORPASS SERVERID NIKTO \ - NIKTO_CONFIG) - + NIKTO_CONFIG STAGING_DEPLOY_PATH PRODUCTION_DEPLOY_HOST \ + PRODUCTION_DEPLOY_PATH SCP_DEPLOY_USER SCP_DEPLOY_PASS) + info "Project configuration backup created at ${project_config}.bak" cp "${project_config}" "${project_config}.bak" cp "${deployPath}"/deploy.sh "${trshFile}" diff --git a/lib/scp.sh b/lib/scp.sh new file mode 100755 index 0000000..e3b449f --- /dev/null +++ b/lib/scp.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# scp.sh +# +############################################################################### +# Extremely simple deployment via scp +############################################################################### + +# Initialize variables +read -r STAGING_DEPLOY_PATH PRODUCTION_DEPLOY_HOST PRODUCTION_DEPLOY_PATH \ + SCP_DEPLOY_USER SCP_DEPLOY_PASS <<< "" +echo "${STAGING_DEPLOY_PATH} ${PRODUCTION_DEPLOY_HOST} ${PRODUCTION_DEPLOY_PATH} + ${ SCP_DEPLOY_USER} ${SCP_DEPLOY_PASS}" > /dev/null + +function deploy_scp() { + # This is ghetto + if [[ -n "${PRODUCTION_DEPLOY_PATH}" ]]; then + # Todo: Add proper checks/fallbacks here + TMP=$(<$SCP_DEPLOY_PASS) + SCP_DEPLOY_PASS="${TMP}" + sshpass -p "${SCP_DEPLOY_PASS}" scp -o StrictHostKeyChecking=no -r -v "${WORKPATH}/${APP}/${STAGING_DEPLOY_PATH}"/* "${SCP_DEPLOY_USER}@${PRODUCTION_DEPLOY_HOST}:${PRODUCTION_DEPLOY_PATH}/" &>> "${logFile}"; error_check; + else + echo "ERROR: Can not find production server path"; exit 1 + fi +} diff --git a/lib/server-check.sh b/lib/server-check.sh index 0a1d83a..ab9e09e 100755 --- a/lib/server-check.sh +++ b/lib/server-check.sh @@ -8,7 +8,7 @@ trace "Loading server checks" function server_check() { - if [ "${SERVERCHECK}" == "TRUE" ]; then + if [[ "${SERVERCHECK}" == "TRUE" ]]; then notice "Checking servers..." # Set SERVERFAIL to 0 SERVERFAIL="0" diff --git a/lib/utilities.sh b/lib/utilities.sh index d844217..a238c2c 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -178,7 +178,7 @@ function dependency_check() { # This is probably not the best way to do this but for now it works. It # strips everything after the first space that is declared in DEPLOY and # then checks that it's a valid command. - if [[ ! -z "${DEPLOY}" ]]; then + if [[ ! -z "${DEPLOY}" ]] && [[ "${DEPLOY}" != "SCP" ]]; then deploy_cmd=$(echo "$DEPLOY" | head -n1 | awk '{print $1;}') hash "${deploy_cmd}" 2>/dev/null || { error >&2 "Unknown deployment command: ${DEPLOY} (${deploy_cmd} not found)"; From 6d926f05822b59bf0ff38989251dc9bd3dcc008e Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 19 Mar 2018 19:13:27 -0700 Subject: [PATCH 049/334] Resolve #131 --- deploy.sh | 5 ----- lib/main.sh | 1 + lib/utilities.sh | 6 ++++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/deploy.sh b/deploy.sh index 89a0fcd..f4abe17 100755 --- a/deploy.sh +++ b/deploy.sh @@ -481,11 +481,6 @@ if [[ "${AUTOMERGE}" == "TRUE" ]]; then trace "Automerge is enabled" fi -if [[ "${NOCHECK}" == "1" ]]; then - SERVERCHECK="FALSE"; - ACTIVECHECK="FALSE" -fi - # If using --automate, force branch checking if [[ "${AUTOMATE}" == "1" ]] || [[ "${REPAIR}" == "1" ]]; then CHECKBRANCH="${MASTER}"; diff --git a/lib/main.sh b/lib/main.sh index f9bf9b0..8bb0a9b 100755 --- a/lib/main.sh +++ b/lib/main.sh @@ -25,6 +25,7 @@ function main() { scan_host else server_check # Check that servers are up and running + if [[ "${DISABLESSHCHECK}" != "TRUE" ]]; then ssh_check # Check keys fi diff --git a/lib/utilities.sh b/lib/utilities.sh index a238c2c..15d10ae 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -84,6 +84,12 @@ function go() { warning "Can't continue deployment as root."; quickExit fi + # Disallow server check? + if [[ "${NOCHECK}" == "1" ]]; then + SERVERCHECK="FALSE"; + ACTIVECHECK="FALSE" + fi + # Force sudo password input if needed if [[ "${FIXPERMISSIONS}" == "TRUE" ]]; then sudo sleep 1 From b028432c00f7f85e5b5b9047a9d977b3b0cc27dd Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 23 Mar 2018 14:55:52 -0700 Subject: [PATCH 050/334] Resolve #132 --- CHANGELOG.md | 1 + deploy.sh | 8 ++++---- etc/deploy-example.conf | 3 ++- etc/deploy.sh | 4 ++++ lib/env-check.sh | 23 ++++++++++++----------- lib/log-handling.sh | 7 +++++++ lib/post-log.sh | 20 +++++++++++++++----- lib/scan.sh | 4 ++-- lib/scp.sh | 11 ++++++++--- 9 files changed, 55 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f6cebe..0e504ed 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added a very simple built-in web deployment method for instances when using something like `mina` is unavailble - Added malware scanning using (Nikto](https://www.cirt.net/Nikto2) ### Changed +- Ports other than 22 can be used for SSH/SCP functions - Running with the `--automate` switch now requires the branch defined as "master" must be currently checked out. The behavior is the equivalent of setting `CHECKBRANCH="${MASTER}"` ## [3.6.7] - 03-05-2018 diff --git a/deploy.sh b/deploy.sh index f4abe17..b2a7fc6 100755 --- a/deploy.sh +++ b/deploy.sh @@ -56,10 +56,10 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ WFOFF REMOTELOG REMOTETEMPLATE LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL DIGESTSLACK \ DIGESTURL CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD \ SSHCMD LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME \ - TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ + SCPPORT TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ REFRESHTOKEN PROFILEID ALLOWROOT SHORTEMAIL INCOGNITO \ REPORTURL CLIENTCONTACT INCLUDEHOSTING GLOBAL_VERSION USER_VERSION \ - PROJECT_VERSION NIKTO NIKTO_CONFIG <<< "" + PROJECT_VERSION <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} ${DEVGROUP} ${APACHEUSER} ${APACHEGROUP} ${TO} ${FROM} ${SUBJECT} ${EMAILERROR} @@ -72,10 +72,10 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${CLIENTLOGO} ${REMOTEURL} ${SCPPOST} ${SCPUSER} ${SCPHOST} ${SCPHOSTPATH} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} ${EXPIRELOGS} ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} - ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} + ${SCPPORT} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} ${INCLUDEHOSTING} ${GLOBAL_VERSION} ${USER_VERSION} - ${PROJECT_VERSION} ${NIKTO} ${NIKTO_CONFIG}" > /dev/null + ${PROJECT_VERSION}" > /dev/null # Internal variables read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \ urlFile htmlFile htmlSendmail htmlEmail clientEmail textSendmail deployPath \ diff --git a/etc/deploy-example.conf b/etc/deploy-example.conf index b7b49c9..eb6502f 100755 --- a/etc/deploy-example.conf +++ b/etc/deploy-example.conf @@ -219,7 +219,8 @@ GLOBAL_VERSION="3.7" # SCPUSER="{{SCPUSER}}" # SCPHOST="{{SCPHOST}}" # SCPHOSTPATH="{{SCPHOSTPATH}}" -# +# SCPPORT="{{SCPPORT}}" + # DANGER DANGER: If for some reason you absolutely can't use an SSH key you # can configure your password here # SCPPASS="{{SCPPASS}}" diff --git a/etc/deploy.sh b/etc/deploy.sh index 687c4d9..8d40184 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -135,6 +135,9 @@ PROJECT_VERSION="3.7" # can configure the path to a text file containing *only* your password. # SCP_DEPLOY_PASS="{{SCP_DEPLOY_PASS}}" +# Set your port number if using a port other than the standard 22 +# SCP_DEPLOY_PORT="{{SCP_DEPLOY_PORT}}" + ############################################################################### # Notifications @@ -229,6 +232,7 @@ PROJECT_VERSION="3.7" # SCPUSER="{{SCPUSER}}" # SCPHOST="{{SCPHOST}}" # SCPHOSTPATH="{{SCPHOSTPATH}}" +# SCPPORT="{{SCPPORT}}" # DANGER DANGER: If for some reason you absolutely can't use an SSH key you # can configure the path to a text file containing *only* your password. diff --git a/lib/env-check.sh b/lib/env-check.sh index 65a695f..635086b 100755 --- a/lib/env-check.sh +++ b/lib/env-check.sh @@ -57,7 +57,7 @@ function env_check() { } function update_config() { - info "\r\nNew version (${VERSION}) requires configuration updates." + empty_line; info "New version (${VERSION}) requires configuration updates." if yesno --default yes "Update now? [Y/n] "; then [[ "${update_global}" == "1" ]] && update_global [[ "${update_user}" == "1" ]] && update_user @@ -76,8 +76,8 @@ function update_global() { MAILPATH TO FROM SUBJECT EMAILERROR EMAILSUCCESS EMAILQUIT SHORTEMAIL \ EMAILHTML HTMLTEMPLATE FROMDOMAIN FROMUSER POSTEMAILHEAD POSTEMAILTAIL \ POSTTOSLACK SLACKURL SLACKERROR POSTURL NOPHP INCOGNITO REMOTELOG \ - REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS \ - LOCALHOSTPOST LOCALHOSTPATH EXPIRELOGS NIKTO NIKTO_CONFIG) + REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPORT \ + SCPPASS LOCALHOSTPOST LOCALHOSTPATH EXPIRELOGS NIKTO NIKTO_CONFIG) if [[ ! -w "${deployPath}" ]]; then info "Requesting sudo access..." @@ -136,13 +136,13 @@ function update_project() { WPROOT WPAPP WPSYSTEM DONOTUPDATEWP ACFKEY DEPLOY REQUIREAPPROVAL \ DONOTDEPLOY TASK TASKUSER ADDTIME POSTTOSLACK SLACKURL SLACKERROR \ DIGESTSLACK POSTURL TO HTMLTEMPLATE CLIENTLOGO COVER INCOGNITO REMOTELOG \ - REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS \ - LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL CLIENTCONTACT INCLUDEHOSTING \ - IN_HOST IN_TOKEN IN_CLIENT_ID IN_PRODUCT IN_ITEM_COST IN_ITEM_QTY \ - IN_NOTES CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ - REFRESHTOKEN PROFILEID MONITORURL MONITORUSER MONITORPASS SERVERID NIKTO \ - NIKTO_CONFIG STAGING_DEPLOY_PATH PRODUCTION_DEPLOY_HOST \ - PRODUCTION_DEPLOY_PATH SCP_DEPLOY_USER SCP_DEPLOY_PASS) + REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPORT\ + SCPPASS LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL CLIENTCONTACT \ + INCLUDEHOSTING IN_HOST IN_TOKEN IN_CLIENT_ID IN_PRODUCT IN_ITEM_COST \ + IN_ITEM_QTY IN_NOTES CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE \ + ACCESSTOKEN REFRESHTOKEN PROFILEID MONITORURL MONITORUSER MONITORPASS \ + SERVERID NIKTO NIKTO_CONFIG STAGING_DEPLOY_PATH PRODUCTION_DEPLOY_HOST \ + PRODUCTION_DEPLOY_PATH SCP_DEPLOY_USER SCP_DEPLOY_PASS SCP_DEPLOY_PORT) info "Project configuration backup created at ${project_config}.bak" cp "${project_config}" "${project_config}.bak" @@ -159,7 +159,8 @@ function update_project() { } function insert_values() { - if [[ -n "${!i}" ]]; then + if [[ -n "${!i:-}" ]]; then + [[ "${INCOGNITO}" != "1" ]] && trace "${i}: ${!i}" sed_hack=$(echo "sed -i 's^{{${i}}}^${!i}^g' ${trshFile}; sed -i 's^# ${i}^${i}^g' ${trshFile}") # Kludgy but works. Ugh. eval "${sed_hack}" diff --git a/lib/log-handling.sh b/lib/log-handling.sh index 9439f78..53a0940 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -58,6 +58,13 @@ function makeLog() { # sed -i "/........../d" "${logFile}" fi + # Filter out noise from scp deployment method + if [[ "${DEPLOY}" == "SCP" ]]; then + sed -i "/Sending file modes:/d" "${logFile}" + sed -i "/Sink:/d" "${logFile}" + sed -i "/debug1:/d" "${logFile}" + fi + # Filter raw log output as configured by user if [[ "${NOPHP}" == "TRUE" ]]; then grep -vE "(PHP |Notice:|Warning:|Strict Standards:)" "${logFile}" > "${postFile}" diff --git a/lib/post-log.sh b/lib/post-log.sh index 958b15a..94b2080 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -7,6 +7,10 @@ ############################################################################### trace "Loading post functions" +# Initialize variables +read -r SCPPORT <<< "" +echo "${SCPPORT}" > /dev/null + # Remote log function; this really needs to be rewritten function postLog() { if [[ "${REMOTELOG}" == "TRUE" ]]; then @@ -56,18 +60,23 @@ function postLog() { fi fi - # Send the files through SCP (not yet enabled) + # Send the files through SSH/SCP if [[ "${SCPPOST}" == "TRUE" ]] && [[ -f "${htmlFile}" ]]; then + # if SCPPORT is empty, set it to 22 + #if [[ -z "${SCPPORT}" ]]; then + # SCPPORT="22" + #fi + # Setup up the proper command, depending on whether we're using key or password if [[ -n "${SCPPASS}" ]]; then TMP=$(<$SCPPASS) SCPPASS="${TMP}" - SCPCMD="sshpass -p \"${SCPPASS}\" scp -o StrictHostKeyChecking=no" - SSHCMD="sshpass -p \"${SCPPASS}\" ssh" + SCPCMD="sshpass -p \"${SCPPASS}\" scp -o StrictHostKeyChecking=no -P \"${SCPPORT}\"" + SSHCMD="sshpass -p \"${SCPPASS}\" ssh -p \"${SCPPORT}\"" else - SCPCMD="scp" - SSHCMD="ssh" + SCPCMD="scp -P \"${SCPPORT}\"" + SSHCMD="ssh -P \"${SCPPORT}\"" fi # Loop through the various scenarios, make directories if needed @@ -96,6 +105,7 @@ function postLog() { if [[ "${REPORT}" == "1" ]]; then REMOTEFILE="${CURYR}-${CURMTH}.php" REPORTURL="${REMOTEURL}/${APP}/report/${REMOTEFILE}" + trace "Running \"${SSHCMD}\" \"${SCPUSER}\"@\"${SCPHOST}\" \"mkdir -p ${SCPHOSTPATH}/${APP}/report\"" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report" eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/report/css" eval "${SCPCMD} -r" "${deployPath}/html/${HTMLTEMPLATE}/report/css" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/report" diff --git a/lib/scan.sh b/lib/scan.sh index 7b15fd0..39e76b5 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -7,8 +7,8 @@ ############################################################################### # Initialize variables -read -r NIKTO_CONFIG <<< "" -echo "${NIKTO_CONFIG}" > /dev/null +read -r NIKTO NIKTO_CONFIG <<< "" +echo "${NIKTO} ${NIKTO_CONFIG}" > /dev/null function scan_host() { if [[ -z "${NIKTO}" ]]; then diff --git a/lib/scp.sh b/lib/scp.sh index e3b449f..b703eb0 100755 --- a/lib/scp.sh +++ b/lib/scp.sh @@ -10,15 +10,20 @@ read -r STAGING_DEPLOY_PATH PRODUCTION_DEPLOY_HOST PRODUCTION_DEPLOY_PATH \ SCP_DEPLOY_USER SCP_DEPLOY_PASS <<< "" echo "${STAGING_DEPLOY_PATH} ${PRODUCTION_DEPLOY_HOST} ${PRODUCTION_DEPLOY_PATH} - ${ SCP_DEPLOY_USER} ${SCP_DEPLOY_PASS}" > /dev/null + ${SCP_DEPLOY_USER} ${SCP_DEPLOY_PASS}" > /dev/null function deploy_scp() { # This is ghetto if [[ -n "${PRODUCTION_DEPLOY_PATH}" ]]; then + # if SCPPORT is empty, set it to 22 + if [[ -z "${SCP_DEPLOY_PORT}" ]]; then + SCP_DEPLOY_PORT="22" + fi + # Todo: Add proper checks/fallbacks here TMP=$(<$SCP_DEPLOY_PASS) - SCP_DEPLOY_PASS="${TMP}" - sshpass -p "${SCP_DEPLOY_PASS}" scp -o StrictHostKeyChecking=no -r -v "${WORKPATH}/${APP}/${STAGING_DEPLOY_PATH}"/* "${SCP_DEPLOY_USER}@${PRODUCTION_DEPLOY_HOST}:${PRODUCTION_DEPLOY_PATH}/" &>> "${logFile}"; error_check; + SCP_DEPLOY_PASS="${TMP}" + sshpass -p "${SCP_DEPLOY_PASS}" scp -o StrictHostKeyChecking=no -P "${SCP_DEPLOY_PORT}" -r -v "${WORKPATH}/${APP}/${STAGING_DEPLOY_PATH}"/* "${SCP_DEPLOY_USER}@${PRODUCTION_DEPLOY_HOST}:${PRODUCTION_DEPLOY_PATH}/" &>> "${logFile}"; error_check; else echo "ERROR: Can not find production server path"; exit 1 fi From f19ce754588773879a7282ceb9eaa244feacf443 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 4 Apr 2018 09:21:02 -0700 Subject: [PATCH 051/334] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e504ed..d879fb1 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Invoice creation is now available via [InvoiceNinja](https://www.invoiceninja.org/)'s API using `deploy --invoice` - Added a very simple built-in web deployment method for instances when using something like `mina` is unavailble -- Added malware scanning using (Nikto](https://www.cirt.net/Nikto2) +- Added malware scanning using [Nikto](https://www.cirt.net/Nikto2) ### Changed - Ports other than 22 can be used for SSH/SCP functions - Running with the `--automate` switch now requires the branch defined as "master" must be currently checked out. The behavior is the equivalent of setting `CHECKBRANCH="${MASTER}"` From b439d57265bb40d659f57260f20915c6f842775c Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 4 Apr 2018 20:06:25 -0700 Subject: [PATCH 052/334] Scan reports --- deploy.sh | 14 +++-- etc/deploy-example.conf | 9 --- etc/html/default/digest/header.html | 8 +++ etc/html/default/scan/footer.html | 20 ++++++ etc/html/default/scan/header.html | 80 ++++++++++++++++++++++++ etc/html/default/stats/index.html | 9 ++- etc/html/default/theme.conf | 2 +- install/doinst.sh | 2 +- lib/log-handling.sh | 19 +++--- lib/main.sh | 2 +- lib/monitor.sh | 2 +- lib/post-log.sh | 5 ++ lib/process-html.sh | 10 ++- lib/scan.sh | 95 ++++++++++++++++++++++++----- lib/utilities.sh | 7 ++- 15 files changed, 233 insertions(+), 51 deletions(-) create mode 100755 etc/html/default/scan/footer.html create mode 100755 etc/html/default/scan/header.html diff --git a/deploy.sh b/deploy.sh index b2a7fc6..fd08072 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.7-alpha.1" +VERSION="3.7-beta.1" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" @@ -37,12 +37,13 @@ echo "${APP} ${UPGRADE} ${SKIPUPDATE} ${CURRENT} ${VERBOSE} ${QUIET} ${STRICT} ${SCAN}" > /dev/null # Temp files -read -r logFile wpFile coreFile postFile trshFile statFile urlFile <<< "" +read -r logFile wpFile coreFile postFile trshFile scanFile statFile \ + urlFile <<< "" echo "${logFile} ${wpFile} ${coreFile} ${postFile} ${trshFile} ${statFile} - ${urlFile}" > /dev/null + ${scanFile} ${urlFile}" > /dev/null # Console Colors -read -r black red green yellow blue magenta cyan white endColor bold underline \ - reset purple tan <<< "" +read -r black red green yellow blue magenta cyan white endColor bold \ + underline reset purple tan <<< "" echo "${black} ${red} ${green} ${yellow} ${blue} ${magenta} ${cyan} ${white} ${endColor} ${bold} ${underline} ${reset} ${purple} ${tan}" > /dev/null @@ -210,7 +211,7 @@ while [[ ${1:-unset} = -?* ]]; do --invoice) CREATE_INVOICE="1" ;; --unlock) UNLOCK="1" ;; --repair) REPAIR="1"; FORCE="1"; STASH="TRUE"; VERBOSE="TRUE" ;; - --scan) SCAN="1" ;; + --scan) SCAN="1"; FORCE="1" ;; --no-check) NOCHECK="1" ;; --function-list) FUNCTIONLIST="1"; CURRENT="1" ;; # Spoofs --current --variable-list) VARIABLELIST="1"; CURRENT="1" ;; # Spoofs --current @@ -287,6 +288,7 @@ echo -e "Launching deploy${STARTUP}\n" >> "${logFile}" # More crappy tmp files postFile="/tmp/${APP}.wtf-$RANDOM.log"; (umask 077 && touch "${postFile}" &> /dev/null) || log_fail trshFile="/tmp/${APP}.trsh-$RANDOM.log"; (umask 077 && touch "${trshFile}" &> /dev/null) || log_fail +scanFile="/tmp/${APP}.scan-$RANDOM.log"; (umask 077 && touch "${scanFile}" &> /dev/null) || log_fail statFile="/tmp/${APP}.stat-$RANDOM.log"; (umask 077 && touch "${statFile}" &> /dev/null) || log_fail urlFile="/tmp/${APP}.url-$RANDOM.log"; (umask 077 && touch "${urlFile}" &> /dev/null) || log_fail htmlFile="/tmp/${APP}.log-$RANDOM.html"; (umask 077 && touch "${htmlFile}" &> /dev/null) || log_fail diff --git a/etc/deploy-example.conf b/etc/deploy-example.conf index eb6502f..7718644 100755 --- a/etc/deploy-example.conf +++ b/etc/deploy-example.conf @@ -234,12 +234,3 @@ GLOBAL_VERSION="3.7" # Set the number of days before logs should be deleted. Currently this only # works for logs stored on localhost. # EXPIRELOGS="{{EXPIRELOGS}}" - -############################################################################### -# Nikto -############################################################################### - -# If you want to make use of nikto for malware/security host scanning, define -# its full path (including command) as well as its configuration file below -# NIKTO="{{NIKTO}}" -# NIKTO_CONFIG="{{NIKTO_CONFIG}}" diff --git a/etc/html/default/digest/header.html b/etc/html/default/digest/header.html index 79a8391..edbfd42 100755 --- a/etc/html/default/digest/header.html +++ b/etc/html/default/digest/header.html @@ -79,7 +79,15 @@

{{PROJCLIENT}}
+ +
+ {{SCAN_MSG}} ✔ +
Uptime: {{UPTIME}}%
+
Latency: {{LATENCY}}s
+
+

{{GREETING}}

{{ANALYTICSMSG}}

+

Here's a summary of code updates made to your website last week:

\ No newline at end of file diff --git a/etc/html/default/scan/footer.html b/etc/html/default/scan/footer.html new file mode 100755 index 0000000..c392ea8 --- /dev/null +++ b/etc/html/default/scan/footer.html @@ -0,0 +1,20 @@ +
+ + + + + + + + + + + + + diff --git a/etc/html/default/scan/header.html b/etc/html/default/scan/header.html new file mode 100755 index 0000000..fa20367 --- /dev/null +++ b/etc/html/default/scan/header.html @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + @@ -164,7 +167,7 @@

{{GA_SEARCHES}}

-

vistors from organic searches

+

hits from organic searches

+

Site Scan BETA

+ + +

Report generated {{NOW}}

+

{{PROJNAME}} + ({{PRODURL}}) +

+ +
{{PROJCLIENT}}
+
+ +

{{SCAN_RESULT}}

+
{{SCAN_MSG}} ✔
+
Uptime: {{UPTIME}}%
+
Latency: {{LATENCY}}s
+
+
\ No newline at end of file diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index e89adb9..5bce26a 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -68,8 +68,11 @@

-

Weekly overview

Uptime: {{UPTIME}}%
-
Latency: {{LATENCY}}s
+

Weekly overview

+ {{SCAN_MSG}} ✔ +
Uptime: {{UPTIME}}%
+
Latency: {{LATENCY}}s
+
diff --git a/etc/html/default/theme.conf b/etc/html/default/theme.conf index 979de25..65184bf 100755 --- a/etc/html/default/theme.conf +++ b/etc/html/default/theme.conf @@ -7,7 +7,7 @@ # Setup theme colors DEFAULTC="#555555" PRIMARYC="#47ACDF" -SUCCESSC="#47ACDF" +SUCCESSC="#28a745" INFOC="#5D6D7E" WARNINGC="#E37222" DANGERC="#CC2233" diff --git a/install/doinst.sh b/install/doinst.sh index f941615..75c8b14 100755 --- a/install/doinst.sh +++ b/install/doinst.sh @@ -87,7 +87,7 @@ else fi # Declare dependencies -dependencies=(awk cal cat curl echo eval git grep pkill printf read sed sendmail sleep tput) +dependencies=(awk cal cat curl echo eval git grep pkill printf read sed sendmail sleep tee tput) # Declare optional stuff options=(gitchart grunt npm scp ssh sshpass wget wkhtmltopdf wp) diff --git a/lib/log-handling.sh b/lib/log-handling.sh index 53a0940..6c25be0 100755 --- a/lib/log-handling.sh +++ b/lib/log-handling.sh @@ -163,18 +163,19 @@ function build_html() { if [[ "${PUBLISH}" == "1" ]]; then LOGURL="${REMOTEURL}/${APP}/${EPOCH}.${LOGSUFFIX}" REMOTEFILE="${EPOCH}.${LOGSUFFIX}" - else - if [[ "${message_state}" == "APPROVAL NEEDED" ]]; then + elif [[ "${SCAN}" == "1" ]]; then + LOGURL="${REMOTEURL}/${APP}/scan" + REMOTEFILE="index.html" + elif [[ "${message_state}" == "APPROVAL NEEDED" ]]; then LOGURL="${REMOTEURL}/${APP}/APPROVAL-${EPOCH}.${LOGSUFFIX}" REMOTEFILE="APPROVAL-${EPOCH}.${LOGSUFFIX}" + else + if [[ "${message_state}" != "SUCCESS" ]] || [[ -z "${COMMITHASH}" ]]; then + LOGURL="${REMOTEURL}/${APP}/${message_state}-${EPOCH}.${LOGSUFFIX}" + REMOTEFILE="${message_state}-${EPOCH}.${LOGSUFFIX}" else - if [[ "${message_state}" != "SUCCESS" ]] || [[ -z "${COMMITHASH}" ]]; then - LOGURL="${REMOTEURL}/${APP}/${message_state}-${EPOCH}.${LOGSUFFIX}" - REMOTEFILE="${message_state}-${EPOCH}.${LOGSUFFIX}" - else - LOGURL="${REMOTEURL}/${APP}/${COMMITHASH}.${LOGSUFFIX}" - REMOTEFILE="${COMMITHASH}.${LOGSUFFIX}" - fi + LOGURL="${REMOTEURL}/${APP}/${COMMITHASH}.${LOGSUFFIX}" + REMOTEFILE="${COMMITHASH}.${LOGSUFFIX}" fi fi diff --git a/lib/main.sh b/lib/main.sh index 8bb0a9b..9f42631 100755 --- a/lib/main.sh +++ b/lib/main.sh @@ -9,7 +9,7 @@ function main() { dependency_check # Check that required commands are available release_check # Check for newer version at Github - env_check # Check for configuration files that need updating + env_check # Check for configuration files that need updating gitStart # Check for a valid git project and get set up lock # Create lock file go # Start a deployment work session diff --git a/lib/monitor.sh b/lib/monitor.sh index 240a6c5..5d6e9ed 100755 --- a/lib/monitor.sh +++ b/lib/monitor.sh @@ -18,7 +18,7 @@ function server_monitor() { # for. The PHP Monitor API has no ability to look up results in that way MONITORHOURS="720" elif [[ "${DIGEST}" == "1" ]] || [[ "${PROJSTATS}" == "1" ]]; then - # Digests and reports should average the 7 days + # Digests and stats should average 7 days MONITORHOURS="168" else # Anything else defaults to 24 hours diff --git a/lib/post-log.sh b/lib/post-log.sh index 94b2080..c0cf047 100755 --- a/lib/post-log.sh +++ b/lib/post-log.sh @@ -102,6 +102,11 @@ function postLog() { fi fi + if [[ "${SCAN}" == "1" ]]; then + eval "${SSHCMD}" "${SCPUSER}"@"${SCPHOST}" "mkdir -p ${SCPHOSTPATH}/${APP}/scan" + eval "${SCPCMD} -r" "${scan_html}" "${SCPUSER}"@"${SCPHOST}":"${SCPHOSTPATH}/${APP}/scan/index.html" + fi + if [[ "${REPORT}" == "1" ]]; then REMOTEFILE="${CURYR}-${CURMTH}.php" REPORTURL="${REMOTEURL}/${APP}/report/${REMOTEFILE}" diff --git a/lib/process-html.sh b/lib/process-html.sh index 8dd5d47..2eca0b1 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -8,9 +8,10 @@ trace "Loading html handling" # Initialize variables -read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID COVER <<< "" +read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID COVER \ + SCANC <<< "" echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} - ${SMOOCHID} ${COVER}" > /dev/null + ${SMOOCHID} ${COVER} ${SCANC}" > /dev/null function process_html() { # Clean out the stuff we don't need @@ -18,6 +19,7 @@ function process_html() { [[ -z "${PRODURL}" ]] && sed -i '/PRODURL/d' "${htmlFile}" [[ -z "${UPTIME}" ]] && sed -i '/UPTIME/d' "${htmlFile}" [[ -z "${LATENCY}" ]] && sed -i '/LATENCY/d' "${htmlFile}" + [[ -z "${SCAN_MSG}" ]] && sed -i '/SCAN_MSG/d' "${htmlFile}" [[ -z "${PROJCLIENT}" ]] && sed -i 's/()//' "${htmlFile}" [[ -z "${CLIENTLOGO}" ]] && sed -i '/CLIENTLOGO/d' "${htmlFile}" [[ -z "${CLIENTCONTACT}" ]] && sed -i '/CLIENTCONTACT/d' "${htmlFile}" @@ -59,6 +61,10 @@ function process_html() { -e "s^{{INFO}}^${INFOC}^g" \ -e "s^{{WARNING}}^${WARNINGC}^g" \ -e "s^{{DANGER}}^${DANGERC}^g" \ + -e "s^{{SCAN}}^${SCANC}^g" \ + -e "s^{{SCAN_MSG}}^${SCAN_MSG}^g" \ + -e "s^{{SCAN_RESULT}}^${SCAN_RESULT}^g" \ + -e "s^{{SCAN_URL}}^${SCAN_URL}^g" \ -e "s^{{SMOOCHID}}^${SMOOCHID}^g" \ -e "s^{{GRAVATARURL}}^${REMOTEURL}\/${APP}\/avatar^g" \ -e "s^{{DIGESTWRAP}}^${DIGESTWRAP}^g" \ diff --git a/lib/scan.sh b/lib/scan.sh index 39e76b5..5dad033 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -5,14 +5,34 @@ ############################################################################### # Integrates with nikto to run malware and security scans ############################################################################### +trace "Loading malware scanning utilities" # Initialize variables -read -r NIKTO NIKTO_CONFIG <<< "" -echo "${NIKTO} ${NIKTO_CONFIG}" > /dev/null +read -r NIKTO NIKTO_CONFIG scan_html SCAN_RESULT SCAN_MSG SCAN_URL <<< "" +echo "${NIKTO} ${NIKTO_CONFIG} ${scan_html} ${SCAN_RESULT} ${SCAN_MSG} + ${SCAN_URL}" > /dev/null + +function scan_check() { + if [[ -n "${PRODURL}" ]] || [[ -n "${NIKTO}" ]]; then + SCAN_URL="${REMOTEURL}/${APP}/scan/" + + SCAN_RESULT=$(echo "${SCAN_MSG//
}") + if curl -Is "${SCAN_URL}" | grep -q "200"; then + if curl -s "${SCAN_URL}" | grep -q "0 error"; then + SCANC="${SUCCESSC}" + SCAN_MSG="Scan Passed" + elif curl -s "${SCAN_URL}" | grep -q "error"; then + SCANC="${DANGERC}" + SCAN_MSG="Problem Detected" + fi + trace "Malware report: ${SCAN_MSG} (see ${SCAN_URL}" + fi + fi +} function scan_host() { if [[ -z "${NIKTO}" ]]; then - return + warning "Scanning command is not configured, check your setup."; quietExit fi # Check for nikto @@ -20,26 +40,71 @@ function scan_host() { error "Can't find scanning command (${NITKO})" } + # Create temp files + scan_html="/tmp/${APP}.scan-$RANDOM.html"; (umask 077 && touch "${scan_html}" &> /dev/null) || log_fail + # Get headers # trace "Header" # curl -X HEAD -i "${PRODURL}" &>> "${logFile}" # Run the scan trace "Scanning ${PRODURL}..." - "${NIKTO}" -config "${NIKTO_CONFIG}" -nointeractive -ask no -Display VP14 -Tuning x12b -host "${PRODURL}" >> "${logFile}" & + "${NIKTO}" -config "${NIKTO_CONFIG}" -nointeractive -ask no -Display VE14 -output "${scan_html}" -host "${PRODURL}" > "${scanFile}" & spinner $! - message_state="NOTICE" - LOGTITLE="Malware Scan" - notes="Malware scan on ${PRODURL}" - - # Clean up the log - sed -i 's/^.*Running/Running/' "${logFile}" - sed -i 's/^.*Loaded/Loaded/' "${logFile}" - sed -i '/^V\:/d' "${logFile}" - sed -i '/^Running average\: Not enough data/d' "${logFile}" - sed -i "s/: currently in plugin 'Nikto Tests'//" "${logFile}" + + # For testing only + cp "${scanFile}" ~/verbose_scan.txt + + # Create and clean up the log + sed -i 's/^.*Running/Running/' "${scanFile}" + sed -i 's/^.*Loaded/Loaded/' "${scanFile}" + sed -i '/^V\:/d' "${scanFile}" + sed -i '/^Running average\: Not enough data/d' "${scanFile}" + sed -i "s/: currently in plugin 'Nikto Tests'//" "${scanFile}" # Strip redirects, can get very spammy on a Wordpress project - sed -i '/Redirects (301) to/d' "${logFile}" + sed -i '/Redirects (301) to/d' "${scanFile}" + cat "${scanFile}" >> "${logFile}" + + # Here's what we need to do to get the HTML ready. Ouch. + sed -i '/<\/p>/d' "${scan_html}" + sed -i '/
/d' "${scan_html}" + sed -i '/<\/div>/d' "${scan_html}" + sed -i 's^class=\"dataTable\"^style=\"width: 100%; max-width: 600px;\">
"${htmlFile}" + + SCAN_MSG=$(grep "error" "${htmlFile}") + + # Create scan result text + SCAN_RESULT=$(echo "${SCAN_MSG// diff --git a/etc/html/default/scan/index.html b/etc/html/default/scan/index.html new file mode 100755 index 0000000..e76bfed --- /dev/null +++ b/etc/html/default/scan/index.html @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + +
}") + SCAN_RESULT=$(echo "${SCAN_RESULT//<\/td>}") + trace "${SCAN_RESULT}" + + # Set the scan result label text and color + if [[ "${SCAN_MSG}" == *"0 errors"* ]]; then + SCANC="${SUCCESSC}" + SCAN_MSG="Scan Passed" + else + SCANC="${DANGERC}" + SCAN_MSG="Problem Detected" + fi + + process_html + + cp "${htmlFile}" "${scan_html}" + + message_state="NOTICE" + LOGTITLE="Malware Scan" + notes="Malware scan on ${PRODURL}: ${SCAN_RESULT}" + safeExit } diff --git a/lib/utilities.sh b/lib/utilities.sh index 15d10ae..32c2bf5 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -17,14 +17,15 @@ function go() { tput cnorm; fi + # Weird spot for this stuff I know + server_monitor + scan_check + console "deploy ${VERSION}" if [[ "${INCOGNITO}" != "TRUE" ]]; then console "Current working path is ${WORKPATH}/${APP}" fi - - # Weird spot for this I know - server_monitor # Slack test if [[ "${SLACKTEST}" == "1" ]]; then From 6d804d756621f4f162d6f0904c086a33506ea87f Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 6 Apr 2018 14:21:04 -0700 Subject: [PATCH 053/334] Minor scan refinements --- README.md | 2 ++ deploy.sh | 2 +- etc/deploy.sh | 2 +- lib/scan.sh | 5 +++-- lib/slack.sh | 9 +++++++++ 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bf0a003..353bcc8 100755 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ Other Options: --strict Any error will halt deployment completely --debug Run in debug mode --unlock Delete expired lock files + --repair Repair a deployment after merge failure + --scan Scan production hosts for malware issues --ssh-test Validate SSH key setup --email-test Test email configuration --slack-test Test Slack integration diff --git a/deploy.sh b/deploy.sh index fd08072..55f93ea 100755 --- a/deploy.sh +++ b/deploy.sh @@ -137,7 +137,7 @@ Other Options: --strict Any error will halt deployment completely --debug Run in debug mode --unlock Delete expired lock files - --repair Repair a deployment after key failure + --repair Repair a deployment after merge failure --scan Scan production hosts for malware issues --ssh-test Validate SSH key setup --email-test Test email configuration diff --git a/etc/deploy.sh b/etc/deploy.sh index 8d40184..d6880f8 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -338,7 +338,7 @@ PROJECT_VERSION="3.7" ############################################################################### -# Nikto +# Malware scanning ############################################################################### # If you want to make use of nikto for malware/security host scanning, define diff --git a/lib/scan.sh b/lib/scan.sh index 5dad033..2e15762 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -93,16 +93,17 @@ function scan_host() { if [[ "${SCAN_MSG}" == *"0 errors"* ]]; then SCANC="${SUCCESSC}" SCAN_MSG="Scan Passed" + message_state="PASSED" else SCANC="${DANGERC}" SCAN_MSG="Problem Detected" + message_state="ERROR" fi process_html cp "${htmlFile}" "${scan_html}" - - message_state="NOTICE" + LOGTITLE="Malware Scan" notes="Malware scan on ${PRODURL}: ${SCAN_RESULT}" diff --git a/lib/slack.sh b/lib/slack.sh index 94e1e5b..5ad3331 100755 --- a/lib/slack.sh +++ b/lib/slack.sh @@ -85,6 +85,7 @@ function slackPost () { fi fi + # Add a details link to online logfiles if they exist if [[ -n "${REMOTEURL}" ]] && [[ -n "${REMOTELOG}" ]]; then slack_message="${slack_message} (<${LOGURL}|Details>)" @@ -117,6 +118,11 @@ function slackPost () { fi fi + # Arf I hate this + if [[ "${SCAN}" == "1" ]]; then + slack_message="${notes} (<${LOGURL}|Details>)" + fi + # Set icon for message state case "${message_state}" in ERROR) @@ -125,6 +131,9 @@ function slackPost () { DIGEST) slack_icon=':black_small_square:' ;; + PASSED) + slack_icon=':heavy_check_mark:' + ;; *) slack_icon='' ;; From e58a8b7764beb6e9213c44c1fb1d2df88bb05822 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 6 Apr 2018 21:11:33 -0700 Subject: [PATCH 054/334] Cleaning up html --- deploy.sh | 2 +- etc/html/default/digest/header.html | 10 +- etc/html/default/digest/mockup.html | 144 ++++++++++++++++++++++++++ etc/html/default/digest/scan-pass.png | Bin 0 -> 2132 bytes etc/html/default/digest/speed.png | Bin 0 -> 3155 bytes etc/html/default/digest/uptime.png | Bin 0 -> 3468 bytes etc/html/default/remote/scan.png | Bin 0 -> 2132 bytes etc/html/default/remote/scan.svg | 9 ++ etc/html/default/remote/speed.png | Bin 0 -> 3155 bytes etc/html/default/remote/speed.svg | 13 +++ etc/html/default/remote/uptime.png | Bin 0 -> 3468 bytes etc/html/default/remote/uptime.svg | 10 ++ etc/html/default/scan/header.html | 13 ++- etc/html/default/scan/index.html | 101 ++++++++++++++++++ etc/html/default/stats/index.html | 17 +-- etc/html/default/theme.conf | 3 +- lib/digest.sh | 2 +- lib/monitor.sh | 25 ++++- lib/process-html.sh | 9 +- lib/scan.sh | 23 ++-- 20 files changed, 344 insertions(+), 37 deletions(-) create mode 100755 etc/html/default/digest/mockup.html create mode 100755 etc/html/default/digest/scan-pass.png create mode 100755 etc/html/default/digest/speed.png create mode 100755 etc/html/default/digest/uptime.png create mode 100755 etc/html/default/remote/scan.png create mode 100755 etc/html/default/remote/scan.svg create mode 100755 etc/html/default/remote/speed.png create mode 100755 etc/html/default/remote/speed.svg create mode 100755 etc/html/default/remote/uptime.png create mode 100755 etc/html/default/remote/uptime.svg create mode 100755 etc/html/default/scan/index.html diff --git a/deploy.sh b/deploy.sh index 55f93ea..ab4cc84 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.7-beta.1" +VERSION="3.7-beta.2" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" diff --git a/etc/html/default/digest/header.html b/etc/html/default/digest/header.html index edbfd42..58d6c40 100755 --- a/etc/html/default/digest/header.html +++ b/etc/html/default/digest/header.html @@ -80,10 +80,12 @@

-
- {{SCAN_MSG}} ✔ -
Uptime: {{UPTIME}}%
-
Latency: {{LATENCY}}s
+

{{GREETING}}

diff --git a/etc/html/default/digest/mockup.html b/etc/html/default/digest/mockup.html new file mode 100755 index 0000000..fca5ed0 --- /dev/null +++ b/etc/html/default/digest/mockup.html @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Your Weekly Digest

+ +

+ March 30, 2018 – April 06, 2018

+ +
California Apartment Association
+
+ + + +

Happy Monday and welcome back!

+

You had traffic from 4275 organic searches last week. Not bad!

+ +

Here's a summary of code updates made to your website last week:

+
+
+commit ecd2340
+Author: Joe Horning
+Date: Thu, 5 Apr 2018 02:01:34 -0700 (2 days ago)

+Updated 6 of 6 plugins (instapage 3.2.3, jetpack 6.0, pardot 1.4.6, relevanssi 4.0.7, wordfence 7.1.2, wordpress-seo 7.2) and system (wp-core 4.9.5)
+ +
+commit 46a7bbe
+Author: Corey Worrell
+Date: Fri, 30 Mar 2018 18:07:27 -0700 (7 days ago)

+emrl-base update
+ +
+ + + + +
+ Looking for more? Check out your project's stats page. +
+
+ + + + + + +
+
+ + + diff --git a/etc/html/default/digest/scan-pass.png b/etc/html/default/digest/scan-pass.png new file mode 100755 index 0000000000000000000000000000000000000000..4974672a415771b5e3a05a946a1e1870fd2fc1aa GIT binary patch literal 2132 zcmah~cTkgg8vZ^)LcmZWQgad@iVDgC7EnrHB?1Z(B6Sy}tW*USL|7oi7(qe$M%Drt zR6xo}KLrsIFccf1SQ6j}!GZ|V1VTx^Ts;4{o4cFacjhhgJkR{*o%y}h4vGre z3IG5oIyu^tq?z^2kqBudjh>N{CapMvcif5Skhp}v*kHi!RCG|Vu2W=SXfP=_@Kj>V z(_kwAkXv!Gw{!n(c&1noIpV02cJ09?qWoipK5z*i2hTUE^R=5%h^=D9!#xetNf)Lh zGJRehm9p{U?}m>Rja%gWWlutA2vb=_R#qMt!yid(^d((ZDhW_J2&aO82>>9zb)^Xa zFo63p{Y2iaEO!=<*TOZtYWdXxqQ+9t+HKBec zV>hkKqDl_&duTQwtfEg?Um~SHf3;4LLFn>_b_4*6e^y$qQE-np-t!8bvLYAC>aafpCxO%OGm0v@MbOZ~;?$`&BKL6xgNSeu*qu_R@EY z0QRQ*SVmlpSQO{*9a=$mp?rb9p!&HmP0KczL6p~M`%Lz)0Nt163y6ZkUSS%>mLP>y z+bhYr;Crc9MNs%csA)@K5QDe0t$hbQ5CRSQoGDdgbNlE{(Zr%PB9FhrT^`ZQ{m$>A zV8-1t*#~PX3dyS~#!ZC=w4KENq$8M$`^iTfCFbrIk8DLFkCqw898Abn zGNHPEA%X$UP^pZCkg9a$gZHC&l4i+aIeXp8VXeOdLsLxzxesVhD*N;f4`o|5T`2vP zvmvZs?rM*N;>`}gL|qZF$~fPD*lmq(&Qw*rd0U3c=J7q@pIQ@9M0G|}n8AmFadkf; zSQ@^)4CJc1AAzFv-%FA+5xNNUd3JajQnvm^_~219*piEjgPIBUyy!JQiO<7_b@{^Q z$qokP2Zze@U;R^UAHJ;;?3}eZ(9O%jyQHoTm5o^!z00Xni`9c12aYI7+u|0A#g8Pu zcMrsBQf*i|XYUU?Mp5h71XM%UB(lf5+!uP`16uhHWZhWZdp72lguY!n)YNt^&L8wv z3|hf{q8U6hv8~C$-r+#mnTc5#2L9Lr@wI_A;K|XJw?H)i-ULTKHZ??iy?B@WO zDym@=t`91lP*m|QF;Z)kq-6@Lf6(vtO$9rh`}Iu+CTejZl7dv8KM7u<&^!6;i=agIkt=E z9|6})0&A2;GHr_4Hm*frC#s{XUL<|qfq@BcWWG`uZ<+PSoVs2RjlRn<$f|q-w0c~~ zxnwatGk47@7pJpmCcK4>R10X1Y#`)3?ZVITjrDaJUY={c8v&E;3s0wC;d9o#(8|0t z(>Tp>AttCsJ*+@X2Wrph2FT8dKAIp=!vhUN^*%FoRzCfz@;C6-VXAgT_qBXi- z1Oj*RmU#TyW%GPxBb)WGYT)pei2O5g3mhCazD#euVlUw4n6}zgyIyg?w)uMO6*A$9 zB%JnpQpfY7aLrjv2{vyBlvbJowUTz}I5J~Rhm3nq?(?|wb^pvX_Fd81Xl;WTcITTM z&P`t|*H4sy%aGXpJZm0Ey-%T*uSPCxEQ^_bqvxP6?#|hbipQ%wr?84MljZ21g15%dn@exm z%%)%4mHvt%YvxUNcHuFmbIQIyqnY>2cGBwKwDg!osR=A|Ti7kx=c_JOHYWzW1Olml z)TnfB^_A#Dv}EdAXO$#cOQ1kGA$q?&WJ#YeKMg!LK*mfO3WR1x6gv7-fkjiDz6e_O zO1G?JGwXI!=q~e^gKM4@PSsmO$daoF$;DUnRP2}0!3ftF^ry7mOVhGm8HA?X@Y6z( z734F)5iL2FZ)W>D!+#sF7;`e~c&?yQ_1fpo$CdpH4DaU7OmOU$Pft5(-58d-$(v#J zVrAsF0Y(IWSfehi@f10wLt5&!@I literal 0 HcmV?d00001 diff --git a/etc/html/default/digest/speed.png b/etc/html/default/digest/speed.png new file mode 100755 index 0000000000000000000000000000000000000000..551522ea45ca35d02a6a8b7f9d3a5b4fa79c46ed GIT binary patch literal 3155 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 VCxh58{^z{{40Q22)mn~`{|BIsV&nh- literal 0 HcmV?d00001 diff --git a/etc/html/default/remote/scan.png b/etc/html/default/remote/scan.png new file mode 100755 index 0000000000000000000000000000000000000000..4974672a415771b5e3a05a946a1e1870fd2fc1aa GIT binary patch literal 2132 zcmah~cTkgg8vZ^)LcmZWQgad@iVDgC7EnrHB?1Z(B6Sy}tW*USL|7oi7(qe$M%Drt zR6xo}KLrsIFccf1SQ6j}!GZ|V1VTx^Ts;4{o4cFacjhhgJkR{*o%y}h4vGre z3IG5oIyu^tq?z^2kqBudjh>N{CapMvcif5Skhp}v*kHi!RCG|Vu2W=SXfP=_@Kj>V z(_kwAkXv!Gw{!n(c&1noIpV02cJ09?qWoipK5z*i2hTUE^R=5%h^=D9!#xetNf)Lh zGJRehm9p{U?}m>Rja%gWWlutA2vb=_R#qMt!yid(^d((ZDhW_J2&aO82>>9zb)^Xa zFo63p{Y2iaEO!=<*TOZtYWdXxqQ+9t+HKBec zV>hkKqDl_&duTQwtfEg?Um~SHf3;4LLFn>_b_4*6e^y$qQE-np-t!8bvLYAC>aafpCxO%OGm0v@MbOZ~;?$`&BKL6xgNSeu*qu_R@EY z0QRQ*SVmlpSQO{*9a=$mp?rb9p!&HmP0KczL6p~M`%Lz)0Nt163y6ZkUSS%>mLP>y z+bhYr;Crc9MNs%csA)@K5QDe0t$hbQ5CRSQoGDdgbNlE{(Zr%PB9FhrT^`ZQ{m$>A zV8-1t*#~PX3dyS~#!ZC=w4KENq$8M$`^iTfCFbrIk8DLFkCqw898Abn zGNHPEA%X$UP^pZCkg9a$gZHC&l4i+aIeXp8VXeOdLsLxzxesVhD*N;f4`o|5T`2vP zvmvZs?rM*N;>`}gL|qZF$~fPD*lmq(&Qw*rd0U3c=J7q@pIQ@9M0G|}n8AmFadkf; zSQ@^)4CJc1AAzFv-%FA+5xNNUd3JajQnvm^_~219*piEjgPIBUyy!JQiO<7_b@{^Q z$qokP2Zze@U;R^UAHJ;;?3}eZ(9O%jyQHoTm5o^!z00Xni`9c12aYI7+u|0A#g8Pu zcMrsBQf*i|XYUU?Mp5h71XM%UB(lf5+!uP`16uhHWZhWZdp72lguY!n)YNt^&L8wv z3|hf{q8U6hv8~C$-r+#mnTc5#2L9Lr@wI_A;K|XJw?H)i-ULTKHZ??iy?B@WO zDym@=t`91lP*m|QF;Z)kq-6@Lf6(vtO$9rh`}Iu+CTejZl7dv8KM7u<&^!6;i=agIkt=E z9|6})0&A2;GHr_4Hm*frC#s{XUL<|qfq@BcWWG`uZ<+PSoVs2RjlRn<$f|q-w0c~~ zxnwatGk47@7pJpmCcK4>R10X1Y#`)3?ZVITjrDaJUY={c8v&E;3s0wC;d9o#(8|0t z(>Tp>AttCsJ*+@X2Wrph2FT8dKAIp=!vhUN^*%FoRzCfz@;C6-VXAgT_qBXi- z1Oj*RmU#TyW%GPxBb)WGYT)pei2O5g3mhCazD#euVlUw4n6}zgyIyg?w)uMO6*A$9 zB%JnpQpfY7aLrjv2{vyBlvbJowUTz}I5J~Rhm3nq?(?|wb^pvX_Fd81Xl;WTcITTM z&P`t|*H4sy%aGXpJZm0Ey-%T*uSPCxEQ^_bqvxP6?#|hbipQ%wr?84MljZ21g15%dn@exm z%%)%4mHvt%YvxUNcHuFmbIQIyqnY>2cGBwKwDg!osR=A|Ti7kx=c_JOHYWzW1Olml z)TnfB^_A#Dv}EdAXO$#cOQ1kGA$q?&WJ#YeKMg!LK*mfO3WR1x6gv7-fkjiDz6e_O zO1G?JGwXI!=q~e^gKM4@PSsmO$daoF$;DUnRP2}0!3ftF^ry7mOVhGm8HA?X@Y6z( z734F)5iL2FZ)W>D!+#sF7;`e~c&?yQ_1fpo$CdpH4DaU7OmOU$Pft5(-58d-$(v#J zVrAsF0Y(IWSfehi@f10wLt5&!@I literal 0 HcmV?d00001 diff --git a/etc/html/default/remote/scan.svg b/etc/html/default/remote/scan.svg new file mode 100755 index 0000000..e5aad72 --- /dev/null +++ b/etc/html/default/remote/scan.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/etc/html/default/remote/speed.png b/etc/html/default/remote/speed.png new file mode 100755 index 0000000000000000000000000000000000000000..551522ea45ca35d02a6a8b7f9d3a5b4fa79c46ed GIT binary patch literal 3155 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 + + + + + + + diff --git a/etc/html/default/remote/uptime.png b/etc/html/default/remote/uptime.png new file mode 100755 index 0000000000000000000000000000000000000000..158e8a40e23f3ede2c5cb6ff3811007bf99cfaf1 GIT binary patch literal 3468 zcmai1`8U*!_kYirVUU<1B}3Ct7~+v+XY7LvM%iVFkg*h!b%yLqkF6(b)@%(SON6mi zmM}>O*&j=iB};=b-=6P3@Hyvo@9W$j?hp5z*X!PM<4y7UTpZ^)007`J!08bFaL0cE zWBro}(?qsEB;c=Wz`Fe$zJ!}ZAy$7i+yd^I$orgi6E z%lC+Rej<|U^fgpkPcRlL(b#*{e!fChzjMVKfW@vM5&G6d9ddfIej9!c#`+a*n7fxKJWy60JYe$nZ;jRod8jSY)!mw(n~7h&L~YUvtZLpq*iy`)Ugv8I zRrkOw=<&h^vrt^Bp zE&DvzAttiu!_si6={oLl=L?wX5ZHS2=h(eC_6uhak(3BZG{xW%6Thcl^R1biuVZSa zcaqoO38^DiuMGpSPG_+fkwjJ`XMx+a!Uu$A@I!TilU>21J%dl~&%1Z}obnrH&yRTh z+Ssrm{+6=x_FT$7QmWq7`^{)*{?e@4rOF2B8pHVN(S1fT@Lnii*I_D$^Wu((Nffk)7cOUq|ot?olmIf^16$>MI^CD7(%}KPjQ-HE5OyW7yKThR?8b`8+)s zelYndOdIeiQr})2Sdz@+6k2`Un#GFYd_&NR6%0Y!dJ6woR8<^{j$vqi(0#f3&V_4q_T`4D;*R z_0b+z!2UDp`0bK-^i}#5LcaBodF~ZL0V$Hw*Hs54n)2$HL_6z~{r;kd&~clATD|na ztMVfa4rKGf@|Ct?T|liUFmcycDNoIq2lTvn3}-FyjpdNC??mZRzYD>7m*7q-m!;2E z=%qN*r^U~I{Bl{iIqf!(Em_3j(8sORe=Hknb`h&^;R`=+16L*L`9nRxQFmBz>NAJ- znNGMBgxkZy+pNP5s9Zv19{80Am=kAKVaf!Ev*1nhq9i!fSqI2S^IT^5<_aQBv)HEl z>ONDT!2XHcGUIY4TDRc3)(fp=vkql&0blEa?0bq1s?kPi(bNv)o*KP%g8N%b2N5xO z3SMh80B0Y7`GQ-9E}7?-qZL_7wVn;V+2y?PFf!oWy0F>r>zvKAuJ3gx*-vn74UliM zYT~>TP3|OHT_D5xYy>XCZ|?5bE`hQl7+}^sj`TN*i7k6~@%`n|m%puGO_|^#EL1#(Ev{=C4Mv6YxiP{PN8^+2Ie^q0S;qDSDC=pW>W(BS z%kAKy$j3Q`olcAdt>D8_ep)0S&}U)_h-zqpQjT@Fp{8laUyJ&`?ZZHS_Ou!aJvDC6 z1N7y~5dM~X%Y(H|!krM!R=h)^fJyHdu0KWUB%%@8V+b0170Z5jSd@c-AUmpN1xQ>4irK-f;$x*4jvK#%~ z9fW%DT0|ztS(M3b*Eq=dQfmJHl4BLd>F+SfyNqFAJnN4zlYvnqRQMcw-zKXZEr92` zeW63S_kB@Btr_~=R>qRa6HcAZibknC-*quQJ<&tCdX1l#3j_0)Okx6FpRg4Kv~g$t z9H!C~JOaeSvuycLbkb!(%pNG1`Qlx&4f`CzKhHc(!+3@}G>Bdy_n)a0ZeTiIh;rnn z9)Ieif~z(JdXFr<6y}}vff8pE%03i#;_x{s#@&cEnJ|dYy)W?`7)?aol;jN3-O<0u zJoTvUB{^u>JUIbZD?)YzwG5?x5V)AeG+seBDnEE%XlZ8kN~#>ne=Qq{QqVdFz0oUk znchzh3fLas zy35rfUn!`8j!ZnA&eZCovZj*?o?mcH=lJqu4*{Yj2p6q`&P067`FIN!5F6(pxxKRL z(eD<-q)AwY!mwo=sg=)dHW2GRYOB}?DHjKvbh4`6@2;anuF?)8>8fzgI`nVd@>aI9 zIdTKO70nwSy|3(BZtjf7c*;Rn80d+xV)3W90oJ@oadhtu*m92on(FA>yuPjlv^;78 zF{NlM{`U_Qw0IT=K$|0GPe?+5iT9(G^SE{|$vQ+*DV#+2__-J>3Pj>Fx~o*}*=8<9eGdN09bfQ!@aWuybK5p^g_%n~& zKYdr|_ME5)n6gY%Z%?2}_yHIv8d=37&9Yn-Cp=~nM?x&%U$uyl_`z{@72tJv%N#f z5ML64l#`Pd*7V~IsL{l$clT~4|9}+4XmGrVeO0t$Z;;v^`{C2S4WGie8~6|Th{HlZ zIPL@KNQJYn`8wpT+9{zsJ&Puy%Ep7o<{5w6NaP3oGMm_t(o8t|_;BJcTac;|h~j1$ z*K~1h2)%;0{sfg&yb<4WI^8Xi0BjU@8h2=TMgA7Z%Pz#U+!fMe31-;WjLS<_L@d=# z8a?1=wH{zpr&815bGp}3{8uwPKE;}Oq}m4Tu_mfp$#(Ks{VdluDZP`ZZv9jXwH%Y?eReayB+C_e&9v*20Wx)!q8aJRhcvp{r$MKM5u%HGt> zxOnnr$3|mH4koG!O|j$aKI?pLEl}X0PxILP+HuD>IquCvv+od1p8tul-&BAFc$@Vi VCxh58{^z{{40Q22)mn~`{|BIsV&nh- literal 0 HcmV?d00001 diff --git a/etc/html/default/remote/uptime.svg b/etc/html/default/remote/uptime.svg new file mode 100755 index 0000000..0a98f23 --- /dev/null +++ b/etc/html/default/remote/uptime.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/etc/html/default/scan/header.html b/etc/html/default/scan/header.html index fa20367..00cab13 100755 --- a/etc/html/default/scan/header.html +++ b/etc/html/default/scan/header.html @@ -68,11 +68,14 @@

-

{{SCAN_RESULT}}

-
{{SCAN_MSG}} ✔
-
Uptime: {{UPTIME}}%
-
Latency: {{LATENCY}}s
-
+

{{SCAN_RESULT}}

+

+ + + +
+
+ + + + + + + + + + +
+

Project Statistics BETA

+ + +

Weekly report generated {{NOW}}

+

{{PROJNAME}} + ({{PRODURL}}) +

+ +
{{PROJCLIENT}}
+
+ +

Weekly overview

Malware: 0 errors ✔
+ +
Uptime: {{UPTIME}}%
+
Latency: {{LATENCY}}s
+
+
+ {{SCAN_RESULTS}} +
+ + + + + + +
+
+ + + diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index 5bce26a..010a3b4 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -56,8 +56,8 @@ - diff --git a/etc/html/default/theme.conf b/etc/html/default/theme.conf index 65184bf..ecd4e28 100755 --- a/etc/html/default/theme.conf +++ b/etc/html/default/theme.conf @@ -7,9 +7,10 @@ # Setup theme colors DEFAULTC="#555555" PRIMARYC="#47ACDF" +SECONDARYC="#CCCCCC" SUCCESSC="#28a745" INFOC="#5D6D7E" -WARNINGC="#E37222" +WARNINGC="#FFC107" DANGERC="#CC2233" # If you'd like to add Smooch.io live chat to your digest and statistics pages, diff --git a/lib/digest.sh b/lib/digest.sh index e9fe28c..c1ba25b 100755 --- a/lib/digest.sh +++ b/lib/digest.sh @@ -74,7 +74,7 @@ function create_digest() { # Strip out useless analytics results if [[ -z "${RESULT}" ]] || [[ "${RESULT}" == "0" ]] || [[ "${SIZE}" == "0" ]]; then sed -i '/ANALYTICS/d' "${htmlFile}" - else + # else if [[ "${METRIC}" == "hits" ]] && [[ "${RESULT}" -lt "499" ]]; then sed -i '/ANALYTICS/d' "${htmlFile}" fi diff --git a/lib/monitor.sh b/lib/monitor.sh index 5d6e9ed..9be4ba1 100755 --- a/lib/monitor.sh +++ b/lib/monitor.sh @@ -17,7 +17,7 @@ function server_monitor() { # which is dealing with the last calendar month, but oh well we get what we pay # for. The PHP Monitor API has no ability to look up results in that way MONITORHOURS="720" - elif [[ "${DIGEST}" == "1" ]] || [[ "${PROJSTATS}" == "1" ]]; then + elif [[ "${DIGEST}" == "1" ]] || [[ "${PROJSTATS}" == "1" ]] || [[ "${SCAN}" == "1" ]]; then # Digests and stats should average 7 days MONITORHOURS="168" else @@ -25,7 +25,7 @@ function server_monitor() { MONITORHOURS="24" fi server_monitor_log - trace "Uptime: ${UPTIME} / Latency: ${LATENCY} (avg. over last ${MONITORHOURS} hours)" + trace "Uptime: ${UPTIME}% / Latency: ${LATENCY} (avg. over last ${MONITORHOURS} hours)" fi } @@ -44,7 +44,7 @@ function server_monitor_test() { # Check last 7 days for the sake of the test MONITORHOURS="24" server_monitor_log - console "API: ${MONITORAPI}" + console "API: ${MONITORAPI}" notice "Results (last 24 hours)" console "Uptime: ${UPTIME}%" console "Latency: ${LATENCY}s" @@ -74,4 +74,23 @@ function server_monitor_log() { LATENCY="$(sed 's/^[^:]*://g' <<< "${LATENCY}")" # Round to two decimal places LATENCY="$(printf '%0.2f\n' "${LATENCY}")" + + # Set colors for html reports + if [[ "${UPTIME}" == "100" ]]; then + UPTIMEC="${SUCCESSC}" + elif [[ "${UPTIME}" > "97" || "${UPTIME}" == "97" ]]; then + UPTIMEC="${SUCCESSC}" + elif [[ "${UPTIME}" > "88" && "${UPTIME}" < "97" ]]; then + UPTIMEC="${WARNINGC}" + else + UPTIMEC="${DANGERC}" + fi + + if [[ "${LATENCY}" < "2.2" || "${LATENCY}" == "2.2" ]]; then + LATENCYC="${SUCCESSC}" + elif [[ "${LATENCY}" > "2.2" && "${LATENCY}" < "3.8" ]]; then + LATENCYC="${WARNINGC}" + else + LATENCYC="${DANGERC}" + fi } diff --git a/lib/process-html.sh b/lib/process-html.sh index 2eca0b1..6ced103 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -9,9 +9,9 @@ trace "Loading html handling" # Initialize variables read -r DEFAULTC PRIMARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID COVER \ - SCANC <<< "" + SCANC UPTIMEC LATENCYC <<< "" echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} - ${SMOOCHID} ${COVER} ${SCANC}" > /dev/null + ${SMOOCHID} ${COVER} ${SCANC} ${UPTIMEC} ${LATENCYC}" > /dev/null function process_html() { # Clean out the stuff we don't need @@ -57,11 +57,14 @@ function process_html() { -e "s^{{PRODURL}}^${PRODURL}^g" \ -e "s^{{DEFAULT}}^${DEFAULTC}^g" \ -e "s^{{PRIMARY}}^${PRIMARYC}^g" \ + -e "s^{{SECONDARY}}^${SECONDARYC}^g" \ -e "s^{{SUCCESS}}^${SUCCESSC}^g" \ -e "s^{{INFO}}^${INFOC}^g" \ -e "s^{{WARNING}}^${WARNINGC}^g" \ -e "s^{{DANGER}}^${DANGERC}^g" \ - -e "s^{{SCAN}}^${SCANC}^g" \ + -e "s^{{SCAN_STATUS}}^${SCANC}^g" \ + -e "s^{{UPTIME_STATUS}}^${UPTIMEC}^g" \ + -e "s^{{LATENCY_STATUS}}^${LATENCYC}^g" \ -e "s^{{SCAN_MSG}}^${SCAN_MSG}^g" \ -e "s^{{SCAN_RESULT}}^${SCAN_RESULT}^g" \ -e "s^{{SCAN_URL}}^${SCAN_URL}^g" \ diff --git a/lib/scan.sh b/lib/scan.sh index 2e15762..66e8477 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -17,15 +17,18 @@ function scan_check() { SCAN_URL="${REMOTEURL}/${APP}/scan/" SCAN_RESULT=$(echo "${SCAN_MSG// @@ -118,7 +113,7 @@

-

Project Statistics BETA

+
+

Site Smart BETA

Weekly report generated {{NOW}}

@@ -68,11 +68,14 @@

-

Weekly overview

- {{SCAN_MSG}} ✔ -
Uptime: {{UPTIME}}%
-
Latency: {{LATENCY}}s
-
+

Weekly overview

+
}") - if curl -Is "${SCAN_URL}" | grep -q "200"; then - if curl -s "${SCAN_URL}" | grep -q "0 error"; then + # Piping through tac is a workaround to solve a timing issue, see + # https://github.com/EMRL/deploy/issues/134 + #if curl -LIs --speed-time 900 "${SCAN_URL}" tac | tac | grep -q "200"; then + if curl -LIs "${SCAN_URL}" | grep -q "200"; then + if curl -Ls --speed-time 900 "${SCAN_URL}" tac | tac | grep -q "0 error"; then SCANC="${SUCCESSC}" - SCAN_MSG="Scan Passed" - elif curl -s "${SCAN_URL}" | grep -q "error"; then + SCAN_MSG="Scan passed" + elif curl -Ls --speed-time 900 "${SCAN_URL}" tac | tac | grep -q "error"; then SCANC="${DANGERC}" - SCAN_MSG="Problem Detected" + SCAN_MSG="Problem found" fi - trace "Malware report: ${SCAN_MSG} (see ${SCAN_URL}" + trace "Malware report: ${SCAN_MSG} (see ${SCAN_URL})" fi fi } @@ -42,10 +45,6 @@ function scan_host() { # Create temp files scan_html="/tmp/${APP}.scan-$RANDOM.html"; (umask 077 && touch "${scan_html}" &> /dev/null) || log_fail - - # Get headers - # trace "Header" - # curl -X HEAD -i "${PRODURL}" &>> "${logFile}" # Run the scan trace "Scanning ${PRODURL}..." @@ -92,11 +91,11 @@ function scan_host() { # Set the scan result label text and color if [[ "${SCAN_MSG}" == *"0 errors"* ]]; then SCANC="${SUCCESSC}" - SCAN_MSG="Scan Passed" + SCAN_MSG="Scan passed" message_state="PASSED" else SCANC="${DANGERC}" - SCAN_MSG="Problem Detected" + SCAN_MSG="Problem found" message_state="ERROR" fi From a894108916918a086b3ff0d28d58d39a3a646cb8 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sat, 7 Apr 2018 14:00:12 -0700 Subject: [PATCH 055/334] Resolve #133, resolve #134 --- etc/html/default/digest/header.html | 15 ++++++++--- etc/html/default/digest/mockup.html | 41 +++++++++++++---------------- etc/html/default/scan/header.html | 15 ++++++++--- etc/html/default/stats/index.html | 13 ++++++--- lib/scan.sh | 12 +++++---- 5 files changed, 57 insertions(+), 39 deletions(-) diff --git a/etc/html/default/digest/header.html b/etc/html/default/digest/header.html index 58d6c40..8f35c33 100755 --- a/etc/html/default/digest/header.html +++ b/etc/html/default/digest/header.html @@ -79,14 +79,21 @@

{{PROJCLIENT}}
- + +
- +

{{SCAN_MSG}}
- +

{{UPTIME}}% uptime
- +

{{LATENCY}}s latency
+

{{GREETING}}

{{ANALYTICSMSG}}

diff --git a/etc/html/default/digest/mockup.html b/etc/html/default/digest/mockup.html index fca5ed0..950dd79 100755 --- a/etc/html/default/digest/mockup.html +++ b/etc/html/default/digest/mockup.html @@ -77,38 +77,33 @@

March 30, 2018 – April 06, 2018

- -
California Apartment Association
+
+
EMRL
+
- -
Scan passed ✔
- -
99.40% uptime
- -
2.33s latency
- - - +

Scan passed
+

100% uptime
+

1.54s latency
+

Happy Monday and welcome back!

-

You had traffic from 4275 organic searches last week. Not bad!

+

You had 215 hits in the last week.

Here's a summary of code updates made to your website last week:

-
-commit ecd2340
+
+commit 8a8dff7
Author: Joe Horning
-Date: Thu, 5 Apr 2018 02:01:34 -0700 (2 days ago)

-Updated 6 of 6 plugins (instapage 3.2.3, jetpack 6.0, pardot 1.4.6, relevanssi 4.0.7, wordfence 7.1.2, wordpress-seo 7.2) and system (wp-core 4.9.5)
- -
-commit 46a7bbe
-Author: Corey Worrell
-Date: Fri, 30 Mar 2018 18:07:27 -0700 (7 days ago)

-emrl-base update
+Date: Thu, 5 Apr 2018 02:05:46 -0700 (2 days ago)

+Updated 2 of 2 plugins (advanced-custom-fields-pro 5.6.10, wordfence 7.1.2, wordpress-seo 7.2) and system (wp-core 4.9.5)
- Looking for more? Check out your project's stats page. + Looking for more? Check out your project's stats page.
diff --git a/etc/html/default/scan/header.html b/etc/html/default/scan/header.html index 00cab13..84080ca 100755 --- a/etc/html/default/scan/header.html +++ b/etc/html/default/scan/header.html @@ -56,7 +56,7 @@ - diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index 010a3b4..068c84d 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -69,13 +69,20 @@

Weekly overview

+
-
{{SCAN_MSG}}
+

{{SCAN_MSG}}
-
{{UPTIME}}% uptime
+

{{UPTIME}}% uptime
-
{{LATENCY}}s latency
+

{{LATENCY}}s latency
+ diff --git a/lib/scan.sh b/lib/scan.sh index 66e8477..a0384f7 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -55,11 +55,12 @@ function scan_host() { cp "${scanFile}" ~/verbose_scan.txt # Create and clean up the log - sed -i 's/^.*Running/Running/' "${scanFile}" - sed -i 's/^.*Loaded/Loaded/' "${scanFile}" - sed -i '/^V\:/d' "${scanFile}" - sed -i '/^Running average\: Not enough data/d' "${scanFile}" - sed -i "s/: currently in plugin 'Nikto Tests'//" "${scanFile}" + sed -i -e 's/^.*Running/Running/' "${scanFile}" \ + -e 's/^.*Loaded/Loaded/' \ + -e '/^V\:/d' \ + -e '/^Running average\: Not enough data/d' \ + -e "s/: currently in plugin 'Nikto Tests'//" \ + "${scanFile}" # Strip redirects, can get very spammy on a Wordpress project sed -i '/Redirects (301) to/d' "${scanFile}" @@ -76,6 +77,7 @@ function scan_host() { sed -i -n '/Scan Summary/q;p' "${scan_html}" sed -i '1,5d' "${scan_html}" sed -i '/OSVDB/d' "${scan_html}" + sed -i 's^ Date: Sun, 8 Apr 2018 11:38:45 -0700 Subject: [PATCH 056/334] Improved digest emails on Windows 10 Mail --- etc/html/default/digest/header.html | 6 +++--- etc/html/default/scan/header.html | 6 +++--- etc/html/default/stats/index.html | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/etc/html/default/digest/header.html b/etc/html/default/digest/header.html index 8f35c33..b74b7ef 100755 --- a/etc/html/default/digest/header.html +++ b/etc/html/default/digest/header.html @@ -85,11 +85,11 @@

-

{{SCAN_MSG}}
+

{{SCAN_MSG}}
-

{{UPTIME}}% uptime
+

{{UPTIME}}% uptime
-

{{LATENCY}}s latency
+

{{LATENCY}}s latency
-

{{SCAN_MSG}}
+

{{SCAN_MSG}}
-

{{UPTIME}}% uptime
+

{{UPTIME}}% uptime
-

{{LATENCY}}s latency
+

{{LATENCY}}s latency
-

{{SCAN_MSG}}
+

{{SCAN_MSG}}
-

{{UPTIME}}% uptime
+

{{UPTIME}}% uptime
-

{{LATENCY}}s latency
+

{{LATENCY}}s latency
+ + + + + + + diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index ae61e9c..40a547f 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -74,11 +74,13 @@

-

{{SCAN_MSG}}
+

{{SCAN_MSG}}
-

{{UPTIME}}% uptime
+

{{UPTIME}}% uptime
-

{{LATENCY}}s latency
+

{{LATENCY}}s latency
+ +

{{LAST_BACKUP}}
- {{PROJCLIENT}} + {{PROJCLIENT}}

Date: {{NOW}}
Project: {{PROJNAME}} ({{PROJCLIENT}})
Staging URL: {{DEVURL}}
Production URL: {{PRODURL}}

-
Uptime: {{UPTIME}}%
-
Latency: {{LATENCY}}s
+
Uptime: {{UPTIME}}%
+
Latency: {{LATENCY}}s
\ No newline at end of file diff --git a/etc/html/default/remote/warning.png b/etc/html/default/remote/warning.png new file mode 100755 index 0000000000000000000000000000000000000000..741aba5532000142c6da9a9e0b09fc65ee8eb879 GIT binary patch literal 1857 zcma)5eK^zW8~<*IO?J(@pO>0cb0l&ozlm*5mY8=crSh`rWh!qWrA(c}*h(zNs>4DZ zZ;?fbDT-O^crBf?Xk=nKF{T&a8r%8m_xJC*&U0PQb3fPp+|PaApU>xca)ScA^^oRB z008uSc6o(Bwqs@Ltb*2%H^;Rh^Pf|s@Kd4jhfk$NC&dDu)cBZKjL(VaBe5Z|(bV)r zQLH-vteWug@(epYJR^~uu<$k#C?=m04;_R{JP%~;zVI^34IK)5mE{|R@X@XAkasvd zxqH5_rNfSA_2iFbPD#E}sc|7nR9H3cv6vw58~kF0=8PK90RRr@02tu^Zk*G<$(ohp zy}s`zq7e6t)rtjqC)nSdlNL#cFh-$sKTghm22&qcYND%^V+Teh=NHRH6ch}7i$r}r zKMT12LA67cSv@kw;$HMmXcQ3IM%6Bl<(avT#m%n8dJ5B80c`)fPfY1tcYle!q4rUp za;;FFsTa>8;fQhW>%X;Q=u*DA;r%(n1bNhgX+R{q%Rg;}oUQ7HY7YnS#RPc20a5<| ztn4lgHDe~xYjAgMRrSdM42`fi%b9bk?XxYvjyFwv*FJ~Qq-6thTJT+?%Q*$I-t4i3 z!%L+YdV++jize=ts2@jc`|_LA7ViVxHdP0KX^6}&3Fu4&NcU1_aKG?uVMZg%IsS9A zD1vWBA&@t#+N`pNcTgACx~WSK?#oEBIe<76xfIv0v=`v4Q9+uOs|D&d>(u>MHK?;H zq5K<%V%LSm5x1>zUj=6jL#g5svg9r-0;8M1Mi=Hat0Ge_SEIR2h|xMQ`I1NF7L!SU zpWx5{^828C4LcUcew^YU6+WmdEbaUPpgdJw_x-|p`g}=|F1q7)2*Rc~s8w)1?D(Fg z9phuW!2tml5oPFWbv7W{-WRA{Ve(NOxEIY`Pk$vDnTNS|xX)N>Ms{hS||L>E^)nWqd6z^iPhIC=95 z(KPDq)1djSI-BT72$GH!cjltWiUc%uc|H2dM7fiBCm%4;BX6Y6c=-L3gQ08YZ=F8bV=O^&MZ&sDr5_%$>F zHjJ;)l%8VH!W#_>2#>CDWNh}gM0g#=G3F*R>-9c^H zDZ^L&GO)UmMA(@EPy(rSS;rygl!~_e`q8Q8&gUs-keCYuhYz39T(k$43m%!=(;@cg z6ACutUmvkmmCZ|f^$F}rIU8r$AvSa@h_!F6g`{wLB3FnWSwWGcSI5vw(!}C<*Js;l$so#9FSZm-u9&>q@U`in} za?E0R6y){B#f8Ei-mo!f{3^jJUl=2A-Qo2@G%-rVF8J5+VbFJG;$j@PF#}Id0^Oxz zc5l6Noe7;|^CQnEBS}{~L15bQ-y_B=jz%|pdU)`X>JH8~>J@iimsJUKZ1XZ=0tO2R*MTPYJ2LT;&Jc0-SFhCPP1OIvx ctMI480Ik2ZH{1{p`2o=IAq9BV{}grMALYMgvH$=8 literal 0 HcmV?d00001 diff --git a/etc/html/default/scan/header.html b/etc/html/default/scan/header.html index 9f5b446..2a8c822 100755 --- a/etc/html/default/scan/header.html +++ b/etc/html/default/scan/header.html @@ -69,20 +69,6 @@

{{SCAN_RESULT}}

- -
-

{{SCAN_MSG}}
- -

{{UPTIME}}% uptime
- -

{{LATENCY}}s latency
-
- diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index 40a547f..93081a1 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -60,7 +60,7 @@

Site Smart BETA

-

Weekly report generated {{NOW}}

+

Report generated {{NOW}}

{{PROJNAME}} ({{PRODURL}})

@@ -68,7 +68,7 @@

-

Weekly overview

+

Overview (last 7 days)

/d" "${logFile}" - sed -i "/0m/d" "${logFile}" - sed -i "/Resolving deltas:/d" "${logFile}" - sed -i "/remote:/d" "${logFile}" - sed -i "/Receiving objects:/d" "${logFile}" - sed -i "/Resolving deltas:/d" "${logFile}" + sed -i -e "/----->/d" "${logFile}" \ + -e "/0m/d" "${logFile}" \ + -e "/Resolving deltas:/d" "${logFile}" \ + -e "/remote:/d" "${logFile}" \ + -e "/Receiving objects:/d" "${logFile}" \ + -e "/Resolving deltas:/d" "${logFile}" \ + -e "/No Kerberos credentials available/d" \ + "${logFile}" # If incognito, remove this stuff for privacy if [[ "${INCOGNITO}" == "TRUE" ]]; then diff --git a/lib/scan.sh b/lib/scan.sh index 409783a..4a205e0 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -79,7 +79,7 @@ function scan_host() { sed -i '/<\/div>/d' "${scan_html}" sed -i 's^class=\"dataTable\"^style=\"width: 100%; max-width: 600px;\">

+

Site Scan BETA

@@ -69,13 +69,20 @@

{{SCAN_RESULT}}

+
-
{{SCAN_MSG}}
+

{{SCAN_MSG}}
-
{{UPTIME}}% uptime
+

{{UPTIME}}% uptime
-
{{LATENCY}}s latency
+

{{LATENCY}}s latency
+
Date: Thu, 3 May 2018 10:20:06 -0700 Subject: [PATCH 062/334] Improved speed of malware scans --- lib/scan.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scan.sh b/lib/scan.sh index 4a205e0..c8e12b1 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -50,7 +50,7 @@ function scan_host() { # Run the scan trace "Scanning ${PRODURL}..." if [[ -z "${NIKTO_PROXY}" ]]; then - "${NIKTO}" -config "${NIKTO_CONFIG}" -nointeractive -ask no -Display VE14 -no404 -output "${scan_html}" -host "${PRODURL}" > "${scanFile}" & + "${NIKTO}" -config "${NIKTO_CONFIG}" -nointeractive -ask no -Display VE14 -Tuning 013789c -no404 -output "${scan_html}" -host "${PRODURL}" > "${scanFile}" & spinner $! else "${NIKTO}" -config "${NIKTO_CONFIG}" -nointeractive -ask no -Display VE14 -no404 -output "${scan_html}" -useproxy "${NIKTO_PROXY}" -host "${PRODURL}" > "${scanFile}" & From 804d0c25032908a61b7f285af1c260b8fac6dac4 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 3 May 2018 10:30:28 -0700 Subject: [PATCH 063/334] Release 3.7 --- CHANGELOG.md | 6 +++--- README.md | 3 ++- deploy.sh | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbacee7..7c9478c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [3.7] - 05-03-2018 ### Added - Digests now include information about recent malware scans, uptime, latency, and backup stats - Invoice creation is now available via [InvoiceNinja](https://www.invoiceninja.org/)'s API using `deploy --invoice` @@ -204,8 +204,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Old monolithic script rewritten -[Unreleased]: https://github.com/EMRL/deploy/compare/v3.6.7...HEAD -[3.6.8]: https://github.com/EMRL/deploy/compare/v3.6.7...v3.6.8 +[Unreleased]: https://github.com/EMRL/deploy/compare/v3.7...HEAD +[3.6.8]: https://github.com/EMRL/deploy/compare/v3.6.7...v3.7 [3.6.7]: https://github.com/EMRL/deploy/compare/v3.6.6...v3.6.7 [3.6.6]: https://github.com/EMRL/deploy/compare/v3.6.5...v3.6.6 [3.6.5]: https://github.com/EMRL/deploy/compare/v3.6.4...v3.6.5 diff --git a/README.md b/README.md index 353bcc8..62d1d71 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Deploy -[![release](https://img.shields.io/badge/release-v3.6.7-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) +[![release](https://img.shields.io/badge/release-v3.7-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest) [![Build Status](https://travis-ci.org/EMRL/deploy.svg?branch=master)](https://travis-ci.org/EMRL/deploy) `deploy` is designed to speed up, automate, and integrate project deployment. Its main focus is Wordpress websites, but it can be used with any code repository. @@ -49,6 +49,7 @@ Other Options: --slack-test Test Slack integration --post-test Test webhook integration --analytics-test Test Google Analytics authentication + --monitor-test Test production server uptime and latency monitoring --function-list Output a list of all functions() --variable-list Output a project's declared variables ``` diff --git a/deploy.sh b/deploy.sh index a40f33d..2a55c88 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.7-beta.3" +VERSION="3.7" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" @@ -147,7 +147,7 @@ Other Options: --analytics-test Test Google Analytics authentication --monitor-test Test production server uptime and latency monitoring --function-list Output a list of all functions() - --variable-list Output a project's declared variables + --variable-list Output a project's declared variables More information at https://github.com/EMRL/deploy " From 7da996596f521aa4f750276cbb20c36b726ac07d Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 3 May 2018 10:34:37 -0700 Subject: [PATCH 064/334] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c9478c..c6fbb12 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -205,7 +205,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). [Unreleased]: https://github.com/EMRL/deploy/compare/v3.7...HEAD -[3.6.8]: https://github.com/EMRL/deploy/compare/v3.6.7...v3.7 +[3.7]: https://github.com/EMRL/deploy/compare/v3.6.7...v3.7 [3.6.7]: https://github.com/EMRL/deploy/compare/v3.6.6...v3.6.7 [3.6.6]: https://github.com/EMRL/deploy/compare/v3.6.5...v3.6.6 [3.6.5]: https://github.com/EMRL/deploy/compare/v3.6.4...v3.6.5 From e77d15cbe3edce3cf46da96e0a074eac7a26f34e Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Wed, 16 May 2018 13:43:41 -0700 Subject: [PATCH 065/334] Added --build switch --- CHANGELOG.md | 4 ++++ README.md | 3 ++- deploy.sh | 8 ++++---- lib/git.sh | 5 +++++ lib/package-manager.sh | 2 +- lib/utilities.sh | 5 +++++ 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c9478c..24419a9 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Added +- Added `deploy --build [project name]` for quickly building project assets + ## [3.7] - 05-03-2018 ### Added - Digests now include information about recent malware scans, uptime, latency, and backup stats diff --git a/README.md b/README.md index 62d1d71..beb869c 100755 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Other Options: --automate For unattended deployment via cron --approve Approve and deploy queued code changes --deny Deny queued code changes + --build Build project assets --digest Create and send weekly digest --report Create a monthly activity report --no-check Override active file and server checks @@ -51,7 +52,7 @@ Other Options: --analytics-test Test Google Analytics authentication --monitor-test Test production server uptime and latency monitoring --function-list Output a list of all functions() - --variable-list Output a project's declared variables + --variable-list Output a project's declared variables ``` [![asciicast](https://asciinema.org/a/mMCid9O2BK7JrpocQuSl3CRkP.png)](https://asciinema.org/a/mMCid9O2BK7JrpocQuSl3CRkP?t=0) diff --git a/deploy.sh b/deploy.sh index 2a55c88..f8c00d2 100755 --- a/deploy.sh +++ b/deploy.sh @@ -10,7 +10,7 @@ ############################################################################### IFS=$'\n\t' -VERSION="3.7" +VERSION="3.7.1-dev" EPOCH="$(date +%s)" NOW="$(date +"%B %d, %Y")" LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')" @@ -27,7 +27,7 @@ set -uo pipefail # Startup switches read -r APP UPGRADE SKIPUPDATE CURRENT VERBOSE QUIET STRICT DEBUG FORCE \ SLACKTEST FUNCTIONLIST VARIABLELIST AUTOMATE EMAILTEST APPROVE \ - DENY PUBLISH DIGEST ANALYTICS ANALYTICSTEST PROJSTATS UNLOCK \ + DENY PUBLISH DIGEST ANALYTICS ANALYTICSTEST BUILD PROJSTATS UNLOCK \ SSHTEST TIME UPDATEONLY POSTTEST REPORT REPAIR CREATE_INVOICE SCAN \ CHECK_BACKUP <<< "" echo "${APP} ${UPGRADE} ${SKIPUPDATE} ${CURRENT} ${VERBOSE} ${QUIET} ${STRICT} @@ -130,6 +130,7 @@ Other Options: --automate For unattended deployment via cron --approve Approve and deploy queued code changes --deny Deny queued code changes + --build Build project assets --digest Create and send weekly digest --report Create a monthly activity report --no-check Override active file and server checks @@ -209,6 +210,7 @@ while [[ ${1:-unset} = -?* ]]; do --analytics-test) ANALYTICSTEST="1" ;; --monitor-test) MONITORTEST="1" ;; --stats) PROJSTATS="1" ;; + --build) BUILD="1"; NOCHECK="1"; FORCE="1" ;; --invoice) CREATE_INVOICE="1" ;; --unlock) UNLOCK="1" ;; --repair) REPAIR="1"; FORCE="1"; STASH="TRUE"; VERBOSE="TRUE" ;; @@ -361,8 +363,6 @@ if [[ "${VARIABLELIST}" == "1" ]]; then ( set -o posix ; set ) | cat -v; quickExit fi - - # Spam all the things! trace "Version ${VERSION}" if [[ "${INCOGNITO}" != "TRUE" ]]; then diff --git a/lib/git.sh b/lib/git.sh index e4d5b0c..ee51637 100755 --- a/lib/git.sh +++ b/lib/git.sh @@ -12,6 +12,11 @@ gitLock="${WORKPATH}/${APP}/.git/index.lock" # Make sure we're in a git repository. function gitStart() { + # If this is just a build, we won't require git repo functions + if [[ "${BUILD}" == "1" ]]; then + return 0 + fi + # Directory exists? if [[ ! -d "${WORKPATH}/${APP}" ]]; then info "${WORKPATH}/${APP} is not a valid directory." diff --git a/lib/package-manager.sh b/lib/package-manager.sh index 6ca656b..9ac408b 100755 --- a/lib/package-manager.sh +++ b/lib/package-manager.sh @@ -36,7 +36,7 @@ function pkgMgr() { trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json found." notice "Found npm configuration!" - if [[ "${FORCE}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then + if [[ "${BUILD}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then cd "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}" || errorCheck if [[ "${VERBOSE}" == "TRUE" ]]; then diff --git a/lib/utilities.sh b/lib/utilities.sh index 661c4f9..2b2f7f4 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -24,6 +24,11 @@ function go() { console "deploy ${VERSION}" + # Build only + if [[ "${BUILD}" == "1" ]]; then + pkgMgr; quietExit + fi + if [[ "${INCOGNITO}" != "TRUE" ]]; then console "Current working path is ${WORKPATH}/${APP}" fi From fb9acbcfd2e711636bacdb516b2881f29e06c705 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 29 May 2018 12:44:20 -0700 Subject: [PATCH 066/334] Redundant build steps are now skipped --- CHANGELOG.md | 1 + lib/package-manager.sh | 118 ++++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e8414..5f342e2 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Added `deploy --build [project name]` for quickly building project assets +- If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step. ## [3.7] - 05-03-2018 ### Added diff --git a/lib/package-manager.sh b/lib/package-manager.sh index 9ac408b..b5c72fb 100755 --- a/lib/package-manager.sh +++ b/lib/package-manager.sh @@ -5,55 +5,75 @@ ############################################################################### # Checks if project uses node.js or grunt, and runs package manager if needed. ############################################################################### -trace "Loading package management" +trace "Loading build management" + +# Initialize variables +read -r deploy_config SKIP_BUILD <<< "" +echo "${deploy_config} ${SKIP_BUILD}" > /dev/null function pkgMgr() { - if [[ "${FORCE}" != "1" ]]; then - if [[ "${UPGRADE}" != "1" ]]; then - - # Checking for app/lib, which assumes we're using Grunt - if [[ -f "${WORKPATH}/${APP}/Gruntfile.coffee" ]]; then - notice "Found grunt configuration!" - - if [[ "${FORCE}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then - cd "${WORKPATH}"/"${APP}" || errorCheck - - if [[ "${VERBOSE}" == "TRUE" ]]; then - /usr/local/bin/grunt build --force 2>&1 | tee --append "${trshFile}" - else - /usr/local/bin/grunt build --force &>> "${trshFile}" & - spinner $! - info "Packages successfully compiled." - fi - else - info "Skipping Grunt..." - fi - else - sleep 1 - - # node.js check - if [[ -f "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json" ]]; then - trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json found." - notice "Found npm configuration!" - - if [[ "${BUILD}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then - cd "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}" || errorCheck - - if [[ "${VERBOSE}" == "TRUE" ]]; then - npm run build | tee --append "${trshFile}" - else - npm run build &>> "${trshFile}" & - spinner $! - info "Packages successfully compiled." - fi - else - info "Skipping Node Package Manager..." - fi - else - # info "No package management needed." - trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/$APP/package.json not found, skipping." - fi - fi - fi - fi + if [[ "${FORCE}" != "1" ]] || [[ "${BUILD}" == "1" ]]; then + if [[ "${UPGRADE}" != "1" ]]; then + + # Checking for app/lib, which assumes we're using Grunt + if [[ -f "${WORKPATH}/${APP}/Gruntfile.coffee" ]]; then + notice "Found grunt configuration!" + + if [[ "${FORCE}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then + cd "${WORKPATH}"/"${APP}" || errorCheck + + if [[ "${VERBOSE}" == "TRUE" ]]; then + /usr/local/bin/grunt build --force 2>&1 | tee --append "${trshFile}" + else + /usr/local/bin/grunt build --force &>> "${trshFile}" & + spinner $! + info "Packages successfully compiled." + fi + else + info "Skipping Grunt..." + fi + else + sleep 1 + + # node.js check + if [[ -f "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json" ]]; then + trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json found." + notice "Found npm configuration!" + + check_build_method + if [[ "${SKIP_BUILD}" == "1" ]]; then + return + fi + + if [[ "${BUILD}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then + cd "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}" || errorCheck + + if [[ "${VERBOSE}" == "TRUE" ]]; then + npm run build | tee --append "${trshFile}" + else + npm run build &>> "${trshFile}" & + spinner $! + info "Packages successfully compiled." + fi + else + info "Skipping Node Package Manager..." + fi + else + # info "No package management needed." + trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/$APP/package.json not found, skipping." + fi + fi + fi + fi +} + +# Check deployment method, and determine if it includes a build upon each +# deployment. +function check_build_method() { + if [[ "${DEPLOY}" == *"mina"* ]]; then + deploy_config="$(cat ${WORKPATH}/${APP}/Minafile)" + if [[ "${deploy_config}" =~ "npm run build" ]] && [[ "${BUILD}" != "1" ]]; then + SKIP_BUILD="1" + fi + fi } From 8f1527fe53d92982c1f8ea5a475e0c23e41efd5f Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Tue, 29 May 2018 12:46:13 -0700 Subject: [PATCH 067/334] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f342e2..d58163a 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Added `deploy --build [project name]` for quickly building project assets -- If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step. +- If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step ## [3.7] - 05-03-2018 ### Added From bec5963f72489c347e6613009b25b7fa86661832 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 31 May 2018 11:16:46 -0700 Subject: [PATCH 068/334] Test email improvements --- CHANGELOG.md | 2 + deploy.sh | 32 ++-- lib/mail-log.sh | 368 +++++++++++++++++++++++++++-------------- lib/package-manager.sh | 111 +++++++------ 4 files changed, 319 insertions(+), 194 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d58163a..2f7443f 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Added `deploy --build [project name]` for quickly building project assets +### Changed - If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step +- Test emails now contain more project information ## [3.7] - 05-03-2018 ### Added diff --git a/deploy.sh b/deploy.sh index f8c00d2..c50d2f6 100755 --- a/deploy.sh +++ b/deploy.sh @@ -55,13 +55,13 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \ POSTEMAILHEAD POSTEMAILTAIL POSTTOSLACK SLACKURL SLACKERROR POSTURL NOKEY \ PROJNAME PROJCLIENT DEVURL PRODURL REPO MASTER PRODUCTION COMMITMSG DEPLOY \ DONOTDEPLOY TASK CHECKBRANCH ACTIVECHECK CHECKTIME GARBAGE WFCHECK ACFKEY \ - WFOFF REMOTELOG REMOTETEMPLATE LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL DIGESTSLACK \ - DIGESTURL CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPASS SCPCMD \ - SSHCMD LOGMSG EXPIRELOGS SERVERCHECK STASH MAILPATH REQUIREAPPROVAL ADDTIME \ - SCPPORT TASKUSER CLIENTID CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN \ - REFRESHTOKEN PROFILEID ALLOWROOT SHORTEMAIL INCOGNITO \ - REPORTURL CLIENTCONTACT INCLUDEHOSTING GLOBAL_VERSION USER_VERSION \ - PROJECT_VERSION <<< "" + WFOFF REMOTELOG REMOTETEMPLATE LOCALHOSTPOST LOCALHOSTPATH DIGESTEMAIL \ + DIGESTSLACK DIGESTURL CLIENTLOGO REMOTEURL SCPPOST SCPUSER SCPHOST \ + SCPHOSTPATH SCPHOSTPORT SCPPASS SCPCMD SSHCMD LOGMSG EXPIRELOGS SERVERCHECK \ + STASH MAILPATH REQUIREAPPROVAL ADDTIME SCPPORT TASKUSER CLIENTID \ + CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN REFRESHTOKEN \ + PROFILEID ALLOWROOT SHORTEMAIL INCOGNITO REPORTURL CLIENTCONTACT \ + INCLUDEHOSTING GLOBAL_VERSION USER_VERSION PROJECT_VERSION <<< "" echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} ${DEVGROUP} ${APACHEUSER} ${APACHEGROUP} ${TO} ${FROM} ${SUBJECT} ${EMAILERROR} @@ -69,15 +69,15 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} ${POSTEMAILTAIL} ${POSTTOSLACK} ${SLACKURL} ${SLACKERROR} ${POSTURL} ${NOKEY} ${PROJNAME} ${PROJCLIENT} ${DEVURL} ${PRODURL} ${REPO} ${MASTER} ${PRODUCTION} ${COMMITMSG} ${DEPLOY} ${DONOTDEPLOY} ${TASK} ${CHECKBRANCH} ${ACTIVECHECK} - ${CHECKTIME} ${GARBAGE} ${WFCHECK} ${ACFKEY} ${WFOFF} ${REMOTELOG} ${REMOTETEMPLATE} - ${LOCALHOSTPOST} ${LOCALHOSTPATH} ${DIGESTEMAIL} ${DIGESTSLACK} ${DIGESTURL} - ${CLIENTLOGO} ${REMOTEURL} ${SCPPOST} ${SCPUSER} ${SCPHOST} - ${SCPHOSTPATH} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} ${EXPIRELOGS} - ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} ${ADDTIME} ${TASKUSER} - ${SCPPORT} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} ${AUTHORIZATIONCODE} ${ACCESSTOKEN} - ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} - ${CLIENTCONTACT} ${INCLUDEHOSTING} ${GLOBAL_VERSION} ${USER_VERSION} - ${PROJECT_VERSION}" > /dev/null + ${CHECKTIME} ${GARBAGE} ${WFCHECK} ${ACFKEY} ${WFOFF} ${REMOTELOG} + ${REMOTETEMPLATE} ${LOCALHOSTPOST} ${LOCALHOSTPATH} ${DIGESTEMAIL} + ${DIGESTSLACK} ${DIGESTURL} ${CLIENTLOGO} ${REMOTEURL} ${SCPPOST} ${SCPUSER} + ${SCPHOST} ${SCPHOSTPATH} ${SCPPORT} ${SCPPASS} ${SCPCMD} ${SSHCMD} ${LOGMSG} + ${EXPIRELOGS} ${SERVERCHECK} ${STASH} ${MAILPATH} ${REQUIREAPPROVAL} + ${ADDTIME} ${TASKUSER} ${SCPPORT} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI} + ${AUTHORIZATIONCODE} ${ACCESSTOKEN} ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} + ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} ${INCLUDEHOSTING} + ${GLOBAL_VERSION} ${USER_VERSION} ${PROJECT_VERSION}" > /dev/null # Internal variables read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \ urlFile htmlFile htmlSendmail htmlEmail clientEmail textSendmail deployPath \ diff --git a/lib/mail-log.sh b/lib/mail-log.sh index 295022d..b81032e 100755 --- a/lib/mail-log.sh +++ b/lib/mail-log.sh @@ -79,76 +79,133 @@ function email_test() { echo "Current user is ${DEV}

"; echo echo "Project Information
" - [[ -n "${PROJNAME}" ]] && echo "Name: ${PROJNAME}
" + [[ -n "${PROJNAME}" ]] && echo "Name: ${PROJNAME}
" [[ -n "${PROJCLIENT}" ]] && echo "Client: ${PROJCLIENT}
" - [[ -n "${CLIENTLOGO}" ]] && echo "Logo: ${CLIENTLOGO}
" - [[ -n "${DIGESTEMAIL}" ]] && echo "Digest email(s): ${DIGESTEMAIL}
" - [[ -n "${DEVURL}" ]] && echo "Staging URL: ${DEVURL}
" - [[ -n "${PRODURL}" ]] && echo "Production URL: ${PRODURL}
" + [[ -n "${DEVURL}" ]] && echo "Staging URL: ${DEVURL}
" + [[ -n "${PRODURL}" ]] && echo "Production URL: ${PRODURL}
" echo "
" # Git if [[ -n "${REPO}" ]] || [[ -n "${MASTER}" ]] || [[ -n "${PRODUCTION}" ]] || [[ -n "${AUTOMERGE}" ]] || [[ -n "${STASH}" ]] || [[ -n "${CHECKBRANCH}" ]]; then - echo "Git Configuration
" - [[ -n "${REPO}" ]] && echo "Repo: ${REPOHOST}/${REPO}
" - [[ -n "${MASTER}" ]] && echo "Master branch: ${MASTER}
" - [[ -n "${PRODUCTION}" ]] && echo "Production branch: ${PRODUCTION}
" - [[ -n "${AUTOMERGE}" ]] && echo "Auto merge: ${AUTOMERGE}
" - [[ -n "${STASH}" ]] && echo "File Stashing: ${STASH}
" - [[ -n "${CHECKBRANCH}" ]] && echo "Force branch checking: ${CHECKBRANCH}
" - echo "
" - fi - # Wordpress - if [[ -n "${WPROOT}" ]] || [[ -n "${WPAPP}" ]] || [[ -n "${WPSYSTEM}" ]]; then - echo "Wordpress Setup
" - [[ -n "${WPROOT}" ]] && echo "Wordpress root: ${WPROOT}
" - [[ -n "${WPAPP}" ]] && echo "Wordpress application: ${WPAPP}
" - [[ -n "${WPSYSTEM}" ]] && echo "Wordpress system: ${WPSYSTEM}
" - echo "
" - fi - # Deployment - if [[ -n "${DEPLOY}" ]] || [[ -n "${DONOTDEPLOY}" ]]; then - echo "Deployment Configuration
" - [[ -n "${DEPLOY}" ]] && echo "Deploy command: ${DEPLOY}
" - [[ -n "${DONOTDEPLOY}" ]] && echo "Disallow deployment: ${DONOTDEPLOY}
" - echo "
" - fi - # Integration - if [[ -n "${TASK}" ]] || [[ -n "${TASKUSER}" ]] || [[ -n "${ADDTIME}" ]] || [[ -n "${POSTTOSLACK}" ]] || [[ -n "${SLACKERROR}" ]] || [[ -n "${PROFILEID}" ]] || [[ -n "${POSTURL}" ]]; then - echo "Integration
" - [[ -n "${TASK}" ]] && echo "Task #: ${TASK}
" - [[ -n "${TASKUSER}" ]] && echo "Task user: ${TASKUSER}
" - [[ -n "${ADDTIME}" ]] && echo "Task time: ${ADDTIME}
" - [[ -n "${POSTTOSLACK}" ]] && echo "Post to Slack: ${POSTTOSLACK}
" - [[ -n "${SLACKERROR}" ]] && echo "Post errors to Slack: ${SLACKERROR}
" - [[ -n "${POSTURL}" ]] && echo "Webhook URL: ${POSTURL}
" - [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}
" - echo "
" - fi + echo "Git Configuration
" + [[ -n "${REPO}" ]] && echo "Repo: ${REPOHOST}/${REPO}
" + [[ -n "${MASTER}" ]] && echo "Master branch: ${MASTER}
" + [[ -n "${PRODUCTION}" ]] && echo "Production branch: ${PRODUCTION}
" + [[ -n "${AUTOMERGE}" ]] && echo "Auto merge: ${AUTOMERGE}
" + [[ -n "${STASH}" ]] && echo "File Stashing: ${STASH}
" + [[ -n "${CHECKBRANCH}" ]] && echo "Force branch checking: ${CHECKBRANCH}
" + echo "
" + fi + # Wordpress + if [[ -n "${WPROOT}" ]] || [[ -n "${WPAPP}" ]] || [[ -n "${WPSYSTEM}" ]]; then + echo "Wordpress Setup
" + [[ -n "${WPROOT}" ]] && echo "Wordpress root: ${WPROOT}
" + [[ -n "${WPAPP}" ]] && echo "Wordpress application: ${WPAPP}
" + [[ -n "${WPSYSTEM}" ]] && echo "Wordpress system: ${WPSYSTEM}
" + echo "
" + fi + # Deployment + if [[ -n "${DEPLOY}" ]] || [[ -n "${DONOTDEPLOY}" ]]; then + echo "Deployment Configuration
" + [[ -n "${DEPLOY}" ]] && echo "Deploy command: ${DEPLOY}
" + [[ -n "${DONOTDEPLOY}" ]] && echo "Disallow deployment: ${DONOTDEPLOY}
" + echo "
" + fi + # Notifications + if [[ -n "${TASK}" ]] || [[ -n "${TASKUSER}" ]] || [[ -n "${ADDTIME}" ]] || [[ -n "${POSTTOSLACK}" ]] || [[ -n "${SLACKERROR}" ]] || [[ -n "${PROFILEID}" ]] || [[ -n "${POSTURL}" ]]; then + echo "Notifications
" + [[ -n "${TASK}" ]] && echo "Task #: ${TASK}
" + [[ -n "${TASKUSER}" ]] && echo "Task user: ${TASKUSER}
" + [[ -n "${ADDTIME}" ]] && echo "Task time: ${ADDTIME}
" + [[ -n "${POSTTOSLACK}" ]] && echo "Post to Slack: ${POSTTOSLACK}
" + [[ -n "${SLACKERROR}" ]] && echo "Post errors to Slack: ${SLACKERROR}
" + [[ -n "${POSTURL}" ]] && echo "Webhook URL: ${POSTURL}
" + [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}
" + echo "
" + fi + # Logging + if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then + echo "Logging
" + [[ -n "${TO}" ]] && echo "Send to: ${TO}
" + [[ -n "${HTMLTEMPLATE}" ]] && echo "Email template: ${HTMLTEMPLATE}
" + [[ -n "${CLIENTLOGO}" ]] && echo "Logo: ${CLIENTLOGO}
" + [[ -n "${COVER}" ]] && echo "Cover image: ${COVER}
" + [[ -n "${INCOGNITO}" ]] && echo "Logo: ${INCOGNITO}
" + [[ -n "${REMOTELOG}" ]] && echo "Web logs: ${REMOTELOG}
" + [[ -n "${REMOTEURL}" ]] && echo "Address: ${REMOTEURL}
" + [[ -n "${EXPIRELOGS}" ]] && echo "Log expiration: ${EXPIRELOGS} days
" + [[ -n "${REMOTETEMPLATE}" ]] && echo "Log template: ${REMOTETEMPLATE}
" + [[ -n "${SCPPOST}" ]] && echo "Post with SCP/SSH: ${SCPPOST}
" + [[ -n "${SCPUSER}" ]] && echo "SCP user: ${SCPUSER}
" + [[ -n "${SCPHOST}" ]] && echo "Remote log host: ${SCPHOST}
" + [[ -n "${SCPHOSTPATH}" ]] && echo "Remote log path: ${SCPHOSTPATH}
" + [[ -n "${SCPHOSTPORT}" ]] && echo "Remote log path: ${SCPHOSTPORT}
" + [[ -n "${LOCALHOSTPOST}" ]] && echo "Save logs locally: ${LOCALHOSTPOST}
" + [[ -n "${LOCALHOSTPATH}" ]] && echo "Path to local logs: ${}LOCALHOSTPATH
" + echo "
" + fi + # Weekly Digests + if [[ -n "${DIGESTEMAIL}" ]]; then + echo "Weekly Digests
" + [[ -n "${DIGESTEMAIL}" ]] && echo "Send to: ${DIGESTEMAIL}
" + echo "
" + fi + # Monthly Reporting + if [[ -n "${CLIENTCONTACT}" ]] || [[ -n "${INCLUDEHOSTING}" ]]; then + echo "Monthly Reporting
" + [[ -n "${CLIENTCONTACT}" ]] && echo "Client contact: ${CLIENTCONTACT}
" + [[ -n "${INCLUDEHOSTING}" ]] && echo "Hosting notes: ${INCLUDEHOSTING}
" + echo "
" + fi + # Invoice Ninja integration + if [[ -n "${IN_HOST}" ]] || [[ -n "${IN_TOKEN}" ]] || [[ -n "${IN_CLIENT_ID}" ]] || [[ -n "${IN_PRODUCT}" ]] || [[ -n "${IN_ITEM_COST}" ]] || [[ -n "${IN_ITEM_QTY}" ]] || [[ -n "${IN_NOTES}" ]] || [[ -n "${IN_NOTES}" ]]; then + echo "Invoice Ninja Integration
" + [[ -n "${IN_HOST}" ]] && echo "Host: ${IN_HOST}
" + [[ -n "${IN_TOKEN}" ]] && echo "Token: ${IN_TOKEN}
" + [[ -n "${IN_CLIENT_ID}" ]] && echo "Client ID: ${IN_CLIENT_ID}
" + [[ -n "${IN_PRODUCT}" ]] && echo "Product: ${IN_PRODUCT}
" + [[ -n "${IN_ITEM_COST}" ]] && echo "Item cost: ${IN_ITEM_COST}
" + [[ -n "${IN_ITEM_QTY}" ]] && echo "Item quantity: ${IN_ITEM_QTY}
" + [[ -n "${IN_NOTES}" ]] && echo "Notes: ${IN_NOTES}
" + echo "
" + fi + # Google Analytics + if [[ -n "${CLIENTID}" ]] || [[ -n "${CLIENTSECRET}" ]] || [[ -n "${REDIRECTURI}" ]] || [[ -n "${AUTHORIZATIONCODE}" ]] || [[ -n "${ACCESSTOKEN}" ]] || [[ -n "${REFRESHTOKEN}" ]] || [[ -n "${PROFILEID}" ]]; then + echo "Google Analytics
" + [[ -n "${CLIENTID}" ]] && echo "Client ID: ${CLIENTID}
" + [[ -n "${CLIENTSECRET}" ]] && echo "Client secret: ${CLIENTSECRET}
" + [[ -n "${REDIRECTURI}" ]] && echo "Redirect URI: ${REDIRECTURI}
" + [[ -n "${AUTHORIZATIONCODE}" ]] && echo "Authorization code: ${AUTHORIZATIONCODE}
" + [[ -n "${ACCESSTOKEN}" ]] && echo "Access token: ${ACCESSTOKEN}
" + [[ -n "${REFRESHTOKEN}" ]] && echo "Refresh token: ${REFRESHTOKEN}
" + [[ -n "${PROFILEID}" ]] && echo "Profile ID: ${PROFILEID}
" + echo "
" + fi # Server monitoring if [[ -n "${MONITORURL}" ]] || [[ -n "${MONITORUSER}" ]] || [[ -n "${SERVERID}" ]]; then echo "Server Monitoring
" - [[ -n "${TASK}" ]] && echo "Monitor URL: ${MONITORURL}
" - [[ -n "${TASK}" ]] && echo "User: ${MONITORUSER}
" - [[ -n "${TASK}" ]] && echo "Server ID: ${SERVERID}
" + [[ -n "${MONITORURL}" ]] && echo "Monitor URL: ${MONITORURL}
" + [[ -n "${MONITORUSER}" ]] && echo "User: ${MONITORUSER}
" + [[ -n "${SERVERID}" ]] && echo "Server ID: ${SERVERID}
" + echo "
" + fi + # Dropbox integration + if [[ -n "${DB_API_TOKEN}" ]] || [[ -n "${DB_BACKUP_PATH}" ]]; then + echo "Dropbox Integration
" + [[ -n "${DB_API_TOKEN}" ]] && echo "Token: ${DB_API_TOKEN}
" + [[ -n "${DB_BACKUP_PATH}" ]] && echo "Backup path: ${DB_BACKUP_PATH}
" + echo "
" + fi + # Malware scanning + if [[ -n "${NIKTO}" ]] || [[ -n "${NIKTO_CONFIG}" ]] || [[ -n "${NIKTO_PROXY}" ]]; then + echo "Malware Scanning
" + [[ -n "${NIKTO}" ]] && echo "Scanner: ${NIKTO}
" + [[ -n "${NIKTO_CONFIG}" ]] && echo "Configuration path: ${NIKTO_CONFIG}
" + [[ -n "${NIKTO_PROXY}" ]] && echo "Proxy: ${NIKTO_PROXY}
" echo "
" fi - # Logging - if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then - echo "Logging
" - [[ -n "${REMOTELOG}" ]] && echo "Web logs: ${REMOTELOG}
" - [[ -n "${REMOTEURL}" ]] && echo "Address: ${REMOTEURL}
" - [[ -n "${EXPIRELOGS}" ]] && echo "Log expiration: ${EXPIRELOGS} days
" - [[ -n "${LOCALHOSTPOST}" ]] && echo "Save logs locally: ${LOCALHOSTPOST}
" - [[ -n "${LOCALHOSTPATH}" ]] && echo "Path to local logs: ${}LOCALHOSTPATH
" - [[ -n "${SCPPOST}" ]] && echo "Post with SCP/SSH: ${SCPPOST}
" - [[ -n "${SCPUSER}" ]] && echo "SCP user: ${SCPUSER}
" - [[ -n "${SCPHOST}" ]] && echo "Remote log host: ${SCPHOST}
" - [[ -n "${SCPHOSTPATH}" ]] && echo "Remote log path: ${SCPHOSTPATH}
" - [[ -n "${REMOTETEMPLATE}" ]] && echo "Log template: ${REMOTETEMPLATE}
" - echo "
" - fi ) | "${MAILPATH}"/sendmail -t - # Send Text mail + + # Send text mail ( echo "Sender: ${FROM}" echo "From: ${FROM} <${FROM}>" @@ -162,81 +219,141 @@ function email_test() { echo echo "Project Information" echo "-------------------" - [[ -n "${PROJNAME}" ]] && echo "Name: ${PROJNAME}" + [[ -n "${PROJNAME}" ]] && echo "Name: ${PROJNAME}" [[ -n "${PROJCLIENT}" ]] && echo "Client: ${PROJCLIENT}" - [[ -n "${CLIENTLOGO}" ]] && echo "Logo: ${CLIENTLOGO}" - [[ -n "${DIGESTEMAIL}" ]] && echo "Digest email(s): ${DIGESTEMAIL}" [[ -n "${DEVURL}" ]] && echo "Staging URL: ${DEVURL}" [[ -n "${PRODURL}" ]] && echo "Production URL: ${PRODURL}" echo # Git if [[ -n "${REPO}" ]] || [[ -n "${MASTER}" ]] || [[ -n "${PRODUCTION}" ]] || [[ -n "${AUTOMERGE}" ]] || [[ -n "${STASH}" ]] || [[ -n "${CHECKBRANCH}" ]]; then - echo "Git Configuration" - echo "-----------------" - [[ -n "${REPO}" ]] && echo "Repo: ${REPOHOST}/${REPO}" - [[ -n "${MASTER}" ]] && echo "Master branch: ${MASTER}" - [[ -n "${PRODUCTION}" ]] && echo "Production branch: ${PRODUCTION}" - [[ -n "${AUTOMERGE}" ]] && echo "Auto merge: ${AUTOMERGE}" - [[ -n "${STASH}" ]] && echo "File Stashing: ${STASH}" - [[ -n "${CHECKBRANCH}" ]] && echo "Force branch checking: ${CHECKBRANCH}" - echo - fi - # Wordpress - if [[ -n "${WPROOT}" ]] || [[ -n "${WPAPP}" ]] || [[ -n "${WPSYSTEM}" ]]; then - echo "Wordpress Setup" - echo "---------------" - [[ -n "${WPROOT}" ]] && echo "Wordpress root: ${WPROOT}" - [[ -n "${WPAPP}" ]] && echo "Wordpress application: ${WPAPP}" - [[ -n "${WPSYSTEM}" ]] && echo "Wordpress system: ${WPSYSTEM}" - echo - fi - # Deployment - if [[ -n "${DEPLOY}" ]] || [[ -n "${DONOTDEPLOY}" ]]; then - echo "Deployment Configuration" - echo "------------------------" - [[ -n "${DEPLOY}" ]] && echo "Deploy command: ${DEPLOY}" - [[ -n "${DONOTDEPLOY}" ]] && echo "Disallow deployment: ${DONOTDEPLOY}" - echo - fi - # Integration - if [[ -n "${TASK}" ]] || [[ -n "${TASKUSER}" ]] || [[ -n "${ADDTIME}" ]] || [[ -n "${POSTTOSLACK}" ]] || [[ -n "${SLACKERROR}" ]] || [[ -n "${PROFILEID}" ]] || [[ -n "${POSTURL}" ]]; then - echo "Integration" - echo "-----------" - [[ -n "${TASK}" ]] && echo "Task #: ${TASK}" - [[ -n "${TASKUSER}" ]] && echo "Task user: ${TASKUSER}" - [[ -n "${ADDTIME}" ]] && echo "Task time: ${ADDTIME}" - [[ -n "${POSTTOSLACK}" ]] && echo "Post to Slack: ${POSTTOSLACK}" - [[ -n "${SLACKERROR}" ]] && echo "Post errors to Slack: ${SLACKERROR}" - [[ -n "${POSTURL}" ]] && echo "Webhook URL: ${POSTURL}" - [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}" - echo - fi + echo "Git Configuration" + echo "-----------------" + [[ -n "${REPO}" ]] && echo "Repo: ${REPOHOST}/${REPO}" + [[ -n "${MASTER}" ]] && echo "Master branch: ${MASTER}" + [[ -n "${PRODUCTION}" ]] && echo "Production branch: ${PRODUCTION}" + [[ -n "${AUTOMERGE}" ]] && echo "Auto merge: ${AUTOMERGE}" + [[ -n "${STASH}" ]] && echo "File Stashing: ${STASH}" + [[ -n "${CHECKBRANCH}" ]] && echo "Force branch checking: ${CHECKBRANCH}" + echo + fi + # Wordpress + if [[ -n "${WPROOT}" ]] || [[ -n "${WPAPP}" ]] || [[ -n "${WPSYSTEM}" ]]; then + echo "Wordpress Setup" + echo "---------------" + [[ -n "${WPROOT}" ]] && echo "Wordpress root: ${WPROOT}" + [[ -n "${WPAPP}" ]] && echo "Wordpress application: ${WPAPP}" + [[ -n "${WPSYSTEM}" ]] && echo "Wordpress system: ${WPSYSTEM}" + echo + fi + # Deployment + if [[ -n "${DEPLOY}" ]] || [[ -n "${DONOTDEPLOY}" ]]; then + echo "Deployment Configuration" + echo "------------------------" + [[ -n "${DEPLOY}" ]] && echo "Deploy command: ${DEPLOY}" + [[ -n "${DONOTDEPLOY}" ]] && echo "Disallow deployment: ${DONOTDEPLOY}" + echo + fi + # Notifications + if [[ -n "${TASK}" ]] || [[ -n "${TASKUSER}" ]] || [[ -n "${ADDTIME}" ]] || [[ -n "${POSTTOSLACK}" ]] || [[ -n "${SLACKERROR}" ]] || [[ -n "${PROFILEID}" ]] || [[ -n "${POSTURL}" ]]; then + echo "Notifications" + echo "-------------" + [[ -n "${TASK}" ]] && echo "Task #: ${TASK}" + [[ -n "${TASKUSER}" ]] && echo "Task user: ${TASKUSER}" + [[ -n "${ADDTIME}" ]] && echo "Task time: ${ADDTIME}" + [[ -n "${POSTTOSLACK}" ]] && echo "Post to Slack: ${POSTTOSLACK}" + [[ -n "${SLACKERROR}" ]] && echo "Post errors to Slack: ${SLACKERROR}" + [[ -n "${POSTURL}" ]] && echo "Webhook URL: ${POSTURL}" + [[ -n "${PROFILEID}" ]] && echo "Google Analytics ID: ${PROFILEID}" + echo + fi + # Logging + if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then + echo "Logging" + echo "-------" + [[ -n "${TO}" ]] && echo "Send to: ${TO}" + [[ -n "${HTMLTEMPLATE}" ]] && echo "Email template: ${HTMLTEMPLATE}" + [[ -n "${CLIENTLOGO}" ]] && echo "Logo: ${CLIENTLOGO}" + [[ -n "${COVER}" ]] && echo "Cover image: ${COVER}" + [[ -n "${INCOGNITO}" ]] && echo "Logo: ${INCOGNITO}" + [[ -n "${REMOTELOG}" ]] && echo "Web logs: ${REMOTELOG}" + [[ -n "${REMOTEURL}" ]] && echo "Address: ${REMOTEURL}" + [[ -n "${EXPIRELOGS}" ]] && echo "Log expiration: ${EXPIRELOGS} days" + [[ -n "${REMOTETEMPLATE}" ]] && echo "Log template: ${REMOTETEMPLATE}" + [[ -n "${SCPPOST}" ]] && echo "Post with SCP/SSH: ${SCPPOST}" + [[ -n "${SCPUSER}" ]] && echo "SCP user: ${SCPUSER}" + [[ -n "${SCPHOST}" ]] && echo "Remote log host: ${SCPHOST}" + [[ -n "${SCPHOSTPATH}" ]] && echo "Remote log path: ${SCPHOSTPATH}" + [[ -n "${SCPHOSTPORT}" ]] && echo "Remote log path: ${SCPHOSTPORT}" + [[ -n "${LOCALHOSTPOST}" ]] && echo "Save logs locally: ${LOCALHOSTPOST}" + [[ -n "${LOCALHOSTPATH}" ]] && echo "Path to local logs: ${}LOCALHOSTPATH" + echo + fi + # Weekly Digests + if [[ -n "${DIGESTEMAIL}" ]]; then + echo "Weekly Digests" + echo "--------------" + [[ -n "${DIGESTEMAIL}" ]] && echo "Send to: ${DIGESTEMAIL}" + echo + fi + # Monthly Reporting + if [[ -n "${CLIENTCONTACT}" ]] || [[ -n "${INCLUDEHOSTING}" ]]; then + echo "Monthly Reporting" + echo "-----------------" + [[ -n "${CLIENTCONTACT}" ]] && echo "Client contact: ${CLIENTCONTACT}" + [[ -n "${INCLUDEHOSTING}" ]] && echo "Hosting notes: ${INCLUDEHOSTING}" + echo + fi + # Invoice Ninja integration + if [[ -n "${IN_HOST}" ]] || [[ -n "${IN_TOKEN}" ]] || [[ -n "${IN_CLIENT_ID}" ]] || [[ -n "${IN_PRODUCT}" ]] || [[ -n "${IN_ITEM_COST}" ]] || [[ -n "${IN_ITEM_QTY}" ]] || [[ -n "${IN_NOTES}" ]] || [[ -n "${IN_NOTES}" ]]; then + echo "Invoice Ninja Integration" + [[ -n "${IN_HOST}" ]] && echo "Host: ${IN_HOST}" + [[ -n "${IN_TOKEN}" ]] && echo "Token: ${IN_TOKEN}" + [[ -n "${IN_CLIENT_ID}" ]] && echo "Client ID: ${IN_CLIENT_ID}" + [[ -n "${IN_PRODUCT}" ]] && echo "Product: ${IN_PRODUCT}" + [[ -n "${IN_ITEM_COST}" ]] && echo "Item cost: ${IN_ITEM_COST}" + [[ -n "${IN_ITEM_QTY}" ]] && echo "Item quantity: ${IN_ITEM_QTY}" + [[ -n "${IN_NOTES}" ]] && echo "Notes: ${IN_NOTES}" + echo + fi + # Google Analytics + if [[ -n "${CLIENTID}" ]] || [[ -n "${CLIENTSECRET}" ]] || [[ -n "${REDIRECTURI}" ]] || [[ -n "${AUTHORIZATIONCODE}" ]] || [[ -n "${ACCESSTOKEN}" ]] || [[ -n "${REFRESHTOKEN}" ]] || [[ -n "${PROFILEID}" ]]; then + echo "Google Analytics" + echo "----------------" + [[ -n "${CLIENTID}" ]] && echo "Client ID: ${CLIENTID}" + [[ -n "${CLIENTSECRET}" ]] && echo "Client secret: ${CLIENTSECRET}" + [[ -n "${REDIRECTURI}" ]] && echo "Redirect URI: ${REDIRECTURI}" + [[ -n "${AUTHORIZATIONCODE}" ]] && echo "Authorization code: ${AUTHORIZATIONCODE}" + [[ -n "${ACCESSTOKEN}" ]] && echo "Access token: ${ACCESSTOKEN}" + [[ -n "${REFRESHTOKEN}" ]] && echo "Refresh token: ${REFRESHTOKEN}" + [[ -n "${PROFILEID}" ]] && echo "Profile ID: ${PROFILEID}" + echo + fi # Server monitoring if [[ -n "${MONITORURL}" ]] || [[ -n "${MONITORUSER}" ]] || [[ -n "${SERVERID}" ]]; then echo "Server Monitoring" echo "-----------------" - [[ -n "${TASK}" ]] && echo "Monitor URL: ${MONITORURL}" - [[ -n "${TASK}" ]] && echo "User: ${MONITORUSER}" - [[ -n "${TASK}" ]] && echo "Server ID: ${SERVERID}" + [[ -n "${MONITORURL}" ]] && echo "Monitor URL: ${MONITORURL}" + [[ -n "${MONITORUSER}" ]] && echo "User: ${MONITORUSER}" + [[ -n "${SERVERID}" ]] && echo "Server ID: ${SERVERID}" + echo + fi + # Dropbox integration + if [[ -n "${DB_API_TOKEN}" ]] || [[ -n "${DB_BACKUP_PATH}" ]]; then + echo "Dropbox Integration" + echo "-------------------" + [[ -n "${DB_API_TOKEN}" ]] && echo "Token: ${DB_API_TOKEN}" + [[ -n "${DB_BACKUP_PATH}" ]] && echo "Backup path: ${DB_BACKUP_PATH}" + echo + fi + # Malware scanning + if [[ -n "${NIKTO}" ]] || [[ -n "${NIKTO_CONFIG}" ]] || [[ -n "${NIKTO_PROXY}" ]]; then + echo "Malware Scanning" + echo "----------------" + [[ -n "${NIKTO}" ]] && echo "Scanner: ${NIKTO}" + [[ -n "${NIKTO_CONFIG}" ]] && echo "Configuration path: ${NIKTO_CONFIG}" + [[ -n "${NIKTO_PROXY}" ]] && echo "Proxy: ${NIKTO_PROXY}" echo fi - # Logging - if [[ -n "${REMOTELOG}" ]] || [[ -n "${REMOTEURL}" ]] || [[ -n "${EXPIRELOGS}" ]] || [[ -n "${LOCALHOSTPOST}" ]] || [[ -n "${LOCALHOSTPATH}" ]] || [[ -n "${SCPPOST}" ]] || [[ -n "${SCPUSER}" ]] || [[ -n "${SCPHOST}" ]] || [[ -n "${SCPHOSTPATH}" ]] || [[ -n "${SCPPASS}" ]] || [[ -n "${REMOTETEMPLATE}" ]] || [[ -n "${REMOTETEMPLATE}" ]]; then - echo "Logging" - echo "-------" - [[ -n "${REMOTELOG}" ]] && echo "Web logs: ${REMOTELOG}" - [[ -n "${REMOTEURL}" ]] && echo "Address: ${REMOTEURL}" - [[ -n "${EXPIRELOGS}" ]] && echo "Log expiration: ${EXPIRELOGS} days" - [[ -n "${LOCALHOSTPOST}" ]] && echo "Save logs locally: ${LOCALHOSTPOST}" - [[ -n "${LOCALHOSTPATH}" ]] && echo "Path to local logs: ${}LOCALHOSTPATH" - [[ -n "${SCPPOST}" ]] && echo "Post with SCP/SSH: ${SCPPOST}" - [[ -n "${SCPUSER}" ]] && echo "SCP user: ${SCPUSER}" - [[ -n "${SCPHOST}" ]] && echo "Remote log host: ${SCPHOST}" - [[ -n "${SCPHOSTPATH}" ]] && echo "Remote log path: ${SCPHOSTPATH}" - [[ -n "${REMOTETEMPLATE}" ]] && echo "Log template: ${REMOTETEMPLATE}s" - echo - fi - ) | "${MAILPATH}"/sendmail -t fi @@ -258,6 +375,11 @@ function email_test() { echo echo "This is a test email integration from deploy ${VERSION}" echo "(https://github.com/EMRL/deploy/)" + echo + [[ -n "${PROJNAME}" ]] && echo "Name: ${PROJNAME}" + [[ -n "${PROJCLIENT}" ]] && echo "Client: ${PROJCLIENT}" + [[ -n "${DEVURL}" ]] && echo "Staging URL: ${DEVURL}" + [[ -n "${PRODURL}" ]] && echo "Production URL: ${PRODURL}" ) | "${MAILPATH}"/sendmail -t quietExit else diff --git a/lib/package-manager.sh b/lib/package-manager.sh index b5c72fb..d33dc0b 100755 --- a/lib/package-manager.sh +++ b/lib/package-manager.sh @@ -3,7 +3,7 @@ # package-manager # ############################################################################### -# Checks if project uses node.js or grunt, and runs package manager if needed. +# Checks if project uses node.js or grunt, and builds assets if needed. ############################################################################### trace "Loading build management" @@ -12,68 +12,69 @@ read -r deploy_config SKIP_BUILD <<< "" echo "${deploy_config} ${SKIP_BUILD}" > /dev/null function pkgMgr() { - if [[ "${FORCE}" != "1" ]] || [[ "${BUILD}" == "1" ]]; then - if [[ "${UPGRADE}" != "1" ]]; then + if [[ "${FORCE}" != "1" ]] || [[ "${BUILD}" == "1" ]]; then + if [[ "${UPGRADE}" != "1" ]]; then - # Checking for app/lib, which assumes we're using Grunt - if [[ -f "${WORKPATH}/${APP}/Gruntfile.coffee" ]]; then - notice "Found grunt configuration!" + # Checking for app/lib, which assumes we're using Grunt + if [[ -f "${WORKPATH}/${APP}/Gruntfile.coffee" ]]; then + notice "Found grunt configuration!" - if [[ "${FORCE}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then - cd "${WORKPATH}"/"${APP}" || errorCheck - - if [[ "${VERBOSE}" == "TRUE" ]]; then - /usr/local/bin/grunt build --force 2>&1 | tee --append "${trshFile}" - else - /usr/local/bin/grunt build --force &>> "${trshFile}" & - spinner $! - info "Packages successfully compiled." - fi - else - info "Skipping Grunt..." - fi - else - sleep 1 + if [[ "${FORCE}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then + cd "${WORKPATH}"/"${APP}" || errorCheck + + if [[ "${VERBOSE}" == "TRUE" ]]; then + /usr/local/bin/grunt build --force 2>&1 | tee --append "${trshFile}" + else + /usr/local/bin/grunt build --force &>> "${trshFile}" & + spinner $! + info "Packages successfully compiled." + fi + else + info "Skipping Grunt..." + fi + else + sleep 1 - # node.js check - if [[ -f "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json" ]]; then - trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json found." - notice "Found npm configuration!" + # node.js check + if [[ -f "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json" ]]; then + trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}/package.json found." - check_build_method - if [[ "${SKIP_BUILD}" == "1" ]]; then - return - fi + check_build_method + if [[ "${SKIP_BUILD}" == "1" ]]; then + return + fi + + notice "Found npm configuration!" + if [[ "${BUILD}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then + cd "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}" || errorCheck - if [[ "${BUILD}" = "1" ]] || yesno --default no "Build assets? [y/N] "; then - cd "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/${APP}" || errorCheck - - if [[ "${VERBOSE}" == "TRUE" ]]; then - npm run build | tee --append "${trshFile}" - else - npm run build &>> "${trshFile}" & - spinner $! - info "Packages successfully compiled." - fi - else - info "Skipping Node Package Manager..." - fi - else - # info "No package management needed." - trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/$APP/package.json not found, skipping." - fi - fi - fi - fi + if [[ "${VERBOSE}" == "TRUE" ]]; then + npm run build | tee --append "${trshFile}" + else + npm run build &>> "${trshFile}" & + spinner $! + info "Packages successfully compiled." + fi + else + info "Skipping Node Package Manager..." + fi + else + # info "No package management needed." + trace "${WORKPATH}/${APP}${WPROOT}${WPAPP}/themes/$APP/package.json not found, skipping." + fi + fi + fi + fi } # Check deployment method, and determine if it includes a build upon each # deployment. function check_build_method() { - if [[ "${DEPLOY}" == *"mina"* ]]; then - deploy_config="$(cat ${WORKPATH}/${APP}/Minafile)" - if [[ "${deploy_config}" =~ "npm run build" ]] && [[ "${BUILD}" != "1" ]]; then - SKIP_BUILD="1" - fi - fi + if [[ "${DEPLOY}" == *"mina"* ]]; then + # Trying this a weirdo way, turn it into a loop or something later + [[ -f "${WORKPATH}/${APP}/Minafile" ]] && deploy_config="$(cat ${WORKPATH}/${APP}/Minafile)" + if [[ "${deploy_config}" =~ "npm run build" ]] && [[ "${BUILD}" != "1" ]]; then + SKIP_BUILD="1" + fi + fi } From 5b7efde60c9048873c4e3b95ca64c63720ccae97 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 31 May 2018 11:44:04 -0700 Subject: [PATCH 069/334] Fixed bug with server monitor test --- CHANGELOG.md | 2 ++ lib/utilities.sh | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f7443f..9628166 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step - Test emails now contain more project information +### Fixed +- Trapped an error that prevented `deploy --monitor-test [project]` from working ## [3.7] - 05-03-2018 ### Added diff --git a/lib/utilities.sh b/lib/utilities.sh index 2b2f7f4..cb0dc40 100755 --- a/lib/utilities.sh +++ b/lib/utilities.sh @@ -17,8 +17,12 @@ function go() { tput cnorm; fi - # Weird spot for this stuff I know - server_monitor + # Get some project data for the logs; we only want to get server monitor + # info if we're not running a monitor test since we already loaded the + # password file contents into a variable + if [[ "${MONITORTEST}" != "1" ]]; then + server_monitor + fi scan_check check_backup From acbbf80cbee7be6630730cee236ce6a536f2888b Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Thu, 31 May 2018 13:14:34 -0700 Subject: [PATCH 070/334] Email log improvements --- etc/html/default/approval.php | 2 +- etc/html/default/approve.html | 2 +- etc/html/default/error.html | 2 +- etc/html/default/success.html | 2 +- lib/process-html.sh | 9 +++++++++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/etc/html/default/approval.php b/etc/html/default/approval.php index 7820028..bc57500 100755 --- a/etc/html/default/approval.php +++ b/etc/html/default/approval.php @@ -133,4 +133,4 @@ function approved()
-

+							  				

diff --git a/etc/html/default/approve.html b/etc/html/default/approve.html
index 3016d05..f75aacf 100755
--- a/etc/html/default/approve.html
+++ b/etc/html/default/approve.html
@@ -20,4 +20,4 @@
 		
-

+			  		

diff --git a/etc/html/default/error.html b/etc/html/default/error.html
index 5288396..73087db 100755
--- a/etc/html/default/error.html
+++ b/etc/html/default/error.html
@@ -16,4 +16,4 @@
 				
-

+							

diff --git a/etc/html/default/success.html b/etc/html/default/success.html
index 787a54e..3664460 100755
--- a/etc/html/default/success.html
+++ b/etc/html/default/success.html
@@ -17,4 +17,4 @@
 				
-

\ No newline at end of file
+				  			

\ No newline at end of file
diff --git a/lib/process-html.sh b/lib/process-html.sh
index 11cd70e..6c84afc 100755
--- a/lib/process-html.sh
+++ b/lib/process-html.sh
@@ -32,6 +32,15 @@ function process_html() {
     sed -i '/ANALYTICS/d' "${htmlFile}"
   fi
 
+  # Prettify errors, warning, and successes
+  sed -i -e '/ERROR/s/$/<\/span>/' \
+    -e '/^ERROR/s/^//' \
+    -e '/WARNING/s/$/<\/span>/' \
+    -e '/^WARNING/s/^//' \
+    -e '/SUCCESS/s/$/<\/span>/' \
+    -e '/^SUCCESS/s/^//' \
+    "${htmlFile}"
+
   # Get to work
   sed -i -e "s^{{VIEWPORT}}^${VIEWPORT}^g" \
     -e "s^{{NOW}}^${NOW}^g" \

From a13b0460cd32aeaa01aac77eaed0a57591087eda Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 31 May 2018 13:26:43 -0700
Subject: [PATCH 071/334] A few more tweaks

---
 CHANGELOG.md                  | 1 +
 etc/html/default/success.html | 2 +-
 etc/html/default/theme.conf   | 2 ++
 lib/process-html.sh           | 8 +++++---
 4 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9628166..d23c859 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Changed
 - If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step
 - Test emails now contain more project information
+- Improved readability of HTML log files
 ### Fixed
 - Trapped an error that prevented `deploy --monitor-test [project]` from working
 
diff --git a/etc/html/default/success.html b/etc/html/default/success.html
index 3664460..53fc33b 100755
--- a/etc/html/default/success.html
+++ b/etc/html/default/success.html
@@ -17,4 +17,4 @@
 				
- + +

\ No newline at end of file
+				  			

diff --git a/etc/html/default/theme.conf b/etc/html/default/theme.conf
index ecd4e28..d4affdb 100755
--- a/etc/html/default/theme.conf
+++ b/etc/html/default/theme.conf
@@ -12,6 +12,8 @@ SUCCESSC="#28a745"
 INFOC="#5D6D7E"
 WARNINGC="#FFC107"
 DANGERC="#CC2233"
+LOGC="#ffffff"
+LOGB="#33403a"
 
 # If you'd like to add Smooch.io live chat to your digest and statistics pages, 
 # enter your App ID below
diff --git a/lib/process-html.sh b/lib/process-html.sh
index 6c84afc..d6e37a5 100755
--- a/lib/process-html.sh
+++ b/lib/process-html.sh
@@ -9,7 +9,7 @@ trace "Loading html handling"
 
 # Initialize variables 
 read -r DEFAULTC PRIMARYC SECONDARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID \
-  COVER SCANC UPTIMEC LATENCYC <<< ""
+  COVER SCANC UPTIMEC LATENCYC LOGC LOGBC <<< ""
 echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} 
   ${SMOOCHID} ${COVER} ${SCANC} ${UPTIMEC} ${LATENCYC}" > /dev/null
 
@@ -36,9 +36,9 @@ function process_html() {
   sed -i -e '/ERROR/s/$/<\/span>/' \
     -e '/^ERROR/s/^//' \
     -e '/WARNING/s/$/<\/span>/' \
-    -e '/^WARNING/s/^//' \
+    -e '/^WARNING/s/^//' \
     -e '/SUCCESS/s/$/<\/span>/' \
-    -e '/^SUCCESS/s/^//' \
+    -e '/^SUCCESS/s/^//' \
     "${htmlFile}"
 
   # Get to work
@@ -72,6 +72,8 @@ function process_html() {
     -e "s^{{INFO}}^${INFOC}^g" \
     -e "s^{{WARNING}}^${WARNINGC}^g" \
     -e "s^{{DANGER}}^${DANGERC}^g" \
+    -e "s^{{LOG}}^${LOGC}^g" \
+    -e "s^{{LOGBACKGROUND}}^${LOGBC}^g" \
     -e "s^{{SCAN_STATUS}}^${SCANC}^g" \
     -e "s^{{UPTIME_STATUS}}^${UPTIMEC}^g" \
     -e "s^{{LATENCY_STATUS}}^${LATENCYC}^g" \

From 0810fb3f02f13f3e814776535b373bfe5bfe8a51 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:38:54 -0700
Subject: [PATCH 072/334] Update test.sh

---
 test/test.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/test.sh b/test/test.sh
index 789fb46..d0f197a 100644
--- a/test/test.sh
+++ b/test/test.sh
@@ -4,6 +4,6 @@ testEquality() {
 	assertEquals 1 1
 }
 
-. shunit2-2.0.3/src/shell/shunit2
+. shunit2/shunit2
 
 

From 331b43c176e52c2417b347ed96c72283610290d2 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:39:43 -0700
Subject: [PATCH 073/334] Update .travis.yml

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 0858cf4..e0e880e 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: bash
 
 before_script:
-    - curl -L "http://downloads.sourceforge.net/shunit2/shunit2-2.0.3.tgz" | tar zx
+    - git clone git@github.com:kward/shunit2.git
     - wget https://raw.github.com/lehmannro/assert.sh/v1.1/assert.sh
 
 script:

From de18872dfdb73e62f5a752a9b3919b45a8683587 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:42:37 -0700
Subject: [PATCH 074/334] Update .travis.yml

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index e0e880e..ce370bf 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: bash
 
 before_script:
-    - git clone git@github.com:kward/shunit2.git
+    - git clone https://github.com/kward/shunit2.git
     - wget https://raw.github.com/lehmannro/assert.sh/v1.1/assert.sh
 
 script:

From d999b375d872029de186de432445a1d5f2baf60f Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:46:32 -0700
Subject: [PATCH 075/334] Update .travis.yml

---
 .travis.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.travis.yml b/.travis.yml
index ce370bf..12d4cca 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,3 +7,5 @@ before_script:
 script:
     - bash test/test.sh
     - bash test/extended.sh
+    - bash install/doinst.sh
+    - bash /usr/local/bin/deploy --version

From d550c2301d1ecf479eaa238a4e7f8b5772f61e98 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:48:38 -0700
Subject: [PATCH 076/334] Update .travis.yml

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 12d4cca..393000e 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,5 +7,5 @@ before_script:
 script:
     - bash test/test.sh
     - bash test/extended.sh
-    - bash install/doinst.sh
+    - bash sudo install/doinst.sh
     - bash /usr/local/bin/deploy --version

From 965b7e8233f47dc22e9ff6adf556b54dfb31fae0 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:55:39 -0700
Subject: [PATCH 077/334] Update .travis.yml

---
 .travis.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.travis.yml b/.travis.yml
index 393000e..df3afd3 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,5 @@
 language: bash
+sudo: required
 
 before_script:
     - git clone https://github.com/kward/shunit2.git

From 204bf8f5931028f1841c92684c1d209b52268643 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:55:51 -0700
Subject: [PATCH 078/334] Update .travis.yml

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index df3afd3..210b5ad 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
 language: bash
-sudo: required
+sudo: enabled
 
 before_script:
     - git clone https://github.com/kward/shunit2.git

From b64b71aab0271439b89cde2d978b18ddb75b8f2b Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 12:57:29 -0700
Subject: [PATCH 079/334] Update .travis.yml

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 210b5ad..717c107 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,5 +8,5 @@ before_script:
 script:
     - bash test/test.sh
     - bash test/extended.sh
-    - bash sudo install/doinst.sh
+    - sudo bash install/doinst.sh
     - bash /usr/local/bin/deploy --version

From 75c2be75e8669bb8c70082a96797472a312beef4 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 13:03:28 -0700
Subject: [PATCH 080/334] Touchup default HTML theme

---
 CHANGELOG.md                | 1 +
 etc/html/default/theme.conf | 2 +-
 install/doinst.sh           | 4 ++--
 lib/deployment.sh           | 3 ++-
 lib/process-html.sh         | 2 ++
 5 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d23c859..b7e3129 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step
 - Test emails now contain more project information
 - Improved readability of HTML log files
+- Improved unit testing
 ### Fixed
 - Trapped an error that prevented `deploy --monitor-test [project]` from working
 
diff --git a/etc/html/default/theme.conf b/etc/html/default/theme.conf
index d4affdb..13d06da 100755
--- a/etc/html/default/theme.conf
+++ b/etc/html/default/theme.conf
@@ -13,7 +13,7 @@ INFOC="#5D6D7E"
 WARNINGC="#FFC107"
 DANGERC="#CC2233"
 LOGC="#ffffff"
-LOGB="#33403a"
+LOGBC="#272727"
 
 # If you'd like to add Smooch.io live chat to your digest and statistics pages, 
 # enter your App ID below
diff --git a/install/doinst.sh b/install/doinst.sh
index 75c8b14..dd70576 100755
--- a/install/doinst.sh
+++ b/install/doinst.sh
@@ -87,10 +87,10 @@ else
 fi
 
 # Declare dependencies
-dependencies=(awk cal cat curl echo eval git grep pkill printf read sed sendmail sleep tee tput)
+dependencies=(awk cal cat curl echo eval git grep pkill printf read sed sleep tee tput)
 
 # Declare optional stuff
-options=(gitchart grunt npm scp ssh sshpass wget wkhtmltopdf wp)
+options=(gitchart grunt npm scp sendmail ssh sshpass wget wkhtmltopdf wp)
 
 message=''
 fg_red="$(tput setaf 1)"
diff --git a/lib/deployment.sh b/lib/deployment.sh
index 3b5754f..51c9fc4 100755
--- a/lib/deployment.sh
+++ b/lib/deployment.sh
@@ -151,7 +151,8 @@ function postDeploy() {
       # info "Deployment Success."
     else
       info ""
-      if yesno --default yes "Deployment succeeded, but something unexpected happened. View status? [Y/n] "; then
+      warning "Deployment succeeded, but something unexpected happened."
+      if yesno --default yes "View status? [Y/n] "; then
         git status
       fi
     fi
diff --git a/lib/process-html.sh b/lib/process-html.sh
index d6e37a5..8c87f8c 100755
--- a/lib/process-html.sh
+++ b/lib/process-html.sh
@@ -39,6 +39,8 @@ function process_html() {
     -e '/^WARNING/s/^//' \
     -e '/SUCCESS/s/$/<\/span>/' \
     -e '/^SUCCESS/s/^//' \
+    -e '/Deployed to/s/$/<\/span>/' \
+    -e '/^Deployed to/s/^//' \
     "${htmlFile}"
 
   # Get to work

From 99a0fb105d8570945ec568ba6cb95e92a534bcb5 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Thu, 21 Jun 2018 13:51:04 -0700
Subject: [PATCH 081/334] Better sendmail checking

---
 lib/mail-log.sh | 16 ++++++++++++++--
 lib/main.sh     |  2 +-
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/lib/mail-log.sh b/lib/mail-log.sh
index b81032e..fd68197 100755
--- a/lib/mail-log.sh
+++ b/lib/mail-log.sh
@@ -11,6 +11,12 @@ function mailLog() {
   # Only send email if a commit has been made, an approval is required, or there has been an error
   if [[ -n "${COMMITHASH}" ]] || [[ "${message_state}" == "ERROR" ]] || [[ "${message_state}" == "APPROVAL NEEDED" ]] || [[ "${AUTOMATE}" == "1" ]]; then
 
+    # Make sure sendmail exists and is configured 
+    if [[ ! -f "${MAILPATH}/sendmail" ]]; then
+      empty_line; warning "Sendmail misconfigured or not found."
+      quietExit
+    fi
+
     # If using --current, use the REPO value instead of the APP (current directory)
     if [[ "${CURRENT}" == "1" ]]; then
       APP="${REPO}"
@@ -62,9 +68,15 @@ function mailLog() {
 
 function email_test() {
   console "Testing email..."
+
+  # Confirm we have a recipient address
   if [[ -z "${TO}" ]]; then
-    warning "No recipient address found."; empty_line
-    clean_up; exit 1
+    empty_line; warning "No recipient address found."
+    quietExit
+  # Make sure sendmail exists and is configured 
+  elif [[ ! -f "${MAILPATH}/sendmail" ]]; then
+    empty_line; warning "Sendmail misconfigured or not found."
+    quietExit
   else
     # Send HTML mail
     (
diff --git a/lib/main.sh b/lib/main.sh
index 9f42631..6ada921 100755
--- a/lib/main.sh
+++ b/lib/main.sh
@@ -6,7 +6,7 @@
 # The main application
 ###############################################################################
 
-function main() {
+function main() { 
   dependency_check  # Check that required commands are available
   release_check     # Check for newer version at Github
   env_check         # Check for configuration files that need updating

From ab9673d651f1c69b037a05c88c93b6913048168a Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Fri, 22 Jun 2018 10:50:27 -0700
Subject: [PATCH 082/334] Improved success messages

---
 CHANGELOG.md            |  1 +
 lib/deployment.sh       | 11 ++++++-----
 lib/post-integration.sh |  1 -
 lib/quit.sh             |  1 -
 4 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b7e3129..509fddb 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - Test emails now contain more project information
 - Improved readability of HTML log files
 - Improved unit testing
+- Cleaned up language for more consistency
 ### Fixed
 - Trapped an error that prevented `deploy --monitor-test [project]` from working
 
diff --git a/lib/deployment.sh b/lib/deployment.sh
index 51c9fc4..78d19f0 100755
--- a/lib/deployment.sh
+++ b/lib/deployment.sh
@@ -74,7 +74,7 @@ function pkgDeploy() {
     if [[ "${REQUIREAPPROVAL}" != "TRUE" ]]; then
 
       # If we don't require approval to push to live, keep going
-      if [[ "${FORCE}" = "1" ]] || yesno --default yes "Deploy to live server? [Y/n] "; then
+      if [[ "${FORCE}" == "1" ]] || yesno --default yes "Deploy to live server? [Y/n] "; then
 
         # Test deployment command before running
         if [[ "${DEPLOY}" != "SCP" ]]; then
@@ -160,13 +160,14 @@ function postDeploy() {
     # Run integration hooks
     postCommit
     # This needs a check.
-    if [[ "${APPROVE}" == "1" ]] || [[ "${REQUIREAPPROVAL}" != "TRUE" ]]; then
+    if [[ "${APPROVE}" == "1" ]]; then
+    	info "Deployment queued for approval."
+    else
       if [[ -z "${PRODURL}" ]]; then
-        warning "No production URL configured."
-        info "Successfully deployed."
+        warning "No production URL configured, but deployment command ran successfully."
       else
         info "Deployed to ${PRODURL}"
       fi
     fi
   fi
-} 
+}
diff --git a/lib/post-integration.sh b/lib/post-integration.sh
index 2fb4593..a2ebd40 100755
--- a/lib/post-integration.sh
+++ b/lib/post-integration.sh
@@ -76,7 +76,6 @@ function postCommit() {
   # Check for a Wordpress core update, update production database if needed
   if [[ "${UPDCORE}" == "1" ]] && [[ -n "${PRODUCTION}" ]] && [[ -n "${PRODURL}" ]] && [[ -n "${DEPLOY}" ]]; then
     info "Upgrading production database..."; curl --silent "${PRODURL}${WPSYSTEM}"/wp-admin/upgrade.php?step=1 >/dev/null 2>&1
-
   fi
 
   # Just for yuks, display git stats for this user (user can override this if it annoys them)
diff --git a/lib/quit.sh b/lib/quit.sh
index 3aceb9a..2a58588 100755
--- a/lib/quit.sh
+++ b/lib/quit.sh
@@ -59,7 +59,6 @@ function safeExit() {
     slackPost
   else
     if [[ "${POSTTOSLACK}" == "TRUE" ]]; then
-      trace "Posting to Slack"
       build_log; slackPost > /dev/null 2>&1
     fi
   fi

From 80442f45f47c18346c0f5e79d7810405902e0bf6 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Fri, 22 Jun 2018 12:11:17 -0700
Subject: [PATCH 083/334] Added TERSE logging

---
 CHANGELOG.md            |  1 +
 deploy.sh               |  4 ++--
 etc/deploy-example.conf |  4 ++++
 lib/env-check.sh        |  2 +-
 lib/error-check.sh      |  4 +---
 lib/log-handling.sh     | 27 ++++++++++++++++++++++++++-
 lib/ssh-check.sh        |  2 +-
 7 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 509fddb..4c502c8 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ## [Unreleased]
 ### Added
 - Added `deploy --build [project name]` for quickly building project assets
+- Option to set `TERSE="TRUE"` in `deploy.conf` for slightly cleaner log files 
 ### Changed
 - If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step
 - Test emails now contain more project information
diff --git a/deploy.sh b/deploy.sh
index c50d2f6..6991e6f 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -61,7 +61,7 @@ read -r CLEARSCREEN WORKPATH CONFIGDIR REPOHOST WPCLI SMARTCOMMIT GITSTATS \
   STASH MAILPATH REQUIREAPPROVAL ADDTIME SCPPORT TASKUSER CLIENTID \
   CLIENTSECRET REDIRECTURI AUTHORIZATIONCODE ACCESSTOKEN REFRESHTOKEN \
   PROFILEID ALLOWROOT SHORTEMAIL INCOGNITO REPORTURL CLIENTCONTACT \
-  INCLUDEHOSTING GLOBAL_VERSION USER_VERSION PROJECT_VERSION <<< ""
+  INCLUDEHOSTING GLOBAL_VERSION USER_VERSION PROJECT_VERSION TERSE <<< ""
 echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI} 
   ${SMARTCOMMIT} ${GITSTATS} ${EMAILHTML} ${NOPHP} ${FIXPERMISSIONS} ${DEVUSER} 
   ${DEVGROUP} ${APACHEUSER} ${APACHEGROUP} ${TO} ${FROM} ${SUBJECT} ${EMAILERROR} 
@@ -77,7 +77,7 @@ echo "${CLEARSCREEN} ${WORKPATH} ${CONFIGDIR} ${REPOHOST} ${WPCLI}
   ${ADDTIME} ${TASKUSER} ${SCPPORT} ${CLIENTID} ${CLIENTSECRET} ${REDIRECTURI}
   ${AUTHORIZATIONCODE} ${ACCESSTOKEN} ${REFRESHTOKEN} ${PROFILEID} ${ALLOWROOT} 
   ${SHORTEMAIL} ${INCOGNITO} ${REPORTURL} ${CLIENTCONTACT} ${INCLUDEHOSTING} 
-  ${GLOBAL_VERSION} ${USER_VERSION} ${PROJECT_VERSION}" > /dev/null
+  ${GLOBAL_VERSION} ${USER_VERSION} ${PROJECT_VERSION} ${TERSE}" > /dev/null
 # Internal variables
 read -r var optstring options logFile wpFile coreFile postFile trshFile statFile \
   urlFile htmlFile htmlSendmail htmlEmail clientEmail textSendmail deployPath \
diff --git a/etc/deploy-example.conf b/etc/deploy-example.conf
index 7718644..d3afc39 100755
--- a/etc/deploy-example.conf
+++ b/etc/deploy-example.conf
@@ -197,6 +197,10 @@ GLOBAL_VERSION="3.7"
 # log files that are emailed upon deployment.
 # NOPHP="{{NOPHP}}" 
 
+# If you are not debugging your deploy install, you should probably set TERSE 
+# to "TRUE", which will keep your log files from including debug information.
+# TERSE="{{TERSE}}"
+  
 # IF INCOGNITO is set to true, log files as well as verbose output to screen 
 # will be stripped of details such as email addresses and system file paths.
 # INCOGNITO="{{INCOGNITO}}"
diff --git a/lib/env-check.sh b/lib/env-check.sh
index 8667fd8..ff4f2db 100755
--- a/lib/env-check.sh
+++ b/lib/env-check.sh
@@ -75,7 +75,7 @@ function update_global() {
     FIXPERMISSIONS DEVUSER DEVGROUP APACHEUSER APACHEGROUP FIXINDEX \
     MAILPATH TO FROM SUBJECT EMAILERROR EMAILSUCCESS EMAILQUIT SHORTEMAIL \
     EMAILHTML HTMLTEMPLATE FROMDOMAIN FROMUSER POSTEMAILHEAD POSTEMAILTAIL \
-    POSTTOSLACK SLACKURL SLACKERROR POSTURL NOPHP INCOGNITO REMOTELOG \
+    POSTTOSLACK SLACKURL SLACKERROR POSTURL NOPHP TERSE INCOGNITO REMOTELOG \
     REMOTEURL REMOTETEMPLATE SCPPOST SCPUSER SCPHOST SCPHOSTPATH SCPPORT \
     SCPPASS LOCALHOSTPOST LOCALHOSTPATH EXPIRELOGS)
 
diff --git a/lib/error-check.sh b/lib/error-check.sh
index bc86554..6be1200 100755
--- a/lib/error-check.sh
+++ b/lib/error-check.sh
@@ -24,8 +24,6 @@ function error_status() {
   if [[ "${EXITCODE}" != 0 ]]; then 
     error_msg="WARNING: Error code ${EXITCODE}"
     trace "${error_msg}"
-  else
-    trace "OK"
   fi
 }
 
@@ -71,7 +69,7 @@ function deploy_check() {
 
     if [[ "${SSHSTATUS}" == *"ok"* ]] ; then
       # Continue deploying
-      trace "OK"
+      trace "${SSHTARGET}: OK"
     elif [[ "${SSHSTATUS}" == *"Permission denied"* ]] ; then
       # Not authorized, no key etc.
       error "Connection refused for ${SSHTARGET}"
diff --git a/lib/log-handling.sh b/lib/log-handling.sh
index cf23fcb..8895077 100755
--- a/lib/log-handling.sh
+++ b/lib/log-handling.sh
@@ -67,12 +67,37 @@ function makeLog() {
     sed -i "/debug1:/d" "${logFile}"
   fi
 
-  # Filter raw log output as configured by user
+  # Filter PHP log output as configured by user
   if [[ "${NOPHP}" == "TRUE" ]]; then
     grep -vE "(PHP |Notice:|Warning:|Strict Standards:)" "${logFile}" > "${postFile}"
     cat "${postFile}" > "${logFile}"
   fi
 
+  # If logs should be terse, remove some more stuff
+  if [[ "${TERSE}" == "TRUE" ]]; then
+    sed -i -e '/Loading/d'  \
+      -e "/Enabled/d" "${logFile}" \
+      -e "/enabled/d" "${logFile}" \
+      -e "/Current user/d" "${logFile}" \
+      -e "/Current project/d" "${logFile}" \
+      -e "/Project workpath/d" "${logFile}" \
+      -e "/Locking process/d" "${logFile}" \
+      -e "/Running from/d" "${logFile}" \
+      -e "/lock/d" "${logFile}" \
+      -e "/Checking for deploy updates/d" "${logFile}" \
+      -e "/Log file is/d" "${logFile}" \
+      -e "/lock/d" "${logFile}" \
+      "${logFile}"
+  fi
+
+  # Remove double line breaks
+  sed -i '/^$/d' "${logFile}"
+
+  # Replace empty lines where we want
+  sed -i -e '/Checking servers/s/^/\n/' \
+    -e '/Launching deployment/s/^/\n/' \
+    "${logFile}"
+
   # Is this a publish only?
   if [[ "${PUBLISH}" == "1" ]] && [[ -z "${notes}" ]]; then   
     notes="Published to production and marked as deployed"
diff --git a/lib/ssh-check.sh b/lib/ssh-check.sh
index cb44293..1ac5598 100755
--- a/lib/ssh-check.sh
+++ b/lib/ssh-check.sh
@@ -24,7 +24,7 @@ function ssh_check() {
       if [[ "${EXITCODE}" != "0" ]]; then
         error "git@github.org: SSH check failed (Error code ${EXITCODE})"
       else
-        [[ "${SSHTEST}" == "1" ]] && console "git@bitbucket.org: OK"
+        [[ "${SSHTEST}" == "1" ]] && console "git@github.org: OK"
         trace "git@github.org: OK"
       fi
     fi

From a70cb8028b65a2a3478f27fdb5a701e092ff737e Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Fri, 22 Jun 2018 12:33:43 -0700
Subject: [PATCH 084/334] Logfile cleanup

---
 CHANGELOG.md            |  2 +-
 deploy.sh               |  2 +-
 etc/.deployrc           |  2 +-
 etc/deploy-example.conf |  2 +-
 etc/deploy.sh           |  2 +-
 lib/log-handling.sh     | 32 ++++++++++++++++++++------------
 6 files changed, 25 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c502c8..ece9514 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 ### Changed
 - If a project is using `mina` and configured to build assets on every deployment, `deploy` will skip the redunant build step
 - Test emails now contain more project information
-- Improved readability of HTML log files
+- Improved readability of log files
 - Improved unit testing
 - Cleaned up language for more consistency
 ### Fixed
diff --git a/deploy.sh b/deploy.sh
index 6991e6f..c328754 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -10,7 +10,7 @@
 ###############################################################################
 
 IFS=$'\n\t'
-VERSION="3.7.1-dev"
+VERSION="3.7.1-rc"
 EPOCH="$(date +%s)"
 NOW="$(date +"%B %d, %Y")"
 LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')"
diff --git a/etc/.deployrc b/etc/.deployrc
index fbd237f..2b68b04 100755
--- a/etc/.deployrc
+++ b/etc/.deployrc
@@ -11,7 +11,7 @@
 # This value indicates the version number when this file was last changed: 
 # it does not necessarily reflect deploy's current version number.
 # DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP
-USER_VERSION="3.6.7"
+USER_VERSION="3.7.1"
 
 # Clear screen on startup, when not running with --quiet or --verbose switch
 # CLEARSCREEN="{{CLEARSCREEN}}"
diff --git a/etc/deploy-example.conf b/etc/deploy-example.conf
index d3afc39..8dda96b 100755
--- a/etc/deploy-example.conf
+++ b/etc/deploy-example.conf
@@ -7,7 +7,7 @@
 # This value indicates the version number when this file was last changed: 
 # it does not necessarily reflect deploy's current version number.
 # DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP
-GLOBAL_VERSION="3.7"
+GLOBAL_VERSION="3.7.1"
 
 ###############################################################################
 # General Setup
diff --git a/etc/deploy.sh b/etc/deploy.sh
index 0be1259..e8423b0 100755
--- a/etc/deploy.sh
+++ b/etc/deploy.sh
@@ -9,7 +9,7 @@
 # This value indicates the version number when this file was last changed: 
 # it does not necessarily reflect deploy's current version number.
 # DO NOT EDIT THIS NUMBER OR YOU MAY BLOW SOMETHING UP
-PROJECT_VERSION="3.7"
+PROJECT_VERSION="3.7.1"
 
 
 ###############################################################################
diff --git a/lib/log-handling.sh b/lib/log-handling.sh
index 8895077..c6890bc 100755
--- a/lib/log-handling.sh
+++ b/lib/log-handling.sh
@@ -75,18 +75,19 @@ function makeLog() {
 
   # If logs should be terse, remove some more stuff
   if [[ "${TERSE}" == "TRUE" ]]; then
-    sed -i -e '/Loading/d'  \
-      -e "/Enabled/d" "${logFile}" \
-      -e "/enabled/d" "${logFile}" \
-      -e "/Current user/d" "${logFile}" \
-      -e "/Current project/d" "${logFile}" \
-      -e "/Project workpath/d" "${logFile}" \
-      -e "/Locking process/d" "${logFile}" \
-      -e "/Running from/d" "${logFile}" \
-      -e "/lock/d" "${logFile}" \
-      -e "/Checking for deploy updates/d" "${logFile}" \
-      -e "/Log file is/d" "${logFile}" \
-      -e "/lock/d" "${logFile}" \
+    sed -i -e '/Loading/d' \
+      -e "/Enabled/d" \
+      -e "/enabled/d" \
+      -e "/Current user/d" \
+      -e "/Current project/d" \
+      -e "/Project workpath/d" \
+      -e "/Locking process/d" \
+      -e "/Running from/d" \
+      -e "/lock/d" \
+      -e "/Checking for deploy updates/d" \
+      -e "/Log file is/d" \
+      -e "/lock/d" \
+      -e "/not found/d" \
       "${logFile}"
   fi
 
@@ -96,6 +97,13 @@ function makeLog() {
   # Replace empty lines where we want
   sed -i -e '/Checking servers/s/^/\n/' \
     -e '/Launching deployment/s/^/\n/' \
+    -e '/Staging files/s/^/\n/' \
+    -e '/Commit message/s/^/\n/' \
+    -e '/Preparing repository/s/^/\n/' \
+    -e '/Checking for updates/s/^/\n/' \
+    -e '/The following updates/s/^/\n/' \
+    -e '/Building commit message/s/^/\n/' \
+    -e '/installed plugins/s/^/\n/' \
     "${logFile}"
 
   # Is this a publish only?

From f3c3846717f8a2d1895e3424eb0822452a7eeb9f Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Fri, 6 Jul 2018 13:49:00 -0700
Subject: [PATCH 085/334] SSH error check, resolve #136

---
 lib/error-check.sh  | 8 ++++----
 lib/log-handling.sh | 3 ++-
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/lib/error-check.sh b/lib/error-check.sh
index 6be1200..14213ff 100755
--- a/lib/error-check.sh
+++ b/lib/error-check.sh
@@ -32,11 +32,11 @@ function deploy_check() {
     DEPLOYTEST="mina --simulate deploy"
     # Get variables organized
     if [[ -f "${WORKPATH}/${APP}/${CONFIGDIR}/deploy.rb" ]]; then
-      grep -n "set :user" "${WORKPATH}/${APP}/${CONFIGDIR}"/deploy.rb > "${trshFile}"
+      grep -n -w "set :user" "${WORKPATH}/${APP}/${CONFIGDIR}"/deploy.rb > "${trshFile}"
       MINAUSER=$(awk -F\' '{print $2,$4}' ${trshFile})
       echo -n "${MINAUSER}" > "${statFile}"
       echo -n "@" >> ${statFile}
-      grep -n "set :domain" "${WORKPATH}/${APP}/${CONFIGDIR}"/deploy.rb > "${trshFile}"
+      grep -n -w "set :domain" "${WORKPATH}/${APP}/${CONFIGDIR}"/deploy.rb > "${trshFile}"
       MINADOMAIN=$(awk -F\' '{print $2,$4}' ${trshFile})
       echo -n "${MINADOMAIN}" >> "${statFile}"
       SSHTARGET=$(sed -r 's/\s+//g' ${statFile})
@@ -50,11 +50,11 @@ function deploy_check() {
 
     elif [[ -f "${WORKPATH}/${APP}/.deploy.yml" ]]; then
       DEPLOYTEST="bundle exec mina --simulate deploy -f Minafile"
-      grep -n "user:" "${WORKPATH}/${APP}"/.deploy.yml > "${trshFile}"
+      grep -n -w "user:" "${WORKPATH}/${APP}"/.deploy.yml > "${trshFile}"
       MINAUSER=$(awk -F\' '{print $2,$4}' ${trshFile})
       echo -n "${MINAUSER}" > "${statFile}"
       echo -n "@" >> ${statFile}
-      grep -n "domain:" "${WORKPATH}/${APP}"/.deploy.yml > "${trshFile}"
+      grep -n -w "domain:" "${WORKPATH}/${APP}"/.deploy.yml > "${trshFile}"
       MINADOMAIN=$(awk -F\' '{print $2,$4}' ${trshFile})
       echo -n "${MINADOMAIN}" >> "${statFile}"
       SSHTARGET=$(sed -r 's/\s+//g' ${statFile})  
diff --git a/lib/log-handling.sh b/lib/log-handling.sh
index c6890bc..4a12163 100755
--- a/lib/log-handling.sh
+++ b/lib/log-handling.sh
@@ -88,6 +88,8 @@ function makeLog() {
       -e "/Log file is/d" \
       -e "/lock/d" \
       -e "/not found/d" \
+      -e "/Checking for/d" \
+      -e "/Continuing deploy/d" \
       "${logFile}"
   fi
 
@@ -102,7 +104,6 @@ function makeLog() {
     -e '/Preparing repository/s/^/\n/' \
     -e '/Checking for updates/s/^/\n/' \
     -e '/The following updates/s/^/\n/' \
-    -e '/Building commit message/s/^/\n/' \
     -e '/installed plugins/s/^/\n/' \
     "${logFile}"
 

From d9b815a27f8465d89f2b5c0ec82dd3d02acbdbf6 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Fri, 13 Jul 2018 19:53:35 -0700
Subject: [PATCH 086/334] Update CHANGELOG.md

---
 CHANGELOG.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ece9514..bc021d2 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
 All notable changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/).
 
-## [Unreleased]
+## [3.7.1] - 07-13-2018
 ### Added
 - Added `deploy --build [project name]` for quickly building project assets
 - Option to set `TERSE="TRUE"` in `deploy.conf` for slightly cleaner log files 
@@ -217,7 +217,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - Old monolithic script rewritten
 
 
-[Unreleased]: https://github.com/EMRL/deploy/compare/v3.7...HEAD
+[Unreleased]: https://github.com/EMRL/deploy/compare/v3.7.1...HEAD
+[3.7.1]: https://github.com/EMRL/deploy/compare/v3.7...v3.7.1
 [3.7]: https://github.com/EMRL/deploy/compare/v3.6.7...v3.7
 [3.6.7]: https://github.com/EMRL/deploy/compare/v3.6.6...v3.6.7
 [3.6.6]: https://github.com/EMRL/deploy/compare/v3.6.5...v3.6.6

From e81109f7b3fcc31186bec815af927422a804aaea Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Fri, 13 Jul 2018 19:55:24 -0700
Subject: [PATCH 087/334] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index beb869c..437bb81 100755
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Deploy
 
-[![release](https://img.shields.io/badge/release-v3.7-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest)
+[![release](https://img.shields.io/badge/release-v3.7.1-blue.svg?style=flat)](https://github.com/EMRL/deploy/releases/latest)
 [![Build Status](https://travis-ci.org/EMRL/deploy.svg?branch=master)](https://travis-ci.org/EMRL/deploy)
 
 `deploy` is designed to speed up, automate, and integrate project deployment. Its main focus is Wordpress websites, but it can be used with any code repository. 

From 53be646b6ffb8e724930cd5b58788a748cda28a2 Mon Sep 17 00:00:00 2001
From: Floyd Diebel 
Date: Fri, 13 Jul 2018 20:01:38 -0700
Subject: [PATCH 088/334] Roughing in project dashboard

---
 CHANGELOG.md                                  |   4 +
 deploy.sh                                     |   2 +-
 etc/html/active/stats/css/bootstrap.min.css   |   5 +
 etc/html/active/stats/css/styles.css          |  58 +++
 .../fonts/glyphicons-halflings-regular.eot    | Bin 0 -> 20290 bytes
 .../fonts/glyphicons-halflings-regular.svg    | 229 ++++++++++++
 .../fonts/glyphicons-halflings-regular.ttf    | Bin 0 -> 41236 bytes
 .../fonts/glyphicons-halflings-regular.woff   | Bin 0 -> 23292 bytes
 etc/html/active/stats/index.html              | 348 ++++++++++++++++++
 etc/html/active/stats/js/bootstrap.min.js     |   7 +
 etc/html/active/stats/js/scripts.js           |   9 +
 etc/html/default/scan/index.html              | 101 -----
 lib/git.sh                                    |   4 +
 lib/monitor.sh                                |  14 +-
 lib/process-html.sh                           |  18 +-
 lib/scan.sh                                   |   4 +-
 lib/statistics.sh                             | 155 +++++---
 17 files changed, 788 insertions(+), 170 deletions(-)
 create mode 100755 etc/html/active/stats/css/bootstrap.min.css
 create mode 100755 etc/html/active/stats/css/styles.css
 create mode 100755 etc/html/active/stats/fonts/glyphicons-halflings-regular.eot
 create mode 100755 etc/html/active/stats/fonts/glyphicons-halflings-regular.svg
 create mode 100755 etc/html/active/stats/fonts/glyphicons-halflings-regular.ttf
 create mode 100755 etc/html/active/stats/fonts/glyphicons-halflings-regular.woff
 create mode 100755 etc/html/active/stats/index.html
 create mode 100755 etc/html/active/stats/js/bootstrap.min.js
 create mode 100755 etc/html/active/stats/js/scripts.js
 delete mode 100755 etc/html/default/scan/index.html

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc021d2..b6e9b01 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 All notable changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/).
 
+## [Unreleased]
+### Changed 
+- Improved HTML post-processing
+
 ## [3.7.1] - 07-13-2018
 ### Added
 - Added `deploy --build [project name]` for quickly building project assets
diff --git a/deploy.sh b/deploy.sh
index c328754..8ec44b0 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -10,7 +10,7 @@
 ###############################################################################
 
 IFS=$'\n\t'
-VERSION="3.7.1-rc"
+VERSION="3.7.2-dev"
 EPOCH="$(date +%s)"
 NOW="$(date +"%B %d, %Y")"
 LAST_MONTH="$(date --date="$(date +%Y-%m-15) -1 month" +'%B')"
diff --git a/etc/html/active/stats/css/bootstrap.min.css b/etc/html/active/stats/css/bootstrap.min.css
new file mode 100755
index 0000000..28f154d
--- /dev/null
+++ b/etc/html/active/stats/css/bootstrap.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Bootstrap v3.3.2 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}
\ No newline at end of file
diff --git a/etc/html/active/stats/css/styles.css b/etc/html/active/stats/css/styles.css
new file mode 100755
index 0000000..c458b64
--- /dev/null
+++ b/etc/html/active/stats/css/styles.css
@@ -0,0 +1,58 @@
+@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,700');
+
+body {
+  font-family: 'Roboto', sans-serif !important;
+}
+
+p, td {
+  font-weight: 400;
+}
+
+.navbar-static-top {
+  margin-bottom:20px;
+}
+
+i {
+  font-size:16px;
+}
+
+.nav > li > a {
+  color:#787878;
+}
+  
+footer {
+  margin-top:20px;
+  padding-top:20px;
+  padding-bottom:20px;
+  background-color:#efefef;
+}
+
+/* count indicator near icons */
+.nav>li .count {
+  position: absolute;
+  bottom: 12px;
+  right: 6px;
+  font-size: 9px;
+  background: rgba(51,200,51,0.55);
+  color: rgba(255,255,255,0.9);
+  line-height: 1em;
+  padding: 2px 4px;
+  -webkit-border-radius: 10px;
+  -moz-border-radius: 10px;
+  -ms-border-radius: 10px;
+  -o-border-radius: 10px;
+  border-radius: 10px;
+}
+
+textarea.form-control {
+    height: 8.0em !important;
+}
+
+.stat-label {
+  font-weight: 700;
+  font-size: 0.8em;
+}
+
+.nav > li > a > span {
+    padding: 0 10px;
+}
\ No newline at end of file
diff --git a/etc/html/active/stats/fonts/glyphicons-halflings-regular.eot b/etc/html/active/stats/fonts/glyphicons-halflings-regular.eot
new file mode 100755
index 0000000000000000000000000000000000000000..423bd5d3a20b804f596e04e5cd02fb4f16cfcbc1
GIT binary patch
literal 20290
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 
\ No newline at end of file
diff --git a/etc/html/active/stats/fonts/glyphicons-halflings-regular.ttf b/etc/html/active/stats/fonts/glyphicons-halflings-regular.ttf
new file mode 100755
index 0000000000000000000000000000000000000000..a498ef4e7c8b556fc36f580c5ff524025bb11c84
GIT binary patch
literal 41236
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
+
+
+	
+	
+	{{PROJNAME}} • {{PROJCLIENT}}
+	
+	
+	
+	
+	
+	
+	
+	
+	
+
+
+
+	
+
+	
+
+ + +
+ +
    +
  • Updated {{NOW}}
  • +
+ + My Dashboard +
+ +
+ +
+ + + +
+
+
+
+ +

Engagement (Last 7 days)

+
+
+
+
+

{{GA_HITS}}

+

hits from visitors

+
+
+

{{GA_PERCENT}}%

+

traffic from new users

+
+
+

{{GA_SEARCHES}}

+

organic searches

+
+
+
+ + +
+ +
+
+
+ +

Recent Activity

+
+
+ +
+ {{COMMITS_RECENT}} +

+ View all +

+
+
+
+
+
+
+
+ +

Project Stats

+
+
+
+
+

+ Project: {{PROJNAME}}
+ Development: {{DEVURL}}
+ Production: {{PRODURL}} +

+

{{CODE_STATS}}

+
+
+ {{PROJCLIENT}} +
+
+ +
+ +

Commits by day of week

+
+
+ +
+ +
+
+
+ +

Support Request

+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+ + +
+ + +
+ + + + + +
+ +
+
+ + +
+ + + @@ -38,18 +38,17 @@
-
  • Updated {{NOW}}
- - My Dashboard + My Dashboard
-
-
@@ -99,7 +83,6 @@
-

Engagement (Last 7 days)

@@ -121,19 +104,17 @@

{{GA_SEARCHES}}


-
- +

Recent Activity

-
{{COMMITS_RECENT}}

- View all + View all

@@ -142,7 +123,7 @@

Recent Activity

- +

Project Stats

@@ -162,16 +143,17 @@

Project Stats

-

Commits by day of week

+
+

+ View all +

-
- -
+
- +

Support Request

@@ -209,140 +191,17 @@

Support Request

-
- -
- -
- - - - -
-
-
- - @@ -53,10 +67,9 @@ @@ -70,7 +83,7 @@ My Dashboard > Activity
-
+
@@ -82,6 +95,14 @@

Activity history ({{TOTAL_COMMITS}} entries)

+
+
+
+

Every time we update your code, it is "committed" to a development system where it is stored, tracked with a uniqe ID, and logged. That way, if anything ever breaks, we can easily diagnose how things went wrong. +

+
+
+
diff --git a/etc/html/active/stats/backup.html b/etc/html/active/stats/backup.html index 2cbb6d2..6bdca64 100755 --- a/etc/html/active/stats/backup.html +++ b/etc/html/active/stats/backup.html @@ -32,6 +32,20 @@ {{PROJNAME}} + @@ -52,10 +66,9 @@ @@ -69,20 +82,26 @@ My Dashboard > System Backup
-
+
-

Current Backups

+

{{BACKUP_MSG}}

-

{{BACKUP_MSG}}

-

{{BACKUP_FILES}}

+
+
+
+

About the backup +

+
+
+
diff --git a/etc/html/active/stats/css/styles.css b/etc/html/active/stats/css/styles.css index c458b64..2dbf4b1 100755 --- a/etc/html/active/stats/css/styles.css +++ b/etc/html/active/stats/css/styles.css @@ -54,5 +54,24 @@ textarea.form-control { } .nav > li > a > span { - padding: 0 10px; + padding: 0 10px; +} + +.caret { + padding: 0 !important; +} + +.mauticform-post-success .mauticform-message { + color: white; + background-color: green; + padding: 20px; + text-align: center; + margin-bottom: 20px; + margin-top: 5px; + font-weight: 700; + font-size: 20px; +} + +.mauticform-post-success .mauticform-innerform { + display: none; } \ No newline at end of file diff --git a/etc/html/active/stats/firewall.html b/etc/html/active/stats/firewall.html index 3ce0d0e..a2ecdf6 100755 --- a/etc/html/active/stats/firewall.html +++ b/etc/html/active/stats/firewall.html @@ -32,6 +32,20 @@ {{PROJNAME}} + @@ -52,10 +66,9 @@ @@ -69,7 +82,7 @@ My Dashboard > Firewall & Security
-
+
@@ -80,6 +93,13 @@

Not yet functional

+
+
+
+

About the firewall

+
+
+
diff --git a/etc/html/active/stats/fonts/glyphicons-halflings-regular.woff2 b/etc/html/active/stats/fonts/glyphicons-halflings-regular.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/etc/html/active/stats/index.html b/etc/html/active/stats/index.html index 7482b65..4d317d9 100755 --- a/etc/html/active/stats/index.html +++ b/etc/html/active/stats/index.html @@ -32,6 +32,20 @@ {{PROJNAME}} + @@ -52,10 +66,9 @@ @@ -137,7 +150,7 @@

Project Stats

{{CODE_STATS}}

- {{PROJCLIENT}} + {{PROJCLIENT}}
@@ -158,38 +171,7 @@

Support Request

-
-
- -
-
-
-
-
- -
-
-
-
-
- -
- -
-
-
- -
- -
-
-
+
diff --git a/etc/html/active/stats/scan.html b/etc/html/active/stats/scan.html index 93f6a06..531ffcc 100755 --- a/etc/html/active/stats/scan.html +++ b/etc/html/active/stats/scan.html @@ -32,6 +32,20 @@ {{PROJNAME}} + @@ -53,10 +67,9 @@ @@ -70,7 +83,7 @@ My Dashboard > Malware Scan
-
+
@@ -83,6 +96,14 @@

Scan results

+
+
+
+

About the scan +

+
+
+
diff --git a/etc/html/active/stats/stats.html b/etc/html/active/stats/stats.html index 04273cf..92eb41e 100755 --- a/etc/html/active/stats/stats.html +++ b/etc/html/active/stats/stats.html @@ -32,6 +32,20 @@ {{PROJNAME}} + @@ -52,10 +66,9 @@ diff --git a/lib/scan.sh b/lib/scan.sh index 672926f..3482dfe 100755 --- a/lib/scan.sh +++ b/lib/scan.sh @@ -88,7 +88,8 @@ function scan_host() { sed -i -e :a -e '$d;N;2,3ba' -e 'P;D' "${scan_html}" # Create the scan report in the new dashboard format, this is kinda - # maybe termporary, it's getting pretty tangled + # maybe temporary, it's getting pretty tangled + assign_nav project_scan cat "${deployPath}/html/${HTMLTEMPLATE}/scan/header.html" "${scan_html}" "${deployPath}/html/${HTMLTEMPLATE}/scan/footer.html" > "${htmlFile}" diff --git a/lib/statistics.sh b/lib/statistics.sh index 2ff505d..16cc54d 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -40,12 +40,8 @@ function project_stats() { notice "Generating files..." - # Assign URLs - this will change later on - ACTIVITY_NAV="activity.html" - STATISTICS_NAV="stats.html" - [[ -n "${SCAN_MSG}" ]] && SCAN_NAV="scan.html" - [[ -n "${FIREWALL_NAV}" ]] && FIREWALL_NAV="firewall.html" - [[ -n "${BACKUP_STATUS}" ]] && BACKUP_NAV="backup.html" + # Define dashboard navigation + assign_nav # Collect gravatars for all the authors in this repo get_avatars @@ -213,3 +209,12 @@ function validate_urls() { fi done < "${trshFile}" } + +function assign_nav() { + # Assign URLs - this will change later on + ACTIVITY_NAV="activity.html" + STATISTICS_NAV="stats.html" + [[ -n "${SCAN_MSG}" ]] && SCAN_NAV="scan.html" + [[ -n "${FIREWALL_NAV}" ]] && FIREWALL_NAV="firewall.html" + [[ -n "${BACKUP_STATUS}" ]] && BACKUP_NAV="backup.html" +} \ No newline at end of file From 3523138d1cc6c69a22762d99d5b319589c14f1fb Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 3 Aug 2018 11:16:20 -0700 Subject: [PATCH 091/334] Dashboard progress --- etc/deploy.sh | 12 +++++++++ etc/html/active/stats/activity.html | 8 +++++- etc/html/active/stats/css/styles.css | 37 +++++++++++++++++++++------- etc/html/active/stats/index.html | 23 ++++++++++++++++- etc/html/active/stats/scan.html | 16 +++++++++--- lib/process-html.sh | 9 +++++++ lib/statistics.sh | 4 +-- 7 files changed, 92 insertions(+), 17 deletions(-) diff --git a/etc/deploy.sh b/etc/deploy.sh index e8423b0..09222b2 100755 --- a/etc/deploy.sh +++ b/etc/deploy.sh @@ -268,6 +268,18 @@ PROJECT_VERSION="3.7.1" # INCLUDEHOSTING="{{INCLUDEHOSTING}}" +############################################################################### +# Work Logs +############################################################################### + +# EXPERIMENTAL - Ingest work logs from Chrono (or any RSS feed) for display in +# the statistics dashboard + +# Set the URL of your RSS work log. Feed will be parsed and formatted into html +# via feed.emrl.co +# RSS_URL="{{RSS_URL}}" + + ############################################################################### # Invoice Ninja integration ############################################################################### diff --git a/etc/html/active/stats/activity.html b/etc/html/active/stats/activity.html index eba374b..5797017 100755 --- a/etc/html/active/stats/activity.html +++ b/etc/html/active/stats/activity.html @@ -97,8 +97,14 @@

Activity history ({{TOTAL_COMMITS}} entries)

+
+
+

About activity

+
+
-

Every time we update your code, it is "committed" to a development system where it is stored, tracked with a uniqe ID, and logged. That way, if anything ever breaks, we can easily diagnose how things went wrong. +

+ Every time we update your code, it is "committed" to a development system where it is stored, tracked with a uniqe ID, and logged. That way, if anything ever breaks, we can easily diagnose how things went wrong.

diff --git a/etc/html/active/stats/css/styles.css b/etc/html/active/stats/css/styles.css index 2dbf4b1..bb43cce 100755 --- a/etc/html/active/stats/css/styles.css +++ b/etc/html/active/stats/css/styles.css @@ -62,16 +62,35 @@ textarea.form-control { } .mauticform-post-success .mauticform-message { - color: white; - background-color: green; - padding: 20px; - text-align: center; - margin-bottom: 20px; - margin-top: 5px; - font-weight: 700; - font-size: 20px; + color: white; + background-color: green; + padding: 20px; + text-align: center; + margin-bottom: 20px; + margin-top: 5px; + font-weight: 700; + font-size: 20px; } .mauticform-post-success .mauticform-innerform { display: none; -} \ No newline at end of file +} + +@media (min-width: 768px) { + .modal-dialog { + width: 800px !important; + } +} + +.rss-items { + list-style-type: none; + -webkit-padding-start: 0px; +} + +.rss-item { + .margin-bottom: 1.4em; +} + +.text-white { + color: #ffffff; +} diff --git a/etc/html/active/stats/index.html b/etc/html/active/stats/index.html index 4d317d9..6dfa9dd 100755 --- a/etc/html/active/stats/index.html +++ b/etc/html/active/stats/index.html @@ -38,7 +38,7 @@ My EMRL Stuff
+ + + +
diff --git a/etc/html/active/stats/scan.html b/etc/html/active/stats/scan.html index 531ffcc..d27c97f 100755 --- a/etc/html/active/stats/scan.html +++ b/etc/html/active/stats/scan.html @@ -85,21 +85,29 @@
-
+
-

Scan results

+

Malware scan: {{SCAN_MSG}}

-

{{SCAN_RESULT}}

{{SCAN_STATS}}
+
+
+

About the scan

+
+
-

About the scan +

+ Our system performs comprehensive tests against your web server for multiple items, including over 6700 potentially dangerous files and programs, checks for outdated versions of over 1250 servers, and version specific problems on over 270 servers. +

+

+ It also checks for server configuration items such as the presence of multiple index files, HTTP server options, and will attempt to identify installed web servers and software. Scan items and plugins are frequently updated and are automatically updated.

diff --git a/lib/process-html.sh b/lib/process-html.sh index 8c32080..803e044 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -39,6 +39,14 @@ function process_html() { sed -i '/ANALYTICS/d' "${htmlFile}" fi + if [[ -z "${RSS_URL}" ]]; then + sed -i -e '/BEGIN WORK RSS/,/END WORK RSS/d' \ + -e '/RSS_URL/d' "${htmlFile}" \ + "${htmlFile}" + else + sed -i "s^{{RSS_URL}}^${RSS_URL}^g" "${htmlFile}" + fi + # Prettify errors, warning, and successes sed -i -e '/ERROR/s/$/<\/span>/' \ -e '/^ERROR/s/^//' \ @@ -132,5 +140,6 @@ function process_html() { -e "s^{{BACKUP_NAV}}^${BACKUP_NAV}^g" \ -e "s^{{BACKUP_MSG}}^${BACKUP_MSG}^g" \ -e "s^{{TOTAL_COMMITS}}^${TOTAL_COMMITS}^g" \ + -e "s^{{RSS_URL}}^${RSS_URL}^g" \ "${htmlFile}" } diff --git a/lib/statistics.sh b/lib/statistics.sh index 16cc54d..78e2e40 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -12,12 +12,12 @@ read -r DB_API_TOKEN DB_BACKUP_PATH LAST_BACKUP BACKUP_STATUS CODE_STATS \ BACKUP_BTN LATENCY_BTN UPTIME_BTN SCAN_BTN COMMITS_RECENT \ repo_charts ACTIVITY_NAV STATISTICS_NAV SCAN_NAV FIREWALL_NAV \ BACKUP_NAV SCAN_STATS FIREWALL_STATUS BACKUP_MSG BACKUP_FILES \ - TOTAL_COMMITS <<< "" + TOTAL_COMMITS RSS_URL <<< "" echo "${DB_API_TOKEN} ${DB_BACKUP_PATH} ${LAST_BACKUP} ${BACKUP_STATUS} ${CODE_STATS} ${BACKUP_BTN} ${LATENCY_BTN} ${UPTIME_BTN} ${SCAN_BTN} ${COMMITS_RECENT} ${repo_charts} ${ACTIVITY_NAV} ${STATISTICS_NAV} ${SCAN_NAV} ${FIREWALL_NAV} ${BACKUP_NAV} ${FIREWALL_STATUS} - ${BACKUP_MSG} ${BACKUP_FILES} ${TOTAL_COMMITS}" > /dev/null + ${BACKUP_MSG} ${BACKUP_FILES} ${TOTAL_COMMITS} ${RSS_URL}" > /dev/null function project_stats() { hash gitchart 2>/dev/null || { From 18528710991fb417199c6a126090b1841f09b1eb Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 3 Aug 2018 16:02:50 -0700 Subject: [PATCH 092/334] Resolve #137 --- lib/active-check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active-check.sh b/lib/active-check.sh index db5d06f..2742e75 100755 --- a/lib/active-check.sh +++ b/lib/active-check.sh @@ -14,7 +14,7 @@ echo "${active_files}" > /dev/null function active_check() { if [[ "${FORCE}" == "1" ]] && [[ "${UPGRADE}" == "1" ]] && [[ "${QUIET}" == "1" ]] && [[ "${ACTIVECHECK}" = "TRUE" ]]; then trace "Checking for active files" - active_files=$(find "${WORKPATH}/${APP}" -mmin -"${CHECKTIME}" ! -path "${WORKPATH}/${APP}/public/app/wflogs/*" ! -path "${WORKPATH}/${APP}/.git/*" ! -path "${WORKPATH}/${APP}/.git") + active_files=$(find "${WORKPATH}/${APP}" -mmin -"${CHECKTIME}" ! -path "${WORKPATH}/${APP}/public/app/wflogs" ! -path "${WORKPATH}/${APP}/.git/*" ! -path "${WORKPATH}/${APP}/.git") # Check for changed files and make sure they are actually waiting to be committed if [[ ! -z "${active_files}" ]] && [[ ! -z "$(git status --porcelain)" ]]; then From 0d50dcacf29400d2bd9ed689a6ce896b9b9b412e Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 19 Aug 2018 13:36:17 -0700 Subject: [PATCH 093/334] process_html() cleanup --- etc/html/active/stats/backup.html | 2 +- etc/html/active/stats/css/styles.css | 2 +- etc/html/active/stats/stats.html | 30 +++++----- lib/analytics.sh | 6 ++ lib/loader.sh | 27 +++++++-- lib/process-html.sh | 89 +++++++++------------------- lib/wp.sh | 7 ++- 7 files changed, 80 insertions(+), 83 deletions(-) diff --git a/etc/html/active/stats/backup.html b/etc/html/active/stats/backup.html index 6bdca64..1948ddc 100755 --- a/etc/html/active/stats/backup.html +++ b/etc/html/active/stats/backup.html @@ -85,7 +85,7 @@
-
+

{{BACKUP_MSG}}

diff --git a/etc/html/active/stats/css/styles.css b/etc/html/active/stats/css/styles.css index bb43cce..4661481 100755 --- a/etc/html/active/stats/css/styles.css +++ b/etc/html/active/stats/css/styles.css @@ -88,7 +88,7 @@ textarea.form-control { } .rss-item { - .margin-bottom: 1.4em; + margin-bottom: 1.4em; } .text-white { diff --git a/etc/html/active/stats/stats.html b/etc/html/active/stats/stats.html index 92eb41e..e798238 100755 --- a/etc/html/active/stats/stats.html +++ b/etc/html/active/stats/stats.html @@ -84,48 +84,48 @@
-
- +
+

-
- +
+

-
- +
+

-
- +
+

-
- +
+

-
- +
+

-
+
- +
- +
diff --git a/lib/analytics.sh b/lib/analytics.sh index c398d2c..37eaeb0 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -4,6 +4,11 @@ # ############################################################################### # Handles functions related to retrieving and parsing Google Analytics +# +# Thanks to https://jacobsalmela.com/2014/08/18/oauth-2-0-google-analytics-desktop-using-geektool-bash-curl/ +# for breaking this down and helping us get started +# +# https://developers.google.com/analytics/devguides/reporting/core/dimsmets for all the metrics ############################################################################### trace "Loading analytics functions" @@ -88,6 +93,7 @@ function ga_data() { RESULT=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:$METRIC&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) SIZE="$(printf "%.0f\n" "${RESULT}")" + # Make this a proper loop if [[ "${PROJSTATS}" == "1" ]]; then GA_HITS=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:hits&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) GA_PERCENT=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:percentNewSessions&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) diff --git a/lib/loader.sh b/lib/loader.sh index 2dacf16..c510b3e 100755 --- a/lib/loader.sh +++ b/lib/loader.sh @@ -17,15 +17,34 @@ if [[ "${FORCE}" != "1" ]] && [[ "${QUIET}" = "1" ]]; then echo "To deploy using the --quiet flag, you must also use --force."; exit 1 fi -# Creating this function first, so verbose output option is usable early +############################################################################### +# trace() +# Outputs timestamped, verbose info to both console and log files +# +# Arguments: +# status Will place the next trace output on the same line, e.g. +# [trace 1]Checking database... [trace 2]OK will render +# Checking database... OK in the logs +# Returns: +# None +############################################################################### function trace() { if [[ "${VERBOSE}" == "TRUE" ]]; then TIMESTAMP="$(date '+%H:%M:%S')" - echo -e "$(tput setaf 3)${TIMESTAMP}$(tput sgr0) $*" - echo "${TIMESTAMP} $*" >> "${logFile}" + if [[ "${1}" == "status" ]]; then + echo -e -n "$(tput setaf 3)${TIMESTAMP}$(tput sgr0) ${2}" + echo "${TIMESTAMP} ${2}" >> "${logFile}" + else + echo -e "$(tput setaf 3)${TIMESTAMP}$(tput sgr0) $*" + echo "${TIMESTAMP} $*" >> "${logFile}" + fi else TIMESTAMP="$(date '+%H:%M:%S')" - echo "${TIMESTAMP} $*" >> "${logFile}" + if [[ "${1}" == "status" ]]; then + echo -n "${TIMESTAMP} ${2}" >> "${logFile}" + else + echo "${TIMESTAMP} $*" >> "${logFile}" + fi fi } diff --git a/lib/process-html.sh b/lib/process-html.sh index 803e044..4d4b251 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -9,10 +9,11 @@ trace "Loading html handling" # Initialize variables read -r DEFAULTC PRIMARYC SECONDARYC SUCCESSC INFOC WARNINGC DANGERC SMOOCHID \ - COVER SCANC UPTIMEC LATENCYC LOGC LOGBC sed_commits sed_scan sed_backup <<< "" + COVER SCANC UPTIMEC LATENCYC LOGC LOGBC sed_commits sed_scan sed_backup \ + process_var v i <<< "" echo "${DEFAULTC} ${PRIMARYC} ${SUCCESSC} ${INFOC} ${WARNINGC} ${DANGERC} - ${SMOOCHID} ${COVER} ${SCANC} ${UPTIMEC} ${LATENCYC} - ${sed_commits} ${sed_scan} ${sed_backup}" > /dev/null + ${SMOOCHID} ${COVER} ${SCANC} ${UPTIMEC} ${LATENCYC} ${sed_commits} + ${sed_scan} ${sed_backup} ${process_var} ${v} ${i}" > /dev/null function process_html() { # Clean out the stuff we don't need @@ -43,8 +44,8 @@ function process_html() { sed -i -e '/BEGIN WORK RSS/,/END WORK RSS/d' \ -e '/RSS_URL/d' "${htmlFile}" \ "${htmlFile}" - else - sed -i "s^{{RSS_URL}}^${RSS_URL}^g" "${htmlFile}" + #else + # sed -i "s^{{RSS_URL}}^${RSS_URL}^g" "${htmlFile}" fi # Prettify errors, warning, and successes @@ -70,30 +71,30 @@ function process_html() { sed_backup=$(echo "sed -e '/{{BACKUP_FILES}}/ {' -e 'r ${trshFile}' -e 'd' -e '}' -i \"${htmlFile}\"") eval "${sed_backup}" - # Get to work + # Setup variables to process + process_var=(VIEWPORT NOW DEV LOGTITLE USER PROJNAME PROJCLIENT CLIENTLOGO \ + DEVURL PRODURL COMMITURL EXITCODE COMMITHASH USER LOGURL REMOTEURL \ + VIEWPORTPRE PATHTOREPO PROJNAME CLIENTCONTACT DEVURL PRODURL SCAN_MSG \ + SCAN_RESULT SCAN_URL BACKUP_STATUS LAST_BACKUP SMOOCHID DIGESTWRAP \ + GREETING REMOTEURL ANALYTICSMSG COVER WEEKOF UPTIME LATENCY GA_HITS \ + GA_PERCENT GA_SEARCHES GA_DURATION GA_SOCIAL CODE_STATS SCAN_BTN \ + UPTIME_BTN LATENCY_BTN BACKUP_BTN ACTIVITY_NAV STATISTICS_NAV SCAN_NAV \ + FIREWALL_NAV BACKUP_NAV BACKUP_MSG TOTAL_COMMITS RSS_URL) + + # Start the loop + for i in "${process_var[@]}" ; do + # This is essentially the same as insert_values, should consolidate + if [[ -n "${!i:-}" ]]; then + [[ "${INCOGNITO}" != "1" ]] && trace "${i}: ${!i}" + sed_hack=$(echo "sed -i 's^{{${i}}}^${!i}^g' ${htmlFile}") + # Kludgy but works. Ugh. + eval "${sed_hack}" + fi + done + + # Special snowflakes; for some silly reason the variables don't match sed -i -e "s^{{VIEWPORT}}^${VIEWPORT}^g" \ - -e "s^{{NOW}}^${NOW}^g" \ - -e "s^{{DEV}}^${DEV}^g" \ - -e "s^{{LOGTITLE}}^${LOGTITLE}^g" \ - -e "s^{{USER}}^${USER}^g" \ - -e "s^{{PROJNAME}}^${PROJNAME}^g" \ - -e "s^{{PROJCLIENT}}^${PROJCLIENT}^g" \ - -e "s^{{CLIENTLOGO}}^${CLIENTLOGO}^g" \ - -e "s^{{DEVURL}}^${DEVURL}^g" \ - -e "s^{{PRODURL}}^${PRODURL}^g" \ - -e "s^{{COMMITURL}}^${COMMITURL}^g" \ - -e "s^{{EXITCODE}}^${EXITCODE}^g" \ - -e "s^{{COMMITHASH}}^${COMMITHASH}^g" \ - -e "s^{{NOTES}}^${notes}^g" \ - -e "s^{{USER}}^${USER}^g" \ - -e "s^{{LOGURL}}^${LOGURL}^g" \ - -e "s^{{REMOTEURL}}^${REMOTEURL}^g" \ - -e "s^{{VIEWPORTPRE}}^${VIEWPORTPRE}^g" \ - -e "s^{{PATHTOREPO}}^${WORKPATH}/${APP}^g" \ - -e "s^{{PROJNAME}}^${PROJNAME}^g" \ - -e "s^{{CLIENTCONTACT}}^${CLIENTCONTACT}^g" \ - -e "s^{{DEVURL}}^${DEVURL}^g" \ - -e "s^{{PRODURL}}^${PRODURL}^g" \ + -e "s^{{NOTEs}}^${notes}^g" \ -e "s^{{DEFAULT}}^${DEFAULTC}^g" \ -e "s^{{PRIMARY}}^${PRIMARYC}^g" \ -e "s^{{SECONDARY}}^${SECONDARYC}^g" \ @@ -106,40 +107,8 @@ function process_html() { -e "s^{{SCAN_STATUS}}^${SCANC}^g" \ -e "s^{{UPTIME_STATUS}}^${UPTIMEC}^g" \ -e "s^{{LATENCY_STATUS}}^${LATENCYC}^g" \ - -e "s^{{SCAN_MSG}}^${SCAN_MSG}^g" \ - -e "s^{{SCAN_RESULT}}^${SCAN_RESULT}^g" \ - -e "s^{{SCAN_URL}}^${SCAN_URL}^g" \ - -e "s^{{BACKUP_STATUS}}^${BACKUP_STATUS}^g" \ - -e "s^{{LAST_BACKUP}}^${LAST_BACKUP}^g" \ - -e "s^{{SMOOCHID}}^${SMOOCHID}^g" \ -e "s^{{GRAVATARURL}}^${REMOTEURL}\/${APP}\/avatar^g" \ - -e "s^{{DIGESTWRAP}}^${DIGESTWRAP}^g" \ - -e "s^{{GREETING}}^${GREETING}^g" \ - -e "s^{{REMOTEURL}}^${REMOTEURL}^g" \ - -e "s^{{ANALYTICSMSG}}^${ANALYTICSMSG}^g" \ -e "s^{{STATURL}}^${REMOTEURL}\/${APP}\/stats^g" \ - -e "s^{{COVER}}^${COVER}^g" \ - -e "s^{{WEEKOF}}^${WEEKOF}^g" \ -e "s^{{LASTMONTH}}^${LAST_MONTH}^g" \ - -e "s^{{UPTIME}}^${UPTIME}^g" \ - -e "s^{{LATENCY}}^${LATENCY}^g" \ - -e "s^{{GA_HITS}}^${GA_HITS}^g" \ - -e "s^{{GA_PERCENT}}^${GA_PERCENT}^g" \ - -e "s^{{GA_SEARCHES}}^${GA_SEARCHES}^g" \ - -e "s^{{GA_DURATION}}^${GA_DURATION}^g" \ - -e "s^{{GA_SOCIAL}}^${GA_SOCIAL}^g" \ - -e "s^{{CODE_STATS}}^${CODE_STATS}^g" \ - -e "s^{{SCAN_BTN}}^${SCAN_BTN}^g" \ - -e "s^{{UPTIME_BTN}}^${UPTIME_BTN}^g" \ - -e "s^{{LATENCY_BTN}}^${LATENCY_BTN}^g" \ - -e "s^{{BACKUP_BTN}}^${BACKUP_BTN}^g" \ - -e "s^{{ACTIVITY_NAV}}^${ACTIVITY_NAV}^g" \ - -e "s^{{STATISTICS_NAV}}^${STATISTICS_NAV}^g" \ - -e "s^{{SCAN_NAV}}^${SCAN_NAV}^g" \ - -e "s^{{FIREWALL_NAV}}^${FIREWALL_NAV}^g" \ - -e "s^{{BACKUP_NAV}}^${BACKUP_NAV}^g" \ - -e "s^{{BACKUP_MSG}}^${BACKUP_MSG}^g" \ - -e "s^{{TOTAL_COMMITS}}^${TOTAL_COMMITS}^g" \ - -e "s^{{RSS_URL}}^${RSS_URL}^g" \ "${htmlFile}" } diff --git a/lib/wp.sh b/lib/wp.sh index 622be03..cb2d1dd 100755 --- a/lib/wp.sh +++ b/lib/wp.sh @@ -21,7 +21,7 @@ function wpPkg() { cd "${WORKPATH}"/"${APP}${WPROOT}${WPAPP}"; \ # Database check - trace "Checking database..." + trace status "Checking database: " "${WPCLI}"/wp db check &>> /dev/null; EXITCODE=$?; if [[ "${EXITCODE}" != "0" ]]; then "${WPCLI}"/wp db check &>> "${logFile}"; @@ -32,7 +32,10 @@ function wpPkg() { info "There is a problem with your Wordpress installation, check your configuration." fi else - trace "OK" + # Get info + info "OK" + wp core version --extra &>> "${logFile}"; + # Check for Wordfence wfCheck From 454171319bf21c44015c6b65756d8995861d7245 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 20 Aug 2018 10:50:21 -0700 Subject: [PATCH 094/334] Escaping special characters in text strings, resolve #141 --- deploy.sh | 13 ++++++------- lib/analytics.sh | 10 ++++++---- lib/digest.sh | 10 ++++++---- lib/process-html.sh | 5 +++-- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/deploy.sh b/deploy.sh index 8ec44b0..ceccd4a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -86,8 +86,8 @@ read -r var optstring options logFile wpFile coreFile postFile trshFile statFile COMMITURL COMMITHASH UPD1 UPD2 UPDATE gitLock AUTOMERGE MERGE EXITCODE \ currentStash deploy_cmd deps start_branch postSendmail SLACKUSER NOCHECK \ VIEWPORT VIEWPORTPRE LOGTITLE LOGURL TIMESTAMP STARTUP WPROOT \ - WPAPP WPSYSTEM DONOTUPDATEWP gitHistory ANALYTICSMSG digestSendmail MINAUSER \ - MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE GREETING LOGSUFFIX \ + WPAPP WPSYSTEM DONOTUPDATEWP gitHistory digestSendmail MINAUSER \ + MINADOMAIN SSHTARGET SSHSTATUS REMOTEFILE LOGSUFFIX \ DISABLESSHCHECK URL CODE DEPLOYPID DEPLOYTEST payload reportFile \ TMP MONITORURL MONITORUSER MONITORPASS SERVERID \ MONITORHOURS LATENCY UPTIME MONITORTEST MONITORAPI <<< "" @@ -100,11 +100,10 @@ echo "${var} ${optstring} ${options} ${logFile} ${wpFile} ${coreFile} ${postFile ${AUTOMERGE} ${MERGE} ${EXITCODE} ${currentStash} ${deploy_cmd} ${deps} ${start_branch} ${postSendmail} ${SLACKUSER} ${NOCHECK} ${VIEWPORT} ${VIEWPORTPRE} ${LOGTITLE} ${LOGURL} ${TIMESTAMP} ${STARTUP} ${WPROOT} ${WPAPP} - ${WPSYSTEM} ${DONOTUPDATEWP} ${gitHistory} ${ANALYTICSMSG} - ${digestSendmail} ${MINAUSER} ${MINADOMAIN} ${SSHTARGET} ${SSHSTATUS} - ${REMOTEFILE} ${GREETING} ${LOGSUFFIX} ${DISABLESSHCHECK} - ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} ${reportFile} - ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} + ${WPSYSTEM} ${DONOTUPDATEWP} ${gitHistory} ${digestSendmail} ${MINAUSER} + ${MINADOMAIN} ${SSHTARGET} ${SSHSTATUS} ${REMOTEFILE} ${LOGSUFFIX} + ${DISABLESSHCHECK} ${URL} ${CODE} ${DEPLOYPID} ${DEPLOYTEST} ${payload} + ${reportFile} ${TMP} ${MONITORURL} ${MONITORUSER} ${MONITORPASS} ${SERVERID} ${MONITORHOURS} ${LATENCY} ${UPTIME} ${MONITORTEST} ${MONITORAPI}" > /dev/null diff --git a/lib/analytics.sh b/lib/analytics.sh index 37eaeb0..9a4600f 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -13,8 +13,10 @@ trace "Loading analytics functions" # Initialize variables -read -r SIZE RND METRIC RESULT GA_HITS GA_PERCENT GA_SEARCHES GA_DURATION GA_SOCIAL <<< "" -echo "${SIZE} ${RND} ${METRIC} ${RESULT} ${GA_HITS} ${GA_PERCENT} ${GA_SEARCHES} ${GA_DURATION} ${GA_SOCIAL}" > /dev/null +read -r SIZE RND METRIC RESULT GA_HITS GA_PERCENT GA_SEARCHES GA_DURATION \ + GA_SOCIAL ANALYTICSMSG <<< "" +echo "${SIZE} ${RND} ${METRIC} ${RESULT} ${GA_HITS} ${GA_PERCENT} ${GA_SEARCHES} + ${GA_DURATION} ${GA_SOCIAL} ${ANALYTICSMSG}" > /dev/null function ga_metrics() { array[0]="hits" @@ -55,10 +57,10 @@ function analytics() { if [[ "${SIZE}" -gt "100" ]]; then ga_fail elif [[ "${SIZE}" -gt "50" ]]; then - ANALYTICSMSG="Last week ${SIZE} percent of your users were first time visitors. That's great!" + ANALYTICSMSG="Last week ${SIZE} percent of your users were first time visitors. That\'s great!" else RESULT="$((100 - ${SIZE}))" - ANALYTICSMSG="Last week ${RESULT} percent of your users were return visitors. That's great!" + ANALYTICSMSG="Last week ${RESULT} percent of your users were return visitors. That\'s great!" fi fi diff --git a/lib/digest.sh b/lib/digest.sh index 7b3ede8..a5430aa 100755 --- a/lib/digest.sh +++ b/lib/digest.sh @@ -8,9 +8,10 @@ trace "Loading digest handling" # Initializa needed variables -read -r AUTHOR AUTHOREMAIL AUTHORNAME GRAVATAR IMGFILE DIGESTWRAP <<< "" +read -r AUTHOR AUTHOREMAIL AUTHORNAME GRAVATAR IMGFILE DIGESTWRAP \ + GREETING <<< "" echo "${AUTHOR} ${AUTHOREMAIL} ${AUTHORNAME} ${GRAVATAR} ${IMGFILE} - ${DIGESTWRAP}" > /dev/null + ${DIGESTWRAP} ${GREETING}" > /dev/null function get_avatars() { for AUTHOR in $(git log --pretty=format:"%ae|%an" | sort | uniq); do @@ -60,9 +61,10 @@ function create_digest() { cat "${deployPath}/html/${HTMLTEMPLATE}/digest/header.html" "${statFile}" "${deployPath}/html/${HTMLTEMPLATE}/digest/footer.html" > "${htmlFile}" - # Randomize a positive Monday thought + # Randomize a positive Monday thought. Special characters must be escaped + # and use character codes array[0]="Hope you had a good weekend!" - array[1]="Alright Monday, let's do this." + array[1]="Alright Monday, let\'s do this." array[2]="Oh, hello Monday." array[3]="Welcome back, how was your weekend?" array[4]="Happy Monday and welcome back!" diff --git a/lib/process-html.sh b/lib/process-html.sh index 4d4b251..9f81a9c 100755 --- a/lib/process-html.sh +++ b/lib/process-html.sh @@ -83,7 +83,8 @@ function process_html() { # Start the loop for i in "${process_var[@]}" ; do - # This is essentially the same as insert_values, should consolidate + # This is essentially the same as insert_values() [see env-check.sh], we + # should consolidate them into one function if [[ -n "${!i:-}" ]]; then [[ "${INCOGNITO}" != "1" ]] && trace "${i}: ${!i}" sed_hack=$(echo "sed -i 's^{{${i}}}^${!i}^g' ${htmlFile}") @@ -94,7 +95,7 @@ function process_html() { # Special snowflakes; for some silly reason the variables don't match sed -i -e "s^{{VIEWPORT}}^${VIEWPORT}^g" \ - -e "s^{{NOTEs}}^${notes}^g" \ + -e "s^{{NOTES}}^${notes}^g" \ -e "s^{{DEFAULT}}^${DEFAULTC}^g" \ -e "s^{{PRIMARY}}^${PRIMARYC}^g" \ -e "s^{{SECONDARY}}^${SECONDARYC}^g" \ From 2dac8929ac5c045d43c6eddf736dcca4122eb7c6 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Mon, 20 Aug 2018 22:54:58 -0700 Subject: [PATCH 095/334] Analytics work --- lib/analytics.sh | 94 +++++++++++++++++++++++++++++++++++++++++++++--- lib/loader.sh | 2 +- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/lib/analytics.sh b/lib/analytics.sh index 9a4600f..091700a 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -14,9 +14,10 @@ trace "Loading analytics functions" # Initialize variables read -r SIZE RND METRIC RESULT GA_HITS GA_PERCENT GA_SEARCHES GA_DURATION \ - GA_SOCIAL ANALYTICSMSG <<< "" -echo "${SIZE} ${RND} ${METRIC} ${RESULT} ${GA_HITS} ${GA_PERCENT} ${GA_SEARCHES} - ${GA_DURATION} ${GA_SOCIAL} ${ANALYTICSMSG}" > /dev/null + GA_SOCIAL ANALYTICSMSG ga_var ga_day ga_sequence max_value <<< "" +echo "${SIZE} ${RND} ${METRIC} ${RESULT} ${GA_HITS} ${GA_PERCENT} + ${GA_SEARCHES} ${GA_DURATION} ${GA_SOCIAL} ${ANALYTICSMSG} ${ga_var} + ${ga_sequence} ${max_value}" > /dev/null function ga_metrics() { array[0]="hits" @@ -109,7 +110,86 @@ function ga_data() { fi } -# If no other results are worht displaying, fall back to displaying hits +# Just a test for now +function ga_data_loop() { + # Setup variables to process + console "${GASTART} - ${GAEND}" + ga_var=(users newUsers percentNewSessions sessionsPerUser sessions + bounceRate avgSessionDuration hits organicSearches pageviews avgTimeOnPage + avgPageLoadTime avgDomainLookupTime avgServerResponseTime impressions + adClicks adCost CPC CTR) + + # Start the loop + for i in "${ga_var[@]}" ; do + # This is essentially the same as insert_values() [see env-check.sh], we + # should consolidate them into one function + RESULT=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:$i&start-date=$GASTART&end-date=$GAEND&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) + + # Round to two decimal places if needed + if [[ "${RESULT}" = *"."* ]]; then + RESULT="$(printf '%0.2f\n' "${RESULT}")" + fi + + # Output trace + trace "${i}: ${RESULT}" + done +} + +############################################################################### +# ga_over_time() +# Collects Gollge Analytics data over a certain period of time +# +# Arguments: +# [metric] Defines the metric you wish to get from Google's API. Examples +# include 'sessions', 'hits', etc. Refere to Google API docs at +# https://developers.google.com/analytics/devguides/reporting/core/dimsmets +# [days] The number of days for which to gather analytics data. +# +# Returns: +# None +############################################################################### +function ga_over_time() { + # Process arguments + if [[ -n "$2" ]]; then + GASTART="$(date -I -d "$GAEND - $2 day")" + fi + + # Setup variables + ga_day="${GAEND}" + day="0" + METRIC="${1}" + + while [ "$ga_day" != "${GASTART}" ]; do + RESULT=$(curl -s "https://www.googleapis.com/analytics/v3/data/ga?ids=ga:$PROFILEID&metrics=ga:${METRIC}&start-date=$ga_day&end-date=$ga_day&access_token=$ACCESSTOKEN" | tr , '\n' | grep "totalsForAllResults" | cut -d'"' -f6) + trace "${ga_day}: ${RESULT} $1" + + # Store the values + declare "$1_${day}"="${RESULT}" + ga_sequence="${ga_sequence}${RESULT} " + day="$((day+1))" + ga_day="$(date -I -d "$ga_day - 1 day")" + done + + # Create percentage array + ga_sequence="$(echo -e "${ga_sequence}" | sed -e 's/[[:space:]]*$//')" + trace "Calculating array: ${ga_sequence}" + IFS=', ' read -r -a a <<< "${ga_sequence}" + for i in ${a[@]}; do + if [[ $i -gt $max_value ]]; then + max_value=$i + fi + done + trace "Max sequence value=${max_value}" + + # Calculate + for ((n=0; n < $2; n++)); do + var="$1_$n"; var_percent="$1_percent_$n" + var_percent=$(awk "BEGIN { pc=100*${!var}/${max_value}; i=int(pc); print (pc-i<0.5)?i:i+1 }") + trace "100*${!var}/${max_value} = ${var_percent}%" + done +} + +# If no other results are worth displaying, fall back to displaying hits function ga_fail() { METRIC="hits" ga_data @@ -166,6 +246,12 @@ function ga_test() { ACCESSTOKEN="$(awk -F\" '{print $4}' "${trshFile}")" echo "${ACCESSTOKEN}" + # Just here for testing + #ga_data_loop + empty_line; trace "Day count" + ga_over_time hits 7 + return + # Setup the metric we're after array[0]="hits" array[1]="percentNewSessions" diff --git a/lib/loader.sh b/lib/loader.sh index c510b3e..c94b5c8 100755 --- a/lib/loader.sh +++ b/lib/loader.sh @@ -19,7 +19,7 @@ fi ############################################################################### # trace() -# Outputs timestamped, verbose info to both console and log files +# Outputs timestamped, verbose info to both console and log files # # Arguments: # status Will place the next trace output on the same line, e.g. From d607f0e34eb2249566e82a0ed6642229d9361a8f Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Fri, 24 Aug 2018 11:33:34 -0700 Subject: [PATCH 096/334] Cleanup trace output --- etc/html/active/stats/stats.html | 28 +++++++++++++++++++++++++--- lib/loader.sh | 11 +++++++++-- lib/wp.sh | 12 ++++++------ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/etc/html/active/stats/stats.html b/etc/html/active/stats/stats.html index e798238..2f51bd5 100755 --- a/etc/html/active/stats/stats.html +++ b/etc/html/active/stats/stats.html @@ -82,7 +82,7 @@ My Dashboard > Statistics
-
+
@@ -121,15 +121,37 @@
-
+
-
+
+ +
+
+
+
+

About these charts

+
+
+
+

+ These charts represent when, how many, and by who code changes to your website are being made. +

+ What is a commit? +

+ Your website code uses version control systems which allow rolling back to previous versions easily. In this context, a commit within a version control system is protected as it is easily rolled back, even after the commit has been applied. +

+

+ More information about this type of workflow can be found here. +

+
+
+
diff --git a/lib/loader.sh b/lib/loader.sh index c94b5c8..2ce31d9 100755 --- a/lib/loader.sh +++ b/lib/loader.sh @@ -25,6 +25,8 @@ fi # status Will place the next trace output on the same line, e.g. # [trace 1]Checking database... [trace 2]OK will render # Checking database... OK in the logs +# notime Output trace with no timestamp, generally used after a +# `trace status "blahblah"` # Returns: # None ############################################################################### @@ -33,7 +35,10 @@ function trace() { TIMESTAMP="$(date '+%H:%M:%S')" if [[ "${1}" == "status" ]]; then echo -e -n "$(tput setaf 3)${TIMESTAMP}$(tput sgr0) ${2}" - echo "${TIMESTAMP} ${2}" >> "${logFile}" + echo -e -n "${TIMESTAMP} ${2}" >> "${logFile}" + elif [[ "${1}" == "notime" ]]; then + echo -e "${2}" + echo "${2}" >> "${logFile}" else echo -e "$(tput setaf 3)${TIMESTAMP}$(tput sgr0) $*" echo "${TIMESTAMP} $*" >> "${logFile}" @@ -41,7 +46,9 @@ function trace() { else TIMESTAMP="$(date '+%H:%M:%S')" if [[ "${1}" == "status" ]]; then - echo -n "${TIMESTAMP} ${2}" >> "${logFile}" + echo -e -n "${TIMESTAMP} ${2}" >> "${logFile}" + elif [[ "${1}" == "notime" ]]; then + echo "${2}" >> "${logFile}" else echo "${TIMESTAMP} $*" >> "${logFile}" fi diff --git a/lib/wp.sh b/lib/wp.sh index cb2d1dd..5b698ce 100755 --- a/lib/wp.sh +++ b/lib/wp.sh @@ -13,19 +13,19 @@ function wpPkg() { # Is wp-cli installed? if hash "${WPCLI}"/wp 2>/dev/null; then - trace "wp-cli found, checking for Wordpress..." + trace status "wp-cli found, checking for Wordpress... " # Check for Wordpress if [[ -f "${WORKPATH}"/"${APP}${WPROOT}${WPSYSTEM}"/wp-settings.php ]]; then - trace "Wordpress found!" + trace notime "FOUND" cd "${WORKPATH}"/"${APP}${WPROOT}${WPAPP}"; \ # Database check - trace status "Checking database: " + trace status "Checking database... " "${WPCLI}"/wp db check &>> /dev/null; EXITCODE=$?; if [[ "${EXITCODE}" != "0" ]]; then "${WPCLI}"/wp db check &>> "${logFile}"; - trace "ERROR: Database check failed" + trace notime "FAIL" if [[ "${AUTOMATE}" == "1" ]]; then error "There is a problem with your Wordpress installation, check your configuration." else @@ -33,7 +33,7 @@ function wpPkg() { fi else # Get info - info "OK" + trace notime "OK" wp core version --extra &>> "${logFile}"; # Check for Wordfence @@ -69,7 +69,7 @@ function wpPkg() { fi fi else - trace "Wordpress not found" + trace notime "NOT FOUND" fi else trace "wp-cli not found" From 1f27fcf54323c1986f77459d92665cf0c462fe56 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sat, 25 Aug 2018 15:09:28 -0700 Subject: [PATCH 097/334] Getting analytics engagement dashboard started --- etc/html/active/stats/css/styles.css | 20 ++++ etc/html/active/stats/engagement.html | 163 ++++++++++++++++++++++++++ lib/analytics.sh | 19 ++- lib/statistics.sh | 11 ++ 4 files changed, 210 insertions(+), 3 deletions(-) create mode 100755 etc/html/active/stats/engagement.html diff --git a/etc/html/active/stats/css/styles.css b/etc/html/active/stats/css/styles.css index 4661481..56e5926 100755 --- a/etc/html/active/stats/css/styles.css +++ b/etc/html/active/stats/css/styles.css @@ -94,3 +94,23 @@ textarea.form-control { .text-white { color: #ffffff; } + +.progress-bar-vertical { + width: calc(86% / 7); + min-height: 340px; + display: flex; + align-items: flex-end; + float: left; +} + +.progress-bar-vertical-margin { + margin-right: 2%; +} + +.progress-bar-vertical .progress-bar { + width: 100%; + height: 0; + -webkit-transition: height 0.6s ease; + -o-transition: height 0.6s ease; + transition: height 0.6s ease; +} diff --git a/etc/html/active/stats/engagement.html b/etc/html/active/stats/engagement.html new file mode 100755 index 0000000..fd44e0e --- /dev/null +++ b/etc/html/active/stats/engagement.html @@ -0,0 +1,163 @@ + + + + + + {{PROJNAME}} • {{PROJCLIENT}} + + + + + + + + + + + + + + +
+
+ + +
+
    +
  • Updated {{NOW}}
  • +
+ My Dashboard > Audience Engagement +
+
+
+
+
+
+

{{GA_HITS}} hits (Last 7 days)

+
+
+
+ +
+
+ {{hits_6}}
{{hits_date_6}}
+
+
+ +
+
+ {{hits_5}}
{{hits_date_5}}
+
+
+ +
+
+ {{hits_4}}
{{hits_date_4}}
+
+
+ +
+
+ {{hits_3}}
{{hits_date_3}}
+
+
+ +
+
+ {{hits_2}}
{{hits_date_2}}
+
+
+ +
+
+ {{hits_1}}
Yesterday
+
+
+ +
+
+ {{hits_0}}
Today
+
+
+ +
+
+
+
+
+
+
+

About these charts

+
+
+
+

+ blahblah +

+
+
+
+
+
+
+
+ +
+ + + + + diff --git a/lib/analytics.sh b/lib/analytics.sh index 091700a..77941c6 100755 --- a/lib/analytics.sh +++ b/lib/analytics.sh @@ -14,10 +14,10 @@ trace "Loading analytics functions" # Initialize variables read -r SIZE RND METRIC RESULT GA_HITS GA_PERCENT GA_SEARCHES GA_DURATION \ - GA_SOCIAL ANALYTICSMSG ga_var ga_day ga_sequence max_value <<< "" + GA_SOCIAL ANALYTICSMSG ga_var ga_day ga_sequence max_value n GA_TOTAL <<< "" echo "${SIZE} ${RND} ${METRIC} ${RESULT} ${GA_HITS} ${GA_PERCENT} ${GA_SEARCHES} ${GA_DURATION} ${GA_SOCIAL} ${ANALYTICSMSG} ${ga_var} - ${ga_sequence} ${max_value}" > /dev/null + ${ga_sequence} ${max_value} ${n} ${GA_TOTAL}" > /dev/null function ga_metrics() { array[0]="hits" @@ -174,19 +174,32 @@ function ga_over_time() { ga_sequence="$(echo -e "${ga_sequence}" | sed -e 's/[[:space:]]*$//')" trace "Calculating array: ${ga_sequence}" IFS=', ' read -r -a a <<< "${ga_sequence}" + for i in ${a[@]}; do if [[ $i -gt $max_value ]]; then max_value=$i fi done - trace "Max sequence value=${max_value}" # Calculate for ((n=0; n < $2; n++)); do var="$1_$n"; var_percent="$1_percent_$n" var_percent=$(awk "BEGIN { pc=100*${!var}/${max_value}; i=int(pc); print (pc-i<0.5)?i:i+1 }") trace "100*${!var}/${max_value} = ${var_percent}%" + + # Store values + eval $1_$n="${!var}" + eval $1_percent_$n="${var_percent}" + + if [[ "${PROJSTATS}" == "1" ]]; then + sed -i -e "s^{{$1_$n}}^${!var}^g" \ + -e "s^{{$1_percent_$n}}^${var_percent}^g" \ + -e "s^{{$1_date_$n}}^${n} days ago^g" \ + "${htmlFile}" + fi done + + trace "Values: ${hits_0}, ${hits_1}, ${hits_2}, ${hits_3}, ${hits_4}, ${hits_5}, ${hits_6}" } # If no other results are worth displaying, fall back to displaying hits diff --git a/lib/statistics.sh b/lib/statistics.sh index 78e2e40..f96f7cd 100755 --- a/lib/statistics.sh +++ b/lib/statistics.sh @@ -80,6 +80,7 @@ function project_stats() { project_statistics project_firewall project_backup + project_engagement # Post files postLog @@ -119,6 +120,16 @@ function project_firewall() { process_html; cat "${htmlFile}" > "/tmp/stats/firewall.html" } +function project_engagement() { + cat "${deployPath}/html/${HTMLTEMPLATE}/stats/engagement.html" > "${htmlFile}" + # This just for testing, will turn it into a loop + ga_over_time hits 7 + + # Process the HTML + process_html; + cat "${htmlFile}" > "/tmp/stats/engagement.html" +} + function project_backup() { # Get file directory #echo "${BACKUP_FILES}" > "${trshFile}" From 0052977e3d86985486cf01d8c425c02918fbab32 Mon Sep 17 00:00:00 2001 From: Floyd Diebel Date: Sun, 26 Aug 2018 21:24:20 -0700 Subject: [PATCH 098/334] Analytic charts --- CHANGELOG.md | 2 + etc/html/active/digest/commit.html | 1 + etc/html/active/digest/footer.html | 34 ++ etc/html/active/digest/header.html | 102 ++++ etc/html/active/digest/mockup.html | 139 +++++ etc/html/active/digest/scan-pass.png | Bin 0 -> 2132 bytes etc/html/active/digest/speed.png | Bin 0 -> 3155 bytes etc/html/active/digest/uptime.png | Bin 0 -> 3468 bytes etc/html/active/digest/wrap.html | 1 + etc/html/active/stats/engagement.html | 321 +++++++----- etc/html/default/digest/footer.html | 19 +- etc/html/default/digest/header.html | 26 +- etc/html/default/stats/activity.html | 121 +++++ etc/html/default/stats/backup.html | 115 ++++ etc/html/default/stats/css/bootstrap.min.css | 5 + etc/html/default/stats/css/styles.css | 116 ++++ etc/html/default/stats/engagement.html | 220 ++++++++ etc/html/default/stats/firewall.html | 113 ++++ .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20290 bytes .../fonts/glyphicons-halflings-regular.svg | 229 ++++++++ .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41236 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23292 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes etc/html/default/stats/index.html | 495 ++++++++---------- etc/html/default/stats/js/bootstrap.min.js | 7 + etc/html/default/stats/js/scripts.js | 9 + etc/html/default/stats/scan.html | 124 +++++ etc/html/default/stats/stats.html | 165 ++++++ etc/html/default/theme.conf | 4 +- etc/html/minimal/approval.php | 136 +++++ etc/html/minimal/approve.html | 23 + etc/html/minimal/client.html | 30 ++ etc/html/minimal/digest/commit.html | 1 + etc/html/minimal/digest/footer.html | 34 ++ etc/html/minimal/digest/header.html | 102 ++++ etc/html/minimal/digest/mockup.html | 139 +++++ etc/html/minimal/digest/scan-pass.png | Bin 0 -> 2132 bytes etc/html/minimal/digest/speed.png | Bin 0 -> 3155 bytes etc/html/minimal/digest/uptime.png | Bin 0 -> 3468 bytes etc/html/minimal/digest/wrap.html | 1 + etc/html/minimal/error.html | 19 + etc/html/minimal/footer.html | 24 + etc/html/minimal/header.html | 81 +++ etc/html/minimal/remote/backup.png | Bin 0 -> 2056 bytes etc/html/minimal/remote/backup.svg | 12 + etc/html/minimal/remote/index.html | 108 ++++ etc/html/minimal/remote/nolog.html | 97 ++++ etc/html/minimal/remote/scan.png | Bin 0 -> 2132 bytes etc/html/minimal/remote/scan.svg | 9 + etc/html/minimal/remote/speed.png | Bin 0 -> 3155 bytes etc/html/minimal/remote/speed.svg | 13 + etc/html/minimal/remote/uptime.png | Bin 0 -> 3468 bytes etc/html/minimal/remote/uptime.svg | 10 + etc/html/minimal/remote/warning.png | Bin 0 -> 1857 bytes etc/html/minimal/report/css/print.css | 7 + etc/html/minimal/report/css/style.css | 228 ++++++++ etc/html/minimal/report/footer.html | 14 + etc/html/minimal/report/header.html | 53 ++ etc/html/minimal/report/js/edit.js | 139 +++++ etc/html/minimal/scan/footer.html | 20 + etc/html/minimal/scan/header.html | 76 +++ etc/html/minimal/stats/index.html | 285 ++++++++++ etc/html/minimal/success.html | 20 + install/doinst.sh | 2 +- lib/analytics.sh | 49 +- lib/digest.sh | 3 + lib/post-log.sh | 12 +- lib/process-html.sh | 3 +- lib/statistics.sh | 22 +- 69 files changed, 3674 insertions(+), 466 deletions(-) create mode 100755 etc/html/active/digest/commit.html create mode 100755 etc/html/active/digest/footer.html create mode 100755 etc/html/active/digest/header.html create mode 100755 etc/html/active/digest/mockup.html create mode 100755 etc/html/active/digest/scan-pass.png create mode 100755 etc/html/active/digest/speed.png create mode 100755 etc/html/active/digest/uptime.png create mode 100755 etc/html/active/digest/wrap.html create mode 100755 etc/html/default/stats/activity.html create mode 100755 etc/html/default/stats/backup.html create mode 100755 etc/html/default/stats/css/bootstrap.min.css create mode 100755 etc/html/default/stats/css/styles.css create mode 100755 etc/html/default/stats/engagement.html create mode 100755 etc/html/default/stats/firewall.html create mode 100755 etc/html/default/stats/fonts/glyphicons-halflings-regular.eot create mode 100755 etc/html/default/stats/fonts/glyphicons-halflings-regular.svg create mode 100755 etc/html/default/stats/fonts/glyphicons-halflings-regular.ttf create mode 100755 etc/html/default/stats/fonts/glyphicons-halflings-regular.woff create mode 100755 etc/html/default/stats/fonts/glyphicons-halflings-regular.woff2 create mode 100755 etc/html/default/stats/js/bootstrap.min.js create mode 100755 etc/html/default/stats/js/scripts.js create mode 100755 etc/html/default/stats/scan.html create mode 100755 etc/html/default/stats/stats.html create mode 100755 etc/html/minimal/approval.php create mode 100755 etc/html/minimal/approve.html create mode 100755 etc/html/minimal/client.html create mode 100755 etc/html/minimal/digest/commit.html create mode 100755 etc/html/minimal/digest/footer.html create mode 100755 etc/html/minimal/digest/header.html create mode 100755 etc/html/minimal/digest/mockup.html create mode 100755 etc/html/minimal/digest/scan-pass.png create mode 100755 etc/html/minimal/digest/speed.png create mode 100755 etc/html/minimal/digest/uptime.png create mode 100755 etc/html/minimal/digest/wrap.html create mode 100755 etc/html/minimal/error.html create mode 100755 etc/html/minimal/footer.html create mode 100755 etc/html/minimal/header.html create mode 100755 etc/html/minimal/remote/backup.png create mode 100755 etc/html/minimal/remote/backup.svg create mode 100755 etc/html/minimal/remote/index.html create mode 100755 etc/html/minimal/remote/nolog.html create mode 100755 etc/html/minimal/remote/scan.png create mode 100755 etc/html/minimal/remote/scan.svg create mode 100755 etc/html/minimal/remote/speed.png create mode 100755 etc/html/minimal/remote/speed.svg create mode 100755 etc/html/minimal/remote/uptime.png create mode 100755 etc/html/minimal/remote/uptime.svg create mode 100755 etc/html/minimal/remote/warning.png create mode 100755 etc/html/minimal/report/css/print.css create mode 100755 etc/html/minimal/report/css/style.css create mode 100755 etc/html/minimal/report/footer.html create mode 100755 etc/html/minimal/report/header.html create mode 100755 etc/html/minimal/report/js/edit.js create mode 100755 etc/html/minimal/scan/footer.html create mode 100755 etc/html/minimal/scan/header.html create mode 100755 etc/html/minimal/stats/index.html create mode 100755 etc/html/minimal/success.html diff --git a/CHANGELOG.md b/CHANGELOG.md index b6e9b01..c740124 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Digest emails can now include a chart showing the week's analytics ### Changed - Improved HTML post-processing diff --git a/etc/html/active/digest/commit.html b/etc/html/active/digest/commit.html new file mode 100755 index 0000000..a753414 --- /dev/null +++ b/etc/html/active/digest/commit.html @@ -0,0 +1 @@ +
+ + + + + +
\ No newline at end of file diff --git a/etc/html/active/digest/footer.html b/etc/html/active/digest/footer.html new file mode 100755 index 0000000..5d22d9a --- /dev/null +++ b/etc/html/active/digest/footer.html @@ -0,0 +1,34 @@ + + +
+ + + + +
+ Looking for more? Check out your project's stats page. +
+
+ + + + + + +
+
+ + + diff --git a/etc/html/active/digest/header.html b/etc/html/active/digest/header.html new file mode 100755 index 0000000..051cbd2 --- /dev/null +++ b/etc/html/active/digest/header.html @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+

Your Weekly Digest

+ +

+ {{WEEKOF}} – {{NOW}}

+ +
{{PROJCLIENT}}
+
+ + +
+

{{SCAN_MSG}}
+ +

{{UPTIME}}% uptime
+ +

{{LATENCY}}s latency
+
+ + +

{{GREETING}}

+

{{ANALYTICSMSG}}

Analytics Chart

+ +

Here's a summary of code updates made to your website last week:

+
\ No newline at end of file diff --git a/etc/html/active/digest/mockup.html b/etc/html/active/digest/mockup.html new file mode 100755 index 0000000..950dd79 --- /dev/null +++ b/etc/html/active/digest/mockup.html @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+

Your Weekly Digest

+ +

+ March 30, 2018 – April 06, 2018

+ +
EMRL
+
+ + +
+

Scan passed
+

100% uptime
+

1.54s latency
+
+ + +

Happy Monday and welcome back!

+

You had 215 hits in the last week.

+ +

Here's a summary of code updates made to your website last week:

+
+
+commit 8a8dff7
+Author: Joe Horning
+Date: Thu, 5 Apr 2018 02:05:46 -0700 (2 days ago)

+Updated 2 of 2 plugins (advanced-custom-fields-pro 5.6.10, wordfence 7.1.2, wordpress-seo 7.2) and system (wp-core 4.9.5)
+ +
+ + + + +
+ Looking for more? Check out your project's stats page. +
+
+ + + + + + +
+
+ + + diff --git a/etc/html/active/digest/scan-pass.png b/etc/html/active/digest/scan-pass.png new file mode 100755 index 0000000000000000000000000000000000000000..4974672a415771b5e3a05a946a1e1870fd2fc1aa GIT binary patch literal 2132 zcmah~cTkgg8vZ^)LcmZWQgad@iVDgC7EnrHB?1Z(B6Sy}tW*USL|7oi7(qe$M%Drt zR6xo}KLrsIFccf1SQ6j}!GZ|V1VTx^Ts;4{o4cFacjhhgJkR{*o%y}h4vGre z3IG5oIyu^tq?z^2kqBudjh>N{CapMvcif5Skhp}v*kHi!RCG|Vu2W=SXfP=_@Kj>V z(_kwAkXv!Gw{!n(c&1noIpV02cJ09?qWoipK5z*i2hTUE^R=5%h^=D9!#xetNf)Lh zGJRehm9p{U?}m>Rja%gWWlutA2vb=_R#qMt!yid(^d((ZDhW_J2&aO82>>9zb)^Xa zFo63p{Y2iaEO!=<*TOZtYWdXxqQ+9t+HKBec zV>hkKqDl_&duTQwtfEg?Um~SHf3;4LLFn>_b_4*6e^y$qQE-np-t!8bvLYAC>aafpCxO%OGm0v@MbOZ~;?$`&BKL6xgNSeu*qu_R@EY z0QRQ*SVmlpSQO{*9a=$mp?rb9p!&HmP0KczL6p~M`%Lz)0Nt163y6ZkUSS%>mLP>y z+bhYr;Crc9MNs%csA)@K5QDe0t$hbQ5CRSQoGDdgbNlE{(Zr%PB9FhrT^`ZQ{m$>A zV8-1t*#~PX3dyS~#!ZC=w4KENq$8M$`^iTfCFbrIk8DLFkCqw898Abn zGNHPEA%X$UP^pZCkg9a$gZHC&l4i+aIeXp8VXeOdLsLxzxesVhD*N;f4`o|5T`2vP zvmvZs?rM*N;>`}gL|qZF$~fPD*lmq(&Qw*rd0U3c=J7q@pIQ@9M0G|}n8AmFadkf; zSQ@^)4CJc1AAzFv-%FA+5xNNUd3JajQnvm^_~219*piEjgPIBUyy!JQiO<7_b@{^Q z$qokP2Zze@U;R^UAHJ;;?3}eZ(9O%jyQHoTm5o^!z00Xni`9c12aYI7+u|0A#g8Pu zcMrsBQf*i|XYUU?Mp5h71XM%UB(lf5+!uP`16uhHWZhWZdp72lguY!n)YNt^&L8wv z3|hf{q8U6hv8~C$-r+#mnTc5#2L9Lr@wI_A;K|XJw?H)i-ULTKHZ??iy?B@WO zDym@=t`91lP*m|QF;Z)kq-6@Lf6(vtO$9rh`}Iu+CTejZl7dv8KM7u<&^!6;i=agIkt=E z9|6})0&A2;GHr_4Hm*frC#s{XUL<|qfq@BcWWG`uZ<+PSoVs2RjlRn<$f|q-w0c~~ zxnwatGk47@7pJpmCcK4>R10X1Y#`)3?ZVITjrDaJUY={c8v&E;3s0wC;d9o#(8|0t z(>Tp>AttCsJ*+@X2Wrph2FT8dKAIp=!vhUN^*%FoRzCfz@;C6-VXAgT_qBXi- z1Oj*RmU#TyW%GPxBb)WGYT)pei2O5g3mhCazD#euVlUw4n6}zgyIyg?w)uMO6*A$9 zB%JnpQpfY7aLrjv2{vyBlvbJowUTz}I5J~Rhm3nq?(?|wb^pvX_Fd81Xl;WTcITTM z&P`t|*H4sy%aGXpJZm0Ey-%T*uSPCxEQ^_bqvxP6?#|hbipQ%wr?84MljZ21g15%dn@exm z%%)%4mHvt%YvxUNcHuFmbIQIyqnY>2cGBwKwDg!osR=A|Ti7kx=c_JOHYWzW1Olml z)TnfB^_A#Dv}EdAXO$#cOQ1kGA$q?&WJ#YeKMg!LK*mfO3WR1x6gv7-fkjiDz6e_O zO1G?JGwXI!=q~e^gKM4@PSsmO$daoF$;DUnRP2}0!3ftF^ry7mOVhGm8HA?X@Y6z( z734F)5iL2FZ)W>D!+#sF7;`e~c&?yQ_1fpo$CdpH4DaU7OmOU$Pft5(-58d-$(v#J zVrAsF0Y(IWSfehi@f10wLt5&!@I literal 0 HcmV?d00001 diff --git a/etc/html/active/digest/speed.png b/etc/html/active/digest/speed.png new file mode 100755 index 0000000000000000000000000000000000000000..551522ea45ca35d02a6a8b7f9d3a5b4fa79c46ed GIT binary patch literal 3155 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 VCxh58{^z{{40Q22)mn~`{|BIsV&nh- literal 0 HcmV?d00001 diff --git a/etc/html/active/digest/wrap.html b/etc/html/active/digest/wrap.html new file mode 100755 index 0000000..cae2bc8 --- /dev/null +++ b/etc/html/active/digest/wrap.html @@ -0,0 +1 @@ +
+
\ No newline at end of file diff --git a/etc/html/active/stats/engagement.html b/etc/html/active/stats/engagement.html index fd44e0e..ae2bab6 100755 --- a/etc/html/active/stats/engagement.html +++ b/etc/html/active/stats/engagement.html @@ -1,88 +1,89 @@ - - - {{PROJNAME}} • {{PROJCLIENT}} - - - - - - - - - + + + {{PROJNAME}} • {{PROJCLIENT}} + + + + + + + + + - + +
+
+ + +
+
    +
  • Updated {{NOW}}
  • +
+ My Dashboard > Audience Engagement +
+
+
@@ -90,52 +91,108 @@

{{GA_HITS}} hits (Last 7 days)

+
+
+ {{hits_6}}
{{hits_date_6}}
+
+
+ +
+
+ {{hits_5}}
{{hits_date_5}}
+
+
-
-
- {{hits_6}}
{{hits_date_6}}
-
-
- -
-
- {{hits_5}}
{{hits_date_5}}
-
-
- -
-
- {{hits_4}}
{{hits_date_4}}
-
-
- -
-
- {{hits_3}}
{{hits_date_3}}
-
-
- -
-
- {{hits_2}}
{{hits_date_2}}
-
-
- -
-
- {{hits_1}}
Yesterday
-
-
- -
-
- {{hits_0}}
Today
-
-
+
+
+ {{hits_4}}
{{hits_date_4}}
+
+
+
+
+ {{hits_3}}
{{hits_date_3}}
+
+
+ +
+
+ {{hits_2}}
{{hits_date_2}}
+
+
+ +
+
+ {{hits_1}}
Yesterday
+
+
+ +
+
+ {{hits_0}}
Today
+
+
-
+ +
+
+
+

{{GA_SEARCHES}} organic searches (Last 7 days)

+
+
+
+
+
+ {{organicSearches_6}}
{{organicSearches_date_6}}
+
+
+ +
+
+ {{organicSearches_5}}
{{organicSearches_date_5}}
+
+
+ +
+
+ {{organicSearches_4}}
{{organicSearches_date_4}}
+
+
+ +
+
+ {{organicSearches_3}}
{{organicSearches_date_3}}
+
+
+ +
+
+ {{organicSearches_2}}
{{organicSearches_date_2}}
+
+
+ +
+
+ {{organicSearches_1}}
Yesterday
+
+
+ +
+
+ {{organicSearches_0}}
Today
+
+
+
+
+ + + + + +
+ +
@@ -144,20 +201,20 @@

About these charts

-

- blahblah -

+

+ blahblah +

-
-
- - - -
- - - - + + + + + +
+ + + + diff --git a/etc/html/default/digest/footer.html b/etc/html/default/digest/footer.html index 5d22d9a..b7ef1c4 100755 --- a/etc/html/default/digest/footer.html +++ b/etc/html/default/digest/footer.html @@ -1,17 +1,14 @@ - + +
 
+ - @@ -20,8 +17,8 @@
EMRL -

EMRL • 1020 Tenth Street • Sacramento, CA 95814 • (916) 446-2440
- We your code

+

EMRL • 1020 Tenth Street • Sacramento, CA 95814 • (916) 446-2440
+ We your code

diff --git a/etc/html/default/digest/header.html b/etc/html/default/digest/header.html index b74b7ef..08da59d 100755 --- a/etc/html/default/digest/header.html +++ b/etc/html/default/digest/header.html @@ -9,13 +9,13 @@ - + - + - - + +
-
+
-
-

Your Weekly Digest

+
+

Your Weekly Digest

-

+

{{WEEKOF}} – {{NOW}}

{{PROJCLIENT}}
@@ -96,7 +96,7 @@

{{GREETING}}

-

{{ANALYTICSMSG}}

+

{{ANALYTICSMSG}}

Analytics Chart

Here's a summary of code updates made to your website last week:

\ No newline at end of file diff --git a/etc/html/default/stats/activity.html b/etc/html/default/stats/activity.html new file mode 100755 index 0000000..ccb83d9 --- /dev/null +++ b/etc/html/default/stats/activity.html @@ -0,0 +1,121 @@ + + + + + + {{PROJNAME}} • {{PROJCLIENT}} + + + + + + + + + + + + + + +
+
+
+ Reports +
+ + +

 

+
+ +
+
    +
  • Updated {{NOW}}
  • +
+ My Dashboard > Activity +
+
+
+
+
+
+

Activity history ({{TOTAL_COMMITS}} entries)

+
+
+
+ {{COMMITS_RECENT}} +
+
+
+
+
+
+
+

About activity

+
+
+
+

+ Every time we update your code, it is "committed" to a development system where it is stored, tracked with a uniqe ID, and logged. That way, if anything ever breaks, we can easily diagnose how things went wrong. +

+
+
+
+
+
+
+
+ +
+ + + + + diff --git a/etc/html/default/stats/backup.html b/etc/html/default/stats/backup.html new file mode 100755 index 0000000..235ccf7 --- /dev/null +++ b/etc/html/default/stats/backup.html @@ -0,0 +1,115 @@ + + + + + + {{PROJNAME}} • {{PROJCLIENT}} + + + + + + + + + + + + + + +
+
+
+ Reports +
+ + +

 

+
+ +
+
    +
  • Updated {{NOW}}
  • +
+ My Dashboard > System Backup +
+
+
+
+
+
+

{{BACKUP_MSG}}

+
+
+
+

{{BACKUP_FILES}}

+
+
+
+
+
+
+

About the backup +

+
+
+
+
+
+
+
+ +
+ + + + + diff --git a/etc/html/default/stats/css/bootstrap.min.css b/etc/html/default/stats/css/bootstrap.min.css new file mode 100755 index 0000000..28f154d --- /dev/null +++ b/etc/html/default/stats/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/etc/html/default/stats/css/styles.css b/etc/html/default/stats/css/styles.css new file mode 100755 index 0000000..56e5926 --- /dev/null +++ b/etc/html/default/stats/css/styles.css @@ -0,0 +1,116 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,700'); + +body { + font-family: 'Roboto', sans-serif !important; +} + +p, td { + font-weight: 400; +} + +.navbar-static-top { + margin-bottom:20px; +} + +i { + font-size:16px; +} + +.nav > li > a { + color:#787878; +} + +footer { + margin-top:20px; + padding-top:20px; + padding-bottom:20px; + background-color:#efefef; +} + +/* count indicator near icons */ +.nav>li .count { + position: absolute; + bottom: 12px; + right: 6px; + font-size: 9px; + background: rgba(51,200,51,0.55); + color: rgba(255,255,255,0.9); + line-height: 1em; + padding: 2px 4px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + -ms-border-radius: 10px; + -o-border-radius: 10px; + border-radius: 10px; +} + +textarea.form-control { + height: 8.0em !important; +} + +.stat-label { + font-weight: 700; + font-size: 0.8em; +} + +.nav > li > a > span { + padding: 0 10px; +} + +.caret { + padding: 0 !important; +} + +.mauticform-post-success .mauticform-message { + color: white; + background-color: green; + padding: 20px; + text-align: center; + margin-bottom: 20px; + margin-top: 5px; + font-weight: 700; + font-size: 20px; +} + +.mauticform-post-success .mauticform-innerform { + display: none; +} + +@media (min-width: 768px) { + .modal-dialog { + width: 800px !important; + } +} + +.rss-items { + list-style-type: none; + -webkit-padding-start: 0px; +} + +.rss-item { + margin-bottom: 1.4em; +} + +.text-white { + color: #ffffff; +} + +.progress-bar-vertical { + width: calc(86% / 7); + min-height: 340px; + display: flex; + align-items: flex-end; + float: left; +} + +.progress-bar-vertical-margin { + margin-right: 2%; +} + +.progress-bar-vertical .progress-bar { + width: 100%; + height: 0; + -webkit-transition: height 0.6s ease; + -o-transition: height 0.6s ease; + transition: height 0.6s ease; +} diff --git a/etc/html/default/stats/engagement.html b/etc/html/default/stats/engagement.html new file mode 100755 index 0000000..b295d53 --- /dev/null +++ b/etc/html/default/stats/engagement.html @@ -0,0 +1,220 @@ + + + + + + {{PROJNAME}} • {{PROJCLIENT}} + + + + + + + + + + + + + + +
+
+
+ Reports +
+ + +

 

+
+ +
+
    +
  • Updated {{NOW}}
  • +
+ My Dashboard > Audience Engagement +
+
+
+ +
+
+
+

{{GA_HITS}} hits (Last 7 days)

+
+
+
+
+
+ {{hits_6}}
{{hits_date_6}}
+
+
+ +
+
+ {{hits_5}}
{{hits_date_5}}
+
+
+ +
+
+ {{hits_4}}
{{hits_date_4}}
+
+
+ +
+
+ {{hits_3}}
{{hits_date_3}}
+
+
+ +
+
+ {{hits_2}}
{{hits_date_2}}
+
+
+ +
+
+ {{hits_1}}
Yesterday
+
+
+ +
+
+ {{hits_0}}
Today
+
+
+
+
+ +
+
+
+

{{GA_SEARCHES}} organic searches (Last 7 days)

+
+
+
+
+
+ {{organicSearches_6}}
{{organicSearches_date_6}}
+
+
+ +
+
+ {{organicSearches_5}}
{{organicSearches_date_5}}
+
+
+ +
+
+ {{organicSearches_4}}
{{organicSearches_date_4}}
+
+
+ +
+
+ {{organicSearches_3}}
{{organicSearches_date_3}}
+
+
+ +
+
+ {{organicSearches_2}}
{{organicSearches_date_2}}
+
+
+ +
+
+ {{organicSearches_1}}
Yesterday
+
+
+ +
+
+ {{organicSearches_0}}
Today
+
+
+
+
+ + + + + +
+ + +
+
+
+
+

About these charts

+
+
+
+

+ blahblah +

+
+
+
+
+
+
+
+ +
+ + + + + diff --git a/etc/html/default/stats/firewall.html b/etc/html/default/stats/firewall.html new file mode 100755 index 0000000..f2e58c2 --- /dev/null +++ b/etc/html/default/stats/firewall.html @@ -0,0 +1,113 @@ + + + + + + {{PROJNAME}} • {{PROJCLIENT}} + + + + + + + + + + + + + + +
+
+
+ Reports +
+ + +

 

+
+ +
+
    +
  • Updated {{NOW}}
  • +
+ My Dashboard > Firewall & Security +
+
+
+
+
+
+

Not yet functional

+
+
+
+
+
+
+
+
+
+

About the firewall

+
+
+
+
+
+
+
+ +
+ + + + + diff --git a/etc/html/default/stats/fonts/glyphicons-halflings-regular.eot b/etc/html/default/stats/fonts/glyphicons-halflings-regular.eot new file mode 100755 index 0000000000000000000000000000000000000000..423bd5d3a20b804f596e04e5cd02fb4f16cfcbc1 GIT binary patch literal 20290 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/etc/html/default/stats/fonts/glyphicons-halflings-regular.ttf b/etc/html/default/stats/fonts/glyphicons-halflings-regular.ttf new file mode 100755 index 0000000000000000000000000000000000000000..a498ef4e7c8b556fc36f580c5ff524025bb11c84 GIT binary patch literal 41236 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 literal 0 HcmV?d00001 diff --git a/etc/html/default/stats/index.html b/etc/html/default/stats/index.html index 93081a1..eb148cd 100755 --- a/etc/html/default/stats/index.html +++ b/etc/html/default/stats/index.html @@ -1,285 +1,210 @@ - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - -
-

Site Smart BETA

- - -

Report generated {{NOW}}

-

{{PROJNAME}} - ({{PRODURL}}) -

- -
{{PROJCLIENT}}
-
- -

Overview (last 7 days)

- -
-

{{SCAN_MSG}}
- -

{{UPTIME}}% uptime
- -

{{LATENCY}}s latency
- -

{{LAST_BACKUP}}
-
- -
- - - - - - - -
- - - - - - - -
-

Commits by day of week

-

- -

Commits by hour of week

-

- -

Commits by hour of day

-

- -

Commits by month of year

-

- -

Commits by year

-

- -

Commits by year/month

-

-

- -

- - -

- -

Any Questions?

-

If you have any questions, feel free to ask us anything, using the live chat function on this page, or email. Thank you!

- - - - - - - -

-
- - - - - - -
-
- - - + + + + + + {{PROJNAME}} • {{PROJCLIENT}} + + + + + + + + + + + + +
+ +
+
+
+ Reports +
+ + +

 

+
+ +
+
    +
  • Updated {{NOW}}
  • +
+ My Dashboard +
+
+
+ + + +
+
+
+
+

Engagement (Last 7 days)

+
+
+
+
+

{{GA_HITS}}

+

hits from visitors

+
+
+

{{GA_PERCENT}}%

+

traffic from new users

+
+
+

{{GA_SEARCHES}}

+

organic searches

+
+
+
+ + +
+
+
+
+ +

Recent Activity

+
+
+
+ {{COMMITS_RECENT}} +

+ View all +

+
+
+
+
+
+
+
+ +

Project Stats

+
+
+
+
+

+ Project: {{PROJNAME}}
+ Development: {{DEVURL}}
+ Production: {{PRODURL}} +

+

{{CODE_STATS}}

+
+
+ {{PROJCLIENT}} +
+
+ +
+ +
+

+ View all +

+
+
+
+
+
+
+ +

Support Request

+
+
+
+ +
+
+
+
+
+
+
+ + + + + +
+ + + + + + \ No newline at end of file diff --git a/etc/html/default/stats/js/bootstrap.min.js b/etc/html/default/stats/js/bootstrap.min.js new file mode 100755 index 0000000..c6d3692 --- /dev/null +++ b/etc/html/default/stats/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('